File indexing completed on 2024-05-19 05:08:18
0001 /* 0002 SPDX-FileCopyrightText: 2000-2001 Michael Edwardes <mte@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2004 Thomas Baumgart <ipwizard@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2017, 2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include <config-kmymoney.h> 0009 #include "kmymoneyview.h" 0010 0011 // ---------------------------------------------------------------------------- 0012 // Std Includes 0013 0014 #include <memory> 0015 0016 // ---------------------------------------------------------------------------- 0017 // QT Includes 0018 0019 #include <QFile> 0020 #include <QLayout> 0021 #include <QList> 0022 #include <QByteArray> 0023 #include <QUrl> 0024 #include <QIcon> 0025 #include <QTemporaryFile> 0026 #include <QUrlQuery> 0027 0028 // ---------------------------------------------------------------------------- 0029 // KDE Includes 0030 0031 #include <KActionCollection> 0032 #include <KBackup> 0033 #include <KDualAction> 0034 #include <KIO/StoredTransferJob> 0035 #include <KJobWidgets> 0036 #include <KLocalizedString> 0037 #include <KMessageBox> 0038 #include <KSharedConfig> 0039 #include <KTitleWidget> 0040 0041 // ---------------------------------------------------------------------------- 0042 // Project Includes 0043 0044 #include "accountsmodel.h" 0045 #include "equitiesmodel.h" 0046 #include "icons.h" 0047 #include "journalmodel.h" 0048 #include "kaccountsview.h" 0049 #include "kcategoriesview.h" 0050 #include "kcurrencyeditdlg.h" 0051 #include "khomeview.h" 0052 #include "kinstitutionsview.h" 0053 #include "kinvestmentview.h" 0054 #include "kmymoneyaccounttreeview.h" 0055 #include "kmymoneyplugin.h" 0056 #include "kmymoneysettings.h" 0057 #include "kpayeesview.h" 0058 #include "kscheduledview.h" 0059 #include "ktagsview.h" 0060 #include "menuenums.h" 0061 #include "mymoneyaccount.h" 0062 #include "mymoneyenums.h" 0063 #include "mymoneyexception.h" 0064 #include "mymoneyfile.h" 0065 #include "mymoneyinstitution.h" 0066 #include "mymoneymoney.h" 0067 #include "mymoneyprice.h" 0068 #include "mymoneyreport.h" 0069 #include "mymoneyschedule.h" 0070 #include "mymoneysecurity.h" 0071 #include "mymoneysplit.h" 0072 #include "mymoneytag.h" 0073 #include "mymoneyutils.h" 0074 #include "onlinejobadministration.h" 0075 #include "securitiesmodel.h" 0076 #include "selectedobjects.h" 0077 #include "simpleledgerview.h" 0078 0079 using namespace Icons; 0080 using namespace eMyMoney; 0081 0082 class KMyMoneyViewPrivate 0083 { 0084 Q_DECLARE_PUBLIC(KMyMoneyView); 0085 0086 public: 0087 KMyMoneyViewPrivate(KMyMoneyView* qq) 0088 : q_ptr(qq) 0089 , m_model(nullptr) 0090 { 0091 } 0092 0093 /** 0094 * Returns the id of the current selected view 0095 */ 0096 View currentViewId() const 0097 { 0098 Q_Q(const KMyMoneyView); 0099 return viewFrames.key(q->currentPage()); 0100 } 0101 0102 /** 0103 *Returns the id of the view identified by the pointer 0104 */ 0105 View viewIdByWidget(KMyMoneyViewBase* widget) 0106 { 0107 return viewBases.key(widget, View::Home); 0108 } 0109 0110 void selectSharedActions(View viewId) 0111 { 0112 Q_Q(KMyMoneyView); 0113 const auto view = viewBases.value(viewId, nullptr); 0114 if (view) { 0115 const auto sharedActions = view->sharedToolbarActions(); 0116 if (sharedActions.isEmpty()) { 0117 // reset to default 0118 Q_EMIT q->selectSharedActionButton(eMenu::Action::None, nullptr); 0119 } else { 0120 for (auto it = sharedActions.cbegin(); it != sharedActions.cend(); ++it) { 0121 Q_EMIT q->selectSharedActionButton(it.key(), it.value()); 0122 } 0123 } 0124 } 0125 } 0126 0127 void addSharedActions(KMyMoneyViewBase* view) 0128 { 0129 Q_Q(KMyMoneyView); 0130 const auto sharedActions = view->sharedToolbarActions(); 0131 for (auto it = sharedActions.cbegin(); it != sharedActions.cend(); ++it) { 0132 Q_EMIT q->addSharedActionButton(it.key(), it.value()); 0133 } 0134 } 0135 0136 void switchView(eMenu::Action action) 0137 { 0138 Q_Q(KMyMoneyView); 0139 const static QHash<eMenu::Action, View> actionRoutes = { 0140 {eMenu::Action::GoToPayee, View::Payees}, 0141 {eMenu::Action::OpenAccount, View::NewLedgers}, 0142 {eMenu::Action::GoToAccount, View::NewLedgers}, 0143 {eMenu::Action::StartReconciliation, View::NewLedgers}, 0144 {eMenu::Action::ReportOpen, View::Reports}, 0145 {eMenu::Action::ReportAccountTransactions, View::Reports}, 0146 {eMenu::Action::FileClose, View::Home}, 0147 }; 0148 0149 const auto viewId = actionRoutes.value(action, View::None); 0150 0151 if (viewId != View::None) { 0152 q->showPage(viewId); 0153 } 0154 } 0155 0156 void _q_selectionChangeRequested(const SelectedObjects& selection) 0157 { 0158 Q_Q(KMyMoneyView); 0159 // Only emit for current view and if selection has really changed 0160 if (q->sender() == viewBases[currentViewId()]) { 0161 if (selection != m_lastSelection) { 0162 m_lastSelection = selection; 0163 Q_EMIT q->requestSelectionChange(selection); 0164 } 0165 } 0166 } 0167 0168 KMyMoneyView* q_ptr; 0169 KPageWidgetModel* m_model; 0170 SelectedObjects m_lastSelection; 0171 0172 QHash<View, KPageWidgetItem*> viewFrames; 0173 QHash<View, KMyMoneyViewBase*> viewBases; 0174 0175 struct viewInfo { 0176 View id; 0177 QString name; 0178 Icon icon; 0179 }; 0180 0181 // clang-format off 0182 const QVector<viewInfo> viewsInfo 0183 { 0184 {View::Home, i18n("Home"), Icon::Home}, 0185 {View::Institutions, i18n("Institutions"), Icon::Institutions}, 0186 {View::Accounts, i18n("Accounts"), Icon::Accounts}, 0187 {View::Schedules, i18n("Scheduled\ntransactions"), Icon::Schedule}, 0188 {View::Categories, i18n("Categories"), Icon::FinancialCategories}, 0189 {View::Tags, i18n("Tags"), Icon::Tags}, 0190 {View::Payees, i18n("Payees"), Icon::Payees}, 0191 {View::NewLedgers, i18n("Ledgers"), Icon::Ledgers}, 0192 {View::Investments, i18n("Investments"), Icon::Investments}, 0193 }; 0194 // clang-format on 0195 }; 0196 0197 0198 0199 0200 KMyMoneyView::KMyMoneyView() 0201 : KPageWidget(nullptr) 0202 , d_ptr(new KMyMoneyViewPrivate(this)) 0203 { 0204 Q_D(KMyMoneyView); 0205 // this is a workaround for the bug in KPageWidget that causes the header to be shown 0206 // for a short while during page switch which causes a kind of bouncing of the page's 0207 // content and if the page's content is at it's minimum size then during a page switch 0208 // the main window's size is also increased to fit the header that is shown for a sort 0209 // period - reading the code in kpagewidget.cpp we know that the header should be at (1,1) 0210 // in a grid layout so if we find it there remove it for good to avoid the described issues 0211 QGridLayout* gridLayout = qobject_cast<QGridLayout*>(layout()); 0212 if (gridLayout) { 0213 QLayoutItem* headerItem = gridLayout->itemAtPosition(1, 1); 0214 // make sure that we remove only the header - we avoid surprises if the header is not at (1,1) in the layout 0215 if (headerItem && qobject_cast<KTitleWidget*>(headerItem->widget()) != NULL) { 0216 gridLayout->removeItem(headerItem); 0217 } 0218 } 0219 0220 d->m_model = new KPageWidgetModel(this); // cannot be parentless, otherwise segfaults at exit 0221 0222 const auto homeView = new KHomeView; 0223 const auto accountsView = new KAccountsView; 0224 d->viewBases[View::Home] = homeView; 0225 d->viewBases[View::Institutions] = new KInstitutionsView; 0226 d->viewBases[View::Accounts] = accountsView; 0227 d->viewBases[View::Schedules] = new KScheduledView; 0228 d->viewBases[View::Categories] = new KCategoriesView; 0229 d->viewBases[View::Tags] = new KTagsView; 0230 d->viewBases[View::Payees] = new KPayeesView; 0231 d->viewBases[View::NewLedgers] = new SimpleLedgerView; 0232 d->viewBases[View::Investments] = new KInvestmentView; 0233 0234 for (const auto& view : d->viewsInfo) { 0235 addView(d->viewBases[view.id], view.name, view.id, view.icon); 0236 } 0237 0238 // set the model 0239 setModel(d->m_model); 0240 setCurrentPage(d->viewFrames[View::Home]); 0241 connect(this, &KMyMoneyView::currentPageChanged, this, &KMyMoneyView::slotSwitchView); 0242 0243 // suppress update of homepage during statement import 0244 connect(accountsView, &KAccountsView::beginImportingStatements, homeView, &KHomeView::slotDisableRefresh); 0245 connect(accountsView, &KAccountsView::endImportingStatements, homeView, &KHomeView::slotEnableRefresh); 0246 0247 updateViewType(); 0248 } 0249 0250 KMyMoneyView::~KMyMoneyView() 0251 { 0252 } 0253 0254 void KMyMoneyView::updateViewType() 0255 { 0256 // set the face type 0257 KPageView::FaceType faceType = KPageView::List; 0258 switch (KMyMoneySettings::viewType()) { 0259 case 0: 0260 faceType = KPageView::List; 0261 break; 0262 case 1: 0263 faceType = KPageView::Tree; 0264 break; 0265 case 2: 0266 faceType = KPageView::Tabbed; 0267 break; 0268 } 0269 if (faceType != KMyMoneyView::faceType()) { 0270 setFaceType(faceType); 0271 if (faceType == KPageView::Tree) { 0272 const QList<QTreeView*> views = findChildren<QTreeView*>(); 0273 for (QTreeView* view : views) { 0274 if (view && (view->parent() == this)) { 0275 view->setRootIsDecorated(false); 0276 break; 0277 } 0278 } 0279 } 0280 } 0281 } 0282 0283 void KMyMoneyView::setOnlinePlugins(QMap<QString, KMyMoneyPlugin::OnlinePlugin*>* plugins) 0284 { 0285 // propagate to all views 0286 Q_EMIT onlinePluginsChanged(plugins); 0287 } 0288 0289 eDialogs::ScheduleResultCode KMyMoneyView::enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys) 0290 { 0291 Q_D(KMyMoneyView); 0292 return static_cast<KScheduledView*>(d->viewBases[View::Schedules])->enterSchedule(schedule, autoEnter, extendedKeys); 0293 } 0294 0295 void KMyMoneyView::addView(KMyMoneyViewBase* view, const QString& name, View idView, Icons::Icon icon) 0296 { 0297 Q_D(KMyMoneyView); 0298 /* There is a bug in 0299 * static int layoutText(QTextLayout *layout, int maxWidth) 0300 * from kpageview_p.cpp from kwidgetsaddons. 0301 * The method doesn't break strings that are too long. Following line 0302 * workarounds this by using LINE SEPARATOR character which is accepted by 0303 * QTextLayout::createLine().*/ 0304 auto adjustedName(name); 0305 adjustedName.replace(QLatin1Char('\n'), QString::fromUtf8("\xe2\x80\xa8")); 0306 0307 auto isViewInserted = false; 0308 for (auto i = (int)idView; i < (int)View::None; ++i) { 0309 if (d->viewFrames.contains((View)i)) { 0310 d->viewFrames[idView] = d->m_model->insertPage(d->viewFrames[(View)i],view, adjustedName); 0311 isViewInserted = true; 0312 break; 0313 } 0314 } 0315 0316 if (!isViewInserted) 0317 d->viewFrames[idView] = d->m_model->addPage(view, adjustedName); 0318 0319 d->viewFrames[idView]->setIcon(Icons::get(icon)); 0320 d->viewBases[idView] = view; 0321 connect(view, &KMyMoneyViewBase::requestCustomContextMenu, this, &KMyMoneyView::requestCustomContextMenu); 0322 connect(view, &KMyMoneyViewBase::requestActionTrigger, this, &KMyMoneyView::requestActionTrigger); 0323 connect(this, &KMyMoneyView::settingsChanged, view, &KMyMoneyViewBase::slotSettingsChanged); 0324 connect(this, &KMyMoneyView::onlinePluginsChanged, view, &KMyMoneyViewBase::setOnlinePlugins); 0325 0326 connect(view, &KMyMoneyViewBase::viewStateChanged, d->viewFrames[idView], &KPageWidgetItem::setEnabled); 0327 connect(view, SIGNAL(requestSelectionChange(SelectedObjects)), this, SLOT(_q_selectionChangeRequested(SelectedObjects))); 0328 connect(view, &KMyMoneyViewBase::requestView, this, &KMyMoneyView::switchView); 0329 0330 d->addSharedActions(view); 0331 } 0332 0333 void KMyMoneyView::setupSharedActions() 0334 { 0335 Q_D(KMyMoneyView); 0336 for (const auto& view : d->viewsInfo) { 0337 d->addSharedActions(d->viewBases[view.id]); 0338 } 0339 } 0340 0341 void KMyMoneyView::removeView(View idView) 0342 { 0343 Q_D(KMyMoneyView); 0344 auto view = d->viewBases.value(idView, nullptr); 0345 if (!view) 0346 return; 0347 0348 disconnect(view, &KMyMoneyViewBase::viewStateChanged, d->viewFrames[idView], &KPageWidgetItem::setEnabled); 0349 disconnect(view, SIGNAL(requestSelectionChange(SelectedObjects)), this, SLOT(_q_selectionChangeRequested(SelectedObjects))); 0350 0351 disconnect(view, &KMyMoneyViewBase::requestCustomContextMenu, this, &KMyMoneyView::requestCustomContextMenu); 0352 disconnect(view, &KMyMoneyViewBase::requestActionTrigger, this, &KMyMoneyView::requestActionTrigger); 0353 disconnect(this, &KMyMoneyView::settingsChanged, view, &KMyMoneyViewBase::slotSettingsChanged); 0354 disconnect(this, &KMyMoneyView::onlinePluginsChanged, view, &KMyMoneyViewBase::setOnlinePlugins); 0355 0356 d->m_model->removePage(d->viewFrames[idView]); 0357 d->viewFrames.remove(idView); 0358 d->viewBases.remove(idView); 0359 } 0360 0361 void KMyMoneyView::updateActions(const SelectedObjects& selections) 0362 { 0363 Q_D(KMyMoneyView); 0364 0365 const auto currentView = d->currentViewId(); 0366 const auto file = MyMoneyFile::instance(); 0367 0368 pActions[eMenu::Action::NewTransaction]->setDisabled(true); 0369 pActions[eMenu::Action::EditTransaction]->setDisabled(true); 0370 pActions[eMenu::Action::EditSplits]->setDisabled(true); 0371 pActions[eMenu::Action::DeleteTransaction]->setDisabled(true); 0372 pActions[eMenu::Action::DuplicateTransaction]->setDisabled(true); 0373 pActions[eMenu::Action::AddReversingTransaction]->setDisabled(true); 0374 pActions[eMenu::Action::DisplayTransactionDetails]->setDisabled(true); 0375 pActions[eMenu::Action::CopySplits]->setDisabled(true); 0376 pActions[eMenu::Action::MarkNotReconciled]->setDisabled(true); 0377 pActions[eMenu::Action::MarkCleared]->setDisabled(true); 0378 pActions[eMenu::Action::MarkReconciled]->setDisabled(true); 0379 pActions[eMenu::Action::SelectAllTransactions]->setEnabled(false); 0380 pActions[eMenu::Action::MatchTransaction]->setEnabled(false); 0381 pActions[eMenu::Action::AcceptTransaction]->setEnabled(false); 0382 pActions[eMenu::Action::NewScheduledTransaction]->setEnabled(false); 0383 pActions[eMenu::Action::StartReconciliation]->setEnabled(false); 0384 pActions[eMenu::Action::PostponeReconciliation]->setEnabled(false); 0385 pActions[eMenu::Action::FinishReconciliation]->setEnabled(false); 0386 pActions[eMenu::Action::CancelReconciliation]->setEnabled(false); 0387 pActions[eMenu::Action::MoveToToday]->setEnabled(false); 0388 0389 // update actions in all views. process the current last 0390 const auto viewBasesKeys = d->viewBases.keys(); 0391 for (const auto& view : qAsConst(viewBasesKeys)) { 0392 if (view == currentView) 0393 continue; 0394 d->viewBases[view]->updateActions(selections); 0395 } 0396 d->viewBases[currentView]->updateActions(selections); 0397 0398 // global actions 0399 // -------------- 0400 if (!selections.selection(SelectedObjects::JournalEntry).isEmpty()) { 0401 const auto enabled = MyMoneyUtils::transactionWarnLevel(selections.selection(SelectedObjects::JournalEntry)) < OneSplitFrozen; 0402 pActions[eMenu::Action::MarkNotReconciled]->setEnabled(enabled); 0403 pActions[eMenu::Action::MarkCleared]->setEnabled(enabled); 0404 pActions[eMenu::Action::MarkReconciled]->setEnabled(enabled); 0405 } 0406 0407 if (selections.selection(SelectedObjects::ReconciliationAccount).isEmpty() && (selections.selection(SelectedObjects::Account).count() == 1)) { 0408 const auto idx = MyMoneyFile::instance()->accountsModel()->indexById(selections.firstSelection(SelectedObjects::Account)); 0409 const auto disabled = idx.data(eMyMoney::Model::AccountIsClosedRole).toBool(); 0410 pActions[eMenu::Action::StartReconciliation]->setDisabled(disabled); 0411 } 0412 0413 switch (d->currentViewId()) { 0414 case View::NewLedgers: 0415 pActions[eMenu::Action::NewTransaction]->setEnabled(true); 0416 // intentional fall through 0417 0418 case View::Payees: 0419 pActions[eMenu::Action::SelectAllTransactions]->setEnabled(true); 0420 if (selections.selection(SelectedObjects::JournalEntry).isEmpty()) { 0421 pActions[eMenu::Action::EditTransaction]->setDisabled(true); 0422 pActions[eMenu::Action::EditSplits]->setDisabled(true); 0423 pActions[eMenu::Action::DeleteTransaction]->setDisabled(true); 0424 pActions[eMenu::Action::DuplicateTransaction]->setDisabled(true); 0425 pActions[eMenu::Action::AddReversingTransaction]->setDisabled(true); 0426 pActions[eMenu::Action::CopySplits]->setDisabled(true); 0427 pActions[eMenu::Action::MoveToToday]->setDisabled(true); 0428 } else { 0429 const auto warnLevel = MyMoneyUtils::transactionWarnLevel(selections.selection(SelectedObjects::JournalEntry)); 0430 pActions[eMenu::Action::EditTransaction]->setEnabled(true); 0431 pActions[eMenu::Action::EditSplits]->setEnabled(selections.selection(SelectedObjects::JournalEntry).count() == 1); 0432 pActions[eMenu::Action::DeleteTransaction]->setEnabled(warnLevel <= OneSplitReconciled); 0433 pActions[eMenu::Action::DuplicateTransaction]->setEnabled(warnLevel <= OneSplitReconciled); 0434 pActions[eMenu::Action::AddReversingTransaction]->setEnabled(warnLevel <= OneSplitReconciled); 0435 pActions[eMenu::Action::DisplayTransactionDetails]->setEnabled(true); 0436 pActions[eMenu::Action::CopySplits]->setDisabled(true); 0437 pActions[eMenu::Action::MoveToToday]->setEnabled(true); 0438 0439 int singleSplitTransactions(0); 0440 int multipleSplitTransactions(0); 0441 int matchedTransactions(0); 0442 int importedTransactions(0); 0443 0444 const auto journalEntryIds = selections.selection(SelectedObjects::JournalEntry); 0445 for (const auto& journalEntryId : qAsConst(journalEntryIds)) { 0446 const auto idx = file->journalModel()->indexById(journalEntryId); 0447 if ((singleSplitTransactions < 1) || (multipleSplitTransactions < 2)) { 0448 const auto indeces = file->journalModel()->indexesByTransactionId(idx.data(eMyMoney::Model::JournalTransactionIdRole).toString()); 0449 switch (indeces.count()) { 0450 case 0: 0451 break; 0452 case 1: 0453 singleSplitTransactions++; 0454 break; 0455 default: 0456 multipleSplitTransactions++; 0457 break; 0458 } 0459 } 0460 0461 if (idx.data(eMyMoney::Model::JournalSplitIsMatchedRole).toBool()) { 0462 ++matchedTransactions; 0463 } 0464 if (idx.data(eMyMoney::Model::TransactionIsImportedRole).toBool()) { 0465 ++importedTransactions; 0466 } 0467 if ((singleSplitTransactions > 0) && (multipleSplitTransactions > 1) && (matchedTransactions > 0) && (importedTransactions > 0)) { 0468 break; 0469 } 0470 } 0471 0472 if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) { 0473 pActions[eMenu::Action::CopySplits]->setEnabled(true); 0474 } 0475 0476 // Matching is enabled as soon as one regular and one imported transaction is selected 0477 if (selections.selection(SelectedObjects::JournalEntry).count() == 2 /* && pActions[Action::TransactionEdit]->isEnabled() */) { 0478 qobject_cast<KDualAction*>(pActions[eMenu::Action::MatchTransaction])->setActive(true); 0479 pActions[eMenu::Action::MatchTransaction]->setEnabled(true); 0480 } 0481 if ((importedTransactions > 0) || (matchedTransactions > 0)) { 0482 pActions[eMenu::Action::AcceptTransaction]->setEnabled(true); 0483 } 0484 if (matchedTransactions > 0) { 0485 pActions[eMenu::Action::MatchTransaction]->setEnabled(true); 0486 qobject_cast<KDualAction*>(pActions[eMenu::Action::MatchTransaction])->setActive(false); 0487 } 0488 0489 const auto accountId = selections.firstSelection(SelectedObjects::Account); 0490 if (!accountId.isEmpty()) { 0491 const auto idx = file->accountsModel()->indexById(accountId); 0492 if ((idx.data(eMyMoney::Model::AccountIsAssetLiabilityRole).toBool() == true) 0493 && (idx.data(eMyMoney::Model::AccountTypeRole).value<eMyMoney::Account::Type>() != eMyMoney::Account::Type::Investment)) { 0494 pActions[eMenu::Action::NewScheduledTransaction]->setEnabled(selections.selection(SelectedObjects::JournalEntry).count() == 1); 0495 } 0496 pActions[eMenu::Action::NewTransaction]->setEnabled(!idx.data(eMyMoney::Model::AccountIsClosedRole).toBool()); 0497 } 0498 } 0499 break; 0500 0501 case View::Home: 0502 case View::Reports: 0503 pActions[eMenu::Action::Print]->setEnabled(true); 0504 pActions[eMenu::Action::PrintPreview]->setEnabled(true); 0505 break; 0506 0507 default: 0508 break; 0509 } 0510 0511 // the open ledger function only makes sense if we have an account selection 0512 pActions[eMenu::Action::OpenAccount]->setEnabled(!selections.isEmpty(SelectedObjects::Account)); 0513 } 0514 0515 void KMyMoneyView::slotSettingsChanged() 0516 { 0517 updateViewType(); 0518 0519 Q_EMIT settingsChanged(); 0520 } 0521 0522 QHash<eMenu::Action, QAction *> KMyMoneyView::actionsToBeConnected() 0523 { 0524 using namespace eMenu; 0525 // add fast switching of main views through Ctrl + NUM_X 0526 struct pageInfo { 0527 Action action; 0528 View view; 0529 QString text; 0530 QKeySequence shortcut = QKeySequence(); 0531 }; 0532 const QVector<pageInfo> pageInfos { 0533 {Action::ShowHomeView, View::Home, i18n("Show home page"), Qt::CTRL + Qt::Key_1}, 0534 {Action::ShowInstitutionsView, View::Institutions, i18n("Show institutions page"), Qt::CTRL + Qt::Key_2}, 0535 {Action::ShowAccountsView, View::Accounts, i18n("Show accounts page"), Qt::CTRL + Qt::Key_3}, 0536 {Action::ShowSchedulesView, View::Schedules, i18n("Show scheduled transactions page"), Qt::CTRL + Qt::Key_4}, 0537 {Action::ShowCategoriesView, View::Categories, i18n("Show categories page"), Qt::CTRL + Qt::Key_5}, 0538 {Action::ShowTagsView, View::Tags, i18n("Show tags page"), }, 0539 {Action::ShowPayeesView, View::Payees, i18n("Show payees page"), Qt::CTRL + Qt::Key_6}, 0540 {Action::ShowLedgersView, View::NewLedgers, i18n("Show ledgers page"), Qt::CTRL + Qt::Key_7}, 0541 {Action::ShowInvestmentsView, View::Investments, i18n("Show investments page"), Qt::CTRL + Qt::Key_8}, 0542 {Action::ShowReportsView, View::Reports, i18n("Show reports page"), Qt::CTRL + Qt::Key_9}, 0543 {Action::ShowBudgetView, View::Budget, i18n("Show budget page"), }, 0544 {Action::ShowForecastView, View::Forecast, i18n("Show forecast page"), }, 0545 {Action::ShowOnlineJobOutboxView, View::OnlineJobOutbox, i18n("Show outbox page") }, 0546 }; 0547 0548 QHash<Action, QAction *> lutActions; 0549 auto pageCount = 0; 0550 for (const pageInfo& info : pageInfos) { 0551 auto a = new QAction(this); 0552 // KActionCollection::addAction by name sets object name anyways, 0553 // so, as better alternative, set it here right from the start 0554 a->setObjectName(QString::fromLatin1("ShowPage%1").arg(QString::number(pageCount++))); 0555 a->setText(info.text); 0556 a->setData(static_cast<int>(info.view)); 0557 connect(a, &QAction::triggered, this, [this, a] { 0558 showPageAndFocus(static_cast<View>(a->data().toUInt())); 0559 }); 0560 lutActions.insert(info.action, a); // store QAction's pointer for later processing 0561 if (!info.shortcut.isEmpty()) 0562 a->setShortcut(info.shortcut); 0563 } 0564 return lutActions; 0565 } 0566 0567 bool KMyMoneyView::showPageHeader() const 0568 { 0569 return false; 0570 } 0571 0572 void KMyMoneyView::showPageAndFocus(View idView) 0573 { 0574 Q_D(KMyMoneyView); 0575 if (d->viewFrames.contains(idView)) { 0576 showPage(idView); 0577 d->viewBases[idView]->setDefaultFocus(); 0578 } 0579 } 0580 0581 void KMyMoneyView::showPage(View idView) 0582 { 0583 Q_D(KMyMoneyView); 0584 if (!d->viewFrames.contains(idView) || (currentPage() == d->viewFrames[idView])) 0585 return; 0586 0587 setCurrentPage(d->viewFrames[idView]); 0588 } 0589 0590 void KMyMoneyView::enableViewsIfFileOpen(bool fileOpen) 0591 { 0592 Q_D(KMyMoneyView); 0593 // call set enabled only if the state differs to avoid widgets 'bouncing on the screen' while doing this 0594 Q_ASSERT_X((int)(View::Home) == 0, "viewenums.h", "View::Home must be the first entry"); 0595 Q_ASSERT_X(((int)(View::Home)+1) == (int)View::Institutions, "viewenums.h", "View::Institutions must be the second entry"); 0596 0597 // the home view is always enabled 0598 d->viewFrames[View::Home]->setEnabled(true); 0599 for (auto i = (int)View::Institutions; i < (int)View::None; ++i) 0600 if (d->viewFrames.contains(View(i))) 0601 if (d->viewFrames[View(i)]->isEnabled() != fileOpen) 0602 d->viewFrames[View(i)]->setEnabled(fileOpen); 0603 0604 Q_EMIT viewStateChanged(fileOpen); 0605 } 0606 0607 void KMyMoneyView::switchToDefaultView() 0608 { 0609 Q_D(KMyMoneyView); 0610 const auto idView = KMyMoneySettings::startLastViewSelected() ? 0611 static_cast<View>(KMyMoneySettings::lastViewSelected()) : 0612 View::Home; 0613 // if we currently see a different page, then select the right one 0614 if (d->viewFrames.contains(idView) && d->viewFrames[idView] != currentPage()) 0615 showPage(idView); 0616 } 0617 0618 void KMyMoneyView::slotSwitchView(KPageWidgetItem* current, KPageWidgetItem* previous) 0619 { 0620 Q_D(KMyMoneyView); 0621 if (previous != nullptr) { 0622 const auto view = qobject_cast<KMyMoneyViewBase*>(previous->widget()); 0623 if (view) { 0624 view->aboutToHide(); 0625 } 0626 } 0627 0628 if (current != nullptr) { 0629 const auto view = qobject_cast<KMyMoneyViewBase*>(current->widget()); 0630 if (view) { 0631 view->aboutToShow(); 0632 // remember last selected view 0633 // omit the initial page selection 0634 if (previous != nullptr) { 0635 for (auto it = d->viewFrames.constBegin(); it != d->viewFrames.constEnd(); ++it) { 0636 if (it.value() == current) { 0637 Q_EMIT viewActivated(it.key()); 0638 break; 0639 } 0640 } 0641 } 0642 d->selectSharedActions(d->currentViewId()); 0643 } 0644 } 0645 } 0646 0647 void KMyMoneyView::slotRememberLastView(View view) 0648 { 0649 KMyMoneySettings::setLastViewSelected(static_cast<int>(view)); 0650 } 0651 0652 void KMyMoneyView::executeAction(eMenu::Action action, const SelectedObjects& selections) 0653 { 0654 Q_D(KMyMoneyView); 0655 0656 // when closing, we don't remember the switch to the home view anymore 0657 switch (action) { 0658 case eMenu::Action::FileNew: // opened a file or database 0659 // make sure to catch view activations 0660 connect(this, &KMyMoneyView::viewActivated, this, &KMyMoneyView::slotRememberLastView); 0661 0662 // delay the switchToDefaultView call until the event loop is running 0663 QMetaObject::invokeMethod(this, "switchToDefaultView", Qt::QueuedConnection); 0664 break; 0665 0666 case eMenu::Action::FileClose: 0667 disconnect(this, &KMyMoneyView::viewActivated, this, &KMyMoneyView::slotRememberLastView); 0668 // block home view from unnecessary updates 0669 d->viewBases[View::Home]->executeAction(action, selections); 0670 break; 0671 0672 default: 0673 break; 0674 } 0675 0676 d->switchView(action); 0677 0678 // execute the action, at last on the current view 0679 const auto currentView = d->viewBases[d->currentViewId()]; 0680 for (const auto& view : qAsConst(d->viewBases)) { 0681 if (view != currentView) { 0682 view->executeAction(action, selections); 0683 } 0684 } 0685 currentView->executeAction(action, selections); 0686 } 0687 0688 void KMyMoneyView::switchView(QWidget* viewWidget, const QString& accountId, const QString& journalEntryId) 0689 { 0690 Q_D(KMyMoneyView); 0691 auto baseView = qobject_cast<KMyMoneyViewBase*>(viewWidget); 0692 const auto viewId = d->viewIdByWidget(baseView); 0693 showPage(viewId); 0694 0695 SelectedObjects selections; 0696 selections.addSelection(SelectedObjects::Account, accountId); 0697 selections.addSelection(SelectedObjects::JournalEntry, journalEntryId); 0698 executeAction(eMenu::Action::ShowTransaction, selections); 0699 } 0700 0701 bool KMyMoneyView::hasClosableView() const 0702 { 0703 Q_D(const KMyMoneyView); 0704 const auto currentView = d->viewBases[d->currentViewId()]; 0705 if (currentView) { 0706 return currentView->hasClosableView(); 0707 } 0708 return false; 0709 } 0710 0711 void KMyMoneyView::closeCurrentView() 0712 { 0713 Q_D(const KMyMoneyView); 0714 const auto currentView = d->viewBases[d->currentViewId()]; 0715 if (currentView) { 0716 currentView->closeCurrentView(); 0717 } 0718 } 0719 0720 #include "moc_kmymoneyview.cpp"