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 }