diff --git a/kmymoney/widgets/investtransaction.cpp b/kmymoney/widgets/investtransaction.cpp index 5497a8862..f5c8e9571 100644 --- a/kmymoney/widgets/investtransaction.cpp +++ b/kmymoney/widgets/investtransaction.cpp @@ -1,935 +1,935 @@ /* * Copyright 2006-2018 Thomas Baumgart * Copyright 2017-2018 Łukasz Wojniłowicz * * 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, see . */ #include "investtransaction.h" #include "investtransaction_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneypayeecombo.h" #include "transaction.h" #include "mymoneyutils.h" #include "mymoneytransaction.h" #include "mymoneysplit.h" #include "mymoneyfile.h" #include "register.h" #include "transactionform.h" #include "kmymoneylineedit.h" #include "kmymoneyedit.h" #include "kmymoneycombo.h" #include "investtransactioneditor.h" #include "kmymoneyutils.h" #include "kmymoneysettings.h" #include "widgetenums.h" using namespace eWidgets; using namespace KMyMoneyRegister; using namespace KMyMoneyTransactionForm; InvestTransaction::InvestTransaction(Register *parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId) : Transaction(*new InvestTransactionPrivate, parent, transaction, split, uniqueId) { Q_D(InvestTransaction); #ifndef KMM_DESIGNER // dissect the transaction into its type, splits, currency, security etc. KMyMoneyUtils::dissectTransaction(d->m_transaction, d->m_split, d->m_assetAccountSplit, d->m_feeSplits, d->m_interestSplits, d->m_security, d->m_currency, d->m_transactionType); #endif QList::ConstIterator it_s; for (it_s = d->m_feeSplits.constBegin(); it_s != d->m_feeSplits.constEnd(); ++it_s) { d->m_feeAmount += (*it_s).value(); } for (it_s = d->m_interestSplits.constBegin(); it_s != d->m_interestSplits.constEnd(); ++it_s) { d->m_interestAmount += (*it_s).value(); } // check the count of the fee splits and setup the text switch (d->m_feeSplits.count()) { case 0: break; case 1: d->m_feeCategory = MyMoneyFile::instance()->accountToCategory(d->m_feeSplits[0].accountId()); break; default: d->m_feeCategory = i18nc("Split transaction (category replacement)", "Split transaction"); break; } // check the count of the interest splits and setup the text switch (d->m_interestSplits.count()) { case 0: break; case 1: d->m_interestCategory = MyMoneyFile::instance()->accountToCategory(d->m_interestSplits[0].accountId()); break; default: d->m_interestCategory = i18nc("Split transaction (category replacement)", "Split transaction"); break; } d->m_rowsForm = 7; // setup initial size setNumRowsRegister(numRowsRegister(KMyMoneySettings::showRegisterDetailed())); emit parent->itemAdded(this); } InvestTransaction::~InvestTransaction() { } const QString InvestTransaction::sortSecurity() const { Q_D(const InvestTransaction); return d->m_security.name(); } const char* InvestTransaction::className() { return "InvestTransaction"; } void InvestTransaction::setupForm(TransactionForm* form) { Transaction::setupForm(form); form->setSpan(5, 1, 2, 1); } void InvestTransaction::activity(QString& txt, eMyMoney::Split::InvestmentTransactionType type) const { switch (type) { case eMyMoney::Split::InvestmentTransactionType::AddShares: txt = i18n("Add shares"); break; case eMyMoney::Split::InvestmentTransactionType::RemoveShares: txt = i18n("Remove shares"); break; case eMyMoney::Split::InvestmentTransactionType::BuyShares: txt = i18n("Buy shares"); break; case eMyMoney::Split::InvestmentTransactionType::SellShares: txt = i18n("Sell shares"); break; case eMyMoney::Split::InvestmentTransactionType::Dividend: txt = i18n("Dividend"); break; case eMyMoney::Split::InvestmentTransactionType::ReinvestDividend: txt = i18n("Reinvest Dividend"); break; case eMyMoney::Split::InvestmentTransactionType::Yield: txt = i18n("Yield"); break; case eMyMoney::Split::InvestmentTransactionType::SplitShares: txt = i18n("Split shares"); break; case eMyMoney::Split::InvestmentTransactionType::InterestIncome: txt = i18n("Interest Income"); break; default: txt = i18nc("Unknown investment activity", "Unknown"); break; } } bool InvestTransaction::formCellText(QString& txt, Qt::Alignment& align, int row, int col, QPainter* /* painter */) { Q_D(InvestTransaction); bool fieldEditable = false; switch (row) { case 0: switch (col) { case (int)eTransactionForm::Column::Label1: align |= Qt::AlignLeft; txt = i18n("Activity"); break; case (int)eTransactionForm::Column::Value1: align |= Qt::AlignLeft; fieldEditable = true; activity(txt, d->m_transactionType); break; case (int)eTransactionForm::Column::Label2: align |= Qt::AlignLeft; txt = i18n("Date"); break; case (int)eTransactionForm::Column::Value2: align |= Qt::AlignRight; fieldEditable = true; - if (d->m_transaction != MyMoneyTransaction()) + if (!d->m_transaction.id().isEmpty()) txt = QLocale().toString(d->m_transaction.postDate(), QLocale::ShortFormat); break; } break; case 1: switch (col) { case (int)eTransactionForm::Column::Label1: align |= Qt::AlignLeft; txt = i18n("Security"); break; case (int)eTransactionForm::Column::Value1: align |= Qt::AlignLeft; fieldEditable = true; if (d->m_account.isInvest()) txt = d->m_security.name(); break; case (int)eTransactionForm::Column::Label2: align |= Qt::AlignLeft; if (haveShares()) { txt = i18n("Shares"); } else if (haveSplitRatio()) { txt = i18n("Ratio"); } break; case (int)eTransactionForm::Column::Value2: align |= Qt::AlignRight; if ((fieldEditable = haveShares()) == true) { txt = d->m_split.shares().abs().formatMoney(QString(), MyMoneyMoney::denomToPrec(d->m_security.smallestAccountFraction())); } else if (haveSplitRatio()) { txt = QString("1 / %1").arg(d->m_split.shares().abs().formatMoney(QString(), -1)); } break; } break; case 2: switch (col) { case (int)eTransactionForm::Column::Label1: align |= Qt::AlignLeft; if (haveAssetAccount()) txt = i18n("Account"); break; case (int)eTransactionForm::Column::Value1: align |= Qt::AlignLeft; if ((fieldEditable = haveAssetAccount()) == true) { txt = MyMoneyFile::instance()->accountToCategory(d->m_assetAccountSplit.accountId()); } break; case (int)eTransactionForm::Column::Label2: align |= Qt::AlignLeft; if (havePrice()) txt = i18n("Price/share"); break; case (int)eTransactionForm::Column::Value2: align |= Qt::AlignRight; if ((fieldEditable = havePrice()) == true && !d->m_split.shares().isZero()) { txt = d->m_split.price().formatMoney(QString(), d->m_security.pricePrecision()); } break; } break; case 3: switch (col) { case (int)eTransactionForm::Column::Label1: align |= Qt::AlignLeft; if (haveFees()) txt = i18n("Fees"); break; case (int)eTransactionForm::Column::Value1: align |= Qt::AlignLeft; if ((fieldEditable = haveFees()) == true) { txt = d->m_feeCategory; } break; case (int)eTransactionForm::Column::Label2: align |= Qt::AlignLeft; if (haveFees() && !d->m_feeCategory.isEmpty()) txt = i18n("Fee Amount"); break; case (int)eTransactionForm::Column::Value2: align |= Qt::AlignRight; if (haveFees()) { if ((fieldEditable = !d->m_feeCategory.isEmpty()) == true) { txt = MyMoneyUtils::formatMoney(d->m_feeAmount, d->m_currency); } } break; } break; case 4: switch (col) { case (int)eTransactionForm::Column::Label1: align |= Qt::AlignLeft; if (haveInterest()) txt = i18n("Interest"); break; case (int)eTransactionForm::Column::Value1: align |= Qt::AlignLeft; if ((fieldEditable = haveInterest()) == true) { txt = d->m_interestCategory; } break; case (int)eTransactionForm::Column::Label2: align |= Qt::AlignLeft; if (haveInterest() && !d->m_interestCategory.isEmpty()) txt = i18n("Interest"); break; case (int)eTransactionForm::Column::Value2: align |= Qt::AlignRight; if (haveInterest()) { if ((fieldEditable = !d->m_interestCategory.isEmpty()) == true) { txt = MyMoneyUtils::formatMoney(-d->m_interestAmount, d->m_currency); } } break; } break; case 5: switch (col) { case (int)eTransactionForm::Column::Label1: align |= Qt::AlignLeft; txt = i18n("Memo"); break; case (int)eTransactionForm::Column::Value1: align &= ~Qt::AlignVCenter; align |= Qt::AlignTop; align |= Qt::AlignLeft; fieldEditable = true; - if (d->m_transaction != MyMoneyTransaction()) + if (!d->m_transaction.id().isEmpty()) txt = d->m_split.memo().section('\n', 0, 2); break; case (int)eTransactionForm::Column::Label2: align |= Qt::AlignLeft; if (haveAmount()) txt = i18nc("Total balance", "Total"); break; case (int)eTransactionForm::Column::Value2: align |= Qt::AlignRight; if ((fieldEditable = haveAmount()) == true) { txt = d->m_assetAccountSplit.value().abs() .formatMoney(d->m_currency.tradingSymbol(), MyMoneyMoney::denomToPrec(d->m_currency.smallestAccountFraction())); } } break; case 6: switch (col) { case (int)eTransactionForm::Column::Label2: align |= Qt::AlignLeft; txt = i18n("Status"); break; case (int)eTransactionForm::Column::Value2: align |= Qt::AlignRight; fieldEditable = true; txt = reconcileState(); break; } } return fieldEditable; } void InvestTransaction::registerCellText(QString& txt, Qt::Alignment& align, int row, int col, QPainter* /* painter */) { Q_D(InvestTransaction); switch (row) { case 0: switch (col) { case (int)eTransaction::Column::Date: align |= Qt::AlignLeft; txt = QLocale().toString(d->m_transaction.postDate(), QLocale::ShortFormat); break; case (int)eTransaction::Column::Detail: align |= Qt::AlignLeft; activity(txt, d->m_transactionType); break; case (int)eTransaction::Column::Security: align |= Qt::AlignLeft; if (d->m_account.isInvest()) txt = d->m_security.name(); break; case (int)eTransaction::Column::ReconcileFlag: align |= Qt::AlignHCenter; txt = reconcileState(false); break; case (int)eTransaction::Column::Quantity: align |= Qt::AlignRight; if (haveShares()) txt = d->m_split.shares().abs().formatMoney(QString(), MyMoneyMoney::denomToPrec(d->m_security.smallestAccountFraction())); else if (haveSplitRatio()) { txt = QString("1 / %1").arg(d->m_split.shares().abs().formatMoney(QString(), -1)); } break; case (int)eTransaction::Column::Price: align |= Qt::AlignRight; if (havePrice() && !d->m_split.shares().isZero()) { txt = d->m_split.price().formatMoney(d->m_currency.tradingSymbol(), d->m_security.pricePrecision()); } break; case (int)eTransaction::Column::Value: align |= Qt::AlignRight; if (haveAmount()) { txt = MyMoneyUtils::formatMoney(d->m_assetAccountSplit.value().abs(), d->m_currency); } else if (haveInterest()) { txt = MyMoneyUtils::formatMoney(-d->m_interestAmount, d->m_currency); } break; case (int)eTransaction::Column::Balance: align |= Qt::AlignRight; if (d->m_showBalance) txt = d->m_balance.formatMoney(QString(), MyMoneyMoney::denomToPrec(d->m_security.smallestAccountFraction())); else txt = "----"; break; default: break; } break; case 1: switch (col) { case (int)eTransaction::Column::Detail: align |= Qt::AlignLeft; if (haveAssetAccount() && !d->m_assetAccountSplit.accountId().isEmpty()) { txt = MyMoneyFile::instance()->accountToCategory(d->m_assetAccountSplit.accountId()); } else if (haveInterest() && d->m_interestSplits.count()) { txt = d->m_interestCategory; } else if (haveFees() && d->m_feeSplits.count()) { txt = d->m_feeCategory; } else singleLineMemo(txt, d->m_split); break; case (int)eTransaction::Column::Quantity: align |= Qt::AlignRight; if (haveAssetAccount() && !d->m_assetAccountSplit.accountId().isEmpty()) { // txt = m_interestAmount.abs().formatMoney(m_currency); } else if (haveInterest() && d->m_interestSplits.count()) { txt = MyMoneyUtils::formatMoney(-d->m_interestAmount, d->m_currency); } else if (haveFees() && d->m_feeSplits.count()) { txt = MyMoneyUtils::formatMoney(d->m_feeAmount, d->m_currency); } break; default: break; } break; case 2: switch (col) { case (int)eTransaction::Column::Detail: align |= Qt::AlignLeft; if (haveAssetAccount() && !d->m_assetAccountSplit.accountId().isEmpty() && haveInterest() && d->m_interestSplits.count()) { txt = d->m_interestCategory; } else if (haveFees() && d->m_feeSplits.count()) { txt = d->m_feeCategory; } else singleLineMemo(txt, d->m_split); break; case (int)eTransaction::Column::Quantity: align |= Qt::AlignRight; if (haveAssetAccount() && !d->m_assetAccountSplit.accountId().isEmpty() && haveInterest() && d->m_interestSplits.count()) { txt = MyMoneyUtils::formatMoney(-d->m_interestAmount, d->m_currency); } else if (haveFees() && d->m_feeSplits.count()) { txt = MyMoneyUtils::formatMoney(d->m_feeAmount, d->m_currency); } break; default: break; } break; case 3: switch (col) { case (int)eTransaction::Column::Detail: align |= Qt::AlignLeft; if (haveAssetAccount() && !d->m_assetAccountSplit.accountId().isEmpty() && haveInterest() && d->m_interestSplits.count() && haveFees() && d->m_feeSplits.count()) { txt = d->m_feeCategory; } else singleLineMemo(txt, d->m_split); break; case (int)eTransaction::Column::Quantity: align |= Qt::AlignRight; if (haveAssetAccount() && !d->m_assetAccountSplit.accountId().isEmpty() && haveInterest() && d->m_interestSplits.count() && haveFees() && d->m_feeSplits.count()) { txt = MyMoneyUtils::formatMoney(d->m_feeAmount, d->m_currency); } break; default: break; } break; case 4: switch (col) { case (int)eTransaction::Column::Detail: align |= Qt::AlignLeft; singleLineMemo(txt, d->m_split); break; default: break; } break; } } int InvestTransaction::registerColWidth(int col, const QFontMetrics& cellFontMetrics) { Q_D(InvestTransaction); QString txt; MyMoneyMoney amount; int nw = 0; // for now just check all rows in that column for (int row = 0; row < d->m_rowsRegister; ++row) { int w; Transaction::registerCellText(txt, row, col); w = cellFontMetrics.width(txt + " "); nw = qMax(nw, w); } // TODO the optimized way would be to base the size on the contents of a single row // as we do it in StdTransaction::registerColWidth() #if 0 switch (col) { default: break; case PriceColumn: if (havePrice()) { txt = (m_split.value() / m_split.shares()).formatMoney(QString(), KMyMoneySettings::pricePrecision()); nw = cellFontMetrics.width(txt + " "); } break; } #endif return nw; } void InvestTransaction::loadTab(KMyMoneyTransactionForm::TransactionForm* /* form */) { } int InvestTransaction::numColsForm() const { return 4; } void InvestTransaction::arrangeWidgetsInForm(QMap& editWidgets) { Q_D(InvestTransaction); if (!d->m_form || !d->m_parent) return; setupFormPalette(editWidgets); // arrange the edit widgets arrangeWidget(d->m_form, 0, (int)eTransactionForm::Column::Value1, editWidgets["activity"]); arrangeWidget(d->m_form, 0, (int)eTransactionForm::Column::Value2, editWidgets["postdate"]); arrangeWidget(d->m_form, 1, (int)eTransactionForm::Column::Value1, editWidgets["security"]); arrangeWidget(d->m_form, 1, (int)eTransactionForm::Column::Value2, editWidgets["shares"]); arrangeWidget(d->m_form, 2, (int)eTransactionForm::Column::Value1, editWidgets["asset-account"]); arrangeWidget(d->m_form, 2, (int)eTransactionForm::Column::Value2, editWidgets["price"]); arrangeWidget(d->m_form, 3, (int)eTransactionForm::Column::Value1, editWidgets["fee-account"]->parentWidget()); arrangeWidget(d->m_form, 3, (int)eTransactionForm::Column::Value2, editWidgets["fee-amount"]); arrangeWidget(d->m_form, 4, (int)eTransactionForm::Column::Value1, editWidgets["interest-account"]->parentWidget()); arrangeWidget(d->m_form, 4, (int)eTransactionForm::Column::Value2, editWidgets["interest-amount"]); arrangeWidget(d->m_form, 5, (int)eTransactionForm::Column::Value1, editWidgets["memo"]); arrangeWidget(d->m_form, 5, (int)eTransactionForm::Column::Value2, editWidgets["total"]); arrangeWidget(d->m_form, 6, (int)eTransactionForm::Column::Value2, editWidgets["status"]); // arrange dynamic labels arrangeWidget(d->m_form, 0, (int)eTransactionForm::Column::Label1, editWidgets["activity-label"]); arrangeWidget(d->m_form, 0, (int)eTransactionForm::Column::Label2, editWidgets["postdate-label"]); arrangeWidget(d->m_form, 1, (int)eTransactionForm::Column::Label1, editWidgets["security-label"]); arrangeWidget(d->m_form, 1, (int)eTransactionForm::Column::Label2, editWidgets["shares-label"]); arrangeWidget(d->m_form, 2, (int)eTransactionForm::Column::Label1, editWidgets["asset-label"]); arrangeWidget(d->m_form, 2, (int)eTransactionForm::Column::Label2, editWidgets["price-label"]); arrangeWidget(d->m_form, 3, (int)eTransactionForm::Column::Label1, editWidgets["fee-label"]); arrangeWidget(d->m_form, 3, (int)eTransactionForm::Column::Label2, editWidgets["fee-amount-label"]); arrangeWidget(d->m_form, 4, (int)eTransactionForm::Column::Label1, editWidgets["interest-label"]); arrangeWidget(d->m_form, 4, (int)eTransactionForm::Column::Label2, editWidgets["interest-amount-label"]); arrangeWidget(d->m_form, 5, (int)eTransactionForm::Column::Label1, editWidgets["memo-label"]); arrangeWidget(d->m_form, 5, (int)eTransactionForm::Column::Label2, editWidgets["total-label"]); arrangeWidget(d->m_form, 6, (int)eTransactionForm::Column::Label2, editWidgets["status-label"]); // get rid of the hints. we don't need them for the form QMap::iterator it; for (it = editWidgets.begin(); it != editWidgets.end(); ++it) { KMyMoneyCombo* combo = dynamic_cast(*it); KMyMoneyLineEdit* lineedit = dynamic_cast(*it); KMyMoneyEdit* edit = dynamic_cast(*it); KMyMoneyPayeeCombo* payee = dynamic_cast(*it); if (combo) combo->setPlaceholderText(QString()); if (edit) edit->setPlaceholderText(QString()); if (lineedit) lineedit->setPlaceholderText(QString()); if (payee) payee->setPlaceholderText(QString()); } } void InvestTransaction::tabOrderInForm(QWidgetList& tabOrderWidgets) const { Q_D(const InvestTransaction); // activity tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(0, (int)eTransactionForm::Column::Value1))); // date tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(0, (int)eTransactionForm::Column::Value2))); // security tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(1, (int)eTransactionForm::Column::Value1))); // shares tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(1, (int)eTransactionForm::Column::Value2))); // account tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(2, (int)eTransactionForm::Column::Value1))); // price tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(2, (int)eTransactionForm::Column::Value2))); // make sure to have the fee category field and the split button as separate tab order widgets // ok, we have to have some internal knowledge about the KMyMoneyCategory object, but // it's one of our own widgets, so we actually don't care. Just make sure, that we don't // go haywire when someone changes the KMyMoneyCategory object ... QWidget* w = d->m_form->cellWidget(3, (int)eTransactionForm::Column::Value1); tabOrderWidgets.append(focusWidget(w)); w = w->findChild("splitButton"); if (w) tabOrderWidgets.append(w); // fee amount tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(3, (int)eTransactionForm::Column::Value2))); // the same applies for the interest categories w = d->m_form->cellWidget(4, (int)eTransactionForm::Column::Value1); tabOrderWidgets.append(focusWidget(w)); w = w->findChild("splitButton"); if (w) tabOrderWidgets.append(w); // interest amount tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(4, (int)eTransactionForm::Column::Value2))); // memo tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(5, (int)eTransactionForm::Column::Value1))); // state tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(6, (int)eTransactionForm::Column::Value2))); } void InvestTransaction::arrangeWidgetsInRegister(QMap& editWidgets) { Q_D(InvestTransaction); if (!d->m_parent) return; setupRegisterPalette(editWidgets); arrangeWidget(d->m_parent, d->m_startRow + 0, (int)eTransaction::Column::Date, editWidgets["postdate"]); arrangeWidget(d->m_parent, d->m_startRow + 0, (int)eTransaction::Column::Security, editWidgets["security"]); arrangeWidget(d->m_parent, d->m_startRow + 0, (int)eTransaction::Column::Detail, editWidgets["activity"]); arrangeWidget(d->m_parent, d->m_startRow + 1, (int)eTransaction::Column::Detail, editWidgets["asset-account"]); arrangeWidget(d->m_parent, d->m_startRow + 2, (int)eTransaction::Column::Detail, editWidgets["interest-account"]->parentWidget()); arrangeWidget(d->m_parent, d->m_startRow + 3, (int)eTransaction::Column::Detail, editWidgets["fee-account"]->parentWidget()); arrangeWidget(d->m_parent, d->m_startRow + 4, (int)eTransaction::Column::Detail, editWidgets["memo"]); arrangeWidget(d->m_parent, d->m_startRow + 0, (int)eTransaction::Column::Quantity, editWidgets["shares"]); arrangeWidget(d->m_parent, d->m_startRow + 0, (int)eTransaction::Column::Price, editWidgets["price"]); arrangeWidget(d->m_parent, d->m_startRow + 2, (int)eTransaction::Column::Quantity, editWidgets["interest-amount"]); arrangeWidget(d->m_parent, d->m_startRow + 3, (int)eTransaction::Column::Quantity, editWidgets["fee-amount"]); arrangeWidget(d->m_parent, d->m_startRow + 0, (int)eTransaction::Column::Value, editWidgets["total"]); arrangeWidget(d->m_parent, d->m_startRow + 1, (int)eTransaction::Column::Date, editWidgets["status"]); // increase the height of the row containing the memo widget d->m_parent->setRowHeight(d->m_startRow + 4, d->m_parent->rowHeightHint() * 3); } void InvestTransaction::tabOrderInRegister(QWidgetList& tabOrderWidgets) const { Q_D(const InvestTransaction); QWidget* w; // date tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 0, (int)eTransaction::Column::Date))); // security tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 0, (int)eTransaction::Column::Security))); // activity tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 0, (int)eTransaction::Column::Detail))); // shares tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 0, (int)eTransaction::Column::Quantity))); // price tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 0, (int)eTransaction::Column::Price))); // asset account tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 1, (int)eTransaction::Column::Detail))); // make sure to have the category fields and the split button as separate tab order widgets // ok, we have to have some internal knowledge about the KMyMoneyCategory object, but // it's one of our own widgets, so we actually don't care. Just make sure, that we don't // go haywire when someone changes the KMyMoneyCategory object ... w = d->m_parent->cellWidget(d->m_startRow + 2, (int)eTransaction::Column::Detail); // interest account tabOrderWidgets.append(focusWidget(w)); w = w->findChild("splitButton"); if (w) tabOrderWidgets.append(w); // interest amount tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 2, (int)eTransaction::Column::Quantity))); w = d->m_parent->cellWidget(d->m_startRow + 3, (int)eTransaction::Column::Detail); // fee account tabOrderWidgets.append(focusWidget(w)); w = w->findChild("splitButton"); if (w) tabOrderWidgets.append(w); // fee amount tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 3, (int)eTransaction::Column::Quantity))); // memo tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 4, (int)eTransaction::Column::Detail))); // status tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 1, (int)eTransaction::Column::Date))); } eRegister::Action InvestTransaction::actionType() const { return eRegister::Action::None; } int InvestTransaction::numRowsRegister(bool expanded) const { Q_D(const InvestTransaction); int numRows = 1; if (expanded) { if (!d->m_inEdit) { if (haveAssetAccount() && !d->m_assetAccountSplit.accountId().isEmpty()) ++numRows; if (haveInterest() && d->m_interestSplits.count()) ++numRows; if (haveFees() && d->m_feeSplits.count()) ++numRows; if (!d->m_split.memo().isEmpty()) ++numRows; } else numRows = 5; } return numRows; } bool InvestTransaction::haveShares() const { Q_D(const InvestTransaction); auto rc = true; switch (d->m_transactionType) { case eMyMoney::Split::InvestmentTransactionType::Dividend: case eMyMoney::Split::InvestmentTransactionType::Yield: case eMyMoney::Split::InvestmentTransactionType::SplitShares: case eMyMoney::Split::InvestmentTransactionType::InterestIncome: rc = false; break; default: break; } return rc; } bool InvestTransaction::haveFees() const { Q_D(const InvestTransaction); auto rc = true; switch (d->m_transactionType) { case eMyMoney::Split::InvestmentTransactionType::AddShares: case eMyMoney::Split::InvestmentTransactionType::RemoveShares: case eMyMoney::Split::InvestmentTransactionType::SplitShares: rc = false; break; default: break; } return rc; } bool InvestTransaction::haveInterest() const { Q_D(const InvestTransaction); auto rc = false; switch (d->m_transactionType) { case eMyMoney::Split::InvestmentTransactionType::BuyShares: case eMyMoney::Split::InvestmentTransactionType::SellShares: case eMyMoney::Split::InvestmentTransactionType::Dividend: case eMyMoney::Split::InvestmentTransactionType::ReinvestDividend: case eMyMoney::Split::InvestmentTransactionType::Yield: case eMyMoney::Split::InvestmentTransactionType::InterestIncome: rc = true; break; default: break; } return rc; } bool InvestTransaction::havePrice() const { Q_D(const InvestTransaction); auto rc = false; switch (d->m_transactionType) { case eMyMoney::Split::InvestmentTransactionType::BuyShares: case eMyMoney::Split::InvestmentTransactionType::SellShares: case eMyMoney::Split::InvestmentTransactionType::ReinvestDividend: rc = true; break; default: break; } return rc; } bool InvestTransaction::haveAmount() const { Q_D(const InvestTransaction); auto rc = false; switch (d->m_transactionType) { case eMyMoney::Split::InvestmentTransactionType::BuyShares: case eMyMoney::Split::InvestmentTransactionType::SellShares: case eMyMoney::Split::InvestmentTransactionType::Dividend: case eMyMoney::Split::InvestmentTransactionType::Yield: case eMyMoney::Split::InvestmentTransactionType::InterestIncome: rc = true; break; default: break; } return rc; } bool InvestTransaction::haveAssetAccount() const { Q_D(const InvestTransaction); auto rc = true; switch (d->m_transactionType) { case eMyMoney::Split::InvestmentTransactionType::AddShares: case eMyMoney::Split::InvestmentTransactionType::RemoveShares: case eMyMoney::Split::InvestmentTransactionType::SplitShares: case eMyMoney::Split::InvestmentTransactionType::ReinvestDividend: rc = false; break; default: break; } return rc; } bool InvestTransaction::haveSplitRatio() const { Q_D(const InvestTransaction); return d->m_transactionType == eMyMoney::Split::InvestmentTransactionType::SplitShares; } void InvestTransaction::splits(MyMoneySplit& assetAccountSplit, QList& interestSplits, QList& feeSplits) const { Q_D(const InvestTransaction); assetAccountSplit = d->m_assetAccountSplit; interestSplits = d->m_interestSplits; feeSplits = d->m_feeSplits; } int InvestTransaction::numRowsRegister() const { return RegisterItem::numRowsRegister(); } TransactionEditor* InvestTransaction::createEditor(TransactionEditorContainer* regForm, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate) { #ifndef KMM_DESIGNER Q_D(InvestTransaction); d->m_inRegisterEdit = regForm == d->m_parent; return new InvestTransactionEditor(regForm, this, list, lastPostDate); #else return NULL; #endif } diff --git a/kmymoney/widgets/stdtransaction.cpp b/kmymoney/widgets/stdtransaction.cpp index e2dd088bc..729f89b3c 100644 --- a/kmymoney/widgets/stdtransaction.cpp +++ b/kmymoney/widgets/stdtransaction.cpp @@ -1,706 +1,706 @@ /* * Copyright 2006-2018 Thomas Baumgart * Copyright 2017-2018 Łukasz Wojniłowicz * * 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, see . */ #include "stdtransaction.h" #include "stdtransaction_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneypayeecombo.h" #include "kmymoneycombo.h" #include "kmymoneytagcombo.h" #include "tabbar.h" #include "ktagcontainer.h" #include "mymoneytransaction.h" #include "mymoneysplit.h" #include "mymoneyexception.h" #include "mymoneyfile.h" #include "register.h" #include "transactionform.h" #include "kmymoneylineedit.h" #include "kmymoneyutils.h" #ifndef KMM_DESIGNER #include "stdtransactioneditor.h" #endif #include "kmymoneysettings.h" #include "widgetenums.h" #include "mymoneyenums.h" using namespace eWidgets; using namespace KMyMoneyRegister; using namespace KMyMoneyTransactionForm; StdTransaction::StdTransaction(Register *parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId) : Transaction(*new StdTransactionPrivate, parent, transaction, split, uniqueId) { Q_D(StdTransaction); d->m_showAccountRow = false; try { d->m_categoryHeader = i18n("Category"); switch (transaction.splitCount()) { default: d->m_category = i18nc("Split transaction (category replacement)", "Split transaction"); break; case 0: // the empty transaction case 1: break; case 2: setupFormHeader(d->m_transaction.splitByAccount(d->m_split.accountId(), false).accountId()); break; } } catch (const MyMoneyException &e) { qDebug() << "Problem determining the category for transaction '" << d->m_transaction.id() << "'. Reason: " << e.what() << "\n"; } d->m_rowsForm = 6; if (KMyMoneyUtils::transactionType(d->m_transaction) == KMyMoneyUtils::InvestmentTransaction) { MyMoneySplit stockSplit = KMyMoneyUtils::stockSplit(d->m_transaction); d->m_payee = MyMoneyFile::instance()->account(stockSplit.accountId()).name(); QString addon; if (stockSplit.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares)) { if (stockSplit.value().isNegative()) { addon = i18n("Sell"); } else { addon = i18n("Buy"); } } else if (stockSplit.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Dividend)) { addon = i18n("Dividend"); } else if (stockSplit.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Yield)) { addon = i18n("Yield"); } else if (stockSplit.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::InterestIncome)) { addon = i18n("Interest Income"); } if (!addon.isEmpty()) { d->m_payee += QString(" (%1)").arg(addon); } d->m_payeeHeader = i18n("Activity"); d->m_category = i18n("Investment transaction"); } // setup initial size setNumRowsRegister(numRowsRegister(KMyMoneySettings::showRegisterDetailed())); emit parent->itemAdded(this); } StdTransaction::~StdTransaction() { } const char* StdTransaction::className() { return "StdTransaction"; } void StdTransaction::setupFormHeader(const QString& id) { Q_D(StdTransaction); d->m_category = MyMoneyFile::instance()->accountToCategory(id); switch (MyMoneyFile::instance()->account(id).accountGroup()) { case eMyMoney::Account::Type::Asset: case eMyMoney::Account::Type::Liability: d->m_categoryHeader = d->m_split.shares().isNegative() ? i18n("Transfer to") : i18n("Transfer from"); break; default: d->m_categoryHeader = i18n("Category"); break; } } eRegister::Action StdTransaction::actionType() const { Q_D(const StdTransaction); eRegister::Action action = eRegister::Action::None; // if at least one split is referencing an income or // expense account, we will not call it a transfer auto found = false; foreach (const auto split, d->m_transaction.splits()) { if (split.accountId() == d->m_split.accountId()) continue; auto acc = MyMoneyFile::instance()->account(split.accountId()); if (acc.accountGroup() == eMyMoney::Account::Type::Income || acc.accountGroup() == eMyMoney::Account::Type::Expense) { // otherwise, we have to determine between deposit and withdrawal action = d->m_split.shares().isNegative() ? eRegister::Action::Withdrawal : eRegister::Action::Deposit; found = true; break; } } // otherwise, it's a transfer if (!found) action = eRegister::Action::Transfer; return action; } void StdTransaction::loadTab(TransactionForm* form) { Q_D(StdTransaction); KMyMoneyTransactionForm::TabBar* bar = form->getTabBar(); bar->setSignalEmission(eTabBar::SignalEmission::Never); for (auto i = 0; i < bar->count(); ++i) { bar->setTabEnabled(i, true); } if (d->m_transaction.splitCount() > 0) { bar->setCurrentIndex((int)actionType()); } bar->setSignalEmission(eTabBar::SignalEmission::Always); } int StdTransaction::numColsForm() const { return 4; } void StdTransaction::setupForm(TransactionForm* form) { Transaction::setupForm(form); form->setSpan(4, (int)eTransactionForm::Column::Value1, 3, 1); } bool StdTransaction::showRowInForm(int row) const { Q_D(const StdTransaction); return row == 0 ? d->m_showAccountRow : true; } void StdTransaction::setShowRowInForm(int row, bool show) { Q_D(StdTransaction); if (row == 0) d->m_showAccountRow = show; } bool StdTransaction::formCellText(QString& txt, Qt::Alignment& align, int row, int col, QPainter* /* painter */) { Q_D(const StdTransaction); // if(m_transaction != MyMoneyTransaction()) { switch (row) { case 0: switch (col) { case (int)eTransactionForm::Column::Label1: align |= Qt::AlignLeft; txt = i18n("Account"); break; } break; case 1: switch (col) { case (int)eTransactionForm::Column::Label1: align |= Qt::AlignLeft; txt = d->m_payeeHeader; break; case (int)eTransactionForm::Column::Value1: align |= Qt::AlignLeft; txt = d->m_payee; break; case (int)eTransactionForm::Column::Label2: align |= Qt::AlignLeft; if (haveNumberField()) txt = i18n("Number"); break; case (int)eTransactionForm::Column::Value2: align |= Qt::AlignRight; if (haveNumberField()) txt = d->m_split.number(); break; } break; case 2: switch (col) { case (int)eTransactionForm::Column::Label1: align |= Qt::AlignLeft; txt = d->m_categoryHeader; break; case (int)eTransactionForm::Column::Value1: align |= Qt::AlignLeft; txt = d->m_category; - if (d->m_transaction != MyMoneyTransaction()) { + if (!d->m_transaction.id().isEmpty()) { if (txt.isEmpty() && !d->m_split.value().isZero()) txt = i18n("*** UNASSIGNED ***"); } break; case (int)eTransactionForm::Column::Label2: align |= Qt::AlignLeft; txt = i18n("Date"); break; case (int)eTransactionForm::Column::Value2: align |= Qt::AlignRight; - if (d->m_transaction != MyMoneyTransaction()) + if (!d->m_transaction.id().isEmpty()) txt = QLocale().toString(d->m_transaction.postDate(), QLocale::ShortFormat); break; } break; case 3: switch (col) { case (int)eTransactionForm::Column::Label1: align |= Qt::AlignLeft; txt = i18n("Tags"); break; case (int)eTransactionForm::Column::Value1: align |= Qt::AlignLeft; if (!d->m_tagList.isEmpty()) { for (auto i = 0; i < d->m_tagList.size() - 1; ++i) txt += d->m_tagList[i] + ", "; txt += d->m_tagList.last(); } //if (m_transaction != MyMoneyTransaction()) // txt = m_split.tagId(); break; case (int)eTransactionForm::Column::Label2: align |= Qt::AlignLeft; txt = i18n("Amount"); break; case (int)eTransactionForm::Column::Value2: align |= Qt::AlignRight; - if (d->m_transaction != MyMoneyTransaction()) { + if (!d->m_transaction.id().isEmpty()) { txt = (d->m_split.value(d->m_transaction.commodity(), d->m_splitCurrencyId).abs()).formatMoney(d->m_account.fraction()); } break; } break; case 4: switch (col) { case (int)eTransactionForm::Column::Label1: align |= Qt::AlignLeft; txt = i18n("Memo"); break; case (int)eTransactionForm::Column::Value1: align &= ~Qt::AlignVCenter; align |= Qt::AlignTop; align |= Qt::AlignLeft; - if (d->m_transaction != MyMoneyTransaction()) + if (!d->m_transaction.id().isEmpty()) txt = d->m_split.memo().section('\n', 0, 2); break; } break; case 5: switch (col) { case (int)eTransactionForm::Column::Label2: align |= Qt::AlignLeft; txt = i18n("Status"); break; case (int)eTransactionForm::Column::Value2: align |= Qt::AlignRight; txt = reconcileState(); break; } } // } if (col == (int)eTransactionForm::Column::Value2 && row == 1) { return haveNumberField(); } return (col == (int)eTransactionForm::Column::Value1 && row < 5) || (col == (int)eTransactionForm::Column::Value2 && row > 0 && row != 4); } void StdTransaction::registerCellText(QString& txt, Qt::Alignment& align, int row, int col, QPainter* painter) { Q_D(const StdTransaction); switch (row) { case 0: switch (col) { case (int)eTransaction::Column::Number: align |= Qt::AlignLeft; if (haveNumberField()) txt = d->m_split.number(); break; case (int)eTransaction::Column::Date: align |= Qt::AlignLeft; txt = QLocale().toString(d->m_transaction.postDate(), QLocale::ShortFormat); break; case (int)eTransaction::Column::Detail: switch (d->m_parent->getDetailsColumnType()) { case eRegister::DetailColumn::PayeeFirst: txt = d->m_payee; break; case eRegister::DetailColumn::AccountFirst: txt = d->m_category; if (!d->m_tagList.isEmpty()) { txt += " ( "; for (auto i = 0; i < d->m_tagList.size() - 1; ++i) { txt += " " + d->m_tagList[i] + ", "; } txt += " " + d->m_tagList.last() + " )"; } break; } align |= Qt::AlignLeft; if (txt.isEmpty() && d->m_rowsRegister < 3) { singleLineMemo(txt, d->m_split); } if (txt.isEmpty() && d->m_rowsRegister < 2) { if (d->m_account.accountType() != eMyMoney::Account::Type::Income && d->m_account.accountType() != eMyMoney::Account::Type::Expense) { txt = d->m_category; if (txt.isEmpty() && !d->m_split.value().isZero()) { txt = i18n("*** UNASSIGNED ***"); if (painter) painter->setPen(KMyMoneySettings::schemeColor(SchemeColor::TransactionErroneous)); } } } break; case (int)eTransaction::Column::ReconcileFlag: align |= Qt::AlignHCenter; txt = reconcileState(false); break; case (int)eTransaction::Column::Payment: align |= Qt::AlignRight; if (d->m_split.value().isNegative()) { txt = (-d->m_split.value(d->m_transaction.commodity(), d->m_splitCurrencyId)).formatMoney(d->m_account.fraction()); } break; case (int)eTransaction::Column::Deposit: align |= Qt::AlignRight; if (!d->m_split.value().isNegative()) { txt = d->m_split.value(d->m_transaction.commodity(), d->m_splitCurrencyId).formatMoney(d->m_account.fraction()); } break; case (int)eTransaction::Column::Balance: align |= Qt::AlignRight; if (d->m_showBalance) txt = d->m_balance.formatMoney(d->m_account.fraction()); else txt = "----"; break; case (int)eTransaction::Column::Account: // txt = m_objects->account(m_transaction.splits()[0].accountId()).name(); txt = MyMoneyFile::instance()->account(d->m_split.accountId()).name(); break; default: break; } break; case 1: switch (col) { case (int)eTransaction::Column::Detail: switch (d->m_parent->getDetailsColumnType()) { case eRegister::DetailColumn::PayeeFirst: txt = d->m_category; if (txt.isEmpty() && !d->m_split.value().isZero()) { txt = i18n("*** UNASSIGNED ***"); if (painter) painter->setPen(KMyMoneySettings::schemeColor(SchemeColor::TransactionErroneous)); } if (!d->m_tagList.isEmpty()) { txt += " ( "; for (auto i = 0; i < d->m_tagList.size() - 1; ++i) { txt += " " + d->m_tagList[i] + ", "; } txt += " " + d->m_tagList.last() + " )"; } break; case eRegister::DetailColumn::AccountFirst: txt = d->m_payee; if (txt.isEmpty()) { singleLineMemo(txt, d->m_split); } break; } align |= Qt::AlignLeft; break; default: break; } break; case 2: switch (col) { case (int)eTransaction::Column::Detail: align |= Qt::AlignLeft; singleLineMemo(txt, d->m_split); break; default: break; } break; } } int StdTransaction::registerColWidth(int col, const QFontMetrics& cellFontMetrics) { QString txt; int firstRow = 0, lastRow = numRowsRegister(); int nw = 0; for (int i = firstRow; i <= lastRow; ++i) { Qt::Alignment align; registerCellText(txt, align, i, col, 0); int w = cellFontMetrics.width(txt + " "); if (w > nw) nw = w; } return nw; } void StdTransaction::arrangeWidgetsInForm(QMap& editWidgets) { Q_D(StdTransaction); if (!d->m_form || !d->m_parent) return; setupFormPalette(editWidgets); arrangeWidget(d->m_form, 0, (int)eTransactionForm::Column::Label1, editWidgets["account-label"]); arrangeWidget(d->m_form, 0, (int)eTransactionForm::Column::Value1, editWidgets["account"]); arrangeWidget(d->m_form, 1, (int)eTransactionForm::Column::Label1, editWidgets["cashflow"]); arrangeWidget(d->m_form, 1, (int)eTransactionForm::Column::Value1, editWidgets["payee"]); arrangeWidget(d->m_form, 2, (int)eTransactionForm::Column::Label1, editWidgets["category-label"]); arrangeWidget(d->m_form, 2, (int)eTransactionForm::Column::Value1, editWidgets["category"]->parentWidget()); arrangeWidget(d->m_form, 3, (int)eTransactionForm::Column::Label1, editWidgets["tag-label"]); arrangeWidget(d->m_form, 3, (int)eTransactionForm::Column::Value1, editWidgets["tag"]); arrangeWidget(d->m_form, 4, (int)eTransactionForm::Column::Label1, editWidgets["memo-label"]); arrangeWidget(d->m_form, 4, (int)eTransactionForm::Column::Value1, editWidgets["memo"]); if (haveNumberField()) { arrangeWidget(d->m_form, 1, (int)eTransactionForm::Column::Label2, editWidgets["number-label"]); arrangeWidget(d->m_form, 1, (int)eTransactionForm::Column::Value2, editWidgets["number"]); } arrangeWidget(d->m_form, 2, (int)eTransactionForm::Column::Label2, editWidgets["date-label"]); arrangeWidget(d->m_form, 2, (int)eTransactionForm::Column::Value2, editWidgets["postdate"]); arrangeWidget(d->m_form, 3, (int)eTransactionForm::Column::Label2, editWidgets["amount-label"]); arrangeWidget(d->m_form, 3, (int)eTransactionForm::Column::Value2, editWidgets["amount"]); arrangeWidget(d->m_form, 5, (int)eTransactionForm::Column::Label2, editWidgets["status-label"]); arrangeWidget(d->m_form, 5, (int)eTransactionForm::Column::Value2, editWidgets["status"]); // get rid of the hints. we don't need them for the form QMap::iterator it; for (it = editWidgets.begin(); it != editWidgets.end(); ++it) { KMyMoneyCombo* combo = dynamic_cast(*it); KMyMoneyLineEdit* edit = dynamic_cast(*it); KMyMoneyPayeeCombo* payee = dynamic_cast(*it); KTagContainer* tag = dynamic_cast(*it); if (combo) combo->setPlaceholderText(QString()); if (edit) edit->setPlaceholderText(QString()); if (payee) payee->setPlaceholderText(QString()); if (tag) tag->tagCombo()->setPlaceholderText(QString()); } auto form = dynamic_cast(d->m_form); auto w = dynamic_cast(editWidgets["tabbar"]); if (w && form) { // insert the tabbar in the boxlayout so it will take the place of the original tabbar which was hidden if (auto boxLayout = dynamic_cast(form->getTabBar()->parentWidget()->layout())) boxLayout->insertWidget(0, w); } } void StdTransaction::tabOrderInForm(QWidgetList& tabOrderWidgets) const { Q_D(const StdTransaction); QStringList taborder = KMyMoneySettings::stdTransactionFormTabOrder().split(',', QString::SkipEmptyParts); QStringList::const_iterator it_s = taborder.constBegin(); QWidget* w; while (it_s != taborder.constEnd()) { if (*it_s == "account") { tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(0, (int)eTransactionForm::Column::Value1))); } else if (*it_s == "cashflow") { tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(1, (int)eTransactionForm::Column::Label1))); } else if (*it_s == "payee") { tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(1, (int)eTransactionForm::Column::Value1))); } else if (*it_s == "category") { // make sure to have the category field and the split button as separate tab order widgets // ok, we have to have some internal knowledge about the KMyMoneyCategory object, but // it's one of our own widgets, so we actually don't care. Just make sure, that we don't // go haywire when someone changes the KMyMoneyCategory object ... w = d->m_form->cellWidget(2, (int)eTransactionForm::Column::Value1); tabOrderWidgets.append(focusWidget(w)); w = w->findChild("splitButton"); if (w) tabOrderWidgets.append(w); } else if (*it_s == "tag") { tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(3, (int)eTransactionForm::Column::Value1))); } else if (*it_s == "memo") { tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(4, (int)eTransactionForm::Column::Value1))); } else if (*it_s == "number") { if (haveNumberField()) { if ((w = focusWidget(d->m_form->cellWidget(1, (int)eTransactionForm::Column::Value2)))) tabOrderWidgets.append(w); } } else if (*it_s == "date") { tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(2, (int)eTransactionForm::Column::Value2))); } else if (*it_s == "amount") { tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(3, (int)eTransactionForm::Column::Value2))); } else if (*it_s == "state") { tabOrderWidgets.append(focusWidget(d->m_form->cellWidget(5, (int)eTransactionForm::Column::Value2))); } ++it_s; } } void StdTransaction::arrangeWidgetsInRegister(QMap& editWidgets) { Q_D(StdTransaction); if (!d->m_parent) return; setupRegisterPalette(editWidgets); if (haveNumberField()) arrangeWidget(d->m_parent, d->m_startRow + 0, (int)eTransaction::Column::Number, editWidgets["number"]); arrangeWidget(d->m_parent, d->m_startRow + 0, (int)eTransaction::Column::Date, editWidgets["postdate"]); arrangeWidget(d->m_parent, d->m_startRow + 1, (int)eTransaction::Column::Date, editWidgets["status"]); arrangeWidget(d->m_parent, d->m_startRow + 0, (int)eTransaction::Column::Detail, editWidgets["payee"]); arrangeWidget(d->m_parent, d->m_startRow + 1, (int)eTransaction::Column::Detail, editWidgets["category"]->parentWidget()); arrangeWidget(d->m_parent, d->m_startRow + 2, (int)eTransaction::Column::Detail, editWidgets["tag"]); arrangeWidget(d->m_parent, d->m_startRow + 3, (int)eTransaction::Column::Detail, editWidgets["memo"]); arrangeWidget(d->m_parent, d->m_startRow + 0, (int)eTransaction::Column::Payment, editWidgets["payment"]); arrangeWidget(d->m_parent, d->m_startRow + 0, (int)eTransaction::Column::Deposit, editWidgets["deposit"]); // increase the height of the row containing the memo widget d->m_parent->setRowHeight(d->m_startRow + 3, d->m_parent->rowHeightHint() * 3); } void StdTransaction::tabOrderInRegister(QWidgetList& tabOrderWidgets) const { Q_D(const StdTransaction); QStringList taborder = KMyMoneySettings::stdTransactionRegisterTabOrder().split(',', QString::SkipEmptyParts); QStringList::const_iterator it_s = taborder.constBegin(); QWidget* w; while (it_s != taborder.constEnd()) { if (*it_s == "number") { if (haveNumberField()) { if ((w = focusWidget(d->m_parent->cellWidget(d->m_startRow + 0, (int)eTransaction::Column::Number)))) tabOrderWidgets.append(w); } } else if (*it_s == "date") { tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 0, (int)eTransaction::Column::Date))); } else if (*it_s == "payee") { tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 0, (int)eTransaction::Column::Detail))); } else if (*it_s == "category") { // make sure to have the category field and the split button as separate tab order widgets // ok, we have to have some internal knowledge about the KMyMoneyCategory object, but // it's one of our own widgets, so we actually don't care. Just make sure, that we don't // go haywire when someone changes the KMyMoneyCategory object ... w = d->m_parent->cellWidget(d->m_startRow + 1, (int)eTransaction::Column::Detail); tabOrderWidgets.append(focusWidget(w)); w = w->findChild("splitButton"); if (w) tabOrderWidgets.append(w); } else if (*it_s == "tag") { tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 2, (int)eTransaction::Column::Detail))); } else if (*it_s == "memo") { tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 3, (int)eTransaction::Column::Detail))); } else if (*it_s == "payment") { tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 0, (int)eTransaction::Column::Payment))); } else if (*it_s == "deposit") { tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 0, (int)eTransaction::Column::Deposit))); } else if (*it_s == "state") { tabOrderWidgets.append(focusWidget(d->m_parent->cellWidget(d->m_startRow + 1, (int)eTransaction::Column::Date))); } ++it_s; } } int StdTransaction::numRowsRegister(bool expanded) const { Q_D(const StdTransaction); int numRows = 1; if (expanded) { numRows = 4; if (!d->m_inEdit) { //When not in edit Tags haven't a separate row; numRows--; if (d->m_payee.isEmpty()) { numRows--; } if (d->m_split.memo().isEmpty()) { numRows--; } // For income and expense accounts that only have // two splits we only show one line, because the // account name is already contained in the account column. if (d->m_account.accountType() == eMyMoney::Account::Type::Income || d->m_account.accountType() == eMyMoney::Account::Type::Expense) { if (numRows > 2 && d->m_transaction.splitCount() == 2) numRows = 1; } } } return numRows; } int StdTransaction::numRowsRegister() const { return RegisterItem::numRowsRegister(); } TransactionEditor* StdTransaction::createEditor(TransactionEditorContainer* regForm, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate) { #ifndef KMM_DESIGNER Q_D(StdTransaction); d->m_inRegisterEdit = regForm == d->m_parent; return new StdTransactionEditor(regForm, this, list, lastPostDate); #else return NULL; #endif } diff --git a/kmymoney/widgets/transaction.cpp b/kmymoney/widgets/transaction.cpp index 733509c47..b0b166582 100644 --- a/kmymoney/widgets/transaction.cpp +++ b/kmymoney/widgets/transaction.cpp @@ -1,946 +1,946 @@ /* * Copyright 2006-2018 Thomas Baumgart * Copyright 2017-2018 Łukasz Wojniłowicz * * 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, see . */ #include "transaction.h" #include "transaction_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyutils.h" #include "mymoneytransaction.h" #include "mymoneysplit.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "register.h" #include "kmymoneycategory.h" #include "kmymoneydateinput.h" #include "transactionform.h" #include "kmymoneyedit.h" #include "kmymoneyutils.h" #include "registerfilter.h" #include "tabbar.h" #include "kmymoneysettings.h" #include "widgetenums.h" #include "mymoneyenums.h" using namespace eWidgets; using namespace KMyMoneyRegister; using namespace KMyMoneyTransactionForm; static unsigned char attentionSign[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x08, 0x06, 0x00, 0x00, 0x00, 0x8D, 0x89, 0x1D, 0x0D, 0x00, 0x00, 0x00, 0x04, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0x08, 0x7C, 0x08, 0x64, 0x88, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E, 0x6B, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2E, 0x6F, 0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00, 0x02, 0x05, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8D, 0xAD, 0x95, 0xBF, 0x4B, 0x5B, 0x51, 0x14, 0xC7, 0x3F, 0x2F, 0xBC, 0x97, 0x97, 0x97, 0x97, 0x77, 0xF3, 0xF2, 0x1C, 0xA4, 0x54, 0x6B, 0x70, 0x10, 0x44, 0x70, 0x2A, 0x91, 0x2E, 0x52, 0x02, 0x55, 0x8A, 0xB5, 0xA3, 0xAB, 0x38, 0x08, 0x66, 0xCC, 0xEE, 0xE0, 0xE2, 0x20, 0xB8, 0x38, 0xB8, 0xB8, 0xF8, 0x1F, 0x38, 0x29, 0xA5, 0x29, 0x74, 0x90, 0x0E, 0x0D, 0x0E, 0x22, 0x1D, 0x44, 0xA8, 0xD0, 0xD4, 0xB4, 0x58, 0x4B, 0x09, 0xF9, 0xF1, 0x4A, 0x3B, 0xD4, 0xD3, 0xE1, 0x55, 0xD3, 0x34, 0xAF, 0x49, 0x6C, 0x3D, 0xF0, 0x85, 0x7B, 0xCF, 0xFD, 0x9E, 0xEF, 0x3D, 0xE7, 0xFE, 0xD4, 0x44, 0x84, 0xDB, 0xB4, 0x48, 0x2F, 0xA4, 0x94, 0xAB, 0xE5, 0x52, 0xAE, 0x96, 0xEB, 0x49, 0x51, 0x44, 0x3A, 0x02, 0x18, 0x88, 0xC7, 0xF1, 0xE3, 0x71, 0x7C, 0x60, 0xA0, 0x1B, 0xBF, 0x6B, 0x86, 0x49, 0xC5, 0x46, 0x3E, 0x47, 0x34, 0x9F, 0x23, 0x9A, 0x54, 0x6C, 0xFC, 0x57, 0x86, 0x40, 0xC6, 0x4B, 0xE1, 0x37, 0xCA, 0x48, 0xA3, 0x8C, 0x78, 0x29, 0x7C, 0x20, 0xD3, 0x31, 0xA6, 0xD3, 0xA0, 0x52, 0x1C, 0x6D, 0x6F, 0x72, 0xD9, 0x28, 0x23, 0xFE, 0x07, 0x64, 0x7B, 0x93, 0x4B, 0xA5, 0x38, 0xFA, 0x27, 0x41, 0x60, 0x6E, 0x74, 0x84, 0x7A, 0xE5, 0x1D, 0x92, 0x54, 0x88, 0xE7, 0x22, 0xD5, 0x12, 0x32, 0x3A, 0x42, 0x1D, 0x98, 0xBB, 0x91, 0x20, 0x60, 0xDA, 0x36, 0x17, 0xFB, 0x7B, 0xC8, 0xC1, 0x4B, 0x04, 0x02, 0xBC, 0x7E, 0x81, 0xEC, 0xEF, 0x21, 0xB6, 0xCD, 0x05, 0x60, 0xF6, 0x2C, 0x68, 0x9A, 0x2C, 0xCF, 0x4C, 0xE1, 0x4B, 0x05, 0x39, 0x3F, 0x69, 0x0A, 0xBE, 0x7F, 0x83, 0x48, 0x05, 0x99, 0x99, 0xC2, 0x37, 0x4D, 0x96, 0x7B, 0x12, 0x04, 0xFA, 0x2D, 0x8B, 0xC6, 0xE9, 0x61, 0x10, 0x2C, 0x15, 0xC4, 0x8A, 0x21, 0x86, 0x8E, 0xFC, 0xF8, 0x12, 0xF4, 0x4F, 0x0F, 0x11, 0xCB, 0xA2, 0x01, 0xF4, 0x77, 0x3D, 0x36, 0x4E, 0x82, 0xF5, 0xA5, 0x05, 0x8C, 0xE1, 0x74, 0xD3, 0x37, 0x34, 0x18, 0x20, 0xF2, 0x8B, 0x3D, 0x9C, 0x86, 0xA5, 0x05, 0x0C, 0x27, 0xC1, 0x7A, 0xC7, 0x63, 0x03, 0x8C, 0x2B, 0x07, 0xBF, 0x5A, 0x6A, 0x66, 0x27, 0x15, 0x64, 0x3A, 0x8B, 0x3C, 0x7A, 0xD8, 0xEA, 0xAB, 0x96, 0x10, 0xE5, 0xE0, 0x03, 0xE3, 0x7F, 0xCD, 0x50, 0x39, 0x6C, 0xAD, 0xAD, 0x10, 0x53, 0xAA, 0x75, 0xD2, 0xF4, 0xBD, 0x00, 0x2D, 0x5C, 0x05, 0x6B, 0x2B, 0xC4, 0x94, 0xC3, 0xD6, 0xEF, 0xFE, 0x6B, 0x41, 0x4D, 0xD3, 0x66, 0xFB, 0x3C, 0xC6, 0x16, 0xE7, 0xDB, 0x97, 0x61, 0xE2, 0x3E, 0x3C, 0xC8, 0xB4, 0x15, 0xC7, 0xE2, 0x3C, 0x91, 0x3E, 0x8F, 0x31, 0x4D, 0xD3, 0x66, 0x5B, 0x4A, 0x06, 0x8C, 0x84, 0xCD, 0x59, 0x61, 0xA7, 0xB5, 0xAC, 0x2B, 0x9C, 0x1C, 0x04, 0x08, 0x1B, 0x2B, 0xEC, 0x20, 0x09, 0x9B, 0x33, 0xC0, 0xB8, 0xDE, 0x65, 0x43, 0x27, 0x9F, 0x9D, 0xA4, 0x1E, 0x16, 0xF0, 0xF9, 0x6D, 0xB0, 0xC3, 0x86, 0x1E, 0xB4, 0xC3, 0x38, 0xD9, 0x49, 0xEA, 0x86, 0x4E, 0xFE, 0xEA, 0x29, 0xF4, 0x2C, 0x8B, 0xDA, 0x71, 0x31, 0x9C, 0xFC, 0xF5, 0x23, 0x32, 0x34, 0x88, 0xDC, 0xBD, 0x13, 0x5C, 0xBF, 0x30, 0xCE, 0x71, 0x11, 0xB1, 0x2C, 0x6A, 0x80, 0xA7, 0xDB, 0x36, 0xAB, 0x4F, 0xA6, 0x89, 0xBA, 0x49, 0x38, 0xFF, 0xD4, 0xBE, 0x4E, 0x00, 0xAF, 0x9E, 0x81, 0x08, 0xD4, 0xEA, 0x01, 0xFE, 0x34, 0x37, 0x09, 0x4F, 0x1F, 0x13, 0xDD, 0x7D, 0xCE, 0xAA, 0x96, 0x72, 0x29, 0x7C, 0xFB, 0xCE, 0x44, 0xB8, 0xD4, 0xCD, 0x2C, 0x66, 0x52, 0xD4, 0x6E, 0xFB, 0x0B, 0xF8, 0x09, 0x63, 0x63, 0x31, 0xE4, 0x85, 0x76, 0x2E, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; Transaction::Transaction(Register *parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId) : RegisterItem(*new TransactionPrivate, parent) { Q_D(Transaction); d->m_transaction = transaction; d->m_split = split; d->m_form = nullptr; d->m_uniqueId = d->m_transaction.id(); d->init(uniqueId); } Transaction::Transaction(TransactionPrivate &dd, Register* parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId) : RegisterItem(dd, parent) { Q_D(Transaction); d->m_form = nullptr; d->m_transaction = transaction; d->m_split = split; d->m_uniqueId = d->m_transaction.id(); d->init(uniqueId); } Transaction::~Transaction() { } const char* Transaction::className() { return "Transaction"; } bool Transaction::isSelectable() const { return true; } bool Transaction::isSelected() const { Q_D(const Transaction); return d->m_selected; } void Transaction::setSelected(bool selected) { Q_D(Transaction); if (!selected || (selected && isVisible())) d->m_selected = selected; } bool Transaction::canHaveFocus() const { return true; } bool Transaction::hasFocus() const { Q_D(const Transaction); return d->m_focus; } bool Transaction::hasEditorOpen() const { Q_D(const Transaction); return d->m_inEdit; } bool Transaction::isScheduled() const { return false; } void Transaction::setFocus(bool focus, bool updateLens) { Q_D(Transaction); if (focus != d->m_focus) { d->m_focus = focus; } if (updateLens) { if (KMyMoneySettings::ledgerLens() || !KMyMoneySettings::transactionForm() || KMyMoneySettings::showRegisterDetailed() || d->m_parent->ledgerLens()) { if (focus) setNumRowsRegister(numRowsRegister(true)); else setNumRowsRegister(numRowsRegister(KMyMoneySettings::showRegisterDetailed())); } } } bool Transaction::isErroneous() const { Q_D(const Transaction); return d->m_erroneous; } QDate Transaction::sortPostDate() const { Q_D(const Transaction); return d->m_transaction.postDate(); } int Transaction::sortSamePostDate() const { return 2; } QDate Transaction::sortEntryDate() const { Q_D(const Transaction); return d->m_transaction.entryDate(); } const QString& Transaction::sortPayee() const { Q_D(const Transaction); return d->m_payee; } const QList& Transaction::sortTagList() const { Q_D(const Transaction); return d->m_tagList; } MyMoneyMoney Transaction::sortValue() const { Q_D(const Transaction); return d->m_split.shares(); } QString Transaction::sortNumber() const { Q_D(const Transaction); return d->m_split.number(); } const QString& Transaction::sortEntryOrder() const { Q_D(const Transaction); return d->m_uniqueId; } eRegister::CashFlowDirection Transaction::sortType() const { Q_D(const Transaction); return d->m_split.shares().isNegative() ? eRegister::CashFlowDirection::Payment : eRegister::CashFlowDirection::Deposit; } const QString& Transaction::sortCategory() const { Q_D(const Transaction); return d->m_category; } eMyMoney::Split::State Transaction::sortReconcileState() const { Q_D(const Transaction); return d->m_split.reconcileFlag(); } QString Transaction::id() const { Q_D(const Transaction); return d->m_uniqueId; } const MyMoneyTransaction& Transaction::transaction() const { Q_D(const Transaction); return d->m_transaction; } const MyMoneySplit& Transaction::split() const { Q_D(const Transaction); return d->m_split; } void Transaction::setBalance(const MyMoneyMoney& balance) { Q_D(Transaction); d->m_balance = balance; } const MyMoneyMoney& Transaction::balance() const { Q_D(const Transaction); return d->m_balance; } bool Transaction::paintRegisterCellSetup(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index) { Q_D(Transaction); Q_UNUSED(painter) if (d->m_reducedIntensity) { option.palette.setColor(QPalette::Text, option.palette.color(QPalette::Disabled, QPalette::Text)); } if (d->m_selected) { option.state |= QStyle::State_Selected; } else { option.state &= ~QStyle::State_Selected; } if (d->m_focus) { option.state |= QStyle::State_HasFocus; } else { option.state &= ~QStyle::State_HasFocus; } if (option.widget && option.widget->hasFocus()) { option.palette.setCurrentColorGroup(QPalette::Active); } else { option.palette.setCurrentColorGroup(QPalette::Inactive); } if (index.column() == 0) { option.viewItemPosition = QStyleOptionViewItem::Beginning; } else if (index.column() == (int)eTransaction::Column::LastColumn - 1) { option.viewItemPosition = QStyleOptionViewItem::End; } else { option.viewItemPosition = QStyleOptionViewItem::Middle; } // do we need to switch to the error color? if (d->m_erroneous) { option.palette.setColor(QPalette::Text, KMyMoneySettings::schemeColor(SchemeColor::TransactionErroneous)); } // do we need to switch to the negative balance color? if (index.column() == (int)eTransaction::Column::Balance) { bool showNegative = d->m_balance.isNegative(); if (d->m_account.accountGroup() == eMyMoney::Account::Type::Liability && !d->m_balance.isZero()) showNegative = !showNegative; if (showNegative) option.palette.setColor(QPalette::Text, KMyMoneySettings::schemeColor(SchemeColor::TransactionErroneous)); } return true; } void Transaction::registerCellText(QString& txt, int row, int col) { Qt::Alignment align; registerCellText(txt, align, row, col, 0); } void Transaction::paintRegisterCell(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index) { Q_D(Transaction); painter->save(); if (paintRegisterCellSetup(painter, option, index)) { const QStyle *style = option.widget ? option.widget->style() : QApplication::style(); const QWidget* widget = option.widget; // clear the mouse over state before painting the background option.state &= ~QStyle::State_MouseOver; // the background if (option.state & QStyle::State_Selected || option.state & QStyle::State_HasFocus) { // if this is not the first row of the transaction paint the previous rows // since the selection background is painted from the first row of the transaction if (index.row() > startRow()) { QStyleOptionViewItem optionSibling = option; QModelIndex previousRowItem = index.sibling(index.row() - 1, index.column()); optionSibling.rect = d->m_parent->visualRect(previousRowItem); paintRegisterCell(painter, optionSibling, previousRowItem); } // paint the selection background only from the first row on to the last row at once if (index.row() == startRow()) { QRect old = option.rect; int extraHeight = 0; if (d->m_inRegisterEdit) { // since, when editing a transaction inside the register (without the transaction form), // row heights can have various sizes (the memo row is larger than the rest) we have // to iterate over all the items of the transaction to compute the size of the selection rectangle // of course we start with the item after this one because it's size is already in the rectangle for (int i = startRow() + 1; i < startRow() + numRowsRegister(); ++i) { extraHeight += d->m_parent->visualRect(index.sibling(i, index.column())).height(); } } else { // we are not editing in the register so all rows have the same sizes just compute the extra height extraHeight = (numRowsRegister() - 1) * option.rect.height(); } option.rect.setBottom(option.rect.bottom() + extraHeight); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, widget); if (d->m_focus && index.column() == (int)eTransaction::Column::Detail) { option.state |= QStyle::State_HasFocus; style->drawPrimitive(QStyle::PE_FrameFocusRect, &option, painter, widget); } option.rect = old; } } else { if (d->m_alternate) { painter->fillRect(option.rect, option.palette.alternateBase()); } else { painter->fillRect(option.rect, option.palette.base()); } } // the text // construct the text for the cell QString txt; option.displayAlignment = Qt::AlignVCenter; - if (d->m_transaction != MyMoneyTransaction() && !d->m_inRegisterEdit) { + if (!d->m_transaction.id().isEmpty() && !d->m_inRegisterEdit) { registerCellText(txt, option.displayAlignment, index.row() - startRow(), index.column(), painter); } if (Qt::mightBeRichText(txt)) { QTextDocument document; // this should set the alignment of the html but it does not work so htmls will be left aligned document.setDefaultTextOption(QTextOption(option.displayAlignment)); document.setDocumentMargin(2); document.setHtml(txt); painter->translate(option.rect.topLeft()); QAbstractTextDocumentLayout::PaintContext ctx; ctx.palette = option.palette; // Highlighting text if item is selected if (d->m_selected) ctx.palette.setColor(QPalette::Text, option.palette.color(QPalette::HighlightedText)); document.documentLayout()->draw(painter, ctx); painter->translate(-option.rect.topLeft()); } else { // draw plain text properly aligned style->drawItemText(painter, option.rect.adjusted(2, 0, -2, 0), option.displayAlignment, option.palette, true, txt, d->m_selected ? QPalette::HighlightedText : QPalette::Text); } // draw the grid if it's needed if (KMyMoneySettings::showGrid()) { const int gridHint = style->styleHint(QStyle::SH_Table_GridLineColor, &option, widget); const QPen gridPen = QPen(QColor(static_cast(gridHint)), 0); QPen old = painter->pen(); painter->setPen(gridPen); if (index.row() == startRow()) painter->drawLine(option.rect.topLeft(), option.rect.topRight()); painter->drawLine(option.rect.topLeft(), option.rect.bottomLeft()); painter->setPen(old); } // possible icons if (index.row() == startRow() && index.column() == (int)eTransaction::Column::Detail) { if (d->m_erroneous) { QPixmap attention; attention.loadFromData(attentionSign, sizeof(attentionSign), 0, 0); style->drawItemPixmap(painter, option.rect, Qt::AlignRight | Qt::AlignVCenter, attention); } } } painter->restore(); } bool Transaction::formCellText(QString& /* txt */, Qt::Alignment& /* align */, int /* row */, int /* col */, QPainter* /* painter */) { return false; } void Transaction::registerCellText(QString& /* txt */, Qt::Alignment& /* align */, int /* row */, int /* col */, QPainter* /* painter */) { } int Transaction::registerColWidth(int /* col */, const QFontMetrics& /* cellFontMetrics */) { return 0; } int Transaction::formRowHeight(int /*row*/) { Q_D(Transaction); if (d->m_formRowHeight < 0) { d->m_formRowHeight = formRowHeight(); } return d->m_formRowHeight; } int Transaction::formRowHeight() const { Q_D(const Transaction); if (d->m_formRowHeight < 0) { // determine the height of the objects in the table KMyMoneyDateInput dateInput; KMyMoneyCategory category(true, nullptr); return qMax(dateInput.sizeHint().height(), category.sizeHint().height()); } return d->m_formRowHeight; } void Transaction::setupForm(TransactionForm* form) { Q_D(Transaction); d->m_form = form; form->verticalHeader()->setUpdatesEnabled(false); form->horizontalHeader()->setUpdatesEnabled(false); form->setRowCount(numRowsForm()); form->setColumnCount(numColsForm()); // Force all cells to have some text (so that paintCell is called for each cell) for (int r = 0; r < numRowsForm(); ++r) { for (int c = 0; c < numColsForm(); ++c) { if (r == 0 && form->columnWidth(c) == 0) { form->setColumnWidth(c, 10); } } } form->horizontalHeader()->setUpdatesEnabled(true); form->verticalHeader()->setUpdatesEnabled(true); loadTab(form); } void Transaction::paintFormCell(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) { Q_D(Transaction); if (!d->m_form) return; QRect cellRect = option.rect; QRect textRect(cellRect); textRect.setWidth(textRect.width() - 2); textRect.setHeight(textRect.height() - 2); painter->setPen(option.palette.text().color()); QString txt; Qt::Alignment align = Qt::AlignVCenter; bool editField = formCellText(txt, align, index.row(), index.column(), painter); // if we have an editable field and don't currently edit the transaction // show the background in a different color if (editField && !d->m_inEdit) { painter->fillRect(textRect, option.palette.alternateBase()); } if (!d->m_inEdit) painter->drawText(textRect, align, txt); } void Transaction::setupPalette(const QPalette& palette, QMap& editWidgets) { QMap::iterator it_w; for (it_w = editWidgets.begin(); it_w != editWidgets.end(); ++it_w) { if (*it_w) { (*it_w)->setPalette(palette); } } } void Transaction::setupFormPalette(QMap& editWidgets) { Q_D(Transaction); QPalette palette = d->m_parent->palette(); palette.setColor(QPalette::Active, QPalette::Base, palette.color(QPalette::Active, QPalette::Base)); setupPalette(palette, editWidgets); } void Transaction::setupRegisterPalette(QMap& editWidgets) { Q_D(Transaction); // make sure, we're using the right palette QPalette palette = d->m_parent->palette(); // use the highlight color as background palette.setColor(QPalette::Active, QPalette::Background, palette.color(QPalette::Active, QPalette::Highlight)); setupPalette(palette, editWidgets); } QWidget* Transaction::focusWidget(QWidget* w) const { if (w) { while (w->focusProxy()) w = w->focusProxy(); } return w; } void Transaction::arrangeWidget(QTableWidget* tbl, int row, int col, QWidget* w) const { if (w) { tbl->setCellWidget(row, col, w); // remove the widget from the QTable's eventFilter so that all // events will be directed to the edit widget w->removeEventFilter(tbl); } } bool Transaction::haveNumberField() const { Q_D(const Transaction); auto rc = true; switch (d->m_account.accountType()) { case eMyMoney::Account::Type::Savings: case eMyMoney::Account::Type::Cash: case eMyMoney::Account::Type::Loan: case eMyMoney::Account::Type::AssetLoan: case eMyMoney::Account::Type::Asset: case eMyMoney::Account::Type::Liability: case eMyMoney::Account::Type::Equity: rc = KMyMoneySettings::alwaysShowNrField(); break; case eMyMoney::Account::Type::Checkings: case eMyMoney::Account::Type::CreditCard: // the next case is used for the editor when the account // is unknown (eg. when creating new schedules) case eMyMoney::Account::Type::Unknown: break; default: rc = false; break; } return rc; } bool Transaction::maybeTip(const QPoint& cpos, int row, int col, QRect& r, QString& msg) { Q_D(Transaction); if (col != (int)eTransaction::Column::Detail) return false; if (!d->m_erroneous && d->m_transaction.splitCount() < 3) return false; // check for detail column in row 0 of the transaction for a possible // exclamation mark. m_startRow is based 0, whereas the row to obtain // the modelindex is based 1, so we need to add one here r = d->m_parent->visualRect(d->m_parent->model()->index(d->m_startRow + 1, col)); r.setBottom(r.bottom() + (numRowsRegister() - 1)*r.height()); if (r.contains(cpos) && d->m_erroneous) { if (d->m_transaction.splits().count() < 2) { msg = QString("%1").arg(i18n("Transaction is missing a category assignment.")); } else { const auto sec = MyMoneyFile::instance()->security(d->m_account.currencyId()); msg = QString("%1").arg(i18n("The transaction has a missing assignment of %1.", MyMoneyUtils::formatMoney(d->m_transaction.splitSum().abs(), d->m_account, sec))); } return true; } // check if the mouse cursor is located on row 1 of the transaction // and display the details of a split transaction if it is one if (row == 1 && r.contains(cpos) && d->m_transaction.splitCount() > 2) { auto file = MyMoneyFile::instance(); QString txt; const auto sec = file->security(d->m_transaction.commodity()); MyMoneyMoney factor(1, 1); if (!d->m_split.value().isNegative()) factor = -factor; foreach (const auto split, d->m_transaction.splits()) { if (split == d->m_split) continue; const MyMoneyAccount& acc = file->account(split.accountId()); auto category = file->accountToCategory(acc.id()); auto amount = MyMoneyUtils::formatMoney((split.value() * factor), acc, sec); txt += QString("%1%2").arg(category, amount); } msg = QString("%1
").arg(txt); return true; } return false; } QString Transaction::reconcileState(bool text) const { Q_D(const Transaction); auto txt = KMyMoneyUtils::reconcileStateToString(d->m_split.reconcileFlag(), text); if ((text == true) && (txt == i18nc("Unknown reconciliation state", "Unknown")) - && (d->m_transaction == MyMoneyTransaction())) + && (d->m_transaction.id().isEmpty())) txt.clear(); return txt; } void Transaction::startEditMode() { Q_D(Transaction); d->m_inEdit = true; // hide the original tabbar since the edit tabbar will be added if (auto form = dynamic_cast(d->m_form)) form->getTabBar()->setVisible(false); // only update the number of lines displayed if we edit inside the register if (d->m_inRegisterEdit) setNumRowsRegister(numRowsRegister(true)); } int Transaction::numRowsRegister() const { return RegisterItem::numRowsRegister(); } void Transaction::leaveEditMode() { Q_D(Transaction); // show the original tabbar since the edit tabbar was removed if (auto form = dynamic_cast(d->m_form)) form->getTabBar()->setVisible(true); // make sure we reset the row height of all the transaction's rows because it could have been changed during edit if (d->m_parent) { for (auto i = 0; i < numRowsRegister(); ++i) d->m_parent->setRowHeight(d->m_startRow + i, d->m_parent->rowHeightHint()); } d->m_inEdit = false; d->m_inRegisterEdit = false; setFocus(hasFocus(), true); } void Transaction::singleLineMemo(QString& txt, const MyMoneySplit& split) const { txt = split.memo(); // remove empty lines txt.replace("\n\n", "\n"); // replace '\n' with ", " txt.replace('\n', ", "); } int Transaction::rowHeightHint() const { Q_D(const Transaction); return d->m_inEdit ? formRowHeight() : RegisterItem::rowHeightHint(); } bool Transaction::matches(const RegisterFilter& filter) const { Q_D(const Transaction); // check if the state matches if (!transaction().id().isEmpty()) { switch (filter.state) { default: break; case eRegister::ItemState::Imported: if (!transaction().isImported()) return false; break; case eRegister::ItemState::Matched: if (!split().isMatched()) return false; break; case eRegister::ItemState::Erroneous: if (transaction().splitSum().isZero()) return false; break; case eRegister::ItemState::NotMarked: if (split().reconcileFlag() != eMyMoney::Split::State::NotReconciled) return false; break; case eRegister::ItemState::NotReconciled: if (split().reconcileFlag() != eMyMoney::Split::State::NotReconciled && split().reconcileFlag() != eMyMoney::Split::State::Cleared) return false; break; case eRegister::ItemState::Cleared: if (split().reconcileFlag() != eMyMoney::Split::State::Cleared) return false; break; case eRegister::ItemState::Scheduled: if (!isScheduled()) return false; break; } } // check if the text matches if (filter.text.isEmpty() || d->m_transaction.splitCount() == 0) return true; auto file = MyMoneyFile::instance(); foreach (const auto split, d->m_transaction.splits()) { // check if the text is contained in one of the fields // memo, number, payee, tag, account if (split.memo().contains(filter.text, Qt::CaseInsensitive) || split.number().contains(filter.text, Qt::CaseInsensitive)) return true; if (!split.payeeId().isEmpty()) { const MyMoneyPayee& payee = file->payee(split.payeeId()); if (payee.name().contains(filter.text, Qt::CaseInsensitive)) return true; } if (!split.tagIdList().isEmpty()) { const QList& t = split.tagIdList(); for (auto i = 0; i < t.count(); ++i) { if ((file->tag(t[i])).name().contains(filter.text, Qt::CaseInsensitive)) return true; } } const MyMoneyAccount& acc = file->account(split.accountId()); // search for account hierachy if (filter.text.contains(MyMoneyFile::AccountSeparator)) { QStringList names; MyMoneyAccount current = acc; QString accountId; do { names.prepend(current.name()); accountId = current.parentAccountId(); current = file->account(accountId); } while (current.accountType() != eMyMoney::Account::Type::Unknown && !MyMoneyFile::instance()->isStandardAccount(accountId)); if (names.size() > 1 && names.join(MyMoneyFile::AccountSeparator).contains(filter.text, Qt::CaseInsensitive)) return true; } if (acc.name().contains(filter.text, Qt::CaseInsensitive)) return true; QString s(filter.text); s.replace(MyMoneyMoney::thousandSeparator(), QChar()); if (!s.isEmpty()) { // check if any of the value field matches if a value has been entered QString r = split.value().formatMoney(d->m_account.fraction(), false); if (r.contains(s, Qt::CaseInsensitive)) return true; const MyMoneyAccount& splitAcc = file->account(split.accountId()); r = split.shares().formatMoney(splitAcc.fraction(), false); if (r.contains(s, Qt::CaseInsensitive)) return true; } } return false; } void Transaction::setShowBalance(bool showBalance) { Q_D(Transaction); d->m_showBalance = showBalance; } bool Transaction::showRowInForm(int row) const { Q_UNUSED(row) return true; } void Transaction::setShowRowInForm(int row, bool show) { Q_UNUSED(row); Q_UNUSED(show) } void Transaction::setReducedIntensity(bool reduced) { Q_D(Transaction); d->m_reducedIntensity = reduced; } void Transaction::setVisible(bool visible) { Q_D(Transaction); if (visible != isVisible()) { RegisterItem::setVisible(visible); RegisterItem* p; Transaction* t; if (!visible) { // if we are hidden, we need to inform all previous transactions // about it so that they don't show the balance p = prevItem(); while (p) { if ((t = dynamic_cast(p))) { if (!t->d_func()->m_showBalance) break; t->d_func()->m_showBalance = false; } p = p->prevItem(); } } else { // if we are shown, we need to check if the next transaction // is visible and change the display of the balance p = this; do { p = p->nextItem(); t = dynamic_cast(p); } while (!t && p); // if the next transaction is visible or I am the last one if ((t && t->d_func()->m_showBalance) || !t) { d->m_showBalance = true; p = prevItem(); while (p && p->isVisible()) { if ((t = dynamic_cast(p))) { if (t->d_func()->m_showBalance) break; t->d_func()->m_showBalance = true; } p = p->prevItem(); } } } } }