diff --git a/src/business/business-gnome/dialog-payment.c b/src/business/business-gnome/dialog-payment.c index f7ded4135f..3435f4b01e 100644 --- a/src/business/business-gnome/dialog-payment.c +++ b/src/business/business-gnome/dialog-payment.c @@ -154,6 +154,7 @@ void gnc_payment_acct_tree_row_activated_cb (GtkWidget *widget, GtkTreePath *pat GtkTreeViewColumn *column, PaymentWindow *pw); void gnc_payment_leave_amount_cb (GtkWidget *widget, GdkEventFocus *event, PaymentWindow *pw); +void gnc_payment_window_fill_docs_list (PaymentWindow *pw); static void @@ -161,6 +162,7 @@ gnc_payment_window_refresh_handler (GHashTable *changes, gpointer data) { PaymentWindow *pw = data; + gnc_payment_window_fill_docs_list (pw); pw->post_acct = gnc_account_select_combo_fill (pw->post_combo, pw->book, pw->acct_types, pw->acct_commodities); } @@ -324,6 +326,8 @@ gnc_payment_dialog_highlight_document (PaymentWindow *pw) lot = (GNCLot *) g_value_get_pointer (&value); g_value_unset (&value); + if (!lot) + continue; /* Lot has been deleted behind our back... */ invoice = gncInvoiceGetInvoiceFromLot (lot); if (!invoice) @@ -340,7 +344,7 @@ gnc_payment_dialog_highlight_document (PaymentWindow *pw) } } -static void +void gnc_payment_window_fill_docs_list (PaymentWindow *pw) { GtkListStore *store; @@ -1102,37 +1106,13 @@ gnc_ui_payment_new (GncOwner *owner, QofBook *book) } // //////////////////////////////////////////////////////////// - -static gboolean isAssetLiabType(GNCAccountType t) -{ - switch (t) - { - case ACCT_TYPE_RECEIVABLE: - case ACCT_TYPE_PAYABLE: - return FALSE; - default: - return (xaccAccountTypesCompatible(ACCT_TYPE_ASSET, t) - || xaccAccountTypesCompatible(ACCT_TYPE_LIABILITY, t)); - } -} -static gboolean isAPARType(GNCAccountType t) -{ - switch (t) - { - case ACCT_TYPE_RECEIVABLE: - case ACCT_TYPE_PAYABLE: - return TRUE; - default: - return FALSE; - } -} static void increment_if_asset_account (gpointer data, gpointer user_data) { int *r = user_data; const Split *split = data; const Account *account = xaccSplitGetAccount(split); - if (isAssetLiabType(xaccAccountGetType(account))) + if (xaccAccountIsAssetLiabType(xaccAccountGetType(account))) ++(*r); } static int countAssetAccounts(SplitList* slist) @@ -1147,7 +1127,7 @@ static gint predicate_is_asset_account(gconstpointer a, { const Split *split = a; const Account *account = xaccSplitGetAccount(split); - if (isAssetLiabType(xaccAccountGetType(account))) + if (xaccAccountIsAssetLiabType(xaccAccountGetType(account))) return 0; else return -1; @@ -1157,7 +1137,7 @@ static gint predicate_is_apar_account(gconstpointer a, { const Split *split = a; const Account *account = xaccSplitGetAccount(split); - if (isAPARType(xaccAccountGetType(account))) + if (xaccAccountIsAPARType(xaccAccountGetType(account))) return 0; else return -1; diff --git a/src/business/business-ledger/gncEntryLedger.h b/src/business/business-ledger/gncEntryLedger.h index a8668df445..3a7532d024 100644 --- a/src/business/business-ledger/gncEntryLedger.h +++ b/src/business/business-ledger/gncEntryLedger.h @@ -49,17 +49,6 @@ typedef enum GNCENTRY_NUM_REGISTER_TYPES } GncEntryLedgerType; -typedef struct entry_ledger_colors -{ - guint32 header_bg_color; - - guint32 primary_bg_color; - guint32 secondary_bg_color; - - guint32 primary_active_bg_color; - guint32 secondary_active_bg_color; -} GncEntryLedgerColors; - #define ENTRY_IACCT_CELL "inv-account" #define ENTRY_BACCT_CELL "bill-account" #define ENTRY_ACTN_CELL "action" diff --git a/src/business/business-ledger/gncEntryLedgerModel.c b/src/business/business-ledger/gncEntryLedgerModel.c index 96a0dca00e..87b8991632 100644 --- a/src/business/business-ledger/gncEntryLedgerModel.c +++ b/src/business/business-ledger/gncEntryLedgerModel.c @@ -36,15 +36,6 @@ #include "gncEntryLedgerP.h" #include "gncEntryLedgerModel.h" -static GncEntryLedgerColors reg_colors = -{ - 0x96B183, - 0xBFDEB9, - 0xF6FFDA, - - 0xFFEF98, - 0xFFEF98, -}; /** Private Interfaces ***********************************************/ @@ -926,43 +917,85 @@ static CellIOFlags get_qty_io_flags (VirtualLocation virt_loc, gpointer user_dat /* GET BG_COLORS */ +static guint32 +gnc_entry_ledger_get_color_internal (VirtualLocation virt_loc, + GncEntryLedger *ledger, + const guint32 *color_table, + gboolean foreground) +{ + const char *cursor_name; + VirtualCell *vcell; + gboolean is_current; + guint32 colorbase = 0; /* By default return background colors */ + + if (foreground) + colorbase = COLOR_UNKNOWN_FG; /* a bit of enum arithmetic */ + + if (!ledger) + return color_table[colorbase + COLOR_UNKNOWN_BG]; + + if (gnc_table_virtual_location_in_header (ledger->table, virt_loc)) + return color_table[colorbase + COLOR_HEADER_BG]; + + vcell = gnc_table_get_virtual_cell (ledger->table, virt_loc.vcell_loc); + if (!vcell || !vcell->cellblock) + return color_table[colorbase + COLOR_UNKNOWN_BG]; + + if ((virt_loc.phys_col_offset < vcell->cellblock->start_col) || + (virt_loc.phys_col_offset > vcell->cellblock->stop_col)) + return color_table[colorbase + COLOR_UNKNOWN_BG]; + + is_current = virt_cell_loc_equal (ledger->table->current_cursor_loc.vcell_loc, + virt_loc.vcell_loc); + + if (is_current) + return vcell->start_primary_color ? + color_table[colorbase + COLOR_PRIMARY_BG_ACTIVE] : + color_table[colorbase + COLOR_SECONDARY_BG_ACTIVE]; + + return vcell->start_primary_color ? + color_table[colorbase + COLOR_PRIMARY_BG] : color_table[colorbase + COLOR_SECONDARY_BG]; + +} + +static guint32 +gnc_entry_ledger_get_fg_color (VirtualLocation virt_loc, + gpointer user_data) +{ + GncEntryLedger *ledger = user_data; + return gnc_entry_ledger_get_color_internal (virt_loc, ledger, reg_colors_default, TRUE); +} + +static guint32 +gnc_entry_ledger_get_gtkrc_fg_color (VirtualLocation virt_loc, + gpointer user_data) +{ + GncEntryLedger *ledger = user_data; + return gnc_entry_ledger_get_color_internal (virt_loc, ledger, reg_colors_gtkrc, TRUE); +} + static guint32 gnc_entry_ledger_get_bg_color (VirtualLocation virt_loc, gboolean *hatching, gpointer user_data) { GncEntryLedger *ledger = user_data; - VirtualCell *vcell; - guint32 bg_color; - gboolean is_current; if (hatching) *hatching = FALSE; - bg_color = 0xffffff; /* white */ + return gnc_entry_ledger_get_color_internal (virt_loc, ledger, reg_colors_default, FALSE); +} - if (!ledger) return bg_color; +static guint32 +gnc_entry_ledger_get_gtkrc_bg_color (VirtualLocation virt_loc, + gboolean *hatching, gpointer user_data) +{ + GncEntryLedger *ledger = user_data; - if (gnc_table_virtual_location_in_header (ledger->table, virt_loc)) - return reg_colors.header_bg_color; + if (hatching) + *hatching = FALSE; - vcell = gnc_table_get_virtual_cell (ledger->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 - (ledger->table->current_cursor_loc.vcell_loc, virt_loc.vcell_loc); - - 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; + return gnc_entry_ledger_get_color_internal (virt_loc, ledger, reg_colors_gtkrc, FALSE); } /* SAVE CELLS */ @@ -1214,9 +1247,18 @@ static void gnc_entry_ledger_model_new_handlers (TableModel *model, }; unsigned int i; + gnc_table_model_set_default_fg_color_handler + (model, gnc_entry_ledger_get_fg_color); + + gnc_table_model_set_fg_color_handler + (model, gnc_entry_ledger_get_gtkrc_fg_color, "gtkrc"); + gnc_table_model_set_default_bg_color_handler (model, gnc_entry_ledger_get_bg_color); + gnc_table_model_set_bg_color_handler + (model, gnc_entry_ledger_get_gtkrc_bg_color, "gtkrc"); + for (i = 0; i < (sizeof(models) / sizeof(*models)); i++) { diff --git a/src/engine/Account.c b/src/engine/Account.c index c92b0b5631..1c7834018f 100644 --- a/src/engine/Account.c +++ b/src/engine/Account.c @@ -4208,6 +4208,30 @@ xaccAccountTypesValid(void) return mask; } +gboolean xaccAccountIsAssetLiabType(GNCAccountType t) +{ + switch (t) + { + case ACCT_TYPE_RECEIVABLE: + case ACCT_TYPE_PAYABLE: + return FALSE; + default: + return (xaccAccountTypesCompatible(ACCT_TYPE_ASSET, t) + || xaccAccountTypesCompatible(ACCT_TYPE_LIABILITY, t)); + } +} +gboolean xaccAccountIsAPARType(GNCAccountType t) +{ + switch (t) + { + case ACCT_TYPE_RECEIVABLE: + case ACCT_TYPE_PAYABLE: + return TRUE; + default: + return FALSE; + } +} + gboolean xaccAccountIsPriced(const Account *acc) { diff --git a/src/engine/Account.h b/src/engine/Account.h index 6bb25a7704..01bdf4bc58 100644 --- a/src/engine/Account.h +++ b/src/engine/Account.h @@ -943,6 +943,16 @@ gboolean xaccAccountTypesCompatible (GNCAccountType parent_type, * root account types are stripped. */ guint32 xaccAccountTypesValid(void); +/** Convenience function to check if the account is a valid + * Asset or Liability type, but not a business account type + * (meaning not an Accounts Payable/Accounts Receivable). */ +gboolean xaccAccountIsAssetLiabType(GNCAccountType t); + +/** Convenience function to check if the account is a valid + * business account type + * (meaning an Accounts Payable/Accounts Receivable). */ +gboolean xaccAccountIsAPARType(GNCAccountType t); + /** @} */ diff --git a/src/engine/Makefile.am b/src/engine/Makefile.am index 2f8c5d6565..ae41511edf 100644 --- a/src/engine/Makefile.am +++ b/src/engine/Makefile.am @@ -22,6 +22,7 @@ libgncmod_engine_la_SOURCES = \ Scrub.c \ Scrub2.c \ Scrub3.c \ + ScrubBusiness.c \ Split.c \ TransLog.c \ Transaction.c \ @@ -70,6 +71,7 @@ gncinclude_HEADERS = \ Scrub.h \ Scrub2.h \ Scrub3.h \ + ScrubBusiness.h \ Split.h \ TransLog.h \ Transaction.h \ diff --git a/src/engine/Scrub.c b/src/engine/Scrub.c index 70d1e6c4f7..497297ebf0 100644 --- a/src/engine/Scrub.c +++ b/src/engine/Scrub.c @@ -529,7 +529,10 @@ xaccTransScrubImbalance (Transaction *trans, Account *root, /* Return immediately if things are balanced. */ if (xaccTransIsBalanced (trans)) + { + LEAVE ("transaction is balanced"); return; + } currency = xaccTransGetCurrency (trans); @@ -626,7 +629,7 @@ xaccTransScrubImbalance (Transaction *trans, Account *root, imbal_list = xaccTransGetImbalance (trans); if (!imbal_list) { - LEAVE("()"); + LEAVE("transaction is balanced"); return; } diff --git a/src/engine/Scrub2.c b/src/engine/Scrub2.c index 4522183405..6208b772f2 100644 --- a/src/engine/Scrub2.c +++ b/src/engine/Scrub2.c @@ -344,7 +344,7 @@ merge_splits (Split *sa, Split *sb) } gboolean -xaccScrubMergeSubSplits (Split *split) +xaccScrubMergeSubSplits (Split *split, gboolean strict) { gboolean rc = FALSE; Transaction *txn; @@ -352,7 +352,7 @@ xaccScrubMergeSubSplits (Split *split) GNCLot *lot; const GncGUID *guid; - if (FALSE == is_subsplit (split)) return FALSE; + if (strict && (FALSE == is_subsplit (split))) return FALSE; txn = split->parent; lot = xaccSplitGetLot (split); @@ -392,7 +392,7 @@ restart: } gboolean -xaccScrubMergeLotSubSplits (GNCLot *lot) +xaccScrubMergeLotSubSplits (GNCLot *lot, gboolean strict) { gboolean rc = FALSE; SplitList *node; @@ -404,7 +404,7 @@ restart: for (node = gnc_lot_get_split_list(lot); node; node = node->next) { Split *s = node->data; - if (!xaccScrubMergeSubSplits(s)) continue; + if (!xaccScrubMergeSubSplits(s, strict)) continue; rc = TRUE; goto restart; diff --git a/src/engine/Scrub2.h b/src/engine/Scrub2.h index e9ac308e7e..61527d9b7a 100644 --- a/src/engine/Scrub2.h +++ b/src/engine/Scrub2.h @@ -53,7 +53,7 @@ * not in a lot will be used to close the oldest open lot(s). * If there are no open lots, a new lot will be started. * By trying to close the oldest lots, this effectively - * implements a FIFO acounting policy. + * implements a FIFO accounting policy. */ void xaccAccountAssignLots (Account *acc); @@ -89,16 +89,26 @@ void xaccLotScrubDoubleBalance (GNCLot *lot); * the same lot, or in no lot. Note that, by definition, all * subsplits belong to the same transaction. * + * There are two ways to find matching subsplits. The first + * way will consider splits to be subsplits only if they + * are explicitly marked as such while splitting the original + * split. Set strict to TRUE for this matching algorhythm. + * + * The second way is more relaxed. It will consider any two + * splits that happen to be part of the same lot and the + * same transaction to be subsplits. Set strict to FALSE for + * this matching algorhythm. + * * The routine returns TRUE if a merger was performed, else * it returns FALSE. - * - * The xaccScrubMergeTransSubSplits() routine does the same, except - * that it does it for all of the splits in the transaction. - * The xaccScrubMergeLotSubSplits() routine does the same, except - * that it does it for all of the splits in the lot. */ -gboolean xaccScrubMergeSubSplits (Split *split); -gboolean xaccScrubMergeLotSubSplits (GNCLot *lot); +gboolean xaccScrubMergeSubSplits (Split *split, gboolean strict); + +/** The xaccScrubMergeLotSubSplits() routine does the same as + * the xaccScrubMergSubSplits, except that it does it + * for all of the splits in the lot. + */ +gboolean xaccScrubMergeLotSubSplits (GNCLot *lot, gboolean strict); #endif /* XACC_SCRUB2_H */ /** @} */ diff --git a/src/engine/Scrub3.c b/src/engine/Scrub3.c index 1890f308a9..c4c49321dd 100644 --- a/src/engine/Scrub3.c +++ b/src/engine/Scrub3.c @@ -96,7 +96,7 @@ xaccScrubLot (GNCLot *lot) acc = gnc_lot_get_account (lot); pcy = gnc_account_get_policy(acc); xaccAccountBeginEdit(acc); - xaccScrubMergeLotSubSplits (lot); + xaccScrubMergeLotSubSplits (lot, TRUE); /* If the lot balance is zero, we don't need to rebalance */ lot_baln = gnc_lot_get_balance (lot); @@ -132,7 +132,7 @@ rethin: xaccLotFill (lot); /* Make sure there are no subsplits. */ - splits_deleted = xaccScrubMergeLotSubSplits (lot); + splits_deleted = xaccScrubMergeLotSubSplits (lot, TRUE); } /* Now re-compute cap gains, and then double-check that. diff --git a/src/engine/Scrub3.h b/src/engine/Scrub3.h index 6e5a7f8665..2c1a8eae75 100644 --- a/src/engine/Scrub3.h +++ b/src/engine/Scrub3.h @@ -25,7 +25,7 @@ @{ */ /** @file Scrub3.h - * @brief Hiogh-Level API for imposing Lot constraints + * @brief High-Level API for imposing Lot constraints * @author Created by Linas Vepstas Sept 2003 * @author Copyright (c) 2003 Linas Vepstas */ @@ -44,7 +44,7 @@ * self-consistent and properly balanced, and fixes it if its not. * This is an important routine to call if the amount of any split * in the lot is changed. That's because (obviously) changing - * split values is gaurenteed to throw off lot balances. + * split values is guaranteed to throw off lot balances. * This routine may end up closing the lot, or at least trying * to. It will also cause cap gains to be recomputed. * diff --git a/src/engine/ScrubBusiness.c b/src/engine/ScrubBusiness.c new file mode 100644 index 0000000000..9c7b30823a --- /dev/null +++ b/src/engine/ScrubBusiness.c @@ -0,0 +1,333 @@ +/********************************************************************\ + * ScrubBusiness.h -- Cleanup functions for the business objects. * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * + * Boston, MA 02110-1301, USA gnu@gnu.org * +\********************************************************************/ + +/** @file ScrubBusiness.h + * @brief Cleanup functions for business objects + * @author Created by Geert Janssens August 2014 + * @author Copyright (c) 2014 Geert Janssens + * + * Provides the high-level API for checking and repairing ('scrubbing + * clean') the various data objects used by the business functions.*/ + +#include "config.h" + +#include +#include + +#include "gnc-engine.h" +#include "gnc-lot.h" +#include "policy-p.h" +#include "Account.h" +#include "gncInvoice.h" +#include "Scrub2.h" +#include "ScrubBusiness.h" +#include "Transaction.h" + +static QofLogModule log_module = GNC_MOD_LOT; + +// A helper function that takes two splits. If the splits are of opposite sign +// it reduces the biggest split to have the same value (but with opposite sign) +// of the smaller split. +// To make sure everything still continues to balance in addition a "remainder" split +// will be created that will be added to the same lot and transaction as the biggest +// split. +// The opposite sign restriction is because that's the only scenario that makes sense +// in the context of scrubbing business lots below. +// If we created new splits, return TRUE, otherwise FALSE +static gboolean reduce_biggest_split (Split *splitA, Split *splitB) +{ + gnc_numeric valA = xaccSplitGetValue (splitA); + gnc_numeric valB = xaccSplitGetValue (splitB); + + if (gnc_numeric_compare (gnc_numeric_abs (valA), gnc_numeric_abs (valB)) >= 0) + return gncOwnerReduceSplitTo (splitA, gnc_numeric_neg (valB)); + else + return gncOwnerReduceSplitTo (splitB, gnc_numeric_neg (valA)); +} + +// Attempt to eliminate or reduce the lot link splits (ll_*_split) +// between from_lot and to_lot. To do so this function will attempt +// to move a payment split from from_lot to to_lot in order to +// balance the lot link split that will be deleted. +// To ensure everything remains balanced at most +// min (val-ll-*-split, val-pay-split) (in absolute values) can be moved. +// If any split involved has a larger value, it will be split in two +// and only the part matching the other splits' value will be used. +// The leftover splits are kept in the respective transactions/lots. +// A future scrub action can still act on those if needed. +// +// Note that this function assumes that ll_from_split and ll_to_split are +// of opposite sign. The calling function should check this. + +static gboolean +scrub_other_link (GNCLot *from_lot, Split *ll_from_split, + GNCLot *to_lot, Split *ll_to_split) +{ + Split *real_from_split; // This refers to the split in the payment lot representing the payment itself + gnc_numeric from_val, real_from_val, to_val; + gboolean modified = FALSE; + Transaction *ll_txn = xaccSplitGetParent (ll_to_split); + + // Per iteration we can only scrub at most max (val-doc-split, val-pay-split) + // So split the bigger one in two if needed and continue with the equal valued splits only + // The remainder is added to the lot link transaction and the lot to keep everything balanced + // and will be processed in a future iteration + modified = reduce_biggest_split (ll_from_split, ll_to_split); + + // Next we have to find the original payment split so we can + // add (part of) it to the document lot + real_from_split = gncOwnerFindOffsettingSplit (from_lot, xaccSplitGetValue (ll_from_split)); + if (!real_from_split) + return modified; // No usable split in the payment lot + + // Here again per iteration we can only scrub at most max (val-other-pay-split, val-pay-split) + // So split the bigger one in two if needed and continue with the equal valued splits only + // The remainder is added to the lot link transaction and the lot to keep everything balanced + // and will be processed in a future iteration + modified = reduce_biggest_split (real_from_split, ll_from_split); + + // Once more check for max (val-doc-split, val-pay-split), and reduce if necessary. + // It may have changed while looking for the real payment split + modified = reduce_biggest_split (ll_from_split, ll_to_split); + + // At this point ll_to_split and real_from_split should have the same value + // If not, flag a warning and skip to the next iteration + to_val = xaccSplitGetValue (ll_to_split); + from_val = xaccSplitGetValue (ll_from_split); + real_from_val = xaccSplitGetValue (real_from_split); + if (!gnc_numeric_equal (real_from_val, to_val)) + { + // This is unexpected - write a warning message and skip this split + PWARN("real_from_val and to_val differ. " + "This is unexpected! Skip scrubbing of real_from_split %p against ll_to_split %p.", real_from_split, ll_to_split); + return modified; + } + + // Now do the actual split dance + // - move real payment split to doc lot + // - delete both lot link splits from the lot link transaction + gnc_lot_add_split (to_lot, real_from_split); + xaccTransBeginEdit (ll_txn); + xaccSplitDestroy (ll_to_split); + xaccSplitDestroy (ll_from_split); + xaccTransCommitEdit (ll_txn); + + // Cleanup the lots + xaccScrubMergeLotSubSplits (to_lot, FALSE); + xaccScrubMergeLotSubSplits (from_lot, FALSE); + + return TRUE; // We did change splits/transactions/lots... +} + +static gboolean +gncScrubLotLinks (GNCLot *scrub_lot) +{ + gboolean modified = FALSE, restart_needed = FALSE; + SplitList *sls_iter = NULL; + +scrub_start: + restart_needed = FALSE; + + // Iterate over all splits in the lot + for (sls_iter = gnc_lot_get_split_list (scrub_lot); sls_iter; sls_iter = sls_iter->next) + { + Split *sl_split = sls_iter->data; + Transaction *ll_txn = NULL; // ll_txn = "Lot Link Transaction" + SplitList *lts_iter = NULL; + + if (!sl_split) + continue; // next scrub lot split + + // Only lot link transactions need to be scrubbed + ll_txn = xaccSplitGetParent (sl_split); + + if (!ll_txn) + { + // Ooops - the split doesn't belong to any transaction ! + // This is not expected so issue a warning and continue with next split + PWARN("Encountered a split in a business lot that's not part of any transaction. " + "This is unexpected! Skipping split %p.", sl_split); + continue; + } + + if (xaccTransGetTxnType (ll_txn) != TXN_TYPE_LINK) + continue; // next scrub lot split + + // Iterate over all splits in the lot link transaction + for (lts_iter = xaccTransGetSplitList (ll_txn); lts_iter; lts_iter = lts_iter->next) + { + Split *ll_txn_split = lts_iter->data; // These all refer to splits in the lot link transaction + GNCLot *remote_lot = NULL; // lot at the other end of the lot link transaction + gboolean sl_is_doc_lot, rl_is_doc_lot; + + if (!ll_txn_split) + continue; // next lot link transaction split + + // Skip the split in the lot we're currently scrubbing + if (sl_split == ll_txn_split) + continue; // next lot link transaction split + + // Only splits of opposite sign can be scrubbed + if (gnc_numeric_positive_p (xaccSplitGetValue (sl_split)) == + gnc_numeric_positive_p (xaccSplitGetValue (ll_txn_split))) + continue; // next lot link transaction split + + // Find linked lot via split + remote_lot = xaccSplitGetLot (ll_txn_split); + if (!remote_lot) + { + // This is unexpected - write a warning message and skip this split + PWARN("Encountered a Lot Link transaction with a split that's not in any lot. " + "This is unexpected! Skipping split %p from transaction %p.", ll_txn_split, ll_txn); + continue; + } + + sl_is_doc_lot = (gncInvoiceGetInvoiceFromLot (scrub_lot) != NULL); + rl_is_doc_lot = (gncInvoiceGetInvoiceFromLot (remote_lot) != NULL); + + // Depending on the type of lots we're comparing, we need different actions + // - Two document lots (an invoice and a credit note): + // Special treatment - look for all document lots linked via ll_txn + // and update the memo to be of more use to the uses. + // - Two payment lots: + // (Part of) the link will be eliminated and instead (part of) + // one payment will be added to the other lot to keep the balance. + // If the payments are not equal in abs value part of the bigger payment + // will be moved to the smaller payment's lot. + // - A document and a payment lot: + // (Part of) the link will be eliminated and instead (part of) the real + // payment will be added to the document lot to handle the payment. + if (sl_is_doc_lot && rl_is_doc_lot) + gncOwnerSetLotLinkMemo (ll_txn); + else if (!sl_is_doc_lot && !rl_is_doc_lot) + { + gint cmp = gnc_numeric_compare (gnc_numeric_abs (xaccSplitGetValue (sl_split)), + gnc_numeric_abs (xaccSplitGetValue (ll_txn_split))); + if (cmp >= 0) + restart_needed = scrub_other_link (scrub_lot, sl_split, remote_lot, ll_txn_split); + else + restart_needed = scrub_other_link (remote_lot, ll_txn_split, scrub_lot, sl_split); + } + else + { + GNCLot *doc_lot = sl_is_doc_lot ? scrub_lot : remote_lot; + GNCLot *pay_lot = sl_is_doc_lot ? remote_lot : scrub_lot; + Split *ll_doc_split = sl_is_doc_lot ? sl_split : ll_txn_split; + Split *ll_pay_split = sl_is_doc_lot ? ll_txn_split : sl_split; + // Ok, let's try to move a payment from pay_lot to doc_lot + restart_needed = scrub_other_link (pay_lot, ll_pay_split, doc_lot, ll_doc_split); + } + + // If we got here, the splits in our lot and ll_txn have been severely mixed up + // And our iterator lists are probably no longer valid + // So let's start over + if (restart_needed) + { + modified = TRUE; + goto scrub_start; + } + + } + } + + return modified; +} + + +gboolean +gncScrubBusinessLot (GNCLot *lot) +{ + gboolean splits_deleted = FALSE; + Account *acc; + gchar *lotname=NULL; + + if (!lot) return FALSE; + lotname = g_strdup (gnc_lot_get_title (lot)); + ENTER ("(lot=%p) %s", lot, lotname ? lotname : "(no lotname)"); + + acc = gnc_lot_get_account (lot); + if (acc) + xaccAccountBeginEdit(acc); + + // Scrub lot links. + // They should only remain when two document lots are linked together + xaccScrubMergeLotSubSplits (lot, FALSE); + splits_deleted = gncScrubLotLinks (lot); + + // If lot is empty now, delete it + if (0 == gnc_lot_count_splits (lot)) + { + PINFO("All splits were removed from lot, deleting"); + gnc_lot_destroy (lot); + } + + if (acc) + xaccAccountCommitEdit(acc); + + LEAVE ("(lot=%s, deleted=%d)", lotname ? lotname : "(no lotname)", splits_deleted); + g_free (lotname); + + return splits_deleted; +} + +/* ============================================================== */ + +void +gncScrubBusinessAccountLots (Account *acc) +{ + LotList *lots, *node; + if (!acc) return; + if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return; + + ENTER ("(acc=%s)", xaccAccountGetName(acc)); + xaccAccountBeginEdit(acc); + + lots = xaccAccountGetLotList(acc); + for (node = lots; node; node = node->next) + { + GNCLot *lot = node->data; + if (lot) + gncScrubBusinessLot (lot); + } + g_list_free(lots); + xaccAccountCommitEdit(acc); + LEAVE ("(acc=%s)", xaccAccountGetName(acc)); +} + +/* ============================================================== */ + +static void +lot_scrub_cb (Account *acc, gpointer data) +{ + if (FALSE == xaccAccountIsAPARType (xaccAccountGetType (acc))) return; + gncScrubBusinessAccountLots (acc); +} + +void +gncScrubBusinessAccountTreeLots (Account *acc) +{ + if (!acc) return; + + gnc_account_foreach_descendant(acc, lot_scrub_cb, NULL); + gncScrubBusinessAccountLots (acc); +} + +/* ========================== END OF FILE ========================= */ diff --git a/src/engine/ScrubBusiness.h b/src/engine/ScrubBusiness.h new file mode 100644 index 0000000000..59af9f03fd --- /dev/null +++ b/src/engine/ScrubBusiness.h @@ -0,0 +1,79 @@ +/********************************************************************\ + * ScrubBusiness.h -- Cleanup functions for the business objects. * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * + * Boston, MA 02110-1301, USA gnu@gnu.org * +\********************************************************************/ + +/** @addtogroup Engine + @{ */ +/** @addtogroup Scrub + @{ */ + +/** @file ScrubBusiness.h + * @brief Cleanup functions for business objects + * @author Created by Geert Janssens August 2014 + * @author Copyright (c) 2014 Geert Janssens + */ +#ifndef GNC_SCRUBBUSINESS_H +#define GNC_SCRUBBUSINESS_H + +#include "gnc-engine.h" + +/** @name Cleanup functions for business objects + * Provides the high-level API for checking and repairing ('scrubbing + * clean') the various data objects used by the business functions. + @{ */ + +/** The gncScrubBusinessLot() function makes sure that the indicated + * lot has all the correct properties required for a lot used in + * the business features. + * + * Currently this function only does one thing: eliminate lot link + * transactions between invoice lots and payment lots (which were + * generated by GnuCash versions 2.6.0-2.6.3). Lot links + * between invoices and credit notes will still remain. + * + * Scrubbing the lot may cause subsplits to be merged together, + * i.e. for splits to be deleted. This routine returns true if + * any splits were modified or deleted. + */ +gboolean gncScrubBusinessLot (GNCLot *lot); + +/** The gncScrubBusinessAccountLots() function will call + * gncScrubBusinessLot() on each lot in the given account. + * + * This routine is the primary routine for ensuring that the + * lot structure of every lot of a business account is in good + * order. + */ +void gncScrubBusinessAccountLots (Account *acc); + +/** The gncScrubBusinessAccountTreeLots() function will call + * gncScrubBusinessAccountLots() on each lot in the given account + * and its sub accounts. + * + * This routine is the primary routine for ensuring that the + * lot structure of every lot of a business account is in good + * order. + */ +void gncScrubBusinessAccountTreeLots (Account *acc); + +/** @} */ +#endif /* GNC_SCRUBBUSINESS_H */ +/** @} */ +/** @} */ diff --git a/src/engine/Split.c b/src/engine/Split.c index 1d539e428f..e5cf17d455 100644 --- a/src/engine/Split.c +++ b/src/engine/Split.c @@ -43,7 +43,6 @@ #include "Split.h" #include "AccountP.h" #include "Scrub.h" -#include "Scrub3.h" #include "TransactionP.h" #include "TransLog.h" #include "cap-gains.h" diff --git a/src/engine/Transaction.h b/src/engine/Transaction.h index e26ac0896d..7c0004b32a 100644 --- a/src/engine/Transaction.h +++ b/src/engine/Transaction.h @@ -213,7 +213,7 @@ void xaccTransRollbackEdit (Transaction *trans); /** The xaccTransIsOpen() method returns TRUE if the transaction is open for editing. Otherwise, it returns false. - XXX this routne should probably be deprecated. its, umm, + XXX this routine should probably be deprecated. its, umm, hard to imagine legitimate uses (but it is used by the import/export code for reasons I can't understand.) */ diff --git a/src/engine/gnc-lot.c b/src/engine/gnc-lot.c index d86f0bec81..b0474e46d5 100644 --- a/src/engine/gnc-lot.c +++ b/src/engine/gnc-lot.c @@ -309,6 +309,8 @@ gnc_lot_free(GNCLot* lot) priv->is_closed = TRUE; /* qof_instance_release (&lot->inst); */ g_object_unref (lot); + + LEAVE(); } void @@ -635,6 +637,7 @@ gnc_lot_remove_split (GNCLot *lot, Split *split) } gnc_lot_commit_edit(lot); qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_MODIFY, NULL); + LEAVE("removed from lot"); } /* ============================================================== */ diff --git a/src/engine/gncOwner.c b/src/engine/gncOwner.c index 4075425aca..b9d39d9f01 100644 --- a/src/engine/gncOwner.c +++ b/src/engine/gncOwner.c @@ -44,14 +44,17 @@ #include "gncVendorP.h" #include "gncInvoice.h" #include "gnc-commodity.h" -#include "Transaction.h" +#include "Scrub2.h" #include "Split.h" +#include "Transaction.h" #include "engine-helpers.h" #define _GNC_MOD_NAME GNC_ID_OWNER #define GNC_OWNER_ID "gncOwner" +static QofLogModule log_module = GNC_MOD_ENGINE; + GncOwner * gncOwnerNew (void) { GncOwner *o; @@ -834,9 +837,368 @@ gncOwnerCreatePaymentLot (const GncOwner *owner, Transaction *txn, return payment_lot; } +typedef enum +{ + is_equal = 8, + is_more = 4, + is_less = 2, + is_pay_split = 1 +} split_flags; + +Split *gncOwnerFindOffsettingSplit (GNCLot *lot, gnc_numeric target_value) +{ + SplitList *ls_iter = NULL; + Split *best_split = NULL; + gnc_numeric best_val = { 0, 1}; + gint best_flags = 0; + + if (!lot) + return NULL; + + for (ls_iter = gnc_lot_get_split_list (lot); ls_iter; ls_iter = ls_iter->next) + { + Split *split = ls_iter->data; + Transaction *txn; + gnc_numeric split_value; + gint new_flags = 0; + gint val_cmp = 0; + + if (!split) + continue; + + + txn = xaccSplitGetParent (split); + if (!txn) + { + // Ooops - the split doesn't belong to any transaction ! + // This is not expected so issue a warning and continue with next split + PWARN("Encountered a split in a payment lot that's not part of any transaction. " + "This is unexpected! Skipping split %p.", split); + continue; + } + + // Check if this split has the opposite sign of the target value we want to offset + split_value = xaccSplitGetValue (split); + if (gnc_numeric_positive_p (target_value) == gnc_numeric_positive_p (split_value)) + continue; + + // Ok we have found a split that potentially can offset the target value + // Let's see if it's better than what we have found already. + val_cmp = gnc_numeric_compare (gnc_numeric_abs (split_value), + gnc_numeric_abs (target_value)); + if (val_cmp == 0) + new_flags += is_equal; + else if (val_cmp > 0) + new_flags += is_more; + else + new_flags += is_less; + + if (xaccTransGetTxnType (txn) != TXN_TYPE_LINK) + new_flags += is_pay_split; + + if ((new_flags >= best_flags) && + (gnc_numeric_compare (gnc_numeric_abs (split_value), + gnc_numeric_abs (best_val)) > 0)) + { + // The new split is a better match than what we found so far + best_split = split; + best_flags = new_flags; + best_val = split_value; + } + } + + return best_split; +} + +gboolean +gncOwnerReduceSplitTo (Split *split, gnc_numeric target_value) +{ + gnc_numeric split_val = xaccSplitGetValue (split); + gnc_numeric rem_val; + Split *rem_split; + Transaction *txn; + GNCLot *lot; + + if (gnc_numeric_positive_p (split_val) != gnc_numeric_positive_p (target_value)) + return FALSE; // Split and target value have to be of the same sign + + if (gnc_numeric_equal (split_val, target_value)) + return FALSE; // Split already has the target value + + rem_val = gnc_numeric_sub (split_val, target_value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); // note: values are of opposite sign + rem_split = xaccMallocSplit (xaccSplitGetBook (split)); + xaccSplitCopyOnto (split, rem_split); + xaccSplitSetValue (rem_split, rem_val); + + txn = xaccSplitGetParent (split); + xaccTransBeginEdit (txn); + xaccSplitSetValue (split, target_value); + xaccSplitSetParent (rem_split, txn); + xaccTransCommitEdit (txn); + + lot = xaccSplitGetLot (split); + gnc_lot_add_split (lot, rem_split); + + return TRUE; +} + +void +gncOwnerSetLotLinkMemo (Transaction *ll_txn) +{ + gchar *memo_prefix = _("Offset between documents: "); + gchar *new_memo; + SplitList *lts_iter; + SplitList *splits = NULL, *siter; + GList *titles = NULL, *titer; + + if (!ll_txn) + return; + + if (xaccTransGetTxnType (ll_txn) != TXN_TYPE_LINK) + return; + + // Find all splits in the lot link transaction that are also in a document lot + for (lts_iter = xaccTransGetSplitList (ll_txn); lts_iter; lts_iter = lts_iter->next) + { + Split *split = lts_iter->data; + GNCLot *lot; + GncInvoice *invoice; + gchar *title; + + if (!split) + continue; + + lot = xaccSplitGetLot (split); + if (!lot) + continue; + + invoice = gncInvoiceGetInvoiceFromLot (lot); + if (!invoice) + continue; + + title = g_strdup_printf ("%s %s", gncInvoiceGetTypeString (invoice), gncInvoiceGetID (invoice)); + + titles = g_list_insert_sorted (titles, title, (GCompareFunc)g_strcmp0); + splits = g_list_prepend (splits, split); // splits don't need to be sorted + } + + if (!titles) + return; // We didn't find document lots + + // Create the memo as we'd want it to be + new_memo = g_strconcat (memo_prefix, titles->data, NULL); + for (titer = titles->next; titer; titer = titer->next) + { + gchar *tmp_memo = g_strconcat (new_memo, " - ", titer->data, NULL); + g_free (new_memo); + new_memo = tmp_memo; + } + g_list_free_full (titles, g_free); + + // Update the memos of all the splits we found previously (if needed) + for (siter = splits; siter; siter = siter->next) + { + if (g_strcmp0 (xaccSplitGetMemo (siter->data), new_memo) != 0) + xaccSplitSetMemo (siter->data, new_memo); + } + + g_list_free (splits); + g_free (new_memo); +} + +/* Find an existing lot link transaction in the given lot + * Only use a lot link that already links at least two + * documents (to avoid perpetuating the lot link proliferation + * that happened in 2.6.0-2.6.3). + */ +static Transaction * +get_ll_transaction_from_lot (GNCLot *lot) +{ + SplitList *ls_iter; + + /* This should really only be called on a document lot */ + if (!gncInvoiceGetInvoiceFromLot (lot)) + return NULL; + + /* The given lot is a valid document lot. Now iterate over all + * other lot links in this lot to find one more document lot. + */ + for (ls_iter = gnc_lot_get_split_list (lot); ls_iter; ls_iter = ls_iter->next) + { + Split *ls = ls_iter->data; + Transaction *ll_txn = xaccSplitGetParent (ls); + SplitList *ts_iter; + + if (xaccTransGetTxnType (ll_txn) != TXN_TYPE_LINK) + continue; + + for (ts_iter = xaccTransGetSplitList (ll_txn); ts_iter; ts_iter = ts_iter->next) + { + Split *ts = ts_iter->data; + GNCLot *tslot = xaccSplitGetLot (ts); + + if (!tslot) + continue; + + if (tslot == lot) + continue; + + if (gncInvoiceGetInvoiceFromLot (lot)) + return ll_txn; /* Got one more document lot - mission accomplished */ + } + } + + /* The lot doesn't have an ll_txn with the requested criteria... */ + return NULL; +} + +static void +gncOwnerCreateLotLink (GNCLot *from_lot, GNCLot *to_lot, const GncOwner *owner) +{ + const gchar *action = _("Lot Link"); + Account *acct = gnc_lot_get_account (from_lot); + const gchar *name = gncOwnerGetName (gncOwnerGetEndOwner (owner)); + Transaction *ll_txn = NULL; + gnc_numeric from_lot_bal, to_lot_bal; + Timespec from_ts, to_ts; + time64 time_posted; + Split *split; + + /* Sanity check */ + if (!gncInvoiceGetInvoiceFromLot (from_lot) || + !gncInvoiceGetInvoiceFromLot (to_lot)) + return; + + /* Determine transaction date based on lot splits */ + from_ts = xaccTransRetDatePostedTS (xaccSplitGetParent (gnc_lot_get_latest_split (from_lot))); + to_ts = xaccTransRetDatePostedTS (xaccSplitGetParent (gnc_lot_get_latest_split (to_lot))); + if (timespecToTime64 (from_ts) >= timespecToTime64 (to_ts)) + time_posted = timespecToTime64 (from_ts); + else + time_posted = timespecToTime64 (to_ts); + + /* Figure out how much we can offset between the lots */ + from_lot_bal = gnc_lot_get_balance (from_lot); + to_lot_bal = gnc_lot_get_balance (to_lot); + if (gnc_numeric_compare (gnc_numeric_abs (from_lot_bal), + gnc_numeric_abs (to_lot_bal)) > 0) + from_lot_bal = gnc_numeric_neg (to_lot_bal); + else + to_lot_bal = gnc_numeric_neg (from_lot_bal); + + xaccAccountBeginEdit (acct); + + /* Look for a pre-existing lot link we can extend */ + ll_txn = get_ll_transaction_from_lot (from_lot); + + if (!ll_txn) + ll_txn = get_ll_transaction_from_lot (to_lot); + + if (!ll_txn) + { + /* No pre-existing lot link. Create one. */ + Timespec ts; + + timespecFromTime64 (&ts, time_posted); + + ll_txn = xaccMallocTransaction (gnc_lot_get_book (from_lot)); + xaccTransBeginEdit (ll_txn); + + xaccTransSetDescription (ll_txn, name ? name : "(Unknown)"); + xaccTransSetCurrency (ll_txn, xaccAccountGetCommodity(acct)); + xaccTransSetDateEnteredSecs (ll_txn, gnc_time (NULL)); + xaccTransSetDatePostedTS (ll_txn, &ts); + xaccTransSetTxnType (ll_txn, TXN_TYPE_LINK); + } + else + { + Timespec ts = xaccTransRetDatePostedTS (ll_txn); + xaccTransBeginEdit (ll_txn); + + /* Maybe we need to update the post date of the transaction ? */ + if (time_posted > timespecToTime64 (ts)) + { + timespecFromTime64 (&ts, time_posted); + xaccTransSetDatePostedTS (ll_txn, &ts); + + } + } + + /* Create a split for the from_lot */ + split = xaccMallocSplit (gnc_lot_get_book (from_lot)); + /* set Action using utility function */ + gnc_set_num_action (NULL, split, NULL, action); + xaccAccountInsertSplit (acct, split); + xaccTransAppendSplit (ll_txn, split); + /* To offset the lot balance, the split must be of the opposite sign */ + xaccSplitSetBaseValue (split, gnc_numeric_neg (from_lot_bal), xaccAccountGetCommodity(acct)); + gnc_lot_add_split (from_lot, split); + + /* Create a split for the to_lot */ + split = xaccMallocSplit (gnc_lot_get_book (to_lot)); + /* set Action using utility function */ + gnc_set_num_action (NULL, split, NULL, action); + xaccAccountInsertSplit (acct, split); + xaccTransAppendSplit (ll_txn, split); + /* To offset the lot balance, the split must be of the opposite sign */ + xaccSplitSetBaseValue (split, gnc_numeric_neg (to_lot_bal), xaccAccountGetCommodity(acct)); + gnc_lot_add_split (to_lot, split); + + xaccTransCommitEdit (ll_txn); + + + /* Do some post-cleaning on the lots + * The above actions may have created splits that are + * in the same transaction and lot. These can be merged. + */ + xaccScrubMergeLotSubSplits (to_lot, FALSE); + xaccScrubMergeLotSubSplits (from_lot, FALSE); + /* And finally set the same memo for all remaining splits + * It's a convenience for the users to identify all documents + * involved in the link. + */ + gncOwnerSetLotLinkMemo (ll_txn); + xaccAccountCommitEdit (acct); +} + +static void gncOwnerOffsetLots (GNCLot *from_lot, GNCLot *to_lot, const GncOwner *owner) +{ + gnc_numeric target_offset; + Split *split; + + /* from lot should not be a document lot because we're removing a split from there ! */ + if (gncInvoiceGetInvoiceFromLot (from_lot)) + { + PWARN ("from_lot %p is a document lot. That is not allowed in gncOwnerOffsetLots", from_lot); + return; + } + + /* Get best matching split from from_lot to offset to_lot */ + target_offset = gnc_lot_get_balance (to_lot); + if (gnc_numeric_zero_p (target_offset)) + return; // to_lot is already balanced, nothing more to do + + split = gncOwnerFindOffsettingSplit (from_lot, target_offset); + if (!split) + return; // No suitable offsetting split found, nothing more to do + + /* If the offsetting split is bigger than the amount needed to balance + * to_lot, reduce the split so its reduced value closes to_lot exactly. + * Note the negation in the reduction function. The split must be of + * opposite sign of to_lot's balance in order to be able to close it. + */ + if (gnc_numeric_compare (gnc_numeric_abs (xaccSplitGetValue (split)), + gnc_numeric_abs (target_offset)) > 0) + gncOwnerReduceSplitTo (split, gnc_numeric_neg (target_offset)); + + /* Move the reduced split from from_lot to to_lot */ + gnc_lot_add_split (to_lot, split); + +} + void gncOwnerAutoApplyPaymentsWithLots (const GncOwner *owner, GList *lots) { - GList *base_iter; + GList *left_iter; /* General note: in the code below the term "payment" can * both mean a true payment or a document of @@ -850,164 +1212,126 @@ void gncOwnerAutoApplyPaymentsWithLots (const GncOwner *owner, GList *lots) if (!owner) return; if (!lots) return; - for (base_iter = lots; base_iter; base_iter = base_iter->next) + for (left_iter = lots; left_iter; left_iter = left_iter->next) { - GNCLot *base_lot = base_iter->data; - QofBook *book; + GNCLot *left_lot = left_iter->data; + gnc_numeric left_lot_bal; + gboolean left_lot_has_doc; + gboolean left_modified = FALSE; Account *acct; - const gchar *name; - GList *lot_list, *lot_iter; - Transaction *txn = NULL; - gnc_numeric base_lot_bal, val_to_pay, val_paid = { 0, 1 }; - gboolean base_bal_is_pos; - const gchar *action, *memo; + GList *right_iter; /* Only attempt to apply payments to open lots. * Note that due to the iterative nature of this function lots - * in the list may become closed before they are evaluated as + * in the list may become empty/closed before they are evaluated as * base lot, so we should check this for each lot. */ - base_lot_bal = gnc_lot_get_balance (base_lot); - if (gnc_numeric_zero_p (base_lot_bal)) + if (!left_lot) + continue; + if (gnc_lot_count_splits (left_lot) == 0) + { + gnc_lot_destroy (left_lot); + left_iter->data = NULL; + continue; + } + if (gnc_lot_is_closed (left_lot)) continue; - book = gnc_lot_get_book (base_lot); - acct = gnc_lot_get_account (base_lot); - name = gncOwnerGetName (gncOwnerGetEndOwner (owner)); - lot_list = base_iter->next; + acct = gnc_lot_get_account (left_lot); + xaccAccountBeginEdit (acct); - /* Strings used when creating splits later on. */ - action = _("Lot Link"); - memo = _("Internal link between invoice and payment lots"); + left_lot_bal = gnc_lot_get_balance (left_lot); + left_lot_has_doc = (gncInvoiceGetInvoiceFromLot (left_lot) != NULL); - /* Note: to balance the lot the payment to assign - * must have the opposite sign of the existing lot balance */ - val_to_pay = gnc_numeric_neg (base_lot_bal); - base_bal_is_pos = gnc_numeric_positive_p (base_lot_bal); - - - /* Create splits in a linking transaction between lots until - * - either the invoice lot is balanced - * - or there are no more balancing lots. + /* Attempt to offset left_lot with any of the remaining lots. To do so + * iterate over the remaining lots adding lot links or moving payments + * around. */ - for (lot_iter = lot_list; lot_iter; lot_iter = lot_iter->next) + for (right_iter = left_iter->next; right_iter; right_iter = right_iter->next) { - gnc_numeric payment_lot_balance; - Split *split; - Account *bal_acct; - gnc_numeric split_amt; - - GNCLot *balancing_lot = lot_iter->data; + GNCLot *right_lot = right_iter->data; + gnc_numeric right_lot_bal; + gboolean right_lot_has_doc; /* Only attempt to use open lots to balance the base lot. * Note that due to the iterative nature of this function lots - * in the list may become closed before they are evaluated as + * in the list may become empty/closed before they are evaluated as * base lot, so we should check this for each lot. */ - if (gnc_lot_is_closed (balancing_lot)) + if (!right_lot) + continue; + if (gnc_lot_count_splits (right_lot) == 0) + { + gnc_lot_destroy (right_lot); + right_iter->data = NULL; + continue; + } + if (gnc_lot_is_closed (right_lot)) continue; /* Balancing transactions for invoice/payments can only happen * in the same account. */ - bal_acct = gnc_lot_get_account (balancing_lot); - if (acct != bal_acct) + if (acct != gnc_lot_get_account (right_lot)) continue; - payment_lot_balance = gnc_lot_get_balance (balancing_lot); /* Only attempt to balance if the base lot and balancing lot are * of the opposite sign. (Otherwise we would increase the balance * of the lot - Duh */ - if (base_bal_is_pos == gnc_numeric_positive_p (payment_lot_balance)) + right_lot_bal = gnc_lot_get_balance (right_lot); + if (gnc_numeric_positive_p (left_lot_bal) == gnc_numeric_positive_p (right_lot_bal)) continue; - /* - * If there is less to pay than there's open in the lot; we're done -- apply the base_lot_vale. - * Note that payment_value and balance are opposite in sign, so we have to compare absolute values here - * - * Otherwise, apply the balance, subtract that from the payment_value, - * and move on to the next one. + /* Ok we found two lots than can (partly) offset each other. + * Depending on the lot types, a different action is needed to accomplish this. + * 1. Both lots are document lots (invoices/credit notes) + * -> Create a lot linking transaction between the lots + * 2. Both lots are payment lots (lots without a document attached) + * -> Use part of the bigger lot to the close the smaller lot + * 3. One document lot with one payment lot + * -> Use (part of) the payment to offset (part of) the document lot, + * Which one will be closed depends on which is the bigger one */ - if (gnc_numeric_compare (gnc_numeric_abs (val_to_pay), gnc_numeric_abs (payment_lot_balance)) <= 0) + right_lot_has_doc = (gncInvoiceGetInvoiceFromLot (right_lot) != NULL); + if (left_lot_has_doc && right_lot_has_doc) + gncOwnerCreateLotLink (left_lot, right_lot, owner); + else if (!left_lot_has_doc && !right_lot_has_doc) { - /* abs(val_to_pay) <= abs(balance) */ - split_amt = val_to_pay; + gint cmp = gnc_numeric_compare (gnc_numeric_abs (left_lot_bal), + gnc_numeric_abs (right_lot_bal)); + if (cmp >= 0) + gncOwnerOffsetLots (left_lot, right_lot, owner); + else + gncOwnerOffsetLots (right_lot, left_lot, owner); } else { - /* abs(val_to_pay) > abs(balance) - * Remember payment_value and balance are opposite in sign, - * and we want a payment to neutralize the current balance - * so we need to negate here */ - split_amt = payment_lot_balance; + GNCLot *doc_lot = left_lot_has_doc ? left_lot : right_lot; + GNCLot *pay_lot = left_lot_has_doc ? right_lot : left_lot; + // Ok, let's try to move a payment from pay_lot to doc_lot + gncOwnerOffsetLots (pay_lot, doc_lot, owner); } - /* If not created yet, create a new transaction linking - * the base lot and the balancing lot(s) */ - if (!txn) + /* If we get here, then right_lot was modified + * If the lot has a document, send an event for send an event for it as well + * so it gets potentially updated as paid */ + { - Timespec ts = xaccTransRetDatePostedTS (xaccSplitGetParent (gnc_lot_get_latest_split (base_lot))); - - xaccAccountBeginEdit (acct); - - txn = xaccMallocTransaction (book); - xaccTransBeginEdit (txn); - - xaccTransSetDescription (txn, name ? name : ""); - xaccTransSetCurrency (txn, xaccAccountGetCommodity(acct)); - xaccTransSetDateEnteredSecs (txn, gnc_time (NULL)); - xaccTransSetDatePostedTS (txn, &ts); - xaccTransSetTxnType (txn, TXN_TYPE_LINK); - } - - /* Create the split for this link in current balancing lot */ - split = xaccMallocSplit (book); - xaccSplitSetMemo (split, memo); - /* set Action using utility function */ - gnc_set_num_action (NULL, split, NULL, action); - xaccAccountInsertSplit (acct, split); - xaccTransAppendSplit (txn, split); - xaccSplitSetBaseValue (split, gnc_numeric_neg (split_amt), xaccAccountGetCommodity(acct)); - gnc_lot_add_split (balancing_lot, split); - - /* If the balancing lot was linked to a document (invoice/credit note), - * send an event for it as well so it gets potentially updated as paid */ - { - GncInvoice *this_invoice = gncInvoiceGetInvoiceFromLot(balancing_lot); + GncInvoice *this_invoice = gncInvoiceGetInvoiceFromLot(right_lot); if (this_invoice) qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL); } - - val_paid = gnc_numeric_add (val_paid, split_amt, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); - val_to_pay = gnc_numeric_sub (val_to_pay, split_amt, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); - if (gnc_numeric_zero_p (val_to_pay)) - break; + left_modified = TRUE; } - - /* If the above loop managed to create a transaction and some balancing splits, - * create the final split for the link transaction in the base lot */ - if (txn) + /* If left_lot was modified and the lot has a document, + * send an event for send an event for it as well + * so it gets potentially updated as paid */ + if (left_modified) { - GncInvoice *this_invoice; - Split *split = xaccMallocSplit (book); - - xaccSplitSetMemo (split, memo); - /* set Action with utiltity function */ - gnc_set_num_action (NULL, split, NULL, action); - xaccAccountInsertSplit (acct, split); - xaccTransAppendSplit (txn, split); - xaccSplitSetBaseValue (split, val_paid, xaccAccountGetCommodity(acct)); - gnc_lot_add_split (base_lot, split); - - xaccTransCommitEdit (txn); - xaccAccountCommitEdit (acct); - - /* If the base lot was linked to a document (invoice/credit note), - * send an event for it as well so it gets potentially updated as paid */ - this_invoice = gncInvoiceGetInvoiceFromLot(base_lot); + GncInvoice *this_invoice = gncInvoiceGetInvoiceFromLot(left_lot); if (this_invoice) qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL); - } + xaccAccountCommitEdit (acct); } } diff --git a/src/engine/gncOwner.h b/src/engine/gncOwner.h index 0874102fca..3490c341c2 100644 --- a/src/engine/gncOwner.h +++ b/src/engine/gncOwner.h @@ -263,6 +263,34 @@ gncOwnerApplyPayment (const GncOwner *owner, Transaction *txn, GList *lots, gnc_numeric amount, gnc_numeric exch, Timespec date, const char *memo, const char *num, gboolean auto_pay); +/** Helper function to find a split in lot that best offsets target_value + * Obviously it should be of opposite sign. + * If there are more splits of opposite sign the following + * criteria are used in order of preference: + * 1. exact match in abs value is preferred over larger abs value + * 2. larger abs value is preferred over smaller abs value + * 3. if previous and new candidate are in the same value category, + * prefer real payment splits over lot link splits + * 4. if previous and new candiate are of same split type + * prefer biggest abs value. + */ +Split *gncOwnerFindOffsettingSplit (GNCLot *pay_lot, gnc_numeric target_value); + +/** Helper function to reduce the value of a split to target_value. To make + * sure the split's parent transaction remains balanced a second split + * will be created with the remainder. Similarly if the split was part of a + * (business) lot, the remainder split will be added to the same lot to + * keep the lot's balance unchanged. + */ +gboolean gncOwnerReduceSplitTo (Split *split, gnc_numeric target_value); + +/** To help a user understand what a lot link transaction does, + * we set the memo to name all documents involved in the link. + * The function below calculates this memo and sets it for + * all splits in the lot link transaction. + */ +void gncOwnerSetLotLinkMemo (Transaction *ll_txn); + /** Returns a GList of account-types based on the owner type */ GList * gncOwnerGetAccountTypesList (const GncOwner *owner); diff --git a/src/gnome-utils/gnc-main-window.c b/src/gnome-utils/gnc-main-window.c index 923c877565..1504b86177 100644 --- a/src/gnome-utils/gnc-main-window.c +++ b/src/gnome-utils/gnc-main-window.c @@ -130,7 +130,6 @@ static void gnc_main_window_destroy (GtkObject *object); static void gnc_main_window_setup_window (GncMainWindow *window); static void gnc_window_main_window_init (GncWindowIface *iface); -static gboolean main_window_find_tab_event (GncMainWindow *window, GncPluginPage *page, GtkWidget **event_p); #ifndef MAC_INTEGRATION static void gnc_main_window_update_all_menu_items (void); #endif @@ -2009,31 +2008,11 @@ static void gnc_main_window_update_tab_color_one_page (GncPluginPage *page, gpointer user_data) { - GncMainWindow *window = user_data; - GncMainWindowPrivate *priv; const gchar *color_string; - GdkColor tab_color; - GtkWidget *event_box; ENTER("page %p", page); - - priv = GNC_MAIN_WINDOW_GET_PRIVATE(window); - - /* Get the event box to update the tab */ - main_window_find_tab_event(window, page, &event_box); - color_string = gnc_plugin_page_get_page_color(page); - if (color_string == NULL) color_string = ""; - if (gdk_color_parse(color_string, &tab_color) && priv->show_color_tabs) - { - gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, &tab_color); - gtk_widget_modify_bg(event_box, GTK_STATE_ACTIVE, &tab_color); - } - else - { - gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, NULL); - gtk_widget_modify_bg(event_box, GTK_STATE_ACTIVE, NULL); - } + main_window_update_page_color (page, color_string); LEAVE(" "); } @@ -2142,7 +2121,7 @@ main_window_find_tab_items (GncMainWindow *window, GtkWidget **entry_p) { GncMainWindowPrivate *priv; - GtkWidget *tab_hbox, *widget, *event_box; + GtkWidget *tab_hbox, *widget, *tab_widget; GList *children, *tmp; ENTER("window %p, page %p, label_p %p, entry_p %p", @@ -2156,10 +2135,17 @@ main_window_find_tab_items (GncMainWindow *window, return FALSE; } - event_box = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook), + tab_widget = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook), page->notebook_page); - - tab_hbox = gtk_bin_get_child(GTK_BIN(event_box)); + if (GTK_IS_EVENT_BOX (tab_widget)) + tab_hbox = gtk_bin_get_child(GTK_BIN(tab_widget)); + else if (GTK_IS_HBOX (tab_widget)) + tab_hbox = tab_widget; + else + { + PWARN ("Unknown widget for tab label %p", tab_widget); + return FALSE; + } children = gtk_container_get_children(GTK_CONTAINER(tab_hbox)); for (tmp = children; tmp; tmp = g_list_next(tmp)) @@ -2181,16 +2167,15 @@ main_window_find_tab_items (GncMainWindow *window, } static gboolean -main_window_find_tab_event (GncMainWindow *window, - GncPluginPage *page, - GtkWidget **event_p) +main_window_find_tab_widget (GncMainWindow *window, + GncPluginPage *page, + GtkWidget **widget_p) { GncMainWindowPrivate *priv; - GtkWidget *event_box; - ENTER("window %p, page %p, event %p", - window, page, event_p); - *event_p = NULL; + ENTER("window %p, page %p, widget %p", + window, page, widget_p); + *widget_p = NULL; if (!page->notebook_page) { @@ -2199,17 +2184,11 @@ main_window_find_tab_event (GncMainWindow *window, } priv = GNC_MAIN_WINDOW_GET_PRIVATE(window); - event_box = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook), + *widget_p = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook), page->notebook_page); - if (GTK_IS_EVENT_BOX(event_box)) - { - *event_p = event_box; - LEAVE("event %p", *event_p); - return (TRUE); - } - LEAVE("event %p", *event_p); - return (FALSE); + LEAVE("widget %p", *widget_p); + return TRUE; } void @@ -2218,7 +2197,7 @@ main_window_update_page_name (GncPluginPage *page, { GncMainWindow *window; GncMainWindowPrivate *priv; - GtkWidget *label, *entry, *event_box; + GtkWidget *label, *entry; gchar *name, *old_page_name, *old_page_long_name; ENTER(" "); @@ -2264,14 +2243,15 @@ main_window_update_page_name (GncPluginPage *page, { gchar *new_page_long_name; gint string_position; + GtkWidget *tab_widget; string_position = strlen(old_page_long_name) - strlen(old_page_name); new_page_long_name = g_strconcat(g_strndup(old_page_long_name, string_position), name, NULL); gnc_plugin_page_set_page_long_name(page, new_page_long_name); - if (main_window_find_tab_event(window, page, &event_box)) - gtk_widget_set_tooltip_text(event_box, new_page_long_name); + if (main_window_find_tab_widget(window, page, &tab_widget)) + gtk_widget_set_tooltip_text(tab_widget, new_page_long_name); g_free(new_page_long_name); } @@ -2300,44 +2280,55 @@ main_window_update_page_color (GncPluginPage *page, { GncMainWindow *window; GncMainWindowPrivate *priv; - GtkWidget *event_box; + GtkWidget *tab_widget; GdkColor tab_color; - gchar *color_string; + gchar *color_string = NULL; + gboolean want_color = FALSE; ENTER(" "); - if ((color_in == NULL) || (*color_in == '\0')) - { - LEAVE("no string"); - return; - } - color_string = g_strstrip(g_strdup(color_in)); + if (color_in) + color_string = g_strstrip(g_strdup(color_in)); - /* Optimization, if the color hasn't changed, don't update. */ - if (*color_string == '\0' || 0 == g_strcmp0(color_string, gnc_plugin_page_get_page_color(page))) - { - g_free(color_string); - LEAVE("empty string or color unchanged"); - return; - } + if (color_string && *color_string != '\0') + want_color = TRUE; /* Update the plugin */ window = GNC_MAIN_WINDOW(page->window); - gnc_plugin_page_set_page_color(page, color_string); - - priv = GNC_MAIN_WINDOW_GET_PRIVATE(window); + if (want_color) + gnc_plugin_page_set_page_color(page, color_string); + else + gnc_plugin_page_set_page_color(page, NULL); /* Update the notebook tab */ - main_window_find_tab_event(window, page, &event_box); + main_window_find_tab_widget (window, page, &tab_widget); + priv = GNC_MAIN_WINDOW_GET_PRIVATE(window); - if (gdk_color_parse(color_string, &tab_color) && priv->show_color_tabs) + if (want_color && gdk_color_parse(color_string, &tab_color) && priv->show_color_tabs) { - gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, &tab_color); - gtk_widget_modify_bg(event_box, GTK_STATE_ACTIVE, &tab_color); + if (!GTK_IS_EVENT_BOX (tab_widget)) + { + GtkWidget *event_box = gtk_event_box_new (); + g_object_ref (tab_widget); + gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook), + page->notebook_page, event_box); + gtk_container_add (GTK_CONTAINER(event_box), tab_widget); + g_object_unref (tab_widget); + tab_widget = event_box; + } + gtk_widget_modify_bg(tab_widget, GTK_STATE_NORMAL, &tab_color); + gtk_widget_modify_bg(tab_widget, GTK_STATE_ACTIVE, &tab_color); } else { - gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, NULL); - gtk_widget_modify_bg(event_box, GTK_STATE_ACTIVE, NULL); + if (GTK_IS_EVENT_BOX (tab_widget)) + { + GtkWidget *tab_hbox = gtk_bin_get_child(GTK_BIN(tab_widget)); + g_object_ref (tab_hbox); + gtk_container_remove (GTK_CONTAINER(tab_widget), tab_hbox); + gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook), + page->notebook_page, tab_hbox); + g_object_unref (tab_hbox); + } } g_free(color_string); LEAVE("done"); @@ -2882,7 +2873,7 @@ gnc_main_window_open_page (GncMainWindow *window, { GncMainWindowPrivate *priv; GtkWidget *tab_hbox; - GtkWidget *label, *entry, *event_box; + GtkWidget *label, *entry; const gchar *icon, *text, *color_string; GtkWidget *image; GList *tmp; @@ -2955,27 +2946,10 @@ gnc_main_window_open_page (GncMainWindow *window, else gtk_box_pack_start (GTK_BOX (tab_hbox), label, TRUE, TRUE, 0); - event_box = gtk_event_box_new(); - gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box), TRUE); - gtk_widget_show(event_box); - gtk_container_add(GTK_CONTAINER(event_box), tab_hbox); - color_string = gnc_plugin_page_get_page_color(page); - if (color_string == NULL) color_string = ""; - if (gdk_color_parse(color_string, &tab_color) && priv->show_color_tabs) - { - gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, &tab_color); - gtk_widget_modify_bg(event_box, GTK_STATE_ACTIVE, &tab_color); - } - else - { - gtk_widget_modify_bg(event_box, GTK_STATE_NORMAL, NULL); - gtk_widget_modify_bg(event_box, GTK_STATE_ACTIVE, NULL); - } - text = gnc_plugin_page_get_page_long_name(page); if (text) { - gtk_widget_set_tooltip_text(event_box, text); + gtk_widget_set_tooltip_text(tab_hbox, text); } entry = gtk_entry_new(); @@ -3029,7 +3003,10 @@ gnc_main_window_open_page (GncMainWindow *window, /* * Now install it all in the window. */ - gnc_main_window_connect(window, page, event_box, label); + gnc_main_window_connect(window, page, tab_hbox, label); + + color_string = gnc_plugin_page_get_page_color(page); + main_window_update_page_color (page, color_string); LEAVE(""); } diff --git a/src/gnome-utils/gnc-plugin-page.c b/src/gnome-utils/gnc-plugin-page.c index 86a803b606..55b4dc91e5 100644 --- a/src/gnome-utils/gnc-plugin-page.c +++ b/src/gnome-utils/gnc-plugin-page.c @@ -868,7 +868,8 @@ gnc_plugin_page_set_page_color (GncPluginPage *page, const gchar *color) priv = GNC_PLUGIN_PAGE_GET_PRIVATE(page); if (priv->page_color) g_free(priv->page_color); - priv->page_color = g_strdup(color); + if (color) + priv->page_color = g_strdup(color); } diff --git a/src/gnome-utils/gnc-tree-view.h b/src/gnome-utils/gnc-tree-view.h index 6cef2a6600..e9b56da9bc 100644 --- a/src/gnome-utils/gnc-tree-view.h +++ b/src/gnome-utils/gnc-tree-view.h @@ -259,7 +259,7 @@ gnc_tree_view_add_date_column (GncTreeView *view, * column used to determine the foreground color of any text in this * column. It should be used to display negative numbers in red. * Use GNC_TREE_VIEW_COLUMN_COLOR_NONE if the text in this column - * should always be displayed in black. + * should always be displayed in the default theme color for text. * * @param model_visibility_column The index of the GtkTreeModel data * column used to determine whether or not a checkbox for each row diff --git a/src/gnome/assistant-acct-period.c b/src/gnome/assistant-acct-period.c index f2ee55daa4..e28a99ccd3 100644 --- a/src/gnome/assistant-acct-period.c +++ b/src/gnome/assistant-acct-period.c @@ -40,7 +40,6 @@ #include "Recurrence.h" #include "Query.h" #include "Scrub.h" -#include "Scrub3.h" #include "Transaction.h" #include "dialog-utils.h" #include "assistant-acct-period.h" diff --git a/src/gnome/dialog-lot-viewer.c b/src/gnome/dialog-lot-viewer.c index 0ae82ee831..a2558be7df 100644 --- a/src/gnome/dialog-lot-viewer.c +++ b/src/gnome/dialog-lot-viewer.c @@ -38,6 +38,7 @@ #include "qof.h" #include "gnc-lot.h" #include "Scrub3.h" +#include "ScrubBusiness.h" #include "Transaction.h" #include "engine-helpers.h" #include "gncInvoice.h" @@ -750,14 +751,20 @@ lv_response_cb (GtkDialog *dialog, gint response, gpointer data) case RESPONSE_SCRUB_LOT: if (NULL == lot) return; - xaccScrubLot (lot); + if (xaccAccountIsAPARType (xaccAccountGetType(lv->account))) + gncScrubBusinessLot (lot); + else + xaccScrubLot (lot); gnc_lot_viewer_fill (lv); lv_show_splits_in_lot (lv); break; case RESPONSE_SCRUB_ACCOUNT: gnc_suspend_gui_refresh (); - xaccAccountScrubLots (lv->account); + if (xaccAccountIsAPARType (xaccAccountGetType(lv->account))) + gncScrubBusinessAccountLots (lv->account); + else + xaccAccountScrubLots (lv->account); gnc_resume_gui_refresh (); gnc_lot_viewer_fill (lv); lv_show_splits_free (lv); diff --git a/src/gnome/gnc-plugin-page-account-tree.c b/src/gnome/gnc-plugin-page-account-tree.c index 9c3df362e5..5e396b18fa 100644 --- a/src/gnome/gnc-plugin-page-account-tree.c +++ b/src/gnome/gnc-plugin-page-account-tree.c @@ -42,6 +42,7 @@ #include "Scrub.h" #include "Scrub3.h" +#include "ScrubBusiness.h" #include "Transaction.h" #include "dialog-account.h" #include "dialog-transfer.h" @@ -1568,10 +1569,13 @@ gnc_plugin_page_account_tree_cmd_scrub (GtkAction *action, GncPluginPageAccountT xaccAccountScrubOrphans (account); xaccAccountScrubImbalance (account); - // XXX: Lots are disabled + // XXX: Lots/capital gains scrubbing is disabled if (g_getenv("GNC_AUTO_SCRUB_LOTS") != NULL) xaccAccountScrubLots(account); + gncScrubBusinessAccountLots(account); + + gnc_resume_gui_refresh (); } @@ -1587,10 +1591,12 @@ gnc_plugin_page_account_tree_cmd_scrub_sub (GtkAction *action, GncPluginPageAcco xaccAccountTreeScrubOrphans (account); xaccAccountTreeScrubImbalance (account); - // XXX: Lots are disabled + // XXX: Lots/capital gains scrubbing is disabled if (g_getenv("GNC_AUTO_SCRUB_LOTS") != NULL) xaccAccountTreeScrubLots(account); + gncScrubBusinessAccountTreeLots(account); + gnc_resume_gui_refresh (); } @@ -1603,10 +1609,12 @@ gnc_plugin_page_account_tree_cmd_scrub_all (GtkAction *action, GncPluginPageAcco xaccAccountTreeScrubOrphans (root); xaccAccountTreeScrubImbalance (root); - // XXX: Lots are disabled + // XXX: Lots/capital gains scrubbing is disabled if (g_getenv("GNC_AUTO_SCRUB_LOTS") != NULL) xaccAccountTreeScrubLots(root); + gncScrubBusinessAccountTreeLots(root); + gnc_resume_gui_refresh (); } diff --git a/src/gnome/gnc-plugin-page-register.c b/src/gnome/gnc-plugin-page-register.c index bdbc4da9cc..3acfd35f04 100644 --- a/src/gnome/gnc-plugin-page-register.c +++ b/src/gnome/gnc-plugin-page-register.c @@ -74,6 +74,7 @@ #include "gnucash-sheet.h" #include "dialog-lot-viewer.h" #include "Scrub.h" +#include "ScrubBusiness.h" #include "qof.h" #include "window-reconcile.h" #include "window-autoclear.h" @@ -3691,6 +3692,8 @@ gnc_plugin_page_register_cmd_scrub_current (GtkAction *action, Query *query; Account *root; Transaction *trans; + Split *split; + GNCLot *lot; SplitRegister *reg; g_return_if_fail(GNC_IS_PLUGIN_PAGE_REGISTER(plugin_page)); @@ -3717,6 +3720,11 @@ gnc_plugin_page_register_cmd_scrub_current (GtkAction *action, root = gnc_get_current_root_account(); xaccTransScrubOrphans(trans); xaccTransScrubImbalance(trans, root, NULL); + + split = gnc_split_register_get_current_split (reg); + lot = xaccSplitGetLot (split); + if (lot && xaccAccountIsAPARType (xaccAccountGetType (xaccSplitGetAccount (split)))) + gncScrubBusinessLot (lot); gnc_resume_gui_refresh(); LEAVE(" "); } @@ -3729,6 +3737,7 @@ gnc_plugin_page_register_cmd_scrub_all (GtkAction *action, Query *query; Account *root; Transaction *trans; + GNCLot *lot; Split *split; GList *node; @@ -3754,6 +3763,10 @@ gnc_plugin_page_register_cmd_scrub_all (GtkAction *action, xaccTransScrubOrphans(trans); xaccTransScrubImbalance(trans, root, NULL); + + lot = xaccSplitGetLot (split); + if (lot && xaccAccountIsAPARType (xaccAccountGetType (xaccSplitGetAccount (split)))) + gncScrubBusinessLot (lot); } gnc_resume_gui_refresh(); diff --git a/src/gnome/window-autoclear.c b/src/gnome/window-autoclear.c index 6b7be9de8e..8a1c500432 100644 --- a/src/gnome/window-autoclear.c +++ b/src/gnome/window-autoclear.c @@ -26,7 +26,6 @@ #include #include "Scrub.h" -#include "Scrub3.h" #include "dialog-account.h" #include "dialog-transfer.h" #include "dialog-utils.h" diff --git a/src/register/ledger-core/split-register-model.c b/src/register/ledger-core/split-register-model.c index 5e5a4218e6..262f79b10e 100644 --- a/src/register/ledger-core/split-register-model.c +++ b/src/register/ledger-core/split-register-model.c @@ -39,18 +39,6 @@ #include "split-register-p.h" #include "engine-helpers.h" - -static SplitRegisterColors reg_colors = -{ - 0x96B183, - 0xBFDEB9, - 0xF6FFDA, - 0xFFEF98, - 0xFFEF98, - 0xEDE7D3, - 0xFFEF98, -}; - /* This static indicates the debugging module that this .o belongs to. */ static QofLogModule log_module = GNC_MOD_LEDGER; @@ -513,116 +501,33 @@ get_trans_total_balance (SplitRegister *reg, Transaction *trans) } static guint32 -gnc_split_register_get_shares_fg_color (VirtualLocation virt_loc, - gpointer user_data) +gnc_split_register_get_color_internal (VirtualLocation virt_loc, + SplitRegister *reg, + const guint32 *color_table, + gboolean foreground) { - SplitRegister *reg = user_data; - const guint32 black = 0x000000; - const guint32 red = 0xff0000; - const char * cell_name; - gboolean is_current; - gnc_numeric shares; - Split *split; - - if (!use_red_for_negative) - return black; - - split = gnc_split_register_get_split (reg, virt_loc.vcell_loc); - if (!split) - return black; - - cell_name = gnc_table_get_cell_name (reg->table, virt_loc); - - is_current = virt_cell_loc_equal (reg->table->current_cursor_loc.vcell_loc, - virt_loc.vcell_loc); - - if (gnc_cell_name_equal (cell_name, TSHRS_CELL)) - shares = get_trans_total_amount (reg, xaccSplitGetParent (split)); - else if (is_current) - shares = gnc_price_cell_get_value - ((PriceCell *) gnc_table_layout_get_cell (reg->table->layout, - SHRS_CELL)); - else - shares = xaccSplitGetAmount (split); - - if (gnc_numeric_negative_p (shares)) - return red; - - return black; -} - -static guint32 -gnc_split_register_get_balance_fg_color (VirtualLocation virt_loc, - gpointer user_data) -{ - SplitRegister *reg = user_data; - const guint32 black = 0x000000; - const guint32 red = 0xff0000; - const char * cell_name; - gnc_numeric balance; - Split *split; - - if (!use_red_for_negative) - return black; - - split = gnc_split_register_get_split (reg, virt_loc.vcell_loc); - if (!split) - return black; - - cell_name = gnc_table_get_cell_name (reg->table, virt_loc); - - if (gnc_cell_name_equal (cell_name, BALN_CELL)) - balance = xaccSplitGetBalance (split); - else if (gnc_cell_name_equal (cell_name, RBALN_CELL)) - balance = gnc_split_register_get_rbaln (virt_loc, user_data, TRUE); - else - balance = get_trans_total_balance (reg, xaccSplitGetParent (split)); - - { - Account *account; - - account = xaccSplitGetAccount (split); - - if (gnc_reverse_balance (account)) - balance = gnc_numeric_neg (balance); - } - - if (gnc_numeric_negative_p (balance)) - return red; - - return black; -} - -static guint32 -gnc_split_register_get_bg_color (VirtualLocation virt_loc, - gboolean *hatching, - gpointer user_data) -{ - SplitRegister *reg = user_data; const char *cursor_name; VirtualCell *vcell; - guint32 bg_color; gboolean is_current; gboolean double_alternate_virt; + guint32 colorbase = 0; /* By default return background colors */ - if (hatching) - *hatching = FALSE; - - bg_color = 0xffffff; /* white */ + if (foreground) + colorbase = COLOR_UNKNOWN_FG; /* a bit of enum arithmetic */ if (!reg) - return bg_color; + return color_table[colorbase + COLOR_UNKNOWN_BG]; if (gnc_table_virtual_location_in_header (reg->table, virt_loc)) - return reg_colors.header_bg_color; + return color_table[colorbase + COLOR_HEADER_BG]; vcell = gnc_table_get_virtual_cell (reg->table, virt_loc.vcell_loc); if (!vcell || !vcell->cellblock) - return bg_color; + return color_table[colorbase + COLOR_UNKNOWN_BG]; if ((virt_loc.phys_col_offset < vcell->cellblock->start_col) || (virt_loc.phys_col_offset > vcell->cellblock->stop_col)) - return bg_color; + return color_table[colorbase + COLOR_UNKNOWN_BG]; is_current = virt_cell_loc_equal (reg->table->current_cursor_loc.vcell_loc, virt_loc.vcell_loc); @@ -634,11 +539,11 @@ gnc_split_register_get_bg_color (VirtualLocation virt_loc, { if (is_current) return vcell->start_primary_color ? - reg_colors.primary_active_bg_color : - reg_colors.secondary_active_bg_color; + color_table[colorbase + COLOR_PRIMARY_BG_ACTIVE] : + color_table[colorbase + COLOR_SECONDARY_BG_ACTIVE]; return vcell->start_primary_color ? - reg_colors.primary_bg_color : reg_colors.secondary_bg_color; + color_table[colorbase + COLOR_PRIMARY_BG] : color_table[colorbase + COLOR_SECONDARY_BG]; } if (g_strcmp0 (cursor_name, CURSOR_DOUBLE_JOURNAL) == 0 || @@ -652,119 +557,135 @@ gnc_split_register_get_bg_color (VirtualLocation virt_loc, { if (double_alternate_virt) return vcell->start_primary_color ? - reg_colors.primary_active_bg_color : - reg_colors.secondary_active_bg_color; + color_table[colorbase + COLOR_PRIMARY_BG_ACTIVE] : + color_table[colorbase + COLOR_SECONDARY_BG_ACTIVE]; return (virt_loc.phys_row_offset % 2 == 0) ? - reg_colors.primary_active_bg_color : - reg_colors.secondary_active_bg_color; + color_table[colorbase + COLOR_PRIMARY_BG_ACTIVE] : + color_table[colorbase + COLOR_SECONDARY_BG_ACTIVE]; } if (double_alternate_virt) return vcell->start_primary_color ? - reg_colors.primary_bg_color : - reg_colors.secondary_bg_color; + color_table[colorbase + COLOR_PRIMARY_BG] : + color_table[colorbase + COLOR_SECONDARY_BG]; return (virt_loc.phys_row_offset % 2 == 0) ? - reg_colors.primary_bg_color : - reg_colors.secondary_bg_color; + color_table[colorbase + COLOR_PRIMARY_BG] : + color_table[colorbase + COLOR_SECONDARY_BG]; } if (g_strcmp0 (cursor_name, CURSOR_SPLIT) == 0) { if (is_current) - return reg_colors.split_active_bg_color; + return color_table[colorbase + COLOR_SPLIT_BG_ACTIVE]; - return reg_colors.split_bg_color; + return color_table[colorbase + COLOR_SPLIT_BG]; } PWARN ("Unexpected cursor: %s\n", cursor_name); - return bg_color; + return color_table[colorbase + COLOR_UNKNOWN_BG]; } +static guint32 +gnc_split_register_get_fg_color_internal (VirtualLocation virt_loc, + SplitRegister *reg, + const guint32 *color_table) +{ + const guint32 red_color = color_table[COLOR_NEGATIVE]; + guint32 fg_color; + const char * cell_name; + gboolean is_current; + gnc_numeric value; + Split *split; + + fg_color = gnc_split_register_get_color_internal (virt_loc, reg, color_table, TRUE); + + if (!use_red_for_negative) + return fg_color; + + split = gnc_split_register_get_split (reg, virt_loc.vcell_loc); + if (!split) + return fg_color; + + cell_name = gnc_table_get_cell_name (reg->table, virt_loc); + + if (gnc_cell_name_equal (cell_name, TSHRS_CELL)) + value = get_trans_total_amount (reg, xaccSplitGetParent (split)); + else if (gnc_cell_name_equal (cell_name, SHRS_CELL)) + { + if (virt_cell_loc_equal (reg->table->current_cursor_loc.vcell_loc, + virt_loc.vcell_loc)) + value = gnc_price_cell_get_value + ((PriceCell *) gnc_table_layout_get_cell (reg->table->layout, + SHRS_CELL)); + else + value = xaccSplitGetAmount (split); + } + else if (gnc_cell_name_equal (cell_name, BALN_CELL)) + value = xaccSplitGetBalance (split); + else if (gnc_cell_name_equal (cell_name, RBALN_CELL)) + value = gnc_split_register_get_rbaln (virt_loc, reg, TRUE); + else if (gnc_cell_name_equal (cell_name, TBALN_CELL)) + value = get_trans_total_balance (reg, xaccSplitGetParent (split)); + + if ((gnc_cell_name_equal (cell_name, BALN_CELL)) || + (gnc_cell_name_equal (cell_name, RBALN_CELL)) || + (gnc_cell_name_equal (cell_name, TBALN_CELL))) + { + Account *account = xaccSplitGetAccount (split); + if (gnc_reverse_balance (account)) + value = gnc_numeric_neg (value); + } + + if (gnc_numeric_negative_p (value)) + return red_color; + + return fg_color; +} + +static guint32 +gnc_split_register_get_fg_color (VirtualLocation virt_loc, + gpointer user_data) +{ + SplitRegister *reg = user_data; + return gnc_split_register_get_fg_color_internal (virt_loc, reg, reg_colors_default); +} + +static guint32 +gnc_split_register_get_gtkrc_fg_color (VirtualLocation virt_loc, + gpointer user_data) +{ + SplitRegister *reg = user_data; + return gnc_split_register_get_fg_color_internal (virt_loc, reg, reg_colors_gtkrc); +} + +static guint32 +gnc_split_register_get_bg_color (VirtualLocation virt_loc, + gboolean *hatching, + gpointer user_data) +{ + SplitRegister *reg = user_data; + + if (hatching) + *hatching = FALSE; + + return gnc_split_register_get_color_internal (virt_loc, reg, reg_colors_default, FALSE); +} + + static RegisterColor gnc_split_register_get_gtkrc_bg_color (VirtualLocation virt_loc, gboolean *hatching, gpointer user_data) { SplitRegister *reg = user_data; - const char *cursor_name; - VirtualCell *vcell; - gboolean is_current; - gboolean double_alternate_virt; - if (!reg) - return COLOR_UNKNOWN; + if (hatching) + *hatching = FALSE; - if (gnc_table_virtual_location_in_header (reg->table, virt_loc)) - return COLOR_HEADER; - - vcell = gnc_table_get_virtual_cell (reg->table, virt_loc.vcell_loc); - if (!vcell || !vcell->cellblock) - return COLOR_UNKNOWN; - - if ((virt_loc.phys_col_offset < vcell->cellblock->start_col) || - (virt_loc.phys_col_offset > vcell->cellblock->stop_col)) - return COLOR_UNKNOWN; - - is_current = virt_cell_loc_equal (reg->table->current_cursor_loc.vcell_loc, - virt_loc.vcell_loc); - - cursor_name = vcell->cellblock->cursor_name; - - if (g_strcmp0 (cursor_name, CURSOR_SINGLE_JOURNAL) == 0 || - g_strcmp0 (cursor_name, CURSOR_SINGLE_LEDGER) == 0) - { - if (is_current) - return vcell->start_primary_color ? - COLOR_PRIMARY_ACTIVE : - COLOR_SECONDARY_ACTIVE; - - return vcell->start_primary_color ? - COLOR_PRIMARY : COLOR_SECONDARY; - } - - if (g_strcmp0 (cursor_name, CURSOR_DOUBLE_JOURNAL) == 0 || - g_strcmp0 (cursor_name, CURSOR_DOUBLE_JOURNAL_NUM_ACTN) == 0 || - g_strcmp0 (cursor_name, CURSOR_DOUBLE_LEDGER) == 0 || - g_strcmp0 (cursor_name, CURSOR_DOUBLE_LEDGER_NUM_ACTN) == 0) - { - double_alternate_virt = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL_REGISTER, - GNC_PREF_ALT_COLOR_BY_TRANS); - if (is_current) - { - if (double_alternate_virt) - return vcell->start_primary_color ? - COLOR_PRIMARY_ACTIVE : - COLOR_SECONDARY_ACTIVE; - - return (virt_loc.phys_row_offset % 2 == 0) ? - COLOR_PRIMARY_ACTIVE : - COLOR_SECONDARY_ACTIVE; - } - - if (double_alternate_virt) - return vcell->start_primary_color ? - COLOR_PRIMARY : - COLOR_SECONDARY; - - return (virt_loc.phys_row_offset % 2 == 0) ? - COLOR_PRIMARY : - COLOR_SECONDARY; - } - - if (g_strcmp0 (cursor_name, CURSOR_SPLIT) == 0) - { - if (is_current) - return COLOR_SPLIT_ACTIVE; - - return COLOR_SPLIT; - } - - PWARN ("Unexpected cursor: %s\n", cursor_name); - - return COLOR_UNKNOWN; + return gnc_split_register_get_color_internal (virt_loc, reg, reg_colors_gtkrc, FALSE); } static guint32 @@ -2662,19 +2583,22 @@ gnc_split_register_model_new (void) gnc_table_model_set_fg_color_handler( - model, gnc_split_register_get_shares_fg_color, SHRS_CELL); + model, gnc_split_register_get_fg_color, SHRS_CELL); gnc_table_model_set_fg_color_handler( - model, gnc_split_register_get_shares_fg_color, TSHRS_CELL); + model, gnc_split_register_get_fg_color, TSHRS_CELL); gnc_table_model_set_fg_color_handler( - model, gnc_split_register_get_balance_fg_color, BALN_CELL); + model, gnc_split_register_get_fg_color, BALN_CELL); gnc_table_model_set_fg_color_handler( - model, gnc_split_register_get_balance_fg_color, TBALN_CELL); + model, gnc_split_register_get_fg_color, TBALN_CELL); gnc_table_model_set_fg_color_handler( - model, gnc_split_register_get_balance_fg_color, RBALN_CELL); + model, gnc_split_register_get_fg_color, RBALN_CELL); + + gnc_table_model_set_fg_color_handler( + model, gnc_split_register_get_gtkrc_fg_color, "gtkrc"); gnc_table_model_set_default_bg_color_handler( diff --git a/src/register/ledger-core/split-register-model.h b/src/register/ledger-core/split-register-model.h index e3ace1be1f..1f264c408c 100644 --- a/src/register/ledger-core/split-register-model.h +++ b/src/register/ledger-core/split-register-model.h @@ -28,16 +28,4 @@ TableModel * gnc_split_register_model_new (void); TableModel * gnc_template_register_model_new (void); -typedef enum -{ - COLOR_UNKNOWN, - COLOR_HEADER, - COLOR_PRIMARY, - COLOR_PRIMARY_ACTIVE, - COLOR_SECONDARY, - COLOR_SECONDARY_ACTIVE, - COLOR_SPLIT, - COLOR_SPLIT_ACTIVE, -} RegisterColor; - #endif diff --git a/src/register/ledger-core/split-register.h b/src/register/ledger-core/split-register.h index b7ba1b421d..6c7cd441e0 100644 --- a/src/register/ledger-core/split-register.h +++ b/src/register/ledger-core/split-register.h @@ -238,20 +238,6 @@ typedef enum NUM_CURSOR_CLASSES } CursorClass; -typedef struct split_register_colors -{ - guint32 header_bg_color; - - guint32 primary_bg_color; - guint32 secondary_bg_color; - - guint32 primary_active_bg_color; - guint32 secondary_active_bg_color; - - guint32 split_bg_color; - guint32 split_active_bg_color; -} SplitRegisterColors; - /** @brief A split register created with ::gnc_split_register_new */ typedef struct split_register SplitRegister; @@ -270,7 +256,7 @@ struct split_register or split action for number field in register */ gboolean is_template; - gboolean do_auto_complete; /**< whether to use auto-competion */ + gboolean do_auto_complete; /**< whether to use auto-completion */ SRInfo * sr_info; /**< private data; outsiders should not access this */ }; diff --git a/src/register/register-core/table-allgui.c b/src/register/register-core/table-allgui.c index 3a9041f2d6..8e7a010820 100644 --- a/src/register/register-core/table-allgui.c +++ b/src/register/register-core/table-allgui.c @@ -345,19 +345,21 @@ gnc_table_get_label (Table *table, VirtualLocation virt_loc) return label; } -guint32 -gnc_table_get_fg_color (Table *table, VirtualLocation virt_loc) +static guint32 +gnc_table_get_fg_color_internal (Table *table, VirtualLocation virt_loc, + gboolean want_gtkrc) { TableGetFGColorHandler fg_color_handler; - const char *cell_name; + const char *handler_name = "gtkrc"; if (!table || !table->model) return 0x0; /* black */ - cell_name = gnc_table_get_cell_name (table, virt_loc); + if (!want_gtkrc) + handler_name = gnc_table_get_cell_name (table, virt_loc); fg_color_handler = gnc_table_model_get_fg_color_handler (table->model, - cell_name); + handler_name); if (!fg_color_handler) return 0x0; @@ -365,11 +367,24 @@ gnc_table_get_fg_color (Table *table, VirtualLocation virt_loc) } guint32 -gnc_table_get_bg_color (Table *table, VirtualLocation virt_loc, - gboolean *hatching) +gnc_table_get_fg_color (Table *table, VirtualLocation virt_loc) +{ + return gnc_table_get_fg_color_internal (table, virt_loc, FALSE); +} + +guint32 +gnc_table_get_gtkrc_fg_color (Table *table, VirtualLocation virt_loc) +{ + return gnc_table_get_fg_color_internal (table, virt_loc, TRUE); +} + +static guint32 +gnc_table_get_bg_color_internal (Table *table, VirtualLocation virt_loc, + gboolean *hatching, + gboolean want_gtkrc) { TableGetBGColorHandler bg_color_handler; - const char *cell_name; + const char *handler_name = "gtkrc"; if (hatching) *hatching = FALSE; @@ -377,10 +392,11 @@ gnc_table_get_bg_color (Table *table, VirtualLocation virt_loc, if (!table || !table->model) return 0xffffff; /* white */ - cell_name = gnc_table_get_cell_name (table, virt_loc); + if (!want_gtkrc) + handler_name = gnc_table_get_cell_name (table, virt_loc); bg_color_handler = gnc_table_model_get_bg_color_handler (table->model, - cell_name); + handler_name); if (!bg_color_handler) return 0xffffff; @@ -388,25 +404,18 @@ gnc_table_get_bg_color (Table *table, VirtualLocation virt_loc, table->model->handler_user_data); } +guint32 +gnc_table_get_bg_color (Table *table, VirtualLocation virt_loc, + gboolean *hatching) +{ + return gnc_table_get_bg_color_internal (table, virt_loc, hatching, FALSE); +} + guint32 gnc_table_get_gtkrc_bg_color (Table *table, VirtualLocation virt_loc, gboolean *hatching) { - TableGetBGColorHandler bg_color_handler; - - if (hatching) - *hatching = FALSE; - - if (!table || !table->model) - return 0xffffff; /* white */ - - bg_color_handler = gnc_table_model_get_bg_color_handler (table->model, - "gtkrc"); - if (!bg_color_handler) - return 0xffffff; - - return bg_color_handler (virt_loc, hatching, - table->model->handler_user_data); + return gnc_table_get_bg_color_internal (table, virt_loc, hatching, TRUE); } void diff --git a/src/register/register-core/table-allgui.h b/src/register/register-core/table-allgui.h index 0f7bbd44fd..2738738f08 100644 --- a/src/register/register-core/table-allgui.h +++ b/src/register/register-core/table-allgui.h @@ -156,6 +156,92 @@ struct table gpointer ui_data; }; +/** Color definitions used for table elements */ +typedef enum +{ + /* Colors used for background drawing */ + COLOR_UNKNOWN_BG, + COLOR_HEADER_BG, + COLOR_PRIMARY_BG, + COLOR_PRIMARY_BG_ACTIVE, + COLOR_SECONDARY_BG, + COLOR_SECONDARY_BG_ACTIVE, + COLOR_SPLIT_BG, + COLOR_SPLIT_BG_ACTIVE, + + /* Colors used for foreground drawing (text etc) + * ATTENTION: the background and foreground lists should have + * the same types (the same amount of entries) ! + * The code relies on this ! */ + COLOR_UNKNOWN_FG, + COLOR_HEADER_FG, + COLOR_PRIMARY_FG, + COLOR_PRIMARY_FG_ACTIVE, + COLOR_SECONDARY_FG, + COLOR_SECONDARY_FG_ACTIVE, + COLOR_SPLIT_FG, + COLOR_SPLIT_FG_ACTIVE, + + /* Other colors */ + COLOR_NEGATIVE, /* Color to use for negative numbers */ +} RegisterColor; + + + + +/* Alternative color tables to use for the register. + * The colors in this array are ordered according to the RegisterColor Enum + * Be careful to respect this order ! + */ +static const guint32 reg_colors_default [] = +{ + 0xFFFFFF, // COLOR_UNKNOWN_BG + 0x96B183, // COLOR_HEADER_BG + 0xBFDEB9, // COLOR_PRIMARY_BG + 0xFFEF98, // COLOR_PRIMARY_BG_ACTIVE + 0xF6FFDA, // COLOR_SECONDARY_BG + 0xFFEF98, // COLOR_SECONDARY_BG_ACTIVE + 0xEDE7D3, // COLOR_SPLIT_BG + 0xFFEF98, // COLOR_SPLIT_BG_ACTIVE + + 0x000000, // COLOR_UNKNOWN_FG + 0x000000, // COLOR_HEADER_FG + 0x000000, // COLOR_PRIMARY_FG + 0x000000, // COLOR_PRIMARY_FG_ACTIVE + 0x000000, // COLOR_SECONDARY_FG + 0x000000, // COLOR_SECONDARY_FG_ACTIVE + 0x000000, // COLOR_SPLIT_FG + 0x000000, // COLOR_SPLIT_FG_ACTIVE + + 0xFF0000, // COLOR_NEGATIVE +}; + +/* The colors in this array are ordered according to the RegisterColor Enum + * Be careful to respect this order ! + */ +static const guint32 reg_colors_gtkrc [] = +{ + COLOR_UNKNOWN_BG, // COLOR_UNKNOWN_BG + COLOR_HEADER_BG, // COLOR_HEADER_BG + COLOR_PRIMARY_BG, // COLOR_PRIMARY_BG + COLOR_PRIMARY_BG_ACTIVE, // COLOR_PRIMARY_BG_ACTIVE + COLOR_SECONDARY_BG, // COLOR_SECONDARY_BG + COLOR_SECONDARY_BG_ACTIVE, // COLOR_SECONDARY_BG_ACTIVE + COLOR_SPLIT_BG, // COLOR_SPLIT_BG + COLOR_SPLIT_BG_ACTIVE, // COLOR_SPLIT_BG_ACTIVE + + COLOR_UNKNOWN_FG, // COLOR_UNKNOWN_FG + COLOR_HEADER_FG, // COLOR_HEADER_FG + COLOR_PRIMARY_FG, // COLOR_PRIMARY_FG + COLOR_PRIMARY_FG_ACTIVE, // COLOR_PRIMARY_FG_ACTIVE + COLOR_SECONDARY_FG, // COLOR_SECONDARY_FG + COLOR_SECONDARY_FG_ACTIVE, // COLOR_SECONDARY_FG_ACTIVE + COLOR_SPLIT_FG, // COLOR_SPLIT_FG + COLOR_SPLIT_FG_ACTIVE, // COLOR_SPLIT_FG_ACTIVE + + COLOR_NEGATIVE, // COLOR_NEGATIVE +}; + /* Set the default gui handlers used by new tables. */ void gnc_table_set_default_gui_handlers (TableGUIHandlers *gui_handlers); @@ -210,6 +296,8 @@ CellIOFlags gnc_table_get_io_flags (Table *table, VirtualLocation virt_loc); guint32 gnc_table_get_fg_color (Table *table, VirtualLocation virt_loc); +guint32 gnc_table_get_gtkrc_fg_color (Table *table, VirtualLocation virt_loc); + guint32 gnc_table_get_bg_color (Table *table, VirtualLocation virt_loc, gboolean *hatching); guint32 gnc_table_get_gtkrc_bg_color (Table *table, VirtualLocation virt_loc, diff --git a/src/register/register-gnome/gnucash-grid.c b/src/register/register-gnome/gnucash-grid.c index d357c8f164..327210441a 100644 --- a/src/register/register-gnome/gnucash-grid.c +++ b/src/register/register-gnome/gnucash-grid.c @@ -605,17 +605,25 @@ draw_cell (GnucashGrid *grid, context = pango_layout_get_context (layout); font = pango_font_description_copy (pango_context_get_font_description (context)); - argb = gnc_table_get_fg_color (table, virt_loc); -#ifdef READONLY_LINES_WITH_CHANGED_FG_COLOR - // Are we in a read-only row? Then make the foreground color somewhat less black - if ((virt_loc.phys_row_offset == (block->style->nrows - 1)) - && (table->model->dividing_row_upper >= 0) - && (virt_loc.vcell_loc.virt_row < table->model->dividing_row_upper)) + if (grid->sheet->use_theme_colors) { - argb = inc_intensity_10percent(argb); + color_type = gnc_table_get_gtkrc_fg_color (table, virt_loc); + fg_color = get_gtkrc_color(grid->sheet, color_type); } + else + { + argb = gnc_table_get_fg_color (table, virt_loc); +#ifdef READONLY_LINES_WITH_CHANGED_FG_COLOR + // Are we in a read-only row? Then make the foreground color somewhat less black + if ((virt_loc.phys_row_offset == (block->style->nrows - 1)) + && (table->model->dividing_row_upper >= 0) + && (virt_loc.vcell_loc.virt_row < table->model->dividing_row_upper)) + { + argb = inc_intensity_10percent(argb); + } #endif - fg_color = gnucash_color_argb_to_gdk (argb); + fg_color = gnucash_color_argb_to_gdk (argb); + } gdk_gc_set_foreground (grid->gc, fg_color); diff --git a/src/register/register-gnome/gnucash-header.c b/src/register/register-gnome/gnucash-header.c index 7d902d5c88..dab6873c8d 100644 --- a/src/register/register-gnome/gnucash-header.c +++ b/src/register/register-gnome/gnucash-header.c @@ -75,7 +75,7 @@ gnc_header_draw (GnomeCanvasItem *item, GdkDrawable *drawable, VirtualLocation virt_loc; VirtualCell *vcell; CellDimensions *cd; - GdkColor *bg_color; + GdkColor *bg_color, *fg_color; int xpaint, ypaint; const char *text; CellBlock *cb; @@ -94,11 +94,15 @@ gnc_header_draw (GnomeCanvasItem *item, GdkDrawable *drawable, color_type = gnc_table_get_gtkrc_bg_color (table, virt_loc, NULL); bg_color = get_gtkrc_color(header->sheet, color_type); + color_type = gnc_table_get_gtkrc_fg_color (table, virt_loc); + fg_color = get_gtkrc_color(header->sheet, color_type); } else { argb = gnc_table_get_bg_color (table, virt_loc, NULL); bg_color = gnucash_color_argb_to_gdk (argb); + argb = gnc_table_get_fg_color (table, virt_loc); + fg_color = gnucash_color_argb_to_gdk (argb); } h = style->dimensions->height; @@ -111,7 +115,7 @@ gnc_header_draw (GnomeCanvasItem *item, GdkDrawable *drawable, style->dimensions->width, h); gdk_gc_set_line_attributes (header->gc, 1, GDK_LINE_SOLID, GDK_CAP_NOT_LAST, GDK_JOIN_MITER); - gdk_gc_set_foreground (header->gc, &gn_black); + gdk_gc_set_foreground (header->gc, fg_color); gdk_draw_rectangle (drawable, header->gc, FALSE, -x, -y, style->dimensions->width, h); @@ -121,7 +125,7 @@ gnc_header_draw (GnomeCanvasItem *item, GdkDrawable *drawable, gdk_gc_set_line_attributes (header->gc, 1, GDK_LINE_SOLID, GDK_CAP_NOT_LAST, GDK_JOIN_MITER); gdk_gc_set_background (header->gc, &gn_white); - gdk_gc_set_foreground (header->gc, &gn_black); + gdk_gc_set_foreground (header->gc, fg_color); /*font = gnucash_register_font;*/ vcell = gnc_table_get_virtual_cell diff --git a/src/register/register-gnome/gnucash-item-edit.c b/src/register/register-gnome/gnucash-item-edit.c index 3486ffbc8c..9b34102700 100644 --- a/src/register/register-gnome/gnucash-item-edit.c +++ b/src/register/register-gnome/gnucash-item-edit.c @@ -196,16 +196,20 @@ gnc_item_edit_draw_info (GncItemEdit *item_edit, int x, int y, TextDrawInfo *inf item_edit->virt_loc, &hatching); info->bg_color = get_gtkrc_color(item_edit->sheet, color_type); + color_type = gnc_table_get_gtkrc_fg_color (table, + item_edit->virt_loc); + info->fg_color = get_gtkrc_color(item_edit->sheet, color_type); } else { argb = gnc_table_get_bg_color (table, item_edit->virt_loc, &hatching); info->bg_color = gnucash_color_argb_to_gdk (argb); + argb = gnc_table_get_fg_color (table, item_edit->virt_loc); + info->fg_color = gnucash_color_argb_to_gdk (argb); } info->hatching = hatching; - info->fg_color = &gn_black; info->bg_color2 = &gn_dark_gray; info->fg_color2 = &gn_white; diff --git a/src/register/register-gnome/gnucash-sheet.c b/src/register/register-gnome/gnucash-sheet.c index 1c17b76fd0..06c639e991 100644 --- a/src/register/register-gnome/gnucash-sheet.c +++ b/src/register/register-gnome/gnucash-sheet.c @@ -2609,31 +2609,49 @@ get_gtkrc_color (GnucashSheet *sheet, { GtkWidget *widget = NULL; GtkStyle *style; - GdkColor *white; + GdkColor *white, *black, *red; GdkColor *color = NULL; white = gnucash_color_argb_to_gdk (0xFFFFFF); + black = gnucash_color_argb_to_gdk (0x000000); + red = gnucash_color_argb_to_gdk (0xFF0000); /* Hardcoded...*/ switch (field_type) { default: return white; - case COLOR_HEADER: + case COLOR_UNKNOWN_BG: + return white; + + case COLOR_UNKNOWN_FG: + return black; + + case COLOR_NEGATIVE: + return red; + + case COLOR_HEADER_BG: + case COLOR_HEADER_FG: widget = sheet->header_color; break; - case COLOR_PRIMARY: - case COLOR_PRIMARY_ACTIVE: + case COLOR_PRIMARY_BG: + case COLOR_PRIMARY_BG_ACTIVE: + case COLOR_PRIMARY_FG: + case COLOR_PRIMARY_FG_ACTIVE: widget = sheet->primary_color; break; - case COLOR_SECONDARY: - case COLOR_SECONDARY_ACTIVE: + case COLOR_SECONDARY_BG: + case COLOR_SECONDARY_BG_ACTIVE: + case COLOR_SECONDARY_FG: + case COLOR_SECONDARY_FG_ACTIVE: widget = sheet->secondary_color; break; - case COLOR_SPLIT: - case COLOR_SPLIT_ACTIVE: + case COLOR_SPLIT_BG: + case COLOR_SPLIT_BG_ACTIVE: + case COLOR_SPLIT_FG: + case COLOR_SPLIT_FG_ACTIVE: widget = sheet->split_color; break; } @@ -2647,18 +2665,31 @@ get_gtkrc_color (GnucashSheet *sheet, default: return white; - case COLOR_HEADER: - case COLOR_PRIMARY: - case COLOR_SECONDARY: - case COLOR_SPLIT: + case COLOR_HEADER_BG: + case COLOR_PRIMARY_BG: + case COLOR_SECONDARY_BG: + case COLOR_SPLIT_BG: color = &style->base[GTK_STATE_NORMAL]; break; - case COLOR_PRIMARY_ACTIVE: - case COLOR_SECONDARY_ACTIVE: - case COLOR_SPLIT_ACTIVE: + case COLOR_PRIMARY_BG_ACTIVE: + case COLOR_SECONDARY_BG_ACTIVE: + case COLOR_SPLIT_BG_ACTIVE: color = &style->base[GTK_STATE_SELECTED]; break; + + case COLOR_HEADER_FG: + case COLOR_PRIMARY_FG: + case COLOR_SECONDARY_FG: + case COLOR_SPLIT_FG: + color = &style->text[GTK_STATE_NORMAL]; + break; + + case COLOR_PRIMARY_FG_ACTIVE: + case COLOR_SECONDARY_FG_ACTIVE: + case COLOR_SPLIT_FG_ACTIVE: + color = &style->text[GTK_STATE_SELECTED]; + break; } gnucash_color_alloc_gdk(color);