File indexing completed on 2024-05-19 05:08:18

0001 /*
0002     SPDX-FileCopyrightText: 2002-2004 Kevin Tambascio <ktambascio@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2003-2021 Thomas Baumgart <tbaumgart@kde.org>
0004     SPDX-FileCopyrightText: 2004-2005 Ace Jones <acejones@users.sourceforge.net>
0005     SPDX-FileCopyrightText: 2009-2010 Alvaro Soliverez <asoliverez@kde.org>
0006     SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "kinvestmentview_p.h"
0011 
0012 // ----------------------------------------------------------------------------
0013 // QT Includes
0014 
0015 #include <QTimer>
0016 #include <QAction>
0017 #include <QMenu>
0018 #include <QBitArray>
0019 
0020 // ----------------------------------------------------------------------------
0021 // KDE Includes
0022 
0023 #include <KMessageBox>
0024 
0025 // ----------------------------------------------------------------------------
0026 // Project Includes
0027 
0028 #include "mymoneymoney.h"
0029 #include "mymoneyprice.h"
0030 #include "kequitypriceupdatedlg.h"
0031 #include "kcurrencycalculator.h"
0032 #include "knewinvestmentwizard.h"
0033 #include "kmymoneyutils.h"
0034 #include "menuenums.h"
0035 #include "storageenums.h"
0036 
0037 using namespace Icons;
0038 
0039 KInvestmentView::KInvestmentView(QWidget *parent) :
0040     KMyMoneyViewBase(*new KInvestmentViewPrivate(this), parent)
0041 {
0042     connect(pActions[eMenu::Action::NewInvestment], &QAction::triggered, this, &KInvestmentView::slotNewInvestment);
0043     connect(pActions[eMenu::Action::EditInvestment], &QAction::triggered, this, &KInvestmentView::slotEditInvestment);
0044     connect(pActions[eMenu::Action::DeleteInvestment], &QAction::triggered, this, &KInvestmentView::slotDeleteInvestment);
0045     connect(pActions[eMenu::Action::UpdatePriceOnline], &QAction::triggered, this, &KInvestmentView::slotUpdatePriceOnline);
0046     connect(pActions[eMenu::Action::UpdatePriceManually], &QAction::triggered, this, &KInvestmentView::slotUpdatePriceManually);
0047     connect(pActions[eMenu::Action::EditSecurity], &QAction::triggered, this, &KInvestmentView::slotEditSecurity);
0048     connect(pActions[eMenu::Action::DeleteSecurity], &QAction::triggered, this, &KInvestmentView::slotDeleteSecurity);
0049 }
0050 
0051 KInvestmentView::~KInvestmentView()
0052 {
0053 }
0054 
0055 void KInvestmentView::setDefaultFocus()
0056 {
0057     Q_D(KInvestmentView);
0058     auto tab = static_cast<eView::Investment::Tab>(d->ui->m_tab->currentIndex());
0059 
0060     switch (tab) {
0061     case eView::Investment::Tab::Equities:
0062         QMetaObject::invokeMethod(d->ui->m_equitiesTree, "setFocus", Qt::QueuedConnection);
0063         break;
0064     case eView::Investment::Tab::Securities:
0065         QMetaObject::invokeMethod(d->ui->m_securitiesTree, "setFocus", Qt::QueuedConnection);
0066         break;
0067     }
0068 }
0069 
0070 void KInvestmentView::executeAction(eMenu::Action action, const SelectedObjects& selections)
0071 {
0072     Q_UNUSED(selections)
0073     Q_D(KInvestmentView);
0074     switch (action) {
0075     case eMenu::Action::FileNew:
0076         if (!d->m_needLoad) {
0077             d->ui->m_accountComboBox->expandAll();
0078             d->m_equitiesProxyModel->invalidate();
0079             d->m_securitiesProxyModel->invalidate();
0080             d->selectDefaultInvestmentAccount();
0081         }
0082         break;
0083 
0084     case eMenu::Action::FileClose:
0085         d->m_idInvAcc.clear();
0086         d->m_equitySelections.clearSelections();
0087         d->m_securitySelections.clearSelections();
0088         d->m_selections.clearSelections();
0089         break;
0090 
0091     default:
0092         break;
0093     }
0094 }
0095 void KInvestmentView::showEvent(QShowEvent* event)
0096 {
0097     Q_D(KInvestmentView);
0098     if (d->m_needLoad) {
0099         d->init();
0100 
0101         connect(d->ui->m_equitiesTree, &QWidget::customContextMenuRequested, this, [&](const QPoint& pos) {
0102             Q_D(KInvestmentView);
0103             Q_EMIT requestCustomContextMenu(eMenu::Menu::Investment, d->ui->m_equitiesTree->viewport()->mapToGlobal(pos));
0104         });
0105 
0106         connect(d->ui->m_securitiesTree, &QWidget::customContextMenuRequested, this, [&](const QPoint& pos) {
0107             Q_D(KInvestmentView);
0108             Q_EMIT requestCustomContextMenu(eMenu::Menu::Security, d->ui->m_equitiesTree->viewport()->mapToGlobal(pos));
0109         });
0110 
0111         connect(d->ui->m_equitiesTree->selectionModel(),
0112                 &QItemSelectionModel::currentRowChanged,
0113                 this,
0114                 [&](const QModelIndex& current, const QModelIndex& previous) {
0115                     Q_UNUSED(previous)
0116                     Q_D(KInvestmentView);
0117                     d->m_equitySelections.clearSelections(SelectedObjects::Account);
0118                     // when closing equities, current may still reference a row that
0119                     // is not valid any longer. For this reason, we set the row
0120                     // to the last row in the model
0121                     if (current.isValid()) {
0122                         const auto rows = current.model()->rowCount(current.parent());
0123                         auto idx = current;
0124                         if (idx.row() >= rows) {
0125                             idx = idx.model()->index(rows - 1, idx.column(), idx.parent());
0126                         }
0127                         if (idx.isValid()) {
0128                             d->m_equitySelections.setSelection(SelectedObjects::Account, idx.data(eMyMoney::Model::IdRole).toString());
0129                         }
0130                     } else {
0131                         // suppress display if no more equities are shown
0132                         d->m_equitiesProxyModel->setHideAllEntries(true);
0133                     }
0134                     if (d->ui->m_equitiesTree->isVisible()) {
0135                         d->m_selections = d->m_equitySelections;
0136                         Q_EMIT requestSelectionChange(d->m_selections);
0137                     }
0138                 });
0139 
0140         connect(d->ui->m_securitiesTree->selectionModel(),
0141                 &QItemSelectionModel::currentRowChanged,
0142                 this,
0143                 [&](const QModelIndex& current, const QModelIndex& previous) {
0144                     Q_UNUSED(previous)
0145                     Q_D(KInvestmentView);
0146                     d->m_securitySelections.setSelection(SelectedObjects::Security, current.data(eMyMoney::Model::IdRole).toString());
0147                     if (d->ui->m_securitiesTree->isVisible()) {
0148                         d->m_selections = d->m_securitySelections;
0149                         Q_EMIT requestSelectionChange(d->m_selections);
0150                     }
0151                 });
0152 
0153         connect(d->ui->m_equitiesTree, &QTreeView::doubleClicked, this, &KInvestmentView::slotEditInvestment);
0154 
0155         // use a QueuedConnection here to suppress duplicate call (at least on Qt 5.12.7)
0156         connect(
0157             d->ui->m_tab,
0158             &QTabWidget::currentChanged,
0159             this,
0160             [&](int index) {
0161                 Q_D(KInvestmentView);
0162                 const auto tab = static_cast<eView::Investment::Tab>(index);
0163 
0164                 switch (tab) {
0165                 case eView::Investment::Tab::Equities:
0166                     d->m_selections = d->m_equitySelections;
0167                     break;
0168                 case eView::Investment::Tab::Securities:
0169                     d->m_selections = d->m_securitySelections;
0170                     break;
0171                 }
0172                 Q_EMIT requestSelectionChange(d->m_selections);
0173             },
0174             Qt::QueuedConnection);
0175 
0176         connect(d->ui->m_accountComboBox, &KMyMoneyAccountCombo::accountSelected, this, [&](const QString& accountId) {
0177             Q_D(KInvestmentView);
0178             d->loadAccount(accountId);
0179         });
0180 
0181         d->selectDefaultInvestmentAccount();
0182     }
0183 
0184     // don't forget base class implementation
0185     QWidget::showEvent(event);
0186 
0187     // check if the last selected account was an investment account.
0188     // if so, then select it in this view as well. otherwise, we
0189     // leave the selection as is
0190     const auto accountId = d->m_externalSelections.firstSelection(SelectedObjects::Account);
0191     if (!accountId.isEmpty()) {
0192         const auto account = MyMoneyFile::instance()->account(accountId);
0193         if (account.accountType() == eMyMoney::Account::Type::Investment) {
0194             const auto indexes = d->m_accountsProxyModel->match(d->m_accountsProxyModel->index(0, 0),
0195                                                                 eMyMoney::Model::AccountTypeRole,
0196                                                                 static_cast<int>(eMyMoney::Account::Type::Investment),
0197                                                                 -1,
0198                                                                 Qt::MatchExactly | Qt::MatchRecursive);
0199             if (indexes.count()) {
0200                 qDebug() << indexes.count();
0201                 d->ui->m_accountComboBox->setSelected(QString());
0202             }
0203             d->ui->m_accountComboBox->setSelected(accountId);
0204         }
0205     }
0206 }
0207 
0208 void KInvestmentView::updateActions(const SelectedObjects& selections)
0209 {
0210     Q_D(KInvestmentView);
0211     const auto equityId = selections.firstSelection(SelectedObjects::Account);
0212     const auto securityId = selections.firstSelection(SelectedObjects::Security);
0213     const auto file = MyMoneyFile::instance();
0214 
0215     pActions[eMenu::Action::NewInvestment]->setEnabled(false);
0216     pActions[eMenu::Action::EditInvestment]->setEnabled(false);
0217     pActions[eMenu::Action::DeleteInvestment]->setEnabled(false);
0218     pActions[eMenu::Action::UpdatePriceManually]->setEnabled(false);
0219     pActions[eMenu::Action::UpdatePriceOnline]->setEnabled(false);
0220 
0221     pActions[eMenu::Action::EditSecurity]->setEnabled(false);
0222     pActions[eMenu::Action::DeleteSecurity]->setEnabled(false);
0223 
0224     // check that the selected account (combobox) is an investment account
0225     auto idx = file->accountsModel()->indexById(d->m_idInvAcc);
0226     if (idx.data(eMyMoney::Model::AccountTypeRole).value<eMyMoney::Account::Type>() == eMyMoney::Account::Type::Investment) {
0227         pActions[eMenu::Action::NewInvestment]->setEnabled(true);
0228     }
0229 
0230     if (!equityId.isEmpty()) {
0231         idx = file->accountsModel()->indexById(equityId);
0232         if (idx.data(eMyMoney::Model::AccountIsInvestRole).toBool()) {
0233             pActions[eMenu::Action::EditInvestment]->setEnabled(true);
0234             pActions[eMenu::Action::UpdatePriceManually]->setEnabled(true);
0235             pActions[eMenu::Action::DeleteInvestment]->setDisabled(file->isReferenced(equityId));
0236             const auto secId = idx.data(eMyMoney::Model::AccountCurrencyIdRole).toString();
0237             const auto sec = file->securitiesModel()->itemById(secId);
0238             pActions[eMenu::Action::UpdatePriceOnline]->setDisabled(sec.value("kmm-online-source").isEmpty());
0239         }
0240     }
0241     if (!securityId.isEmpty()) {
0242         QBitArray skip((int)eStorage::Reference::Count);
0243         skip.fill(false);
0244         skip.setBit((int)eStorage::Reference::Price);
0245         pActions[eMenu::Action::EditSecurity]->setEnabled(true);
0246         pActions[eMenu::Action::DeleteSecurity]->setDisabled(file->isReferenced(securityId, skip));
0247     }
0248 
0249     d->m_externalSelections = selections;
0250 }
0251 
0252 void KInvestmentView::slotNewInvestment()
0253 {
0254     Q_D(KInvestmentView);
0255     if (!isVisible())
0256         KNewInvestmentWizard::newInvestment(d->currentEquity());
0257     else
0258         KNewInvestmentWizard::newInvestment(MyMoneyFile::instance()->account(d->m_idInvAcc));
0259 }
0260 
0261 void KInvestmentView::slotEditInvestment()
0262 {
0263     Q_D(KInvestmentView);
0264     KNewInvestmentWizard::editInvestment(d->currentEquity());
0265 }
0266 
0267 void KInvestmentView::slotDeleteInvestment()
0268 {
0269     Q_D(KInvestmentView);
0270     if (KMessageBox::questionTwoActions(this,
0271                                         i18n("<p>Do you really want to delete the investment <b>%1</b>?</p>", d->currentEquity().name()),
0272                                         i18n("Delete investment"),
0273                                         KMMYesNo::yes(),
0274                                         KMMYesNo::no(),
0275                                         "DeleteInvestment")
0276         == KMessageBox::PrimaryAction) {
0277         auto file = MyMoneyFile::instance();
0278         MyMoneyFileTransaction ft;
0279         try {
0280             file->removeAccount(d->currentEquity());
0281             ft.commit();
0282         } catch (const MyMoneyException &e) {
0283             KMessageBox::information(this, i18n("Unable to delete investment: %1", QString::fromLatin1(e.what())));
0284         }
0285     } else {
0286         // we should not keep the 'no' setting because that can confuse people like
0287         // I have seen in some usability tests. So we just delete it right away.
0288         KSharedConfigPtr kconfig = KSharedConfig::openConfig();
0289         if (kconfig) {
0290             kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("DeleteInvestment"));
0291         }
0292     }
0293 }
0294 
0295 void KInvestmentView::slotUpdatePriceOnline()
0296 {
0297     Q_D(KInvestmentView);
0298     if (!d->currentEquity().id().isEmpty()) {
0299         QPointer<KEquityPriceUpdateDlg> dlg = new KEquityPriceUpdateDlg(0, d->currentEquity().currencyId());
0300         if ((dlg->exec() == QDialog::Accepted) && (dlg != nullptr))
0301             dlg->storePrices();
0302         delete dlg;
0303     }
0304 }
0305 
0306 void KInvestmentView::slotUpdatePriceManually()
0307 {
0308     Q_D(KInvestmentView);
0309     if (!d->currentEquity().id().isEmpty()) {
0310         try {
0311             auto security = MyMoneyFile::instance()->security(d->currentEquity().currencyId());
0312             auto currency = MyMoneyFile::instance()->security(security.tradingCurrency());
0313             const auto& price = MyMoneyFile::instance()->price(security.id(), currency.id());
0314 
0315             QPointer<KCurrencyCalculator> calc =
0316                 new KCurrencyCalculator(security, currency, MyMoneyMoney::ONE,
0317                                         price.rate(currency.id()), price.date(),
0318                                         MyMoneyMoney::precToDenom(security.pricePrecision()));
0319             calc->setupPriceEditor();
0320 
0321             // The dialog takes care of adding the price if necessary
0322             calc->exec();
0323             delete calc;
0324         } catch (const MyMoneyException &e) {
0325             qDebug("Error in price update: %s", e.what());
0326         }
0327     }
0328 }
0329 
0330 void KInvestmentView::slotEditSecurity()
0331 {
0332     Q_D(KInvestmentView);
0333     auto sec = d->currentSecurity();
0334 
0335     if (!sec.id().isEmpty()) {
0336         QPointer<KNewInvestmentWizard> dlg = new KNewInvestmentWizard(sec, this);
0337         dlg->setObjectName("KNewInvestmentWizard");
0338         if (dlg->exec() == QDialog::Accepted)
0339             dlg->createObjects(QString());
0340         delete dlg;
0341     }
0342 }
0343 
0344 void KInvestmentView::slotDeleteSecurity()
0345 {
0346     Q_D(KInvestmentView);
0347     auto sec = d->currentSecurity();
0348     if (!sec.id().isEmpty())
0349         KMyMoneyUtils::deleteSecurity(sec, this);
0350 }
0351 
0352 void KInvestmentView::slotSettingsChanged()
0353 {
0354     Q_D(KInvestmentView);
0355     if (d->m_needLoad) {
0356         return;
0357     }
0358 
0359     const bool showAllAccounts = KMyMoneySettings::showAllAccounts();
0360     if (d->m_equitiesProxyModel->hideClosedAccounts() == showAllAccounts) {
0361         d->m_equitiesProxyModel->setHideClosedAccounts(!showAllAccounts);
0362         d->loadAccount(d->m_idInvAcc);
0363     }
0364 }