File indexing completed on 2024-05-12 16:43:41

0001 /*
0002     SPDX-FileCopyrightText: 2019-2020 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 
0007 #include "investtransactioneditor.h"
0008 
0009 // ----------------------------------------------------------------------------
0010 // QT Includes
0011 
0012 #include <QCompleter>
0013 #include <QStringList>
0014 #include <QDebug>
0015 #include <QGlobalStatic>
0016 #include <QAbstractItemView>
0017 #include <QStringListModel>
0018 #include <QSortFilterProxyModel>
0019 
0020 
0021 // ----------------------------------------------------------------------------
0022 // KDE Includes
0023 
0024 #include <KLocalizedString>
0025 #include <KConcatenateRowsProxyModel>
0026 #include <KDescendantsProxyModel>
0027 
0028 // ----------------------------------------------------------------------------
0029 // Project Includes
0030 
0031 #include "ui_investtransactioneditor.h"
0032 #include "creditdebithelper.h"
0033 #include "mymoneyfile.h"
0034 #include "mymoneyaccount.h"
0035 #include "mymoneyexception.h"
0036 #include "kmymoneyutils.h"
0037 #include "kmymoneyaccountcombo.h"
0038 #include "accountsmodel.h"
0039 #include "journalmodel.h"
0040 #include "statusmodel.h"
0041 #include "splitmodel.h"
0042 #include "mymoneysplit.h"
0043 #include "mymoneytransaction.h"
0044 #include "splitdialog.h"
0045 #include "widgethintframe.h"
0046 #include "icons/icons.h"
0047 #include "modelenums.h"
0048 #include "mymoneyenums.h"
0049 #include "mymoneysecurity.h"
0050 #include "kcurrencycalculator.h"
0051 #include "investactivities.h"
0052 #include "kmymoneysettings.h"
0053 #include "mymoneyprice.h"
0054 #include "amounteditcurrencyhelper.h"
0055 
0056 using namespace Icons;
0057 
0058 class InvestTransactionEditor::Private
0059 {
0060 public:
0061     Private(InvestTransactionEditor* parent)
0062         : q(parent)
0063         , ui(new Ui_InvestTransactionEditor)
0064         , accountsModel(new AccountNamesFilterProxyModel(parent))
0065         , feesModel(new AccountNamesFilterProxyModel(parent))
0066         , interestModel(new AccountNamesFilterProxyModel(parent))
0067         , activitiesModel(new QStringListModel(parent))
0068         , securitiesModel(new QSortFilterProxyModel(parent))
0069         , accountsListModel(new KDescendantsProxyModel(parent))
0070         , currentActivity(nullptr)
0071         , feeSplitModel(new SplitModel(parent, &undoStack))
0072         , interestSplitModel(new SplitModel(parent, &undoStack))
0073         , accepted(false)
0074         , bypassPriceEditor(false)
0075     {
0076         accountsModel->setObjectName("InvestTransactionEditor::accountsModel");
0077         feeSplitModel->setObjectName("FeesSplitModel");
0078         interestSplitModel->setObjectName("InterestSplitModel");
0079 
0080         // keep in sync with eMyMoney::Split::InvestmentTransactionType
0081         QStringList activityItems{
0082             i18nc("@item:inlistbox transaction type", "Buy shares"),
0083             i18nc("@item:inlistbox transaction type", "Sell shares"),
0084             i18nc("@item:inlistbox transaction type", "Dividend"),
0085             i18nc("@item:inlistbox transaction type", "Reinvest dividend"),
0086             i18nc("@item:inlistbox transaction type", "Yield"),
0087             i18nc("@item:inlistbox transaction type", "Add shares"),
0088             i18nc("@item:inlistbox transaction type", "Remove shares"),
0089             i18nc("@item:inlistbox transaction type", "Split shares"),
0090             i18nc("@item:inlistbox transaction type", "Interest Income"),
0091         };
0092 
0093         activitiesModel->setStringList(activityItems);
0094     }
0095 
0096     ~Private()
0097     {
0098         delete ui;
0099     }
0100 
0101     void dumpSplitModel(const QString& header, const QAbstractItemModel* model)
0102     {
0103         const auto rows = model->rowCount();
0104         qDebug() << header;
0105         for (int row = 0; row < rows; ++row) {
0106             const auto idx = model->index(row, 0);
0107             qDebug() << row << idx.data(eMyMoney::Model::IdRole).toString() << idx.data(eMyMoney::Model::SplitAccountIdRole).toString()
0108                      << idx.data(eMyMoney::Model::SplitSharesRole).value<MyMoneyMoney>().formatMoney(100) << idx.data(eMyMoney::Model::SplitValueRole).value<MyMoneyMoney>().formatMoney(100);
0109         }
0110     }
0111     void createStatusEntry(eMyMoney::Split::State status);
0112     bool isDatePostOpeningDate(const QDate& date, const QString& accountId);
0113 
0114     bool postdateChanged(const QDate& date);
0115     bool categoryChanged(SplitModel* model, const QString& accountId, AmountEdit* widget, const MyMoneyMoney& factor);
0116 
0117     void setSecurity(const MyMoneySecurity& sec);
0118 
0119     bool valueChanged(const SplitModel* model, AmountEdit* widget);
0120 
0121     void updateWidgetState();
0122 
0123     MyMoneyMoney getPrice(const SplitModel* model, const AmountEdit* widget);
0124 
0125     void editSplits(SplitModel* splitModel, AmountEdit* editWidget, const MyMoneyMoney& factor);
0126     void removeUnusedSplits(MyMoneyTransaction& t, SplitModel* splitModel);
0127     void addSplits(MyMoneyTransaction& t, SplitModel* splitModel);
0128     void setupParentInvestmentAccount(const QString& accountId);
0129     QModelIndex adjustToSecuritySplitIdx(const QModelIndex& idx);
0130 
0131     InvestTransactionEditor*      q;
0132     Ui_InvestTransactionEditor*   ui;
0133 
0134     // models for UI elements
0135     AccountNamesFilterProxyModel* accountsModel;
0136     AccountNamesFilterProxyModel* feesModel;
0137     AccountNamesFilterProxyModel* interestModel;
0138     QStringListModel*             activitiesModel;
0139     QSortFilterProxyModel*        securitiesModel;
0140     KDescendantsProxyModel*       accountsListModel;
0141 
0142     QUndoStack                    undoStack;
0143     Invest::Activity*             currentActivity;
0144 
0145     QSet<AmountEditCurrencyHelper*> amountEditCurrencyHelpers;
0146 
0147     // the selected security and the account holding it
0148     MyMoneySecurity               security;
0149     // and its trading currency
0150     MyMoneySecurity               currency;
0151     // and account holding the security
0152     MyMoneyAccount                stockAccount;
0153 
0154     MyMoneyAccount                assetAccount;
0155 
0156     // the containing investment account (parent of stockAccount)
0157     MyMoneyAccount                parentAccount;
0158 
0159     // the transaction
0160     MyMoneyTransaction            transaction;
0161 
0162     // the various splits
0163     MyMoneySplit                  stockSplit;
0164     MyMoneySplit                  assetSplit;
0165     SplitModel*                   feeSplitModel;
0166     SplitModel*                   interestSplitModel;
0167 
0168     // exchange rate information for assetSplit
0169     MyMoneyPrice                  assetPrice;
0170 
0171     bool                          accepted;
0172     bool                          bypassPriceEditor;
0173 };
0174 
0175 void InvestTransactionEditor::Private::removeUnusedSplits(MyMoneyTransaction& t, SplitModel* splitModel)
0176 {
0177     for (const auto& sp : qAsConst(t.splits())) {
0178         if (sp.id() == stockSplit.id()) {
0179             continue;
0180         }
0181         const auto rows = splitModel->rowCount();
0182         int row;
0183         for (row = 0; row < rows; ++row) {
0184             const QModelIndex index = splitModel->index(row, 0);
0185             if (index.data(eMyMoney::Model::IdRole).toString() == sp.id()) {
0186                 break;
0187             }
0188         }
0189 
0190         // if the split is not in the model, we get rid of it
0191         if (splitModel->rowCount() == row) {
0192             t.removeSplit(sp);
0193         }
0194     }
0195 }
0196 
0197 void InvestTransactionEditor::Private::addSplits(MyMoneyTransaction& t, SplitModel* splitModel)
0198 {
0199     for (int row = 0; row < splitModel->rowCount(); ++row) {
0200         const auto idx = splitModel->index(row, 0);
0201         MyMoneySplit s;
0202         const auto splitId = idx.data(eMyMoney::Model::IdRole).toString();
0203         // Extract the split from the transaction if
0204         // it already exists. Otherwise it remains
0205         // an empty split and will be added later.
0206         try {
0207             s = t.splitById(splitId);
0208         } catch(const MyMoneyException&) {
0209         }
0210         s.setMemo(idx.data(eMyMoney::Model::SplitMemoRole).toString());
0211         s.setAccountId(idx.data(eMyMoney::Model::SplitAccountIdRole).toString());
0212         s.setShares(idx.data(eMyMoney::Model::SplitSharesRole).value<MyMoneyMoney>());
0213         s.setValue(idx.data(eMyMoney::Model::SplitValueRole).value<MyMoneyMoney>());
0214 
0215         if (s.id().isEmpty() || splitModel->isNewSplitId(s.id())) {
0216             s.clearId();
0217             t.addSplit(s);
0218         } else {
0219             t.modifySplit(s);
0220         }
0221     }
0222 }
0223 
0224 bool InvestTransactionEditor::Private::isDatePostOpeningDate(const QDate& date, const QString& accountId)
0225 {
0226     bool rc = true;
0227 
0228     try {
0229         MyMoneyAccount account = MyMoneyFile::instance()->account(accountId);
0230 
0231         // we don't check for categories
0232         if (!account.isIncomeExpense()) {
0233             if (date < account.openingDate())
0234                 rc = false;
0235         }
0236     } catch (MyMoneyException& e) {
0237         qDebug() << "Ooops: invalid account id" << accountId << "in" << Q_FUNC_INFO;
0238     }
0239     return rc;
0240 }
0241 
0242 bool InvestTransactionEditor::Private::postdateChanged(const QDate& date)
0243 {
0244     bool rc = true;
0245     WidgetHintFrame::hide(ui->dateEdit, i18n("The posting date of the transaction."));
0246 
0247     QStringList accountIds;
0248 
0249     auto collectAccounts = [&](const SplitModel* model) {
0250         const auto rows = model->rowCount();
0251         for (int row = 0; row < rows; ++row) {
0252             const auto index = model->index(row, 0);
0253             accountIds << index.data(eMyMoney::Model::SplitAccountIdRole).toString();
0254         }
0255     };
0256 
0257     // collect all account ids
0258     accountIds << parentAccount.id();
0259     if (currentActivity->feesRequired() != Invest::Activity::Unused) {
0260         collectAccounts(feeSplitModel);
0261     }
0262     if (currentActivity->interestRequired() != Invest::Activity::Unused) {
0263         collectAccounts(interestSplitModel);
0264     }
0265 
0266     for (const auto& accountId : qAsConst(accountIds)) {
0267         if (!isDatePostOpeningDate(date, accountId)) {
0268             const auto account = MyMoneyFile::instance()->account(accountId);
0269             WidgetHintFrame::show(ui->dateEdit, i18n("The posting date is prior to the opening date of account <b>%1</b>.", account.name()));
0270             rc = false;
0271             break;
0272         }
0273     }
0274     return rc;
0275 }
0276 
0277 bool InvestTransactionEditor::Private::categoryChanged(SplitModel* model, const QString& accountId, AmountEdit* widget, const MyMoneyMoney& factor)
0278 {
0279     bool rc = true;
0280     if (!accountId.isEmpty() && model->rowCount() <= 1) {
0281         try {
0282             MyMoneyAccount category = MyMoneyFile::instance()->account(accountId);
0283 
0284             bool needValueSet = false;
0285             // make sure we have a split in the model
0286             if (model->rowCount() == 0) {
0287                 // add an empty split
0288                 MyMoneySplit s;
0289                 model->addItem(s);
0290                 needValueSet = true;
0291             }
0292 
0293             const QModelIndex index = model->index(0, 0);
0294             if (!needValueSet) {
0295                 // update the values only if the category changes. This prevents
0296                 // the call of the currency calculator if not needed.
0297                 needValueSet = (index.data(eMyMoney::Model::SplitAccountIdRole).toString().compare(accountId) != 0);
0298             }
0299             model->setData(index, accountId, eMyMoney::Model::SplitAccountIdRole);
0300 
0301             if (!widget->value().isZero() && needValueSet) {
0302                 model->setData(index, QVariant::fromValue<MyMoneyMoney>(factor * widget->value() * getPrice(model, widget)), eMyMoney::Model::SplitValueRole);
0303                 model->setData(index, QVariant::fromValue<MyMoneyMoney>(factor * widget->value()), eMyMoney::Model::SplitSharesRole);
0304             }
0305 
0306         } catch (MyMoneyException& e) {
0307             qDebug() << "Ooops: invalid account id" << accountId << "in" << Q_FUNC_INFO;
0308         }
0309     }
0310     return rc;
0311 }
0312 
0313 void InvestTransactionEditor::Private::setSecurity(const MyMoneySecurity& sec)
0314 {
0315     if (sec.tradingCurrency() != security.tradingCurrency()) {
0316         for (const auto helper : qAsConst(amountEditCurrencyHelpers)) {
0317             helper->setCommodity(sec.tradingCurrency());
0318         }
0319         transaction.setCommodity(sec.tradingCurrency());
0320         currency = MyMoneyFile::instance()->currency(sec.tradingCurrency());
0321 
0322         auto haveValue = [&](const SplitModel* model) {
0323             const auto rows = model->rowCount();
0324             for (int row = 0; row < rows; ++row) {
0325                 const auto idx = model->index(row, 0);
0326                 if (!idx.data(eMyMoney::Model::SplitValueRole).value<MyMoneyMoney>().isZero()) {
0327                     return true;
0328                 }
0329             }
0330             return false;
0331         };
0332 
0333         if (assetPrice.from() != currency.id()) {
0334             /// @todo collect exchange rate from user for asset account
0335         }
0336 
0337         bool needWarning = !assetSplit.value().isZero();
0338         if (currentActivity) {
0339             needWarning |= ((currentActivity->feesRequired() != Invest::Activity::Unused) && haveValue(feeSplitModel));
0340             needWarning |= ((currentActivity->interestRequired() != Invest::Activity::Unused) && haveValue(interestSplitModel));
0341         }
0342 
0343         if (needWarning) {
0344             ui->infoMessage->setText(i18nc("@info:usagetip", "The transaction commodity has been changed which will possibly make all price information invalid. Please check them."));
0345             if (!ui->infoMessage->isShowAnimationRunning()) {
0346                 ui->infoMessage->animatedShow();
0347                 emit q->editorLayoutChanged();
0348             }
0349         }
0350     }
0351 
0352     security = sec;
0353 
0354     // update the precision to that used by the new security
0355     ui->sharesAmountEdit->setPrecision(MyMoneyMoney::denomToPrec(security.smallestAccountFraction()));
0356 }
0357 
0358 MyMoneyMoney InvestTransactionEditor::Private::getPrice(const SplitModel* model, const AmountEdit* widget)
0359 {
0360     auto result(MyMoneyMoney::ONE);
0361     const QModelIndex splitIdx = model->index(0, 0);
0362     if (splitIdx.isValid()) {
0363         const auto shares = splitIdx.data(eMyMoney::Model::SplitSharesRole).value<MyMoneyMoney>();
0364         const auto value = splitIdx.data(eMyMoney::Model::SplitValueRole).value<MyMoneyMoney>();
0365         if (!shares.isZero()) {
0366             result = value / shares;
0367         }
0368     }
0369 
0370     if (!bypassPriceEditor && splitIdx.isValid()) {
0371         const auto categoryId = splitIdx.data(eMyMoney::Model::SplitAccountIdRole).toString();
0372         const auto category = MyMoneyFile::instance()->accountsModel()->itemById(categoryId);
0373         if (!category.id().isEmpty()) {
0374             const auto sec = MyMoneyFile::instance()->security(category.currencyId());
0375             /// @todo I think security in the following three occurrences needs to be replaced with currency
0376             if (sec.id() != security.id()) {
0377                 if (result == MyMoneyMoney::ONE) {
0378                     result = MyMoneyFile::instance()->price(sec.id(), security.id(), QDate()).rate(sec.id());
0379                 }
0380 
0381                 QPointer<KCurrencyCalculator> calc =
0382                     new KCurrencyCalculator(sec,
0383                                             security,
0384                                             widget->value(),
0385                                             widget->value() / result,
0386                                             ui->dateEdit->date(),
0387                                             sec.smallestAccountFraction(),
0388                                             q);
0389 
0390                 if (calc->exec() == QDialog::Accepted && calc) {
0391                     result = calc->price();
0392                 }
0393                 delete calc;
0394 
0395             } else {
0396                 result = MyMoneyMoney::ONE;
0397             }
0398         }
0399     }
0400     return result;
0401 }
0402 
0403 
0404 bool InvestTransactionEditor::Private::valueChanged(const SplitModel* model, AmountEdit* widget)
0405 {
0406     bool rc = true;
0407 #if 0
0408     if (valueHelper->haveValue() && (feeSplitModel.rowCount() <= 1) && (amountHelper->value() != split.value())) {
0409         rc = false;
0410         try {
0411             MyMoneyMoney shares;
0412             if (feeSplitModel.rowCount() == 1) {
0413                 const QModelIndex index = feeSplitModel.index(0, 0);
0414                 feeSplitModel.setData(index, QVariant::fromValue<MyMoneyMoney>(-amountHelper->value()), eMyMoney::Model::SplitValueRole);
0415                 feeSplitModel.setData(index, QVariant::fromValue<MyMoneyMoney>(-amountHelper->value() / getPrice(model)), eMyMoney::Model::SplitSharesRole);
0416             }
0417             rc = true;
0418 
0419         } catch (MyMoneyException& e) {
0420             qDebug() << "Ooops: something went wrong in" << Q_FUNC_INFO;
0421         }
0422     } else {
0423         /// @todo ask what to do: if the rest of the splits is the same amount we could simply reverse the sign
0424         /// of all splits, otherwise we could ask if the user wants to start the split editor or anything else.
0425     }
0426 #endif
0427     return rc;
0428 }
0429 
0430 void InvestTransactionEditor::Private::editSplits(SplitModel* sourceSplitModel, AmountEdit* editWidget, const MyMoneyMoney& transactionFactor)
0431 {
0432     SplitModel splitModel(q, nullptr, *sourceSplitModel);
0433 
0434     // create an empty split at the end
0435     // used to create new splits
0436     splitModel.appendEmptySplit();
0437 
0438     QPointer<SplitDialog> splitDialog = new SplitDialog(parentAccount, security, MyMoneyMoney::autoCalc, transactionFactor, q);
0439     splitDialog->setModel(&splitModel);
0440 
0441     int rc = splitDialog->exec();
0442 
0443     if (splitDialog && (rc == QDialog::Accepted)) {
0444         // remove that empty split again before we update the splits
0445         splitModel.removeEmptySplit();
0446 
0447         // copy the splits model contents
0448         *sourceSplitModel = splitModel;
0449 
0450         // update the transaction amount
0451         editWidget->setValue(splitDialog->transactionAmount() * transactionFactor);
0452 
0453         // the price might have been changed, so we have to update our copy
0454         // but only if there is one counter split
0455         if (sourceSplitModel->rowCount() == 1) {
0456             const auto splitIdx = sourceSplitModel->index(0, 0);
0457             const auto shares = splitIdx.data(eMyMoney::Model::SplitSharesRole).value<MyMoneyMoney>();
0458 
0459             // make sure to show the value in the widget
0460             // according to the currency presented
0461             editWidget->setValue(shares * transactionFactor);
0462         }
0463 
0464         // bypass the currency calculator here, we have all info already
0465         bypassPriceEditor = true;
0466         updateWidgetState();
0467         bypassPriceEditor = false;
0468     }
0469 
0470     if (splitDialog) {
0471         splitDialog->deleteLater();
0472     }
0473 }
0474 
0475 void InvestTransactionEditor::Private::setupParentInvestmentAccount(const QString& accountId)
0476 {
0477     auto const file = MyMoneyFile::instance();
0478     auto const model = file->accountsModel();
0479 
0480     // extract account information from model
0481     const auto index = model->indexById(accountId);
0482     parentAccount = model->itemByIndex(index);
0483 
0484     // show child accounts in the combo box
0485     securitiesModel->setFilterFixedString(accountId);
0486 }
0487 
0488 QModelIndex InvestTransactionEditor::Private::adjustToSecuritySplitIdx(const QModelIndex& index)
0489 {
0490     if (!index.isValid()) {
0491         return {};
0492     }
0493     const auto first = MyMoneyFile::instance()->journalModel()->adjustToFirstSplitIdx(index);
0494     const auto id = first.data(eMyMoney::Model::IdRole).toString();
0495 
0496     const auto rows = first.data(eMyMoney::Model::TransactionSplitCountRole).toInt();
0497     const auto endRow = first.row() + rows;
0498     for(int row = first.row(); row < endRow; ++row) {
0499         const auto idx = index.model()->index(row, 0);
0500         const auto accountId = idx.data(eMyMoney::Model::SplitAccountIdRole).toString();
0501         const auto account = MyMoneyFile::instance()->accountsModel()->itemById(accountId);
0502         if (account.isInvest()) {
0503             return idx;
0504         }
0505     }
0506     return {};
0507 }
0508 
0509 void InvestTransactionEditor::Private::updateWidgetState()
0510 {
0511     WidgetHintFrame::hide(ui->feesCombo, i18nc("@info:tooltip", "Category for fees"));
0512     WidgetHintFrame::hide(ui->feesAmountEdit, i18nc("@info:tooltip", "Amount of fees"));
0513     WidgetHintFrame::hide(ui->interestCombo, i18nc("@info:tooltip", "Category for interest"));
0514     WidgetHintFrame::hide(ui->interestAmountEdit, i18nc("@info:tooltip", "Amount of interest"));
0515     WidgetHintFrame::hide(ui->assetAccountCombo, i18nc("@info:tooltip", "Asset or brokerage account"));
0516     WidgetHintFrame::hide(ui->priceAmountEdit, i18nc("@info:tooltip", "Price information for this transaction"));
0517 
0518     // all the other logic needs a valid activity
0519     if (currentActivity == nullptr) {
0520         return;
0521     }
0522 
0523     const auto widget = ui->sharesAmountEdit;
0524     switch(currentActivity->type()) {
0525     default:
0526         WidgetHintFrame::hide(widget, i18nc("@info:tooltip", "Number of shares"));
0527         if (widget->isVisible()) {
0528             if (widget->value().isZero()) {
0529                 WidgetHintFrame::show(widget, i18nc("@info:tooltip", "Enter number of shares for this transaction"));
0530             }
0531         }
0532         break;
0533     case eMyMoney::Split::InvestmentTransactionType::SplitShares:
0534         WidgetHintFrame::hide(widget, i18nc("@info:tooltip", "Split ratio"));
0535         if (widget->isVisible()) {
0536             if (widget->value().isZero()) {
0537                 WidgetHintFrame::show(widget, i18nc("@info:tooltip", "Enter the split ratio for this transaction"));
0538             }
0539         }
0540         break;
0541     }
0542 
0543     switch(currentActivity->priceRequired()) {
0544     case Invest::Activity::Unused:
0545         break;
0546     case Invest::Activity::Optional:
0547     case Invest::Activity::Mandatory:
0548         if (ui->priceAmountEdit->value().isZero()) {
0549             WidgetHintFrame::show(ui->priceAmountEdit, i18nc("@info:tooltip", "Enter price information for this transaction"));
0550         }
0551         break;
0552     }
0553 
0554     QString accountId;
0555     switch(currentActivity->assetAccountRequired()) {
0556     case Invest::Activity::Unused:
0557         break;
0558     case Invest::Activity::Optional:
0559     case Invest::Activity::Mandatory:
0560         accountId = ui->assetAccountCombo->getSelected();
0561         if (MyMoneyFile::instance()->isStandardAccount(accountId)) {
0562             accountId.clear();
0563         }
0564         if (accountId.isEmpty()) {
0565             WidgetHintFrame::show(ui->assetAccountCombo, i18nc("@info:tooltip", "Select account to balance the transaction"));
0566         }
0567         break;
0568     }
0569 
0570     if (!currentActivity->haveFees(currentActivity->feesRequired())) {
0571         if (ui->feesCombo->currentText().isEmpty()) {
0572             WidgetHintFrame::show(ui->feesCombo, i18nc("@info:tooltip", "Enter category for fees"));
0573         }
0574         if (ui->feesAmountEdit->value().isZero()) {
0575             WidgetHintFrame::show(ui->feesAmountEdit, i18nc("@info:tooltip", "Enter amount of fees"));
0576         }
0577     }
0578 
0579     if (!currentActivity->haveInterest(currentActivity->interestRequired())) {
0580         if (ui->interestCombo->currentText().isEmpty()) {
0581             WidgetHintFrame::show(ui->interestCombo, i18nc("@info:tooltip", "Enter category for interest"));
0582         }
0583         if (ui->interestAmountEdit->value().isZero()) {
0584             WidgetHintFrame::show(ui->interestAmountEdit, i18nc("@info:tooltip", "Enter amount of interest"));
0585         }
0586     }
0587 }
0588 
0589 InvestTransactionEditor::InvestTransactionEditor(QWidget* parent, const QString& accountId)
0590     : TransactionEditorBase(parent, accountId)
0591     , d(new Private(this))
0592 {
0593     d->ui->setupUi(this);
0594 
0595     // initially, the info message is hidden
0596     d->ui->infoMessage->hide();
0597 
0598     d->ui->activityCombo->setModel(d->activitiesModel);
0599 
0600     auto const model = MyMoneyFile::instance()->accountsModel();
0601     d->accountsListModel->setSourceModel(model);
0602     d->securitiesModel->setSourceModel(d->accountsListModel);
0603     d->securitiesModel->setFilterRole(eMyMoney::Model::AccountParentIdRole);
0604     d->securitiesModel->setFilterKeyColumn(0);
0605     d->ui->securityAccountCombo->setModel(d->securitiesModel);
0606     d->ui->securityAccountCombo->lineEdit()->setReadOnly(true);
0607 
0608     d->accountsModel->addAccountGroup(QVector<eMyMoney::Account::Type> { eMyMoney::Account::Type::Asset, eMyMoney::Account::Type::Liability } );
0609     d->accountsModel->setHideEquityAccounts(false);
0610     d->accountsModel->setSourceModel(model);
0611     d->accountsModel->sort(AccountsModel::Column::AccountName);
0612     d->ui->assetAccountCombo->setModel(d->accountsModel);
0613     d->ui->assetAccountCombo->setSplitActionVisible(false);
0614 
0615     d->feesModel->addAccountGroup(QVector<eMyMoney::Account::Type> { eMyMoney::Account::Type::Expense });
0616     d->feesModel->setSourceModel(model);
0617     d->feesModel->sort(AccountsModel::Column::AccountName);
0618     d->ui->feesCombo->setModel(d->feesModel);
0619     auto helper = new KMyMoneyAccountComboSplitHelper(d->ui->feesCombo, d->feeSplitModel);
0620     connect(helper, &KMyMoneyAccountComboSplitHelper::accountComboDisabled, d->ui->feesAmountEdit, &AmountEdit::setReadOnly);
0621 
0622     d->interestModel->addAccountGroup(QVector<eMyMoney::Account::Type> { eMyMoney::Account::Type::Income });
0623     d->interestModel->setSourceModel(model);
0624     d->interestModel->sort(AccountsModel::Column::AccountName);
0625     d->ui->interestCombo->setModel(d->interestModel);
0626     helper = new KMyMoneyAccountComboSplitHelper(d->ui->interestCombo, d->interestSplitModel);
0627     connect(helper, &KMyMoneyAccountComboSplitHelper::accountComboDisabled, d->ui->interestAmountEdit, &AmountEdit::setReadOnly);
0628 
0629     d->ui->enterButton->setIcon(Icons::get(Icon::DialogOK));
0630     d->ui->cancelButton->setIcon(Icons::get(Icon::DialogCancel));
0631 
0632     d->ui->statusCombo->setModel(MyMoneyFile::instance()->statusModel());
0633 
0634     d->ui->dateEdit->setDisplayFormat(QLocale().dateFormat(QLocale::ShortFormat));
0635 
0636     d->ui->sharesAmountEdit->setAllowEmpty(true);
0637     d->ui->sharesAmountEdit->setCalculatorButtonVisible(true);
0638     connect(d->ui->sharesAmountEdit, &AmountEdit::textChanged, this, &InvestTransactionEditor::sharesChanged);
0639 
0640     d->ui->priceAmountEdit->setAllowEmpty(true);
0641     d->ui->priceAmountEdit->setCalculatorButtonVisible(true);
0642     connect(d->ui->priceAmountEdit, &AmountEdit::textChanged, this, &InvestTransactionEditor::updateTotalAmount);
0643 
0644     d->ui->feesAmountEdit->setAllowEmpty(true);
0645     d->ui->feesAmountEdit->setCalculatorButtonVisible(true);
0646     connect(d->ui->feesAmountEdit, &AmountEdit::textChanged, this, &InvestTransactionEditor::updateTotalAmount);
0647 
0648     d->ui->interestAmountEdit->setAllowEmpty(true);
0649     d->ui->interestAmountEdit->setCalculatorButtonVisible(true);
0650     connect(d->ui->interestAmountEdit, &AmountEdit::textChanged, this, &InvestTransactionEditor::updateTotalAmount);
0651 
0652     WidgetHintFrameCollection* frameCollection = new WidgetHintFrameCollection(this);
0653     frameCollection->addFrame(new WidgetHintFrame(d->ui->dateEdit));
0654     frameCollection->addFrame(new WidgetHintFrame(d->ui->assetAccountCombo));
0655     frameCollection->addFrame(new WidgetHintFrame(d->ui->sharesAmountEdit));
0656     frameCollection->addFrame(new WidgetHintFrame(d->ui->priceAmountEdit));
0657     frameCollection->addFrame(new WidgetHintFrame(d->ui->feesCombo));
0658     frameCollection->addFrame(new WidgetHintFrame(d->ui->feesAmountEdit));
0659     frameCollection->addFrame(new WidgetHintFrame(d->ui->interestCombo));
0660     frameCollection->addFrame(new WidgetHintFrame(d->ui->interestAmountEdit));
0661     frameCollection->addWidget(d->ui->enterButton);
0662 
0663 
0664     connect(d->ui->assetAccountCombo, &KMyMoneyAccountCombo::accountSelected, this, &InvestTransactionEditor::assetAccountChanged);
0665     connect(d->ui->dateEdit, &KMyMoneyDateEdit::dateChanged, this, &InvestTransactionEditor::postdateChanged);
0666     connect(d->ui->activityCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &InvestTransactionEditor::activityChanged);
0667     connect(d->ui->securityAccountCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &InvestTransactionEditor::securityAccountChanged);
0668 
0669     connect(d->ui->feesCombo, &KMyMoneyAccountCombo::accountSelected, this, &InvestTransactionEditor::feeCategoryChanged);
0670     connect(d->ui->feesCombo, &KMyMoneyAccountCombo::splitDialogRequest, this, &InvestTransactionEditor::editFeeSplits, Qt::QueuedConnection);
0671 
0672     connect(d->ui->interestCombo, &KMyMoneyAccountCombo::accountSelected, this, &InvestTransactionEditor::interestCategoryChanged);
0673     connect(d->ui->interestCombo, &KMyMoneyAccountCombo::splitDialogRequest, this, &InvestTransactionEditor::editInterestSplits, Qt::QueuedConnection);
0674 
0675     /// @todo convert to new signal/slot syntax
0676     connect(d->ui->cancelButton, &QToolButton::clicked, this, [&]() {
0677         emit done();
0678     } );
0679     connect(d->ui->enterButton, &QToolButton::clicked, this, [&]() {
0680         d->accepted = true;
0681         emit done();
0682     } );
0683 
0684     // handle some events in certain conditions different from default
0685     d->ui->activityCombo->installEventFilter(this);
0686     d->ui->statusCombo->installEventFilter(this);
0687 
0688     d->ui->totalAmountEdit->setCalculatorButtonVisible(false);
0689 
0690     d->setupParentInvestmentAccount(accountId);
0691 
0692     d->amountEditCurrencyHelpers.insert(new AmountEditCurrencyHelper(d->ui->feesCombo, d->ui->feesAmountEdit, d->transaction.commodity()));
0693     d->amountEditCurrencyHelpers.insert(new AmountEditCurrencyHelper(d->ui->interestCombo, d->ui->interestAmountEdit, d->transaction.commodity()));
0694 
0695     // setWindowFlags(Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint);
0696 }
0697 
0698 InvestTransactionEditor::~InvestTransactionEditor()
0699 {
0700 }
0701 
0702 bool InvestTransactionEditor::accepted() const
0703 {
0704     return d->accepted;
0705 }
0706 
0707 void InvestTransactionEditor::updateTotalAmount()
0708 {
0709     d->updateWidgetState();
0710     if (d->currentActivity) {
0711         const auto totalAmount = d->currentActivity->totalAmount(d->stockSplit, d->feeSplitModel, d->interestSplitModel);
0712         d->ui->totalAmountEdit->setValue(totalAmount.abs());
0713         d->assetSplit.setValue(-totalAmount);
0714         d->assetSplit.setShares(d->assetSplit.value() / d->assetPrice.rate(d->assetAccount.currencyId()));
0715     }
0716 }
0717 
0718 
0719 void InvestTransactionEditor::loadTransaction(const QModelIndex& index)
0720 {
0721     d->ui->activityCombo->setCurrentIndex(-1);
0722     d->ui->securityAccountCombo->setCurrentIndex(-1);
0723     const auto file = MyMoneyFile::instance();
0724     auto idx = d->adjustToSecuritySplitIdx(MyMoneyFile::baseModel()->mapToBaseSource(index));
0725     if (!idx.isValid() || idx.data(eMyMoney::Model::IdRole).toString().isEmpty()) {
0726         d->transaction = MyMoneyTransaction();
0727         d->transaction.setCommodity(d->parentAccount.currencyId());
0728         d->currency = MyMoneyFile::instance()->baseCurrency();
0729         d->security = MyMoneySecurity();
0730         d->security.setTradingCurrency(d->currency.id());
0731         d->stockSplit = MyMoneySplit();
0732         d->assetSplit = MyMoneySplit();
0733         d->assetAccount = MyMoneyAccount();
0734         d->ui->activityCombo->setCurrentIndex(0);
0735         d->ui->securityAccountCombo->setCurrentIndex(0);
0736         const auto lastUsedPostDate = KMyMoneySettings::lastUsedPostDate();
0737         if (lastUsedPostDate.isValid()) {
0738             d->ui->dateEdit->setDate(lastUsedPostDate.date());
0739         } else {
0740             d->ui->dateEdit->setDate(QDate::currentDate());
0741         }
0742         // select the associated brokerage account if it exists
0743         const auto brokerageAccount = file->accountsModel()->itemByName(d->parentAccount.brokerageName());
0744         if (!brokerageAccount.id().isEmpty()) {
0745             d->ui->assetAccountCombo->setSelected(brokerageAccount.id());
0746         }
0747     } else {
0748         // keep a copy of the transaction and split
0749         d->transaction = file->journalModel()->itemByIndex(idx).transaction();
0750         d->stockSplit = file->journalModel()->itemByIndex(idx).split();
0751 
0752         QModelIndex assetAccountSplitIdx;
0753         eMyMoney::Split::InvestmentTransactionType transactionType;
0754 
0755         KMyMoneyUtils::dissectInvestmentTransaction(idx, assetAccountSplitIdx, d->feeSplitModel, d->interestSplitModel, d->security, d->currency, transactionType);
0756         d->assetSplit = file->journalModel()->itemByIndex(assetAccountSplitIdx).split();
0757         if (!d->assetSplit.id().isEmpty())
0758             d->assetAccount = file->account(d->assetSplit.accountId());
0759 
0760         // extract conversion rate information for asset split before changing
0761         // the activity because that will need it (in updateTotalAmount() )
0762         if (!(d->assetSplit.shares().isZero() || d->assetSplit.value().isZero())) {
0763             const auto rate = d->assetSplit.value() / d->assetSplit.shares();
0764             d->assetPrice = MyMoneyPrice(d->currency.id(), d->assetAccount.currencyId(), d->transaction.postDate(), rate, QLatin1String("KMyMoney"));
0765         }
0766 
0767         // load the widgets. setting activityCombo also initializes
0768         // d->currentActivity to have the right object
0769         d->ui->activityCombo->setCurrentIndex(static_cast<int>(transactionType));
0770         d->ui->dateEdit->setDate(d->transaction.postDate());
0771 
0772         d->ui->memoEdit->setPlainText(d->stockSplit.memo());
0773 
0774         d->ui->assetAccountCombo->setSelected(d->assetSplit.accountId());
0775 
0776         d->ui->sharesAmountEdit->setPrecision(MyMoneyMoney::denomToPrec(d->security.smallestAccountFraction()));
0777         d->ui->sharesAmountEdit->setValue(d->stockSplit.shares() * d->currentActivity->sharesFactor());
0778 
0779         const auto indexes = d->securitiesModel->match(d->securitiesModel->index(0,0), eMyMoney::Model::IdRole, d->stockSplit.accountId(), 1, Qt::MatchFixedString);
0780         if (!indexes.isEmpty()) {
0781             d->ui->securityAccountCombo->setCurrentIndex(indexes.first().row());
0782             d->stockAccount = file->account(d->stockSplit.accountId());
0783         }
0784 
0785         d->ui->feesAmountEdit->setValue(d->feeSplitModel->valueSum() * d->currentActivity->feesFactor());
0786         d->ui->interestAmountEdit->setValue(d->interestSplitModel->valueSum() * d->currentActivity->interestFactor());
0787 
0788         d->currentActivity->loadPriceWidget(d->stockSplit);
0789     }
0790 
0791     for (const auto helper : qAsConst(d->amountEditCurrencyHelpers)) {
0792         helper->setCommodity(d->transaction.commodity());
0793     }
0794 
0795     // delay update until next run of event loop so that all necessary widgets are visible
0796     QMetaObject::invokeMethod(this, "updateWidgets", Qt::QueuedConnection);
0797 
0798     // set focus to date edit once we return to event loop
0799     QMetaObject::invokeMethod(d->ui->dateEdit, "setFocus", Qt::QueuedConnection);
0800 }
0801 
0802 void InvestTransactionEditor::updateWidgets()
0803 {
0804     d->updateWidgetState();
0805 }
0806 
0807 void InvestTransactionEditor::securityAccountChanged(int index)
0808 {
0809     const auto idx = d->ui->securityAccountCombo->model()->index(index, 0);
0810     if (idx.isValid()) {
0811         const auto accountId = idx.data(eMyMoney::Model::IdRole).toString();
0812         const auto securityId = idx.data(eMyMoney::Model::AccountCurrencyIdRole).toString();
0813         try {
0814             const auto file = MyMoneyFile::instance();
0815             const auto sec = file->security(securityId);
0816 
0817             d->stockAccount = file->account(accountId);
0818             d->stockSplit.setAccountId(accountId);
0819             d->setSecurity(sec);
0820 
0821             updateTotalAmount();
0822 
0823         } catch(MyMoneyException& e) {
0824             qDebug() << "Problem to find securityId" << accountId << "or" << securityId << "in InvestTransactionEditor::securityAccountChanged";
0825         }
0826     }
0827 }
0828 
0829 
0830 void InvestTransactionEditor::activityChanged(int index)
0831 {
0832     const auto type = static_cast<eMyMoney::Split::InvestmentTransactionType>(index);
0833     if (!d->currentActivity || type != d->currentActivity->type()) {
0834         auto oldType = eMyMoney::Split::InvestmentTransactionType::UnknownTransactionType;
0835         if (d->currentActivity) {
0836             oldType = d->currentActivity->type();
0837         }
0838         delete d->currentActivity;
0839         switch(type) {
0840         default:
0841         case eMyMoney::Split::InvestmentTransactionType::BuyShares:
0842             d->currentActivity = new Invest::Buy(this);
0843             break;
0844         case eMyMoney::Split::InvestmentTransactionType::SellShares:
0845             d->currentActivity = new Invest::Sell(this);
0846             break;
0847         case eMyMoney::Split::InvestmentTransactionType::Dividend:
0848         case eMyMoney::Split::InvestmentTransactionType::Yield:
0849             d->currentActivity = new Invest::Div(this);
0850             break;
0851         case eMyMoney::Split::InvestmentTransactionType::ReinvestDividend:
0852             d->currentActivity = new Invest::Reinvest(this);
0853             break;
0854         case eMyMoney::Split::InvestmentTransactionType::AddShares:
0855             d->currentActivity = new Invest::Add(this);
0856             break;
0857         case eMyMoney::Split::InvestmentTransactionType::RemoveShares:
0858             d->currentActivity = new Invest::Remove(this);
0859             break;
0860         case eMyMoney::Split::InvestmentTransactionType::SplitShares:
0861             d->currentActivity = new Invest::Split(this);
0862             break;
0863         case eMyMoney::Split::InvestmentTransactionType::InterestIncome:
0864             d->currentActivity = new Invest::IntInc(this);
0865             break;
0866         }
0867         d->currentActivity->showWidgets();
0868 
0869         if (type != eMyMoney::Split::InvestmentTransactionType::SplitShares &&
0870                 oldType == eMyMoney::Split::InvestmentTransactionType::SplitShares) {
0871             // switch to split
0872             d->stockSplit.setValue(MyMoneyMoney());
0873             d->stockSplit.setPrice(MyMoneyMoney());
0874             d->ui->sharesAmountEdit->setPrecision(-1);
0875         } else if (type == eMyMoney::Split::InvestmentTransactionType::SplitShares &&
0876                    oldType != eMyMoney::Split::InvestmentTransactionType::SplitShares) {
0877             // switch away from split
0878             d->stockSplit.setPrice(d->ui->priceAmountEdit->value());
0879             d->stockSplit.setValue(d->stockSplit.shares() * d->stockSplit.price());
0880             d->ui->sharesAmountEdit->setPrecision(MyMoneyMoney::denomToPrec(d->security.smallestAccountFraction()));
0881         }
0882         updateTotalAmount();
0883         d->updateWidgetState();
0884         emit editorLayoutChanged();
0885     }
0886 }
0887 
0888 void InvestTransactionEditor::sharesChanged()
0889 {
0890     if (d->currentActivity) {
0891         if (d->currentActivity->type() != eMyMoney::Split::InvestmentTransactionType::SplitShares) {
0892             updateTotalAmount();
0893         }
0894     }
0895 }
0896 
0897 void InvestTransactionEditor::assetAccountChanged(const QString& accountId)
0898 {
0899     const auto account = MyMoneyFile::instance()->account(accountId);
0900     if (account.currencyId() != d->assetAccount.currencyId()) {
0901         /// @todo update price rate information
0902     }
0903     d->postdateChanged(d->ui->dateEdit->date());
0904     d->updateWidgetState();
0905 }
0906 
0907 void InvestTransactionEditor::feeCategoryChanged(const QString& accountId)
0908 {
0909     d->categoryChanged(d->feeSplitModel, accountId, d->ui->feesAmountEdit, MyMoneyMoney::ONE);
0910     d->updateWidgetState();
0911     updateTotalAmount();
0912 }
0913 
0914 void InvestTransactionEditor::interestCategoryChanged(const QString& accountId)
0915 {
0916     d->categoryChanged(d->interestSplitModel, accountId, d->ui->interestAmountEdit, MyMoneyMoney::MINUS_ONE);
0917     d->updateWidgetState();
0918     updateTotalAmount();
0919 }
0920 
0921 void InvestTransactionEditor::postdateChanged(const QDate& date)
0922 {
0923     d->postdateChanged(date);
0924 }
0925 
0926 void InvestTransactionEditor::feesValueChanged()
0927 {
0928     d->valueChanged(d->feeSplitModel, d->ui->feesAmountEdit);
0929     d->updateWidgetState();
0930     updateTotalAmount();
0931 }
0932 
0933 void InvestTransactionEditor::interestValueChanged()
0934 {
0935     d->valueChanged(d->interestSplitModel, d->ui->interestAmountEdit);
0936     d->updateWidgetState();
0937     updateTotalAmount();
0938 }
0939 
0940 void InvestTransactionEditor::editFeeSplits()
0941 {
0942     d->editSplits(d->feeSplitModel, d->ui->feesAmountEdit, MyMoneyMoney::ONE);
0943 #if 0
0944     QWidget* next = d->ui->tagComboBox;
0945     next->setFocus();
0946 #endif
0947 }
0948 
0949 void InvestTransactionEditor::editInterestSplits()
0950 {
0951     d->editSplits(d->interestSplitModel, d->ui->interestAmountEdit, MyMoneyMoney::MINUS_ONE);
0952 
0953 #if 0
0954     QWidget* next = d->ui->tagComboBox;
0955     next->setFocus();
0956 #endif
0957 }
0958 
0959 MyMoneyMoney InvestTransactionEditor::transactionAmount() const
0960 {
0961 #if 0
0962     return d->amountHelper->value();
0963 #endif
0964     return {};
0965 }
0966 
0967 MyMoneyMoney InvestTransactionEditor::totalAmount() const
0968 {
0969     return d->assetSplit.value();
0970 }
0971 
0972 void InvestTransactionEditor::saveTransaction()
0973 {
0974     MyMoneyTransaction t;
0975 
0976     if (!d->transaction.id().isEmpty()) {
0977         t = d->transaction;
0978     } else {
0979         // we keep the date when adding a new transaction
0980         // for the next new one
0981         KMyMoneySettings::setLastUsedPostDate(QDateTime(d->ui->dateEdit->date()));
0982     }
0983 
0984     d->removeUnusedSplits(t, d->feeSplitModel);
0985     d->removeUnusedSplits(t, d->interestSplitModel);
0986 
0987     // we start with the previous values, clear id to make sure
0988     // we can add them later on
0989     d->stockSplit.clearId();
0990 
0991     t.setCommodity(d->currency.id());
0992 
0993     t.removeSplits();
0994 
0995     t.setPostDate(d->ui->dateEdit->date());
0996     d->stockSplit.setMemo(d->ui->memoEdit->toPlainText());
0997 
0998     d->currentActivity->adjustStockSplit(d->stockSplit);
0999 
1000     QList<MyMoneySplit> resultSplits;  // concatenates splits for easy processing
1001 
1002     // now update and add what we have in the model(s)
1003     if (d->currentActivity->assetAccountRequired() != Invest::Activity::Unused) {
1004         resultSplits.append(d->assetSplit);
1005     }
1006     if (d->currentActivity->feesRequired() != Invest::Activity::Unused) {
1007         addSplitsFromModel(resultSplits, d->feeSplitModel);
1008     }
1009     if (d->currentActivity->interestRequired() != Invest::Activity::Unused) {
1010         addSplitsFromModel(resultSplits, d->interestSplitModel);
1011     }
1012 
1013     AlkValue::RoundingMethod roundingMethod = AlkValue::RoundRound;
1014     if (d->security.roundingMethod() != AlkValue::RoundNever)
1015         roundingMethod = d->security.roundingMethod();
1016 
1017     int currencyFraction = d->currency.smallestAccountFraction();
1018     int securityFraction = d->security.smallestAccountFraction();
1019 
1020     // assuming that all non-stock splits are monetary
1021     foreach (auto split, resultSplits) {
1022         split.clearId();
1023         split.setShares(MyMoneyMoney(split.shares().convertDenominator(currencyFraction, roundingMethod)));
1024         split.setValue(MyMoneyMoney(split.value().convertDenominator(currencyFraction, roundingMethod)));
1025         t.addSplit(split);
1026     }
1027 
1028     // Don't do any rounding on a split factor
1029     if (d->currentActivity->type() != eMyMoney::Split::InvestmentTransactionType::SplitShares) {
1030         // only the shares variable of a stock split isn't evaluated in currency
1031         d->stockSplit.setShares(MyMoneyMoney(d->stockSplit.shares().convertDenominator(securityFraction, roundingMethod)));
1032         d->stockSplit.setValue(MyMoneyMoney(d->stockSplit.value().convertDenominator(currencyFraction, roundingMethod)));
1033     }
1034     t.addSplit(d->stockSplit);
1035 
1036     MyMoneyFileTransaction ft;
1037     try {
1038         const auto file = MyMoneyFile::instance();
1039         if (t.id().isEmpty()) {
1040             file->addTransaction(t);
1041         } else {
1042             file->modifyTransaction(t);
1043         }
1044         ft.commit();
1045 
1046     } catch (const MyMoneyException& e) {
1047         qDebug() << Q_FUNC_INFO << "something went wrong" << e.what();
1048     }
1049 }
1050 
1051 bool InvestTransactionEditor::eventFilter(QObject* o, QEvent* e)
1052 {
1053     auto cb = qobject_cast<QComboBox*>(o);
1054     if (cb) {
1055         // filter out wheel events for combo boxes if the popup view is not visible
1056         if ((e->type() == QEvent::Wheel) && !cb->view()->isVisible()) {
1057             return true;
1058         }
1059     }
1060     return QFrame::eventFilter(o, e);
1061 }
1062 
1063 void InvestTransactionEditor::keyPressEvent(QKeyEvent* e)
1064 {
1065     if (!e->modifiers() || ((e->modifiers() & Qt::KeypadModifier) && (e->key() == Qt::Key_Enter))) {
1066         switch (e->key()) {
1067         case Qt::Key_Enter:
1068         case Qt::Key_Return: {
1069             if (focusWidget() == d->ui->cancelButton) {
1070                 d->ui->cancelButton->click();
1071             } else {
1072                 if (d->ui->enterButton->isEnabled()) {
1073                     // move focus to enter button which
1074                     // triggers update of widgets
1075                     d->ui->enterButton->setFocus();
1076                     d->ui->enterButton->click();
1077                 }
1078                 return;
1079             }
1080         }
1081         break;
1082 
1083         case Qt::Key_Escape:
1084             d->ui->cancelButton->click();
1085             break;
1086 
1087         default:
1088             e->ignore();
1089             return;
1090         }
1091     } else {
1092         e->ignore();
1093     }
1094 }
1095 
1096 
1097 // kate: indent-mode cstyle; indent-width 4; replace-tabs on; remove-trailing-space on;remove-trailing-space-save on;