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"