diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2011-07-04 22:38:03 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2011-07-04 22:38:03 +0000 |
commit | dadc34655c3ab961b0b0b94a10eaaba710f0b5e8 (patch) | |
tree | 99e72842fe687baea16376a147619b6048d7e441 /kmymoney2/dialogs/investtransactioneditor.cpp | |
download | kmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.tar.gz kmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.zip |
Added kmymoney
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kmymoney@1239792 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kmymoney2/dialogs/investtransactioneditor.cpp')
-rw-r--r-- | kmymoney2/dialogs/investtransactioneditor.cpp | 1094 |
1 files changed, 1094 insertions, 0 deletions
diff --git a/kmymoney2/dialogs/investtransactioneditor.cpp b/kmymoney2/dialogs/investtransactioneditor.cpp new file mode 100644 index 0000000..5a62436 --- /dev/null +++ b/kmymoney2/dialogs/investtransactioneditor.cpp @@ -0,0 +1,1094 @@ +/*************************************************************************** + investtransactioneditor.cpp + ---------- + begin : Fri Dec 15 2006 + copyright : (C) 2006 by Thomas Baumgart + email : Thomas Baumgart <ipwizard@users.sourceforge.net> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include <typeinfo> + +// ---------------------------------------------------------------------------- +// QT Includes + +#include <qlabel.h> +#include <qapplication.h> +#include <qeventloop.h> +#include <qradiobutton.h> +#include <qbuttongroup.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <ktextedit.h> +#include <klocale.h> +#include <kcombobox.h> +#include <kpushbutton.h> +#include <kmessagebox.h> +#include <kstdguiitem.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/investtransactioneditor.h> +#include <kmymoney/kmymoneycategory.h> +#include <kmymoney/kmymoneydateinput.h> +#include <kmymoney/kmymoneyedit.h> +#include <kmymoney/kmymoneylineedit.h> +#include <kmymoney/kmymoneyaccountcompletion.h> +#include <kmymoney/kmymoneyaccountselector.h> +#include <kmymoney/mymoneyfile.h> +#include <kmymoney/transactionform.h> + +#include "../dialogs/ksplittransactiondlg.h" +#include "../dialogs/kcurrencycalculator.h" + +#include "../kmymoneyglobalsettings.h" + +#include "investactivities.h" + +using namespace KMyMoneyRegister; +using namespace KMyMoneyTransactionForm; +using namespace Invest; + +class InvestTransactionEditor::Private { + friend class Invest::Activity; + +public: + Private(InvestTransactionEditor* parent) : + m_parent(parent), + m_activity(0) + { + m_phonyAccount = MyMoneyAccount("Phony-ID", MyMoneyAccount()); + } + + ~Private() { + delete m_activity; + } + + QWidget* haveWidget(const QString& name) { return m_parent->haveWidget(name); } + + InvestTransactionEditor* m_parent; + Activity* m_activity; + MyMoneyAccount m_phonyAccount; + MyMoneySplit m_phonySplit; +}; + + +InvestTransactionEditor::InvestTransactionEditor() : + d(new Private(this)) +{ +} + +InvestTransactionEditor::~InvestTransactionEditor() +{ + delete d; +} + +InvestTransactionEditor::InvestTransactionEditor(TransactionEditorContainer* regForm, KMyMoneyRegister::InvestTransaction* item, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate) : + TransactionEditor(regForm, item, list, lastPostDate), + d(new Private(this)) +{ + // dissect the transaction into its type, splits, currency, security etc. + dissectTransaction(m_transaction, m_split, + m_assetAccountSplit, + m_feeSplits, + m_interestSplits, + m_security, + m_currency, + m_transactionType); + + // determine initial activity object + activityFactory(m_transactionType); +} + +void InvestTransactionEditor::activityFactory(MyMoneySplit::investTransactionTypeE type) +{ + if(!d->m_activity || type != d->m_activity->type()) { + delete d->m_activity; + switch(type) { + default: + case MyMoneySplit::BuyShares: + d->m_activity = new Buy(this); + break; + case MyMoneySplit::SellShares: + d->m_activity = new Sell(this); + break; + case MyMoneySplit::Dividend: + case MyMoneySplit::Yield: + d->m_activity = new Div(this); + break; + case MyMoneySplit::ReinvestDividend: + d->m_activity = new Reinvest(this); + break; + case MyMoneySplit::AddShares: + d->m_activity = new Add(this); + break; + case MyMoneySplit::RemoveShares: + d->m_activity = new Remove(this); + break; + case MyMoneySplit::SplitShares: + d->m_activity = new Split(this); + break; + } + } +} + +void InvestTransactionEditor::dissectTransaction(const MyMoneyTransaction& transaction, const MyMoneySplit& split, MyMoneySplit& assetAccountSplit, QValueList<MyMoneySplit>& feeSplits, QValueList<MyMoneySplit>& interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency, MyMoneySplit::investTransactionTypeE& transactionType) +{ + // collect the splits. split references the stock account and should already + // be set up. assetAccountSplit references the corresponding asset account (maybe + // empty), feeSplits is the list of all expenses and interestSplits + // the list of all incomes + MyMoneyFile* file = MyMoneyFile::instance(); + QValueList<MyMoneySplit>::ConstIterator it_s; + for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) { + MyMoneyAccount acc = file->account((*it_s).accountId()); + if((*it_s).id() == split.id()) { + security = file->security(acc.currencyId()); + } else if(acc.accountGroup() == MyMoneyAccount::Expense) { + feeSplits.append(*it_s); + // feeAmount += (*it_s).value(); + } else if(acc.accountGroup() == MyMoneyAccount::Income) { + interestSplits.append(*it_s); + // interestAmount += (*it_s).value(); + } else { + assetAccountSplit = *it_s; + } + } + + // determine transaction type + if(split.action() == MyMoneySplit::ActionAddShares) { + transactionType = (!split.shares().isNegative()) ? MyMoneySplit::AddShares : MyMoneySplit::RemoveShares; + } else if(split.action() == MyMoneySplit::ActionBuyShares) { + transactionType = (!split.value().isNegative()) ? MyMoneySplit::BuyShares : MyMoneySplit::SellShares; + } else if(split.action() == MyMoneySplit::ActionDividend) { + transactionType = MyMoneySplit::Dividend; + } else if(split.action() == MyMoneySplit::ActionReinvestDividend) { + transactionType = MyMoneySplit::ReinvestDividend; + } else if(split.action() == MyMoneySplit::ActionYield) { + transactionType = MyMoneySplit::Yield; + } else if(split.action() == MyMoneySplit::ActionSplitShares) { + transactionType = MyMoneySplit::SplitShares; + } else + transactionType = MyMoneySplit::BuyShares; + + currency.setTradingSymbol("???"); + try { + currency = file->security(transaction.commodity()); + } catch(MyMoneyException *e) { + delete e; + } +} + +void InvestTransactionEditor::createEditWidgets(void) +{ + KMyMoneyActivityCombo* activity = new KMyMoneyActivityCombo(); + m_editWidgets["activity"] = activity; + connect(activity, SIGNAL(activitySelected(MyMoneySplit::investTransactionTypeE)), this, SLOT(slotUpdateActivity(MyMoneySplit::investTransactionTypeE))); + connect(activity, SIGNAL(activitySelected(MyMoneySplit::investTransactionTypeE)), this, SLOT(slotUpdateButtonState())); + + m_editWidgets["postdate"] = new kMyMoneyDateInput; + connect(m_editWidgets["postdate"], SIGNAL(dateChanged(const QDate&)), this, SLOT(slotUpdateButtonState())); + + KMyMoneySecurity* security = new KMyMoneySecurity; + security->setHint(i18n("Security")); + m_editWidgets["security"] = security; + connect(security, SIGNAL(itemSelected(const QString&)), this, SLOT(slotUpdateSecurity(const QString&))); + connect(security, SIGNAL(textChanged(const QString&)), this, SLOT(slotUpdateButtonState())); + connect(security, SIGNAL(createItem(const QString&, QString&)), this, SLOT(slotCreateSecurity(const QString&, QString&))); + connect(security, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool))); + + KMyMoneyCategory* asset = new KMyMoneyCategory(0, 0, false); + asset->setHint(i18n("Asset account")); + m_editWidgets["asset-account"] = asset; + connect(asset, SIGNAL(textChanged(const QString&)), this, SLOT(slotUpdateButtonState())); + connect(asset, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool))); + + KMyMoneyCategory* fees = new KMyMoneyCategory(0, 0, true); + fees->setHint(i18n("Fees")); + m_editWidgets["fee-account"] = fees; + connect(fees, SIGNAL(itemSelected(const QString&)), this, SLOT(slotUpdateFeeCategory(const QString&))); + connect(fees, SIGNAL(textChanged(const QString&)), this, SLOT(slotUpdateButtonState())); + connect(fees, SIGNAL(textChanged(const QString&)), this, SLOT(slotUpdateFeeVisibility(const QString&))); + connect(fees, SIGNAL(createItem(const QString&, QString&)), this, SLOT(slotCreateFeeCategory(const QString&, QString&))); + connect(fees, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool))); + connect(fees->splitButton(), SIGNAL(clicked()), this, SLOT(slotEditFeeSplits())); + + KMyMoneyCategory* interest = new KMyMoneyCategory(0, 0, true); + interest->setHint(i18n("Interest")); + m_editWidgets["interest-account"] = interest; + connect(interest, SIGNAL(itemSelected(const QString&)), this, SLOT(slotUpdateInterestCategory(const QString&))); + connect(interest, SIGNAL(textChanged(const QString&)), this, SLOT(slotUpdateButtonState())); + connect(interest, SIGNAL(textChanged(const QString&)), this, SLOT(slotUpdateInterestVisibility(const QString&))); + connect(interest, SIGNAL(createItem(const QString&, QString&)), this, SLOT(slotCreateInterestCategory(const QString&, QString&))); + connect(interest, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool))); + connect(interest->splitButton(), SIGNAL(clicked()), this, SLOT(slotEditInterestSplits())); + + KTextEdit* memo = new KTextEdit; + memo->setTabChangesFocus(true); + m_editWidgets["memo"] = memo; + + kMyMoneyEdit* value = new kMyMoneyEdit; + value->setHint(i18n("Shares")); + value->setResetButtonVisible(false); + m_editWidgets["shares"] = value; + connect(value, SIGNAL(textChanged(const QString&)), this, SLOT(slotUpdateButtonState())); + connect(value, SIGNAL(valueChanged(const QString&)), this, SLOT(slotUpdateTotalAmount())); + + value = new kMyMoneyEdit; + value->setHint(i18n("Price")); + value->setResetButtonVisible(false); + value->setPrecision(KMyMoneyGlobalSettings::pricePrecision()); + m_editWidgets["price"] = value; + connect(value, SIGNAL(textChanged(const QString&)), this, SLOT(slotUpdateButtonState())); + connect(value, SIGNAL(valueChanged(const QString&)), this, SLOT(slotUpdateTotalAmount())); + + value = new kMyMoneyEdit; + // TODO once we have the selected transactions as array of Transaction + // we can allow multiple splits for fee and interest + value->setResetButtonVisible(false); + m_editWidgets["fee-amount"] = value; + connect(value, SIGNAL(textChanged(const QString&)), this, SLOT(slotUpdateButtonState())); + connect(value, SIGNAL(valueChanged(const QString&)), this, SLOT(slotUpdateTotalAmount())); + + value = new kMyMoneyEdit; + // TODO once we have the selected transactions as array of Transaction + // we can allow multiple splits for fee and interest + value->setResetButtonVisible(false); + m_editWidgets["interest-amount"] = value; + connect(value, SIGNAL(textChanged(const QString&)), this, SLOT(slotUpdateButtonState())); + connect(value, SIGNAL(valueChanged(const QString&)), this, SLOT(slotUpdateTotalAmount())); + + KMyMoneyReconcileCombo* reconcile = new KMyMoneyReconcileCombo; + m_editWidgets["status"] = reconcile; + connect(reconcile, SIGNAL(itemSelected(const QString&)), this, SLOT(slotUpdateButtonState())); + + KMyMoneyRegister::QWidgetContainer::iterator it_w; + for(it_w = m_editWidgets.begin(); it_w != m_editWidgets.end(); ++it_w) { + (*it_w)->installEventFilter(this); + } + + QLabel* label = new QLabel("", 0); + label->setAlignment(Qt::AlignVCenter | Qt::AlignRight | Qt::DontClip); + m_editWidgets["total"] = label; + + label = new QLabel("", 0); + label->setAlignment(Qt::AlignVCenter | Qt::DontClip); + m_editWidgets["total-label"] = label; + + label = new QLabel("", 0); + label->setAlignment(Qt::AlignVCenter | Qt::DontClip); + m_editWidgets["asset-label"] = label; + + label = new QLabel("", 0); + label->setAlignment(Qt::AlignVCenter | Qt::DontClip); + m_editWidgets["fee-label"] = label; + + label = new QLabel("", 0); + label->setAlignment(Qt::AlignVCenter | Qt::DontClip); + m_editWidgets["fee-amount-label"] = label; + + label = new QLabel("", 0); + label->setAlignment(Qt::AlignVCenter | Qt::DontClip); + m_editWidgets["interest-label"] = label; + + label = new QLabel("", 0); + label->setAlignment(Qt::AlignVCenter | Qt::DontClip); + m_editWidgets["interest-amount-label"] = label; + + label = new QLabel("", 0); + label->setAlignment(Qt::AlignVCenter | Qt::DontClip); + m_editWidgets["price-label"] = label; + + label = new QLabel("", 0); + label->setAlignment(Qt::AlignVCenter | Qt::DontClip); + m_editWidgets["shares-label"] = label; + + // if we don't have more than 1 selected transaction, we don't need + // the "don't change" item in some of the combo widgets + if(m_transactions.count() < 2) { + reconcile->removeDontCare(); + } +} + +int InvestTransactionEditor::slotEditFeeSplits(void) +{ + return editSplits("fee-account", "fee-amount", m_feeSplits, false, SLOT(slotEditFeeSplits())); +} + +int InvestTransactionEditor::slotEditInterestSplits(void) +{ + return editSplits("interest-account", "interest-amount", m_interestSplits, true, SLOT(slotEditInterestSplits())); +} + +int InvestTransactionEditor::editSplits(const QString& categoryWidgetName, const QString& amountWidgetName, QValueList<MyMoneySplit>& splits, bool isIncome, const char* slotEditSplits) +{ + int rc = QDialog::Rejected; + + if(!m_openEditSplits) { + // only get in here in a single instance + m_openEditSplits = true; + + // force focus change to update all data + KMyMoneyCategory* category = dynamic_cast<KMyMoneyCategory*>(m_editWidgets[categoryWidgetName]); + QWidget* w = category->splitButton(); + if(w) + w->setFocus(); + + kMyMoneyEdit* amount = dynamic_cast<kMyMoneyEdit*>(haveWidget(amountWidgetName)); + + MyMoneyTransaction transaction; + transaction.setCommodity(m_currency.id()); + if(splits.count() == 0 && category->selectedItem()) { + MyMoneySplit s; + s.setAccountId(category->selectedItem()); + s.setShares(amount->value()); + s.setValue(s.shares()); + splits << s; + } + // use the transactions commodity as the currency indicator for the splits + // this is used to allow some useful setting for the fractions in the amount fields + try { + d->m_phonyAccount.setCurrencyId(m_transaction.commodity()); + d->m_phonyAccount.fraction(MyMoneyFile::instance()->security(m_transaction.commodity())); + } catch(MyMoneyException *e) { + qDebug("Unable to setup precision"); + delete e; + } + + if(createPseudoTransaction(transaction, splits)) { + MyMoneyMoney value; + + KSplitTransactionDlg* dlg = new KSplitTransactionDlg(transaction, + d->m_phonySplit, + d->m_phonyAccount, + false, + isIncome, + 0, + m_priceInfo, + m_regForm); + // connect(dlg, SIGNAL(newCategory(MyMoneyAccount&)), this, SIGNAL(newCategory(MyMoneyAccount&))); + + if((rc = dlg->exec()) == QDialog::Accepted) { + transaction = dlg->transaction(); + // collect splits out of the transaction + splits.clear(); + QValueList<MyMoneySplit>::const_iterator it_s; + MyMoneyMoney fees; + for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) { + if((*it_s).accountId() == d->m_phonyAccount.id()) + continue; + splits << *it_s; + fees += (*it_s).shares(); + } + if(isIncome) + fees = -fees; + + QString categoryId; + setupCategoryWidget(category, splits, categoryId, slotEditSplits); + amount->setValue(fees); + slotUpdateTotalAmount(); + } + + delete dlg; + } + + // focus jumps into the memo field + if((w = haveWidget("memo")) != 0) { + w->setFocus(); + } + + m_openEditSplits = false; + } + return rc; +} + +bool InvestTransactionEditor::createPseudoTransaction(MyMoneyTransaction& t, const QValueList<MyMoneySplit>& splits) +{ + t.removeSplits(); + + MyMoneySplit split; + split.setAccountId(d->m_phonyAccount.id()); + split.setValue(-subtotal(splits)); + split.setShares(split.value()); + t.addSplit(split); + d->m_phonySplit = split; + + QValueList<MyMoneySplit>::const_iterator it_s; + for(it_s = splits.begin(); it_s != splits.end(); ++it_s) { + split = *it_s; + split.clearId(); + t.addSplit(split); + } + return true; +} + +void InvestTransactionEditor::slotCreateSecurity(const QString& name, QString& id) +{ + MyMoneyAccount acc; + QRegExp exp("([^:]+)"); + if(exp.search(name) != -1) { + acc.setName(exp.cap(1)); + + emit createSecurity(acc, m_account); + + // return id + id = acc.id(); + + if(!id.isEmpty()) { + slotUpdateSecurity(id); + } + } +} + +void InvestTransactionEditor::slotCreateFeeCategory(const QString& name, QString& id) +{ + MyMoneyAccount acc; + acc.setName(name); + + emit createCategory(acc, MyMoneyFile::instance()->expense()); + + // return id + id = acc.id(); +} + +void InvestTransactionEditor::slotUpdateFeeCategory(const QString& id) +{ + haveWidget("fee-amount")->setDisabled(id.isEmpty()); +} + +void InvestTransactionEditor::slotUpdateFeeVisibility(const QString& txt) +{ + haveWidget("fee-amount")->setHidden(txt.isEmpty()); + QWidget* w = haveWidget("fee-amount-label"); + if(w) + w->setShown(haveWidget("fee-amount")->isVisible()); +} + +void InvestTransactionEditor::slotUpdateInterestCategory(const QString& id) +{ + haveWidget("interest-amount")->setDisabled(id.isEmpty()); +} + +void InvestTransactionEditor::slotUpdateInterestVisibility(const QString& txt) +{ + KMyMoneyCategory* interest = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account")); + QWidget* w = haveWidget("interest-amount-label"); + + if(dynamic_cast<Reinvest*>(d->m_activity)) { + interest->splitButton()->hide(); + haveWidget("interest-amount")->setHidden(true); + // for the reinvest case, we don't ever hide the label do avoid a shine through + // of the underlying transaction data. + w = 0; + } else { + haveWidget("interest-amount")->setHidden(txt.isEmpty()); + // FIXME once we can handle split interest, we need to uncomment the next line + // interest->splitButton()->show(); + } + + if(w) + w->setShown(haveWidget("interest-amount")->isVisible()); +} + +void InvestTransactionEditor::slotCreateInterestCategory(const QString& name, QString& id) +{ + MyMoneyAccount acc; + acc.setName(name); + + emit createCategory(acc, MyMoneyFile::instance()->income()); + + // return id + id = acc.id(); +} + +void InvestTransactionEditor::slotReloadEditWidgets(void) +{ + KMyMoneyCategory* interest = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account")); + KMyMoneyCategory* fees = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account")); + KMyMoneySecurity* security = dynamic_cast<KMyMoneySecurity*>(haveWidget("security")); + + AccountSet aSet; + QString id; + + // interest-account + aSet.clear(); + aSet.addAccountGroup(MyMoneyAccount::Income); + aSet.load(interest->selector()); + setupCategoryWidget(interest, m_interestSplits, id, SLOT(slotEditInterestSplits())); + + // fee-account + aSet.clear(); + aSet.addAccountGroup(MyMoneyAccount::Expense); + aSet.load(fees->selector()); + setupCategoryWidget(fees, m_feeSplits, id, SLOT(slotEditFeeSplits())); + + // security + aSet.clear(); + aSet.load(security->selector(), i18n("Security"), m_account.accountList(), true); +} + +void InvestTransactionEditor::loadEditWidgets(KMyMoneyRegister::Action /* action */) +{ + QString id; + + kMyMoneyDateInput* postDate = dynamic_cast<kMyMoneyDateInput*>(haveWidget("postdate")); + KMyMoneyReconcileCombo* reconcile = dynamic_cast<KMyMoneyReconcileCombo*>(haveWidget("status")); + KMyMoneySecurity* security = dynamic_cast<KMyMoneySecurity*>(haveWidget("security")); + KMyMoneyActivityCombo* activity = dynamic_cast<KMyMoneyActivityCombo*>(haveWidget("activity")); + KMyMoneyCategory* asset = dynamic_cast<KMyMoneyCategory*>(haveWidget("asset-account")); + KTextEdit* memo = dynamic_cast<KTextEdit*>(m_editWidgets["memo"]); + kMyMoneyEdit* value; + KMyMoneyCategory* interest = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account")); + KMyMoneyCategory* fees = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account")); + + // check if the current transaction has a reference to an equity account + bool haveEquityAccount = false; + QValueList<MyMoneySplit>::const_iterator it_s; + for(it_s = m_transaction.splits().begin(); !haveEquityAccount && it_s != m_transaction.splits().end(); ++it_s) { + MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId()); + if(acc.accountType() == MyMoneyAccount::Equity) + haveEquityAccount = true; + } + + // asset-account + AccountSet aSet; + aSet.clear(); + aSet.addAccountType(MyMoneyAccount::Checkings); + aSet.addAccountType(MyMoneyAccount::Savings); + aSet.addAccountType(MyMoneyAccount::Cash); + aSet.addAccountType(MyMoneyAccount::Asset); + aSet.addAccountType(MyMoneyAccount::Currency); + aSet.addAccountType(MyMoneyAccount::CreditCard); + if(KMyMoneyGlobalSettings::expertMode() || haveEquityAccount) + aSet.addAccountGroup(MyMoneyAccount::Equity); + aSet.load(asset->selector()); + + // security + security->setSuppressObjectCreation(false); // allow object creation on the fly + aSet.clear(); + aSet.load(security->selector(), i18n("Security"), m_account.accountList(), true); + + if(!isMultiSelection()) { + // date + if(m_transaction.postDate().isValid()) + postDate->setDate(m_transaction.postDate()); + else if(m_lastPostDate.isValid()) + postDate->setDate(m_lastPostDate); + else + postDate->setDate(QDate::currentDate()); + + // security (but only if it's not the investment account) + if(m_split.accountId() != m_account.id()) { + security->completion()->setSelected(m_split.accountId()); + security->slotItemSelected(m_split.accountId()); + } + + // activity + activity->setActivity(d->m_activity->type()); + slotUpdateActivity(activity->activity()); + + asset->completion()->setSelected(m_assetAccountSplit.accountId()); + asset->slotItemSelected(m_assetAccountSplit.accountId()); + + // interest-account + aSet.clear(); + aSet.addAccountGroup(MyMoneyAccount::Income); + aSet.load(interest->selector()); + setupCategoryWidget(interest, m_interestSplits, id, SLOT(slotEditInterestSplits())); + slotUpdateInterestVisibility(interest->currentText()); + + // fee-account + aSet.clear(); + aSet.addAccountGroup(MyMoneyAccount::Expense); + aSet.load(fees->selector()); + setupCategoryWidget(fees, m_feeSplits, id, SLOT(slotEditFeeSplits())); + slotUpdateFeeVisibility(fees->currentText()); + + // memo + memo->setText(m_split.memo()); + + // shares + // don't set the value if the number of shares is zero so that + // we can see the hint + value = dynamic_cast<kMyMoneyEdit*>(haveWidget("shares")); + if(typeid(*(d->m_activity)) != typeid(Invest::Split(this))) + value->setPrecision(MyMoneyMoney::denomToPrec(m_security.smallestAccountFraction())); + else + value->setPrecision(-1); + + if(!m_split.shares().isZero()) + value->setValue(m_split.shares().abs()); + + // price + updatePriceMode(m_split); + + // fee amount + value = dynamic_cast<kMyMoneyEdit*>(haveWidget("fee-amount")); + value->setValue(subtotal(m_feeSplits)); + + // interest amount + value = dynamic_cast<kMyMoneyEdit*>(haveWidget("interest-amount")); + value->setValue(-subtotal(m_interestSplits)); + + // total + slotUpdateTotalAmount(); + + // status + if(m_split.reconcileFlag() == MyMoneySplit::Unknown) + m_split.setReconcileFlag(MyMoneySplit::NotReconciled); + reconcile->setState(m_split.reconcileFlag()); + + } else { + postDate->loadDate(QDate()); + reconcile->setState(MyMoneySplit::Unknown); + memo->setText(QString()); + + // We don't allow to change the activity + activity->setActivity(d->m_activity->type()); + slotUpdateActivity(activity->activity()); + activity->setDisabled(true); + + // scan the list of selected transactions and check that they have + // the same activity. + KMyMoneyRegister::SelectedTransactions::iterator it_t = m_transactions.begin(); + const QString& action = m_item->split().action(); + bool isNegative = m_item->split().shares().isNegative(); + bool allSameActivity = true; + for(it_t = m_transactions.begin(); allSameActivity && (it_t != m_transactions.end()); ++it_t) { + allSameActivity = (action == (*it_t).split().action() && (*it_t).split().shares().isNegative() == isNegative); + } + + QStringList fields; + fields << "shares" << "price" << "fee-amount" << "interest-amount"; + QStringList::const_iterator it_f; + for(it_f = fields.begin(); it_f != fields.end(); ++it_f) { + value = dynamic_cast<kMyMoneyEdit*>(haveWidget((*it_f))); + value->setText(""); + value->setAllowEmpty(); + } + + // if we have transactions with different activities, disable some more widgets + if(!allSameActivity) { + fields << "asset-account" << "fee-account" << "interest-account"; + QStringList::const_iterator it_f; + for(it_f = fields.begin(); it_f != fields.end(); ++it_f) { + haveWidget(*it_f)->setDisabled(true); + } + } + } +} + +QWidget* InvestTransactionEditor::firstWidget(void) const +{ + return 0; // let the creator use the first widget in the tab order +} + +bool InvestTransactionEditor::isComplete(QString& reason) const +{ + reason = QString(); + // reason.clear(); // for Qt4 + return d->m_activity->isComplete(reason); +} + +MyMoneyMoney InvestTransactionEditor::subtotal(const QValueList<MyMoneySplit>& splits) const +{ + QValueList<MyMoneySplit>::const_iterator it_s; + MyMoneyMoney sum; + + for(it_s = splits.begin(); it_s != splits.end(); ++it_s) { + sum += (*it_s).value(); + } + + return sum; +} + +void InvestTransactionEditor::slotUpdateSecurity(const QString& stockId) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + MyMoneyAccount stock = file->account(stockId); + m_security = file->security(stock.currencyId()); + m_currency = file->security(m_security.tradingCurrency()); + bool currencyKnown = !m_currency.id().isEmpty(); + if(!currencyKnown) { + m_currency.setTradingSymbol("???"); + } else { + if(typeid(*(d->m_activity)) != typeid(Invest::Split(this))) { + dynamic_cast<kMyMoneyEdit*>(haveWidget("shares"))->setPrecision(MyMoneyMoney::denomToPrec(m_security.smallestAccountFraction())); + } else { + dynamic_cast<kMyMoneyEdit*>(haveWidget("shares"))->setPrecision(-1); + } + } + + updatePriceMode(); + + d->m_activity->preloadAssetAccount(); + + haveWidget("shares")->setEnabled(currencyKnown); + haveWidget("price")->setEnabled(currencyKnown); + haveWidget("fee-amount")->setEnabled(currencyKnown); + haveWidget("interest-amount")->setEnabled(currencyKnown); + + slotUpdateTotalAmount(); +} + +void InvestTransactionEditor::totalAmount(MyMoneyMoney& amount) const +{ + KMyMoneyActivityCombo* activityCombo = dynamic_cast<KMyMoneyActivityCombo*>(haveWidget("activity")); + kMyMoneyEdit* sharesEdit = dynamic_cast<kMyMoneyEdit*>(haveWidget("shares")); + kMyMoneyEdit* priceEdit = dynamic_cast<kMyMoneyEdit*>(haveWidget("price")); + kMyMoneyEdit* feesEdit = dynamic_cast<kMyMoneyEdit*>(haveWidget("fee-amount")); + kMyMoneyEdit* interestEdit = dynamic_cast<kMyMoneyEdit*>(haveWidget("interest-amount")); + + if(priceMode() == InvestTransactionEditor::PricePerTransaction) + amount = priceEdit->value().abs(); + else + amount = sharesEdit->value().abs() * priceEdit->value().abs(); + + if(feesEdit->isVisible()) { + MyMoneyMoney fee = feesEdit->value(); + MyMoneyMoney factor(-1,1); + switch(activityCombo->activity()) { + case MyMoneySplit::BuyShares: + case MyMoneySplit::ReinvestDividend: + factor = MyMoneyMoney(1,1); + break; + default: + break; + } + amount += (fee * factor); + } + + if(interestEdit->isVisible()) { + MyMoneyMoney interest = interestEdit->value(); + MyMoneyMoney factor(1,1); + switch(activityCombo->activity()) { + case MyMoneySplit::BuyShares: + factor = MyMoneyMoney(-1,1); + break; + default: + break; + } + amount += (interest * factor); + } +} + +void InvestTransactionEditor::slotUpdateTotalAmount(void) +{ + QLabel* total = dynamic_cast<QLabel*>(haveWidget("total")); + + if(total && total->isVisible()) { + MyMoneyMoney amount; + totalAmount(amount); + total->setText(amount.formatMoney(m_currency.tradingSymbol(), MyMoneyMoney::denomToPrec(m_security.smallestAccountFraction()))); + } +} + +void InvestTransactionEditor::slotUpdateActivity(MyMoneySplit::investTransactionTypeE activity) +{ + // create new activity object if required + activityFactory(activity); + + KMyMoneyCategory* cat; + + // hide all dynamic widgets (make sure to use the parentWidget for the + // category widgets) + haveWidget("interest-account")->parentWidget()->hide(); + haveWidget("fee-account")->parentWidget()->hide(); + + QStringList dynwidgets; + dynwidgets << "total-label" << "asset-label" << "fee-label" << "fee-amount-label" << "interest-label" << "interest-amount-label" << "price-label" << "shares-label"; + + // hiding labels works by clearing them. hide() does not do the job + // as the underlying text in the QTable object will shine through + QStringList::const_iterator it_s; + for(it_s = dynwidgets.begin(); it_s != dynwidgets.end(); ++it_s) { + QLabel* w = dynamic_cast<QLabel*>(haveWidget(*it_s)); + if(w) + w->setText(" "); + } + + // real widgets can be hidden + dynwidgets.clear(); + dynwidgets << "asset-account" << "interest-amount" << "fee-amount" << "shares" << "price" << "total"; + + for(it_s = dynwidgets.begin(); it_s != dynwidgets.end(); ++it_s) { + QWidget* w = haveWidget(*it_s); + if(w) + w->hide(); + } + + d->m_activity->showWidgets(); + + d->m_activity->preloadAssetAccount(); + + cat = dynamic_cast<KMyMoneyCategory*>(haveWidget("interest-account")); + if(cat->parentWidget()->isVisible()) + slotUpdateInterestVisibility(cat->currentText()); + + cat = dynamic_cast<KMyMoneyCategory*>(haveWidget("fee-account")); + if(cat->parentWidget()->isVisible()) + slotUpdateFeeVisibility(cat->currentText()); +} + +InvestTransactionEditor::priceModeE InvestTransactionEditor::priceMode(void) const +{ + priceModeE mode = static_cast<priceModeE>(0); + KMyMoneySecurity* sec = dynamic_cast<KMyMoneySecurity*>(m_editWidgets["security"]); + QString accId; + if(!sec->currentText().isEmpty()) { + accId = sec->selectedItem(); + if(accId.isEmpty()) + accId = m_account.id(); + } + while(!accId.isEmpty() && mode == 0) { + MyMoneyAccount acc = MyMoneyFile::instance()->account(accId); + if(acc.value("priceMode").isEmpty()) + accId = acc.parentAccountId(); + else + mode = static_cast<priceModeE>(acc.value("priceMode").toInt()); + } + + // if it's still <default> then use that default + if(mode == 0) + mode = PricePerShare; + return mode; +} + +bool InvestTransactionEditor::setupPrice(const MyMoneyTransaction& t, MyMoneySplit& split) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + MyMoneyAccount acc = file->account(split.accountId()); + MyMoneySecurity toCurrency(file->security(acc.currencyId())); + int fract = acc.fraction(); + + if(acc.currencyId() != t.commodity()) { + QMap<QString, MyMoneyMoney>::Iterator it_p; + QString key = t.commodity() + "-" + acc.currencyId(); + it_p = m_priceInfo.find(key); + + // if it's not found, then collect it from the user first + MyMoneyMoney price; + if(it_p == m_priceInfo.end()) { + MyMoneySecurity fromCurrency = file->security(t.commodity()); + MyMoneyMoney fromValue, toValue; + + fromValue = split.value(); + MyMoneyPrice priceInfo = MyMoneyFile::instance()->price(fromCurrency.id(), toCurrency.id()); + toValue = split.value() * priceInfo.rate(toCurrency.id()); + + KCurrencyCalculator calc(fromCurrency, + toCurrency, + fromValue, + toValue, + t.postDate(), + fract, + m_regForm, "currencyCalculator"); + + if(calc.exec() == QDialog::Rejected) { + return false; + } + price = calc.price(); + m_priceInfo[key] = price; + } else { + price = (*it_p); + } + + // update shares if the transaction commodity is the currency + // of the current selected account + split.setShares((split.value() * price).convert(fract)); + } else { + split.setShares(split.value().convert(fract)); + } + + return true; +} + +bool InvestTransactionEditor::createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool /* skipPriceDialog */) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + // we start with the previous values, make sure we can add them later on + t = torig; + MyMoneySplit s0 = sorig; + s0.clearId(); + + KMyMoneySecurity* sec = dynamic_cast<KMyMoneySecurity*>(m_editWidgets["security"]); + if(!isMultiSelection() || (isMultiSelection() && !sec->currentText().isEmpty())) { + QString securityId = sec->selectedItem(); + if(!securityId.isEmpty()) { + s0.setAccountId(securityId); + MyMoneyAccount stockAccount = file->account(securityId); + QString currencyId = stockAccount.currencyId(); + MyMoneySecurity security = file->security(currencyId); + + t.setCommodity(security.tradingCurrency()); + } else { + s0.setAccountId(m_account.id()); + t.setCommodity(m_account.currencyId()); + } + } + + // extract price info from original transaction + m_priceInfo.clear(); + QValueList<MyMoneySplit>::const_iterator it_s; + if(!torig.id().isEmpty()) { + for(it_s = torig.splits().begin(); it_s != torig.splits().end(); ++it_s) { + if((*it_s).id() != sorig.id()) { + MyMoneyAccount cat = file->account((*it_s).accountId()); + if(cat.currencyId() != m_account.currencyId()) { + if(!(*it_s).shares().isZero() && !(*it_s).value().isZero()) { + m_priceInfo[cat.currencyId()] = ((*it_s).shares() / (*it_s).value()).reduce(); + } + } + } + } + } + + t.removeSplits(); + + kMyMoneyDateInput* postDate = dynamic_cast<kMyMoneyDateInput*>(m_editWidgets["postdate"]); + if(postDate->date().isValid()) { + t.setPostDate(postDate->date()); + } + + // memo and number field are special: if we have multiple transactions selected + // and the edit field is empty, we treat it as "not modified". + // FIXME a better approach would be to have a 'dirty' flag with the widgets + // which identifies if the originally loaded value has been modified + // by the user + KTextEdit* memo = dynamic_cast<KTextEdit*>(m_editWidgets["memo"]); + if(memo) { + if(!isMultiSelection() || (isMultiSelection() && !memo->text().isEmpty() ) ) + s0.setMemo(memo->text()); + } + + MyMoneySplit assetAccountSplit; + QValueList<MyMoneySplit> feeSplits; + QValueList<MyMoneySplit> interestSplits; + MyMoneySecurity security, currency; + MyMoneySplit::investTransactionTypeE transactionType; + + // extract the splits from the original transaction + dissectTransaction(torig, sorig, + assetAccountSplit, + feeSplits, + interestSplits, + security, + currency, + transactionType); + + // check if the trading currency is the same if the security has changed + // in case it differs, check that we have a price (request from user) + // and convert all splits + // TODO + + // do the conversions here + // TODO + + // keep the current activity object and create a new one + // that can be destroyed later on + Activity* activity = d->m_activity; + d->m_activity = 0; // make sure we create a new one + activityFactory(activity->type()); + + // if the activity is not set in the combo widget, we keep + // the one which is used in the original transaction + KMyMoneyActivityCombo* activityCombo = dynamic_cast<KMyMoneyActivityCombo*>(haveWidget("activity")); + if(activityCombo->activity() == MyMoneySplit::UnknownTransactionType) { + activityFactory(transactionType); + } + + // if we mark the split reconciled here, we'll use today's date if no reconciliation date is given + KMyMoneyReconcileCombo* status = dynamic_cast<KMyMoneyReconcileCombo*>(m_editWidgets["status"]); + if(status->state() != MyMoneySplit::Unknown) + s0.setReconcileFlag(status->state()); + + if(s0.reconcileFlag() == MyMoneySplit::Reconciled && !s0.reconcileDate().isValid()) + s0.setReconcileDate(QDate::currentDate()); + + // call the creation logic for the current selected activity + bool rc = d->m_activity->createTransaction(t, s0, assetAccountSplit, feeSplits, m_feeSplits, interestSplits, m_interestSplits, security, currency); + + // now switch back to the original activity + delete d->m_activity; + d->m_activity = activity; + + // add the splits to the transaction + if(rc) { + if(!assetAccountSplit.accountId().isEmpty()) { + assetAccountSplit.clearId(); + t.addSplit(assetAccountSplit); + } + + t.addSplit(s0); + + QValueList<MyMoneySplit>::iterator it_s; + for(it_s = feeSplits.begin(); it_s != feeSplits.end(); ++it_s) { + (*it_s).clearId(); + t.addSplit(*it_s); + } + + for(it_s = interestSplits.begin(); it_s != interestSplits.end(); ++it_s) { + (*it_s).clearId(); + t.addSplit(*it_s); + } + } + + // adjust the value to the smallestAccountFraction found + // for the commodity of the transaction. + for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { + MyMoneySplit s = (*it_s); + s.setValue((*it_s).value().convert(currency.smallestAccountFraction())); + t.modifySplit(s); + } + + return rc; +} + +void InvestTransactionEditor::updatePriceMode(const MyMoneySplit& split) +{ + QLabel* label = dynamic_cast<QLabel*>(haveWidget("price-label")); + if(label) { + kMyMoneyEdit* sharesEdit = dynamic_cast<kMyMoneyEdit*>(haveWidget("shares")); + kMyMoneyEdit* priceEdit = dynamic_cast<kMyMoneyEdit*>(haveWidget("price")); + MyMoneyMoney price; + if(!split.id().isEmpty()) + price = split.price().reduce(); + else + price = priceEdit->value().abs(); + + if(priceMode() == PricePerTransaction && label->text() != i18n("Price")) { + label->setText(i18n("Price")); + if(!sharesEdit->value().isZero()) + priceEdit->setValue(sharesEdit->value().abs() * price); + + } else if(priceMode() == PricePerShare && label->text() == i18n("Price")) { + label->setText(i18n("Price/Share")); + if(!sharesEdit->value().isZero()) + priceEdit->setValue(price / sharesEdit->value().abs()); + + } else if(priceMode() == PricePerTransaction) { + priceEdit->setValue(sharesEdit->value().abs() * price); + + } else + priceEdit->setValue(price); + } +} + +void InvestTransactionEditor::setupFinalWidgets(void) +{ + addFinalWidget(haveWidget("memo")); +} + +#include "investtransactioneditor.moc" + |