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

0001 /*
0002     SPDX-FileCopyrightText: 2015-2020 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 
0007 #include "ledgerviewpage.h"
0008 #include "ledgerviewpage_p.h"
0009 
0010 // ----------------------------------------------------------------------------
0011 // QT Includes
0012 
0013 #include <QAction>
0014 #include <QKeyEvent>
0015 #include <QPointer>
0016 #include <QTimer>
0017 
0018 // ----------------------------------------------------------------------------
0019 // KDE Includes
0020 
0021 // ----------------------------------------------------------------------------
0022 // Project Includes
0023 
0024 #include "icons.h"
0025 #include "journalmodel.h"
0026 #include "kmmsearchwidget.h"
0027 #include "kmymoneysettings.h"
0028 #include "menuenums.h"
0029 #include "mymoneyaccount.h"
0030 #include "mymoneyenums.h"
0031 #include "reconciliationmodel.h"
0032 #include "schedulesjournalmodel.h"
0033 #include "specialdatesmodel.h"
0034 #include "tabordereditor.h"
0035 #include "widgetenums.h"
0036 
0037 using namespace Icons;
0038 using namespace eWidgets;
0039 
0040 LedgerViewPage::LedgerViewPage(QWidget* parent, const QString& configGroupName)
0041     : QWidget(parent)
0042     , d(new Private(this))
0043 {
0044     d->initWidgets(configGroupName);
0045     d->ui->m_ledgerView->setModel(MyMoneyFile::instance()->journalModel()->newTransaction());
0046 
0047     connect(d->ui->m_ledgerView, &LedgerView::modifySortOrder, this, [&]() {
0048         d->selectSortOrder();
0049     });
0050 }
0051 
0052 LedgerViewPage::LedgerViewPage(LedgerViewPage::Private& dd, QWidget* parent, const QString& configGroupName)
0053     : QWidget(parent)
0054     , d(&dd)
0055 {
0056     d->initWidgets(configGroupName);
0057     d->ui->m_ledgerView->setModel(MyMoneyFile::instance()->journalModel()->newTransaction());
0058 
0059     connect(d->ui->m_ledgerView, &LedgerView::modifySortOrder, this, [&]() {
0060         d->selectSortOrder();
0061     });
0062 }
0063 
0064 void LedgerViewPage::showEvent(QShowEvent* event)
0065 {
0066     prepareToShow();
0067     QWidget::showEvent(event);
0068 }
0069 
0070 void LedgerViewPage::prepareToShow()
0071 {
0072     // make sure to run only once
0073     if (d->needModelInit) {
0074         initModel();
0075     }
0076 }
0077 
0078 void LedgerViewPage::initModel()
0079 {
0080     // setup the model stack
0081     const auto file = MyMoneyFile::instance();
0082     auto viewSettings = LedgerViewSettings::instance();
0083     d->accountFilter =
0084         new LedgerAccountFilter(d->ui->m_ledgerView,
0085                                 QVector<QAbstractItemModel*>{file->specialDatesModel(), file->schedulesJournalModel(), file->reconciliationModel()});
0086     d->accountFilter->setHideReconciledTransactions(viewSettings->hideReconciledTransactions());
0087     d->accountFilter->setHideTransactionsBefore(viewSettings->hideTransactionsBefore());
0088 
0089     d->stateFilter = new LedgerFilter(d->ui->m_ledgerView);
0090     d->stateFilter->setSourceModel(d->accountFilter);
0091     d->stateFilter->setComboBox(d->ui->m_searchWidget->comboBox());
0092     d->stateFilter->setLineEdit(d->ui->m_searchWidget->lineEdit());
0093 
0094     d->specialItemFilter = new SpecialLedgerItemFilter(this);
0095     d->specialItemFilter->setSourceModel(d->stateFilter);
0096     d->specialItemFilter->setSortRole(eMyMoney::Model::TransactionPostDateRole);
0097     d->specialItemFilter->setShowReconciliationEntries(viewSettings->showReconciliationEntries());
0098 
0099     connect(d->ui->m_searchWidget, &KMMSearchWidget::closed, this, [&]() {
0100         d->clearFilter();
0101         d->ui->m_ledgerView->setFocus();
0102     });
0103     connect(pActions[eMenu::Action::ShowFilterWidget], &QAction::triggered, this, [&]() {
0104         if (isVisible()) {
0105             d->ui->m_searchWidget->show();
0106         }
0107     });
0108 
0109     // Moving rows in a source model to a QConcatenateTablesProxyModel
0110     // does not get propagated through it which destructs our ledger in such cases.
0111     //
0112     // A workaround is to invalidate the sort filter.
0113     // connect(file->journalModel(), &JournalModel::rowsAboutToBeMoved, this, &LedgerViewPage::keepSelection);
0114     // connect(file->journalModel(), &JournalModel::rowsMoved, this, &LedgerViewPage::reloadFilter, Qt::QueuedConnection);
0115 
0116     d->ui->m_ledgerView->setModel(d->specialItemFilter);
0117 
0118     connect(viewSettings, &LedgerViewSettings::settingsChanged, this, [&]() {
0119         const auto settings = LedgerViewSettings::instance();
0120         d->accountFilter->setHideReconciledTransactions(settings->hideReconciledTransactions());
0121         d->accountFilter->setHideTransactionsBefore(settings->hideTransactionsBefore());
0122         d->specialItemFilter->setHideReconciledTransactions(settings->hideReconciledTransactions());
0123         d->specialItemFilter->setShowReconciliationEntries(settings->showReconciliationEntries());
0124 
0125         // make sure sorting is updated
0126         const auto acc = MyMoneyFile::instance()->accountsModel()->itemById(d->accountId);
0127         d->updateAccountData(acc);
0128     });
0129 
0130     // combine multiple row updates into one
0131     connect(d->stateFilter, &LedgerFilter::rowsRemoved, this, [&]() {
0132         // trigger update
0133         d->delayTimer.start(20);
0134     });
0135 
0136     connect(d->stateFilter, &LedgerFilter::rowsInserted, this, [&]() {
0137         // trigger update
0138         d->delayTimer.start(20);
0139     });
0140 
0141     connect(&d->delayTimer, &QTimer::timeout, this, [&]() {
0142         auto list = d->ui->m_ledgerView->selectedJournalEntryIds();
0143         if (list.isEmpty()) {
0144             d->ui->m_ledgerView->selectMostRecentTransaction();
0145         } else {
0146             d->ui->m_ledgerView->ensureCurrentItemIsVisible();
0147         }
0148     });
0149 
0150     connect(d->ui->m_ledgerView, &LedgerView::sectionResized, this, &LedgerViewPage::sectionResized);
0151     connect(d->ui->m_ledgerView, &LedgerView::sectionMoved, this, &LedgerViewPage::sectionMoved);
0152     connect(this, &LedgerViewPage::resizeSection, d->ui->m_ledgerView, &LedgerView::resizeSection);
0153     connect(this, &LedgerViewPage::moveSection, d->ui->m_ledgerView, &LedgerView::moveSection);
0154 
0155     connect(d->ui->m_ledgerView, &LedgerView::requestView, this, &LedgerViewPage::requestView);
0156 
0157     connect(file->journalModel(), &JournalModel::balancesChanged, this, &LedgerViewPage::updateSummaryInformation);
0158 
0159     d->needModelInit = false;
0160 
0161     // now execute all the postponed actions
0162     const auto acc = file->account(d->accountId);
0163     this->setAccount(acc);
0164 
0165     setShowEntryForNewTransaction(d->showEntryForNewTransaction);
0166 
0167     // now sort everything
0168     d->accountFilter->setSortingEnabled(true);
0169     // the next call will also take care of enabling
0170     // sorting on the stateFilter.
0171     d->specialItemFilter->setSortingEnabled(true);
0172 }
0173 
0174 LedgerViewPage::~LedgerViewPage()
0175 {
0176     delete d;
0177 }
0178 
0179 /**
0180  * @todo need to update that in the case when the journalId changes
0181  * due to a change of the TransactionPostDate
0182  */
0183 void LedgerViewPage::keepSelection()
0184 {
0185     d->selections.setSelection(SelectedObjects::JournalEntry, d->ui->m_ledgerView->selectedJournalEntryIds());
0186 }
0187 
0188 void LedgerViewPage::reloadFilter()
0189 {
0190     d->specialItemFilter->forceReload();
0191 
0192     d->ui->m_ledgerView->setSelectedJournalEntries(d->selections.selection(SelectedObjects::JournalEntry));
0193     // not sure if the following statement must be removed (THB - 2020-09-20)
0194     d->selections.clearSelections(SelectedObjects::JournalEntry);
0195 }
0196 
0197 QString LedgerViewPage::accountId() const
0198 {
0199     return d->accountId;
0200 }
0201 
0202 void LedgerViewPage::setAccount(const MyMoneyAccount& acc)
0203 {
0204     // in case we don't have a model, postpone this call
0205     // but remember the account id
0206     if (d->needModelInit) {
0207         d->accountId = acc.id();
0208         return;
0209     }
0210 
0211     QVector<int> columns;
0212     // get rid of current form
0213     delete d->form;
0214     d->form = nullptr;
0215     d->hideFormReasons.insert(QLatin1String("FormAvailable"));
0216 
0217     switch(acc.accountType()) {
0218     case eMyMoney::Account::Type::Investment:
0219         columns = { JournalModel::Column::Number,
0220                     JournalModel::Column::Account,
0221                     JournalModel::Column::CostCenter,
0222                     JournalModel::Column::Amount,
0223                     JournalModel::Column::Payment,
0224                     JournalModel::Column::Deposit,
0225                   };
0226         d->ui->m_ledgerView->setColumnsHidden(columns);
0227         columns = {
0228             JournalModel::Column::Date,
0229             JournalModel::Column::Security,
0230             JournalModel::Column::Detail,
0231             JournalModel::Column::Quantity,
0232             JournalModel::Column::Price,
0233             JournalModel::Column::Value,
0234             JournalModel::Column::Balance,
0235         };
0236         d->ui->m_ledgerView->setColumnsShown(columns);
0237         d->isInvestmentView = true;
0238         break;
0239 
0240     default:
0241         columns = { JournalModel::Column::Account,
0242                     JournalModel::Column::Security,
0243                     JournalModel::Column::CostCenter,
0244                     JournalModel::Column::Quantity,
0245                     JournalModel::Column::Price,
0246                     JournalModel::Column::Amount,
0247                     JournalModel::Column::Value,
0248                   };
0249         d->ui->m_ledgerView->setColumnsHidden(columns);
0250         columns = {
0251             JournalModel::Column::Date,
0252             JournalModel::Column::Detail,
0253             JournalModel::Column::Payment,
0254             JournalModel::Column::Deposit,
0255             JournalModel::Column::Balance,
0256         };
0257         d->ui->m_ledgerView->setColumnsShown(columns);
0258 
0259         d->form = new NewTransactionForm(d->ui->m_formWidget);
0260         break;
0261     }
0262 
0263     if(d->form) {
0264         d->hideFormReasons.remove(QLatin1String("FormAvailable"));
0265         // make sure we have a layout
0266         if(!d->ui->m_formWidget->layout()) {
0267             d->ui->m_formWidget->setLayout(new QHBoxLayout(d->ui->m_formWidget));
0268         }
0269         d->ui->m_formWidget->layout()->addWidget(d->form);
0270         connect(d->ui->m_ledgerView, &LedgerView::transactionSelected, d->form, &NewTransactionForm::showTransaction);
0271     }
0272     d->ui->m_formWidget->setVisible(d->hideFormReasons.isEmpty());
0273     d->accountFilter->setAccount(acc);
0274 
0275     d->accountId = acc.id();
0276 
0277     d->ui->m_ledgerView->setAccountId(d->accountId);
0278     d->selections.setSelection(SelectedObjects::Account, d->accountId);
0279     if (!acc.institutionId().isEmpty()) {
0280         d->selections.setSelection(SelectedObjects::Institution, acc.institutionId());
0281     }
0282     d->ui->m_ledgerView->selectMostRecentTransaction();
0283 
0284     connect(MyMoneyFile::instance()->accountsModel(), &AccountsModel::dataChanged, this, [&]() {
0285         const auto account = MyMoneyFile::instance()->accountsModel()->itemById(d->accountId);
0286         if (!account.id().isEmpty()) {
0287             d->updateAccountData(account);
0288         }
0289     });
0290 
0291     d->updateAccountData(acc);
0292 
0293     d->ui->m_ledgerView->setSortOrder(d->sortOrder);
0294 
0295     const auto file = MyMoneyFile::instance();
0296     d->totalBalance = file->balance(d->accountId, QDate());
0297     d->clearedBalance = file->clearedBalance(d->accountId, QDate());
0298     d->selectedTotal = MyMoneyMoney();
0299     d->updateSummaryInformation();
0300 }
0301 
0302 void LedgerViewPage::showTransactionForm(bool show)
0303 {
0304     if(show) {
0305         d->hideFormReasons.remove(QLatin1String("General"));
0306     } else {
0307         d->hideFormReasons.insert(QLatin1String("General"));
0308     }
0309     d->ui->m_formWidget->setVisible(d->hideFormReasons.isEmpty());
0310 }
0311 
0312 void LedgerViewPage::startEdit()
0313 {
0314     d->hideFormReasons.insert(QLatin1String("Edit"));
0315     d->ui->m_formWidget->hide();
0316 }
0317 
0318 void LedgerViewPage::finishEdit()
0319 {
0320     d->hideFormReasons.remove(QLatin1String("Edit"));
0321     d->ui->m_formWidget->setVisible(d->hideFormReasons.isEmpty());
0322     // the focus should be on the ledger view once editing ends
0323     d->ui->m_ledgerView->setFocus();
0324 }
0325 
0326 void LedgerViewPage::splitterChanged(int pos, int index)
0327 {
0328     Q_UNUSED(pos);
0329     Q_UNUSED(index);
0330 
0331     QMetaObject::invokeMethod(d->ui->m_ledgerView, &LedgerView::ensureCurrentItemIsVisible, Qt::QueuedConnection);
0332     // d->ui->m_ledgerView->ensureCurrentItemIsVisible();
0333 }
0334 
0335 void LedgerViewPage::setShowEntryForNewTransaction(bool show)
0336 {
0337     // postpone this setting until we are fully initialized
0338     if (d->needModelInit) {
0339         d->showEntryForNewTransaction = show;
0340         return;
0341     }
0342     d->accountFilter->setShowEntryForNewTransaction(show);
0343 }
0344 
0345 void LedgerViewPage::slotSettingsChanged()
0346 {
0347     showTransactionForm(KMyMoneySettings::transactionForm());
0348     d->ui->m_ledgerView->slotSettingsChanged();
0349 }
0350 
0351 void LedgerViewPage::slotRequestSelectionChanged(const SelectedObjects& selections) const
0352 {
0353     if (isVisible()) {
0354         d->selections.setSelection(SelectedObjects::JournalEntry, selections.selection(SelectedObjects::JournalEntry));
0355         if (selections.count(SelectedObjects::JournalEntry) > 1) {
0356             // More than one item selected, so show sum
0357             MyMoneyMoney balance;
0358             const auto journalEntryIds = selections.selection(SelectedObjects::JournalEntry);
0359             for (const auto& journalEntryId : qAsConst(journalEntryIds)) {
0360                 const auto idx = MyMoneyFile::instance()->journalModel()->indexById(journalEntryId);
0361                 if (idx.isValid()) {
0362                     balance += idx.data(eMyMoney::Model::SplitSharesRole).value<MyMoneyMoney>();
0363                 }
0364             }
0365             d->selectedTotal = balance;
0366 
0367         } else { // Only one thing selected, so set selectedTotal back to 0 to show balance
0368             d->selectedTotal = MyMoneyMoney();
0369         }
0370 
0371         d->updateSummaryInformation();
0372 
0373         d->selections.setSelection(SelectedObjects::Schedule, selections.selection(SelectedObjects::Schedule));
0374         Q_EMIT const_cast<LedgerViewPage*>(this)->requestSelectionChanged(d->selections);
0375     }
0376 }
0377 
0378 const SelectedObjects& LedgerViewPage::selections() const
0379 {
0380     d->selections.setSelection(SelectedObjects::JournalEntry, d->ui->m_ledgerView->selectedJournalEntryIds());
0381     return d->selections;
0382 }
0383 
0384 void LedgerViewPage::selectJournalEntry(const QString& id)
0385 {
0386     d->ui->m_ledgerView->setSelectedJournalEntries(QStringList{id});
0387 }
0388 
0389 bool LedgerViewPage::executeAction(eMenu::Action action, const SelectedObjects& selections)
0390 {
0391     const auto journalEntryIds = selections.selection(SelectedObjects::JournalEntry);
0392     switch (action) {
0393     case eMenu::Action::GoToAccount:
0394     case eMenu::Action::OpenAccount:
0395         if (!journalEntryIds.isEmpty()) {
0396             selectJournalEntry(journalEntryIds.first());
0397         }
0398         break;
0399 
0400     case eMenu::Action::NewTransaction:
0401         d->ui->m_ledgerView->editNewTransaction();
0402         break;
0403 
0404     case eMenu::Action::EditTransaction:
0405         d->ui->m_ledgerView->edit(d->ui->m_ledgerView->currentIndex());
0406         break;
0407 
0408     case eMenu::Action::EditSplits: {
0409         d->ui->m_ledgerView->edit(d->ui->m_ledgerView->currentIndex());
0410         const auto editor = d->ui->m_ledgerView->indexWidget(d->ui->m_ledgerView->editIndex());
0411         if (editor) {
0412             QMetaObject::invokeMethod(editor, "editSplits", Qt::QueuedConnection);
0413         }
0414         break;
0415     }
0416     case eMenu::Action::SelectAllTransactions:
0417         d->ui->m_ledgerView->selectAllTransactions();
0418         break;
0419 
0420     case eMenu::Action::MatchTransaction:
0421         d->ui->m_ledgerView->reselectJournalEntry(selections.firstSelection(SelectedObjects::JournalEntry));
0422         break;
0423 
0424     case eMenu::Action::EditTabOrder: {
0425         const auto editor = d->ui->m_ledgerView->indexWidget(d->ui->m_ledgerView->editIndex());
0426         if (editor) {
0427             QPointer<TabOrderDialog> tabOrderDialog = new TabOrderDialog(d->ui->m_ledgerView);
0428             auto tabOrderWidget = static_cast<TabOrderEditorInterface*>(editor->qt_metacast("TabOrderEditorInterface"));
0429             tabOrderDialog->setTarget(tabOrderWidget);
0430             auto tabOrder = editor->property("kmm_defaulttaborder").toStringList();
0431             tabOrderDialog->setDefaultTabOrder(tabOrder);
0432             tabOrder = editor->property("kmm_currenttaborder").toStringList();
0433             tabOrderDialog->setTabOrder(tabOrder);
0434 
0435             if ((tabOrderDialog->exec() == QDialog::Accepted) && tabOrderDialog) {
0436                 tabOrderWidget->storeTabOrder(tabOrderDialog->tabOrder());
0437             }
0438             delete tabOrderDialog;
0439         }
0440         break;
0441     }
0442 
0443     case eMenu::Action::ShowTransaction:
0444         d->ui->m_ledgerView->showEditor();
0445         break;
0446 
0447     default:
0448         break;
0449     }
0450     return true;
0451 }
0452 
0453 void LedgerViewPage::pushView(LedgerViewPage* view)
0454 {
0455     Q_ASSERT(view != nullptr);
0456 
0457     if (d->stackedView) {
0458         qDebug() << "view stack already taken, old one destroyed";
0459         d->stackedView->deleteLater();
0460     }
0461     d->ui->m_ledgerView->setSelectedJournalEntries(view->d->ui->m_ledgerView->selectedJournalEntryIds());
0462     d->stackedView = view;
0463     d->stackedView->blockSignals(true);
0464 }
0465 
0466 LedgerViewPage* LedgerViewPage::popView()
0467 {
0468     const auto view = d->stackedView;
0469     d->stackedView = nullptr;
0470     if (view) {
0471         view->blockSignals(false);
0472     }
0473     return view;
0474 }
0475 
0476 bool LedgerViewPage::hasPushedView() const
0477 {
0478     return d->stackedView != nullptr;
0479 }
0480 
0481 QString LedgerViewPage::accountName()
0482 {
0483     return d->accountName;
0484 }
0485 
0486 void LedgerViewPage::updateSummaryInformation(const QHash<QString, AccountBalances>& balances)
0487 {
0488     if (d->isInvestmentView) {
0489         d->updateSummaryInformation();
0490 
0491     } else {
0492         const auto it = balances.find(d->accountId);
0493         if (it != balances.cend()) {
0494             d->totalBalance = (*it).m_totalBalance;
0495             d->clearedBalance = (*it).m_clearedBalance;
0496             d->updateSummaryInformation();
0497         }
0498     }
0499 }
0500 
0501 QList<int> LedgerViewPage::splitterSizes() const
0502 {
0503     return d->ui->m_splitter->sizes();
0504 }
0505 
0506 void LedgerViewPage::setSplitterSizes(QList<int> sizes)
0507 {
0508     d->ui->m_splitter->setSizes(sizes);
0509 }