File indexing completed on 2024-04-28 05:06:06

0001 /*
0002     SPDX-FileCopyrightText: 2000 Michael Edwardes <mte@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2002-2020 Thomas Baumgart <tbaumgart@kde.org>
0004     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0005     SPDX-FileCopyrightText: 2021 Dawid Wróbel <me@dawidwrobel.com>
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 
0010 #include <config-kmymoney.h>
0011 
0012 #include "kmymoney.h"
0013 
0014 // ----------------------------------------------------------------------------
0015 // Std C++ / STL Includes
0016 
0017 #include <iostream>
0018 #include <memory>
0019 
0020 // ----------------------------------------------------------------------------
0021 // QT Includes
0022 
0023 #include <QApplication>
0024 #include <QBoxLayout>
0025 #include <QByteArray>
0026 #include <QClipboard>
0027 #include <QDateTime> // only for performance tests
0028 #include <QDesktopServices>
0029 #include <QDir>
0030 #include <QElapsedTimer>
0031 #include <QFileDialog>
0032 #include <QFlags>
0033 #include <QGridLayout>
0034 #include <QHBoxLayout>
0035 #include <QIcon>
0036 #include <QInputDialog>
0037 #include <QKeyEvent>
0038 #include <QKeySequence>
0039 #include <QLabel>
0040 #include <QList>
0041 #include <QListWidget>
0042 #include <QMenu>
0043 #include <QProgressBar>
0044 #include <QPushButton>
0045 #include <QStatusBar>
0046 #include <QTextBrowser>
0047 #include <QTimer>
0048 #include <QUrl>
0049 
0050 // ----------------------------------------------------------------------------
0051 // KDE Includes
0052 
0053 #include <KAboutApplicationDialog>
0054 #include <KActionCollection>
0055 #include <KBackup>
0056 #include <KConfig>
0057 #include <KConfigDialog>
0058 #include <KConfigDialogManager>
0059 #include <KDualAction>
0060 #include <KLocalizedString>
0061 #include <KMessageBox>
0062 #include <KProcess>
0063 #include <KRecentDirs>
0064 #include <KRecentFilesAction>
0065 #include <KStandardAction>
0066 #include <KToolBar>
0067 #include <KUndoActions>
0068 #include <KXMLGUIFactory>
0069 #include <kio_version.h>
0070 
0071 #ifdef ENABLE_HOLIDAYS
0072 #include <KHolidays/Holiday>
0073 #include <KHolidays/HolidayRegion>
0074 #include <kholidays_version.h>
0075 #endif
0076 
0077 #ifdef ENABLE_ACTIVITIES
0078 #include <KActivities/ResourceInstance>
0079 #endif
0080 
0081 #include <KDialogJobUiDelegate>
0082 #include <KIO/CommandLauncherJob>
0083 
0084 // ----------------------------------------------------------------------------
0085 // Project Includes
0086 
0087 #include <alkimia/alkenvironment.h>
0088 
0089 #include "accountsmodel.h"
0090 #include "budgetsmodel.h"
0091 #include "dialogs/editpersonaldatadlg.h"
0092 #include "dialogs/kbackupdlg.h"
0093 #include "dialogs/kbalancewarning.h"
0094 #include "dialogs/kcategoryreassigndlg.h"
0095 #include "dialogs/kconfirmmanualenterdlg.h"
0096 #include "dialogs/kcurrencycalculator.h"
0097 #include "dialogs/kcurrencyeditdlg.h"
0098 #include "dialogs/kequitypriceupdatedlg.h"
0099 #include "dialogs/kmymoneyfileinfodlg.h"
0100 #include "dialogs/kmymoneypricedlg.h"
0101 #include "dialogs/knewaccountdlg.h"
0102 #include "dialogs/knewinstitutiondlg.h"
0103 #include "dialogs/kpayeereassigndlg.h"
0104 #include "dialogs/ksaveasquestion.h"
0105 #include "dialogs/settings/ksettingskmymoney.h"
0106 #include "dialogs/transactionmatcher.h"
0107 #include "equitiesmodel.h"
0108 #include "journalmodel.h"
0109 #include "keditscheduledlg.h"
0110 #include "kmymoneyadaptor.h"
0111 #include "kmymoneysettings.h"
0112 #include "kmymoneyutils.h"
0113 #include "kmymoneyview.h"
0114 #include "ksearchtransactiondlg.h"
0115 #include "ktransactionselectdlg.h"
0116 #include "ledgerviewsettings.h"
0117 #include "schedulesjournalmodel.h"
0118 #include "specialdatesmodel.h"
0119 #include "widgets/amountedit.h"
0120 #include "widgets/kmymoneyaccountselector.h"
0121 #include "widgets/kmymoneydateedit.h"
0122 #include "widgets/kmymoneymvccombo.h"
0123 #include "widgets/kmymoneypayeecombo.h"
0124 #include "wizards/endingbalancedlg/kendingbalancedlg.h"
0125 #include "wizards/newaccountwizard/knewaccountwizard.h"
0126 #include "wizards/newinvestmentwizard/knewinvestmentwizard.h"
0127 #include "wizards/newloanwizard/keditloanwizard.h"
0128 #include "wizards/newuserwizard/knewuserwizard.h"
0129 
0130 #include "mymoney/mymoneyaccount.h"
0131 #include "mymoney/mymoneyaccountloan.h"
0132 #include "mymoney/mymoneybudget.h"
0133 #include "mymoney/mymoneyfile.h"
0134 #include "mymoney/mymoneyforecast.h"
0135 #include "mymoney/mymoneyinstitution.h"
0136 #include "mymoney/mymoneyobject.h"
0137 #include "mymoney/mymoneypayee.h"
0138 #include "mymoney/mymoneyprice.h"
0139 #include "mymoney/mymoneyreport.h"
0140 #include "mymoney/mymoneysecurity.h"
0141 #include "mymoney/mymoneysplit.h"
0142 #include "mymoney/mymoneystatement.h"
0143 #include "mymoney/mymoneytag.h"
0144 #include "mymoney/mymoneytransactionfilter.h"
0145 #include "mymoney/mymoneyutils.h"
0146 #include "mymoneyexception.h"
0147 #include "mymoneyreconciliationreport.h"
0148 
0149 #include "mymoneytemplate.h"
0150 #include "templateloader.h"
0151 #include "templatewriter.h"
0152 #include "kloadtemplatedlg.h"
0153 #include "ktemplateexportdlg.h"
0154 
0155 #include "converter/mymoneystatementreader.h"
0156 
0157 #include "interfaces/interfaceloader.h"
0158 #include "interfaces/onlinepluginextended.h"
0159 #include "kmymoneyplugin.h"
0160 #include "pluginloader.h"
0161 #include "plugins/plugin-interfaces/kmmappinterface.h"
0162 #include "plugins/plugin-interfaces/kmmimportinterface.h"
0163 #include "plugins/plugin-interfaces/kmmstatementinterface.h"
0164 #include "plugins/plugin-interfaces/kmmviewinterface.h"
0165 
0166 #include "tasks/credittransfer.h"
0167 
0168 #include "icons/icons.h"
0169 
0170 #include "misc/webconnect.h"
0171 
0172 #include "imymoneystorageformat.h"
0173 
0174 #include "kmymoneyutils.h"
0175 #include "kcreditswindow.h"
0176 
0177 #include "storageenums.h"
0178 #include "mymoneyenums.h"
0179 #include "dialogenums.h"
0180 #include "viewenums.h"
0181 #include "menuenums.h"
0182 #include "kmymoneyenums.h"
0183 
0184 #include "platformtools.h"
0185 #include "kmm_printer.h"
0186 
0187 #ifdef ENABLE_SQLCIPHER
0188 #include "sqlcipher/sqlite3.h"
0189 #endif
0190 
0191 #ifdef KMM_DEBUG
0192 #include "mymoney/storage/mymoneystoragedump.h"
0193 #include "mymoneytracer.h"
0194 #endif
0195 
0196 #include "selectedobjects.h"
0197 
0198 #include "kmmyesno.h"
0199 
0200 using namespace Icons;
0201 using namespace eMenu;
0202 
0203 enum backupStateE {
0204     BACKUP_IDLE = 0,
0205     BACKUP_MOUNTING,
0206     BACKUP_COPYING,
0207     BACKUP_UNMOUNTING,
0208 };
0209 
0210 class ShortCutActionFilter : public QObject
0211 {
0212     Q_OBJECT
0213 public:
0214     ShortCutActionFilter(QAction* parent)
0215         : QObject(parent)
0216     {
0217     }
0218 
0219     bool eventFilter(QObject* watched, QEvent* event) override
0220     {
0221         Q_UNUSED(watched)
0222 
0223         if (event->type() == QEvent::ShortcutOverride) {
0224             const auto kev = static_cast<QKeyEvent*>(event);
0225             const auto keySeq = QKeySequence(kev->modifiers() | kev->key());
0226             const auto action = static_cast<QAction*>(parent());
0227             if (keySeq == action->shortcut()) {
0228                 Q_EMIT shortCutDetected();
0229                 event->accept();
0230                 return true;
0231             }
0232         }
0233         return false;
0234     }
0235 
0236 Q_SIGNALS:
0237     void shortCutDetected();
0238 };
0239 
0240 class KMyMoneyApp::Private
0241 {
0242 public:
0243     Q_DISABLE_COPY_MOVE(Private)
0244 
0245     explicit Private(KMyMoneyApp* app)
0246         : q(app)
0247         , m_backupState(backupStateE::BACKUP_IDLE)
0248         , m_backupResult(0)
0249         , m_backupMount(0)
0250         , m_ignoreBackupExitCode(false)
0251         , m_myMoneyView(nullptr)
0252         , m_startDialog(false)
0253         , m_progressBar(nullptr)
0254         , m_statusLabel(nullptr)
0255         , m_autoSaveEnabled(true)
0256         , m_autoSaveTimer(nullptr)
0257         , m_progressTimer(nullptr)
0258         , m_autoSavePeriod(0)
0259         , m_inAutoSaving(false)
0260         , m_recentFiles(nullptr)
0261 #ifdef ENABLE_HOLIDAYS
0262         , m_holidayRegion(nullptr)
0263 #endif
0264 #ifdef ENABLE_ACTIVITIES
0265         , m_activityResourceInstance(nullptr)
0266 #endif
0267         , m_applicationIsReady(true)
0268         , m_webConnect(new WebConnect(app))
0269         , m_searchDlg(nullptr)
0270     {
0271         // since the days of the week are from 1 to 7,
0272         // and a day of the week is used to index this bit array,
0273         // resize the array to 8 elements (element 0 is left unused)
0274         m_processingDays.resize(8);
0275 
0276         m_actionCollectorTimer.setSingleShot(true);
0277         m_actionCollectorTimer.setInterval(100);
0278     }
0279 
0280     void unlinkStatementXML();
0281     void moveInvestmentTransaction(const QString& fromId,
0282                                    const QString& toId,
0283                                    const MyMoneyTransaction& t);
0284 
0285     struct storageInfo {
0286         eKMyMoney::StorageType type {eKMyMoney::StorageType::None};
0287         bool isOpened {false};
0288         QUrl url;
0289     };
0290 
0291     storageInfo m_storageInfo;
0292     /**
0293       * The public interface.
0294       */
0295     KMyMoneyApp * const q;
0296 
0297     /** the configuration object of the application */
0298     KSharedConfigPtr m_config;
0299 
0300     /**
0301       * The following variable represents the state while crafting a backup.
0302       * It can have the following values
0303       *
0304       * - IDLE: the default value if not performing a backup
0305       * - MOUNTING: when a mount command has been issued
0306       * - COPYING:  when a copy command has been issued
0307       * - UNMOUNTING: when an unmount command has been issued
0308       */
0309     backupStateE   m_backupState;
0310 
0311     /**
0312       * This variable keeps the result of the backup operation.
0313       */
0314     int     m_backupResult;
0315 
0316     /**
0317       * This variable is set, when the user selected to mount/unmount
0318       * the backup volume.
0319       */
0320     bool    m_backupMount;
0321 
0322     /**
0323       * Flag for internal run control
0324       */
0325     bool    m_ignoreBackupExitCode;
0326 
0327     KProcess m_proc;
0328 
0329     /// A pointer to the view holding the tabs.
0330     KMyMoneyView *m_myMoneyView;
0331 
0332     bool m_startDialog;
0333     QString m_mountpoint;
0334 
0335     QProgressBar* m_progressBar;
0336     QTime         m_lastUpdate;
0337     QLabel*       m_statusLabel;
0338 
0339     // allows multiple imports to be launched through web connect and to be executed sequentially
0340     QQueue<QString> m_importUrlsQueue;
0341 
0342     // This is Auto Saving related
0343     bool                  m_autoSaveEnabled;
0344     QTimer*               m_autoSaveTimer;
0345     QTimer*               m_progressTimer;
0346 
0347     int                   m_autoSavePeriod;
0348     bool                  m_inAutoSaving;
0349 
0350     // id's that need to be remembered
0351     QString               m_accountGoto, m_payeeGoto;
0352 
0353     KRecentFilesAction*   m_recentFiles;
0354 
0355 #ifdef ENABLE_HOLIDAYS
0356     // used by the calendar interface for schedules
0357     KHolidays::HolidayRegion* m_holidayRegion;
0358 #endif
0359 
0360 #ifdef ENABLE_ACTIVITIES
0361     KActivities::ResourceInstance * m_activityResourceInstance;
0362 #endif
0363 
0364     QBitArray             m_processingDays;
0365     QMap<QDate, bool>     m_holidayMap;
0366     QStringList           m_consistencyCheckResult;
0367     bool                  m_applicationIsReady;
0368 
0369     WebConnect*           m_webConnect;
0370 
0371     SelectedObjects       m_selections;
0372     QTimer                m_actionCollectorTimer;
0373 
0374     typedef struct SharedActionButtonInfo {
0375         QToolButton* button = nullptr;
0376         QAction* defaultAction = nullptr;
0377     } SharedActionButtonInfo;
0378 
0379     QHash<eMenu::Action, SharedActionButtonInfo> m_sharedActionButtons;
0380 
0381     KSearchTransactionDlg* m_searchDlg;
0382 
0383     // methods
0384     void consistencyCheck(bool alwaysDisplayResults);
0385     static void setThemedCSS();
0386     void copyConsistencyCheckResults();
0387     void saveConsistencyCheckResults();
0388 
0389     void checkAccountName(const MyMoneyAccount& _acc, const QString& name) const
0390     {
0391         auto file = MyMoneyFile::instance();
0392         if (_acc.name() != name) {
0393             MyMoneyAccount acc(_acc);
0394             acc.setName(name);
0395             file->modifyAccount(acc);
0396         }
0397     }
0398 
0399     /**
0400       * This method updates names of currencies from file to localized names
0401       */
0402     void updateCurrencyNames()
0403     {
0404         auto file = MyMoneyFile::instance();
0405         MyMoneyFileTransaction ft;
0406 
0407         QList<MyMoneySecurity> storedCurrencies = MyMoneyFile::instance()->currencyList();
0408         const QList<MyMoneySecurity> availableCurrencies = MyMoneyFile::instance()->availableCurrencyList();
0409         QStringList currencyIDs;
0410 
0411         for (const auto& currency : availableCurrencies)
0412             currencyIDs.append(currency.id());
0413 
0414         try {
0415             for (auto currency : qAsConst(storedCurrencies)) {
0416                 int i = currencyIDs.indexOf(currency.id());
0417                 if (i != -1 && availableCurrencies.at(i).name() != currency.name()) {
0418                     currency.setName(availableCurrencies.at(i).name());
0419                     file->modifyCurrency(currency);
0420                 }
0421             }
0422             ft.commit();
0423         } catch (const MyMoneyException &e) {
0424             qDebug("Error %s updating currency names", e.what());
0425         }
0426     }
0427 
0428     void updateAccountNames()
0429     {
0430         // make sure we setup the name of the base accounts in translated form
0431         try {
0432             MyMoneyFileTransaction ft;
0433             const auto file = MyMoneyFile::instance();
0434             checkAccountName(file->asset(), i18n("Asset"));
0435             checkAccountName(file->liability(), i18n("Liability"));
0436             checkAccountName(file->income(), i18n("Income"));
0437             checkAccountName(file->expense(), i18n("Expense"));
0438             checkAccountName(file->equity(), i18n("Equity"));
0439             ft.commit();
0440         } catch (const MyMoneyException &) {
0441         }
0442     }
0443 
0444     void ungetString(QIODevice *qfile, char *buf, int len)
0445     {
0446         buf = &buf[len-1];
0447         while (len--) {
0448             qfile->ungetChar(*buf--);
0449         }
0450     }
0451 
0452     bool applyFileFixes()
0453     {
0454         const auto file = MyMoneyFile::instance();
0455         QSignalBlocker blocked(file);
0456         KSharedConfigPtr config = KSharedConfig::openConfig();
0457         KConfigGroup grp = config->group("General Options");
0458 
0459         // For debugging purposes, we can turn off the automatic fix manually
0460         // by setting the entry in kmymoneyrc to true
0461         if (grp.readEntry("SkipFix", false) != true) {
0462             MyMoneyFileTransaction ft;
0463             try {
0464                 // Check if we have to modify the file before we allow to work with it
0465                 while (file->fileFixVersion() < file->availableFixVersion()) {
0466                     qDebug() << "testing fileFixVersion" << file->fileFixVersion() << "<" << file->availableFixVersion();
0467                     switch (file->fileFixVersion()) {
0468                     case 0:
0469                         fixFile_0();
0470                         file->setFileFixVersion(1);
0471                         break;
0472 
0473                     case 1:
0474                         fixFile_1();
0475                         file->setFileFixVersion(2);
0476                         break;
0477 
0478                     case 2:
0479                         fixFile_2();
0480                         file->setFileFixVersion(3);
0481                         break;
0482 
0483                     case 3:
0484                         fixFile_3();
0485                         file->setFileFixVersion(4);
0486                         break;
0487 
0488                     case 4:
0489                         fixFile_4();
0490                         file->setFileFixVersion(5);
0491                         break;
0492 
0493                     // add new levels above. Don't forget to increase currentFixVersion() for all
0494                     // the storage backends this fix applies to
0495                     default:
0496                         throw MYMONEYEXCEPTION(QString::fromLatin1("Unknown fix level in input file"));
0497                     }
0498                 }
0499                 ft.commit();
0500             } catch (const MyMoneyException &) {
0501                 return false;
0502             }
0503         } else {
0504             qDebug() << "Skipping automatic transaction fix!";
0505         }
0506         return true;
0507     }
0508 
0509     bool askAboutSaving()
0510     {
0511         const auto isFileNotSaved = q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->isEnabled();
0512         const auto isNewFileNotSaved = m_storageInfo.isOpened && m_storageInfo.url.isEmpty();
0513         auto fileNeedsToBeSaved = false;
0514 
0515         if (isFileNotSaved && KMyMoneySettings::autoSaveOnClose()) {
0516             fileNeedsToBeSaved = true;
0517         } else if (isFileNotSaved || isNewFileNotSaved) {
0518             switch (KMessageBox::warningTwoActionsCancel(q,
0519                                                          i18n("The file has been changed, save it?"),
0520                                                          i18nc("@title:window", "Save file"),
0521                                                          KMMYesNo::yes(),
0522                                                          KMMYesNo::no())) {
0523             case KMessageBox::ButtonCode::PrimaryAction:
0524                 fileNeedsToBeSaved = true;
0525                 break;
0526             case KMessageBox::ButtonCode::SecondaryAction:
0527                 fileNeedsToBeSaved = false;
0528                 break;
0529             case KMessageBox::ButtonCode::Cancel:
0530             default:
0531                 return false;
0532                 break;
0533             }
0534         }
0535         if (fileNeedsToBeSaved) {
0536             if (isFileNotSaved)
0537                 return q->slotFileSave();
0538             else if (isNewFileNotSaved)
0539                 return q->slotFileSaveAs();
0540         }
0541         return true;
0542     }
0543 
0544     /**
0545       * This method removes all data from the MyMoneyFile object
0546       * and resets the dirty flag(s).
0547       */
0548     void removeStorage()
0549     {
0550         MyMoneyFile::instance()->unload();
0551     }
0552 
0553     /**
0554       * if no base currency is defined, start the dialog and force it to be set
0555       */
0556     void selectBaseCurrency()
0557     {
0558         auto file = MyMoneyFile::instance();
0559 
0560         // check if we have a base currency. If not, we need to select one
0561         QString baseId;
0562         try {
0563             baseId = MyMoneyFile::instance()->baseCurrency().id();
0564         } catch (const MyMoneyException &e) {
0565             qDebug("%s", e.what());
0566         }
0567 
0568         if (baseId.isEmpty()) {
0569             QPointer<KCurrencyEditDlg> dlg = new KCurrencyEditDlg(q);
0570             dlg->exec();
0571             delete dlg;
0572         }
0573 
0574         try {
0575             baseId = MyMoneyFile::instance()->baseCurrency().id();
0576         } catch (const MyMoneyException &e) {
0577             qDebug("%s", e.what());
0578         }
0579 
0580         if (!baseId.isEmpty()) {
0581             // check that all accounts have a currency
0582             QList<MyMoneyAccount> list;
0583             file->accountList(list);
0584             QList<MyMoneyAccount>::Iterator it;
0585 
0586             // don't forget those standard accounts
0587             list << file->asset();
0588             list << file->liability();
0589             list << file->income();
0590             list << file->expense();
0591             list << file->equity();
0592 
0593 
0594             for (it = list.begin(); it != list.end(); ++it) {
0595                 QString cid;
0596                 try {
0597                     if (!(*it).currencyId().isEmpty() || (*it).currencyId().length() != 0)
0598                         cid = MyMoneyFile::instance()->security((*it).currencyId()).id();
0599                 } catch (const MyMoneyException &e) {
0600                     qDebug() << QLatin1String("Account") << (*it).id() << (*it).name() << e.what();
0601                 }
0602 
0603                 if (cid.isEmpty()) {
0604                     (*it).setCurrencyId(baseId);
0605                     MyMoneyFileTransaction ft;
0606                     try {
0607                         file->modifyAccount(*it);
0608                         ft.commit();
0609                     } catch (const MyMoneyException &e) {
0610                         qDebug("Unable to setup base currency in account %s (%s): %s", qPrintable((*it).name()), qPrintable((*it).id()), e.what());
0611                     }
0612                 }
0613             }
0614         }
0615     }
0616 
0617     /**
0618       * Call this to see if the MyMoneyFile contains any unsaved data.
0619       *
0620       * @retval true if any data has been modified but not saved
0621       * @retval false otherwise
0622       */
0623     bool dirty()
0624     {
0625         if (!m_storageInfo.isOpened)
0626             return false;
0627 
0628         return MyMoneyFile::instance()->dirty();
0629     }
0630 
0631 
0632     /* DO NOT ADD code to this function or any of it's called ones.
0633        Instead, create a new function, fixFile_n, and modify the initializeStorage()
0634        logic above to call it */
0635 
0636     void fixFile_4()
0637     {
0638         auto file = MyMoneyFile::instance();
0639         const QList<MyMoneySecurity> currencies = file->currencyList();
0640         static const QStringList symbols = {  QStringLiteral("XAU"),
0641                                               QStringLiteral("XPD"),
0642                                               QStringLiteral("XPT"),
0643                                               QStringLiteral("XAG"),
0644                                            };
0645 
0646         for (auto currency : currencies) {
0647             if (symbols.contains(currency.id())) {
0648                 if (currency.smallestAccountFraction() != currency.smallestCashFraction()) {
0649                     currency.setSmallestAccountFraction(currency.smallestCashFraction());
0650                     file->modifyCurrency(currency);
0651                 }
0652             }
0653         }
0654     }
0655 
0656     void fixFile_3()
0657     {
0658         // make sure each storage object contains a (unique) id
0659         MyMoneyFile::instance()->storageId();
0660     }
0661 
0662     void fixFile_2()
0663     {
0664         auto file = MyMoneyFile::instance();
0665         MyMoneyTransactionFilter filter;
0666         filter.setReportAllSplits(false);
0667         QList<MyMoneyTransaction> transactionList;
0668         file->transactionList(transactionList, filter);
0669 
0670         // scan the transactions and modify transactions with two splits
0671         // which reference an account and a category to have the memo text
0672         // of the account.
0673         auto count = 0;
0674         for (const auto& transaction : qAsConst(transactionList)) {
0675             if (transaction.splitCount() == 2) {
0676                 QString accountId;
0677                 QString categoryId;
0678                 QString accountMemo;
0679                 QString categoryMemo;
0680                 const auto splits = transaction.splits();
0681                 for (const auto& split : splits) {
0682                     auto acc = file->account(split.accountId());
0683                     if (acc.isIncomeExpense()) {
0684                         categoryId = split.id();
0685                         categoryMemo = split.memo();
0686                     } else {
0687                         accountId = split.id();
0688                         accountMemo = split.memo();
0689                     }
0690                 }
0691 
0692                 if (!accountId.isEmpty() && !categoryId.isEmpty()
0693                         && accountMemo != categoryMemo) {
0694                     MyMoneyTransaction t(transaction);
0695                     MyMoneySplit s(t.splitById(categoryId));
0696                     s.setMemo(accountMemo);
0697                     t.modifySplit(s);
0698                     file->modifyTransaction(t);
0699                     ++count;
0700                 }
0701             }
0702         }
0703         qDebug("%d transactions fixed in fixFile_2", count);
0704     }
0705 
0706     void fixFile_1()
0707     {
0708         // we need to fix reports. If the account filter list contains
0709         // investment accounts, we need to add the stock accounts to the list
0710         // as well if we don't have the expert mode enabled
0711         if (!KMyMoneySettings::expertMode()) {
0712             try {
0713                 QList<MyMoneyReport> reports = MyMoneyFile::instance()->reportList();
0714                 QList<MyMoneyReport>::iterator it_r;
0715                 for (it_r = reports.begin(); it_r != reports.end(); ++it_r) {
0716                     QStringList list;
0717                     (*it_r).accounts(list);
0718                     QStringList missing;
0719                     QStringList::const_iterator it_a, it_b;
0720                     for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) {
0721                         const auto acc = MyMoneyFile::instance()->account(*it_a);
0722                         if (acc.accountType() == eMyMoney::Account::Type::Investment) {
0723                             const auto accountList = acc.accountList();
0724                             for (const auto& accountID : accountList) {
0725                                 if (!list.contains(accountID)) {
0726                                     missing.append(accountID);
0727                                 }
0728                             }
0729                         }
0730                     }
0731                     if (!missing.isEmpty()) {
0732                         (*it_r).addAccount(missing);
0733                         MyMoneyFile::instance()->modifyReport(*it_r);
0734                     }
0735                 }
0736             } catch (const MyMoneyException &) {
0737             }
0738         }
0739     }
0740 
0741 #if 0
0742     if (!m_accountsView->allItemsSelected())
0743     {
0744         // retrieve a list of selected accounts
0745         QStringList list;
0746         m_accountsView->selectedItems(list);
0747 
0748         // if we're not in expert mode, we need to make sure
0749         // that all stock accounts for the selected investment
0750         // account are also selected
0751         if (!KMyMoneySettings::expertMode()) {
0752             QStringList missing;
0753             QStringList::const_iterator it_a, it_b;
0754             for (it_a = list.begin(); it_a != list.end(); ++it_a) {
0755                 const auto acc = MyMoneyFile::instance()->account(*it_a);
0756                 if (acc.accountType() == Account::Type::Investment) {
0757                     for (const auto& accountID : acc.accountList()) {
0758                         if (!list.contains(accountID)) {
0759                             missing.append(accountID);
0760                         }
0761                     }
0762                 }
0763             }
0764             list += missing;
0765         }
0766 
0767         m_filter.addAccount(list);
0768     }
0769 
0770 #endif
0771 
0772 
0773 
0774 
0775 
0776     void fixFile_0()
0777     {
0778         /* (Ace) I am on a crusade against file fixups.  Whenever we have to fix the
0779          * file, it is really a warning.  So I'm going to print a debug warning, and
0780          * then go track them down when I see them to figure out how they got saved
0781          * out needing fixing anyway.
0782          */
0783 
0784         auto file = MyMoneyFile::instance();
0785         QList<MyMoneyAccount> accountList;
0786         file->accountList(accountList);
0787         QList<MyMoneyAccount>::Iterator it_a;
0788         QList<MyMoneySchedule> scheduleList = file->scheduleList();
0789         QList<MyMoneySchedule>::Iterator it_s;
0790 
0791         MyMoneyAccount equity = file->equity();
0792         MyMoneyAccount asset = file->asset();
0793         bool equityListEmpty = equity.accountList().count() == 0;
0794 
0795         for (it_a = accountList.begin(); it_a != accountList.end(); ++it_a) {
0796             if ((*it_a).accountType() == eMyMoney::Account::Type::Loan
0797                     || (*it_a).accountType() == eMyMoney::Account::Type::AssetLoan) {
0798                 fixLoanAccount_0(*it_a);
0799             }
0800             // until early before 0.8 release, the equity account was not saved to
0801             // the file. If we have an equity account with no sub-accounts but
0802             // find and equity account that has equity() as it's parent, we reparent
0803             // this account. Need to move it to asset() first, because otherwise
0804             // MyMoneyFile::reparent would act as NOP.
0805             if (equityListEmpty && (*it_a).accountType() == eMyMoney::Account::Type::Equity) {
0806                 if ((*it_a).parentAccountId() == equity.id()) {
0807                     auto acc = *it_a;
0808                     // tricky, force parent account to be empty so that we really
0809                     // can re-parent it
0810                     acc.setParentAccountId(QString());
0811                     file->reparentAccount(acc, equity);
0812                     qDebug() << Q_FUNC_INFO << " fixed account " << acc.id() << " reparented to " << equity.id();
0813                 }
0814             }
0815         }
0816 
0817         for (it_s = scheduleList.begin(); it_s != scheduleList.end(); ++it_s) {
0818             fixSchedule_0(*it_s);
0819         }
0820 
0821         fixTransactions_0();
0822     }
0823 
0824     void fixSchedule_0(MyMoneySchedule sched)
0825     {
0826         MyMoneyTransaction t = sched.transaction();
0827         QList<MyMoneySplit> splitList = t.splits();
0828         QList<MyMoneySplit>::ConstIterator it_s;
0829 
0830         try {
0831             bool updated = false;
0832             // Check if the splits contain valid data and set it to
0833             // be valid.
0834             for (it_s = splitList.constBegin(); it_s != splitList.constEnd(); ++it_s) {
0835                 // the first split is always the account on which this transaction operates
0836                 // and if the transaction commodity is not set, we take this
0837                 if (it_s == splitList.constBegin() && t.commodity().isEmpty()) {
0838                     qDebug() << Q_FUNC_INFO << " " << t.id() << " has no commodity";
0839                     try {
0840                         auto acc = MyMoneyFile::instance()->account((*it_s).accountId());
0841                         t.setCommodity(acc.currencyId());
0842                         updated = true;
0843                     } catch (const MyMoneyException &) {
0844                     }
0845                 }
0846                 // make sure the account exists. If not, remove the split
0847                 try {
0848                     MyMoneyFile::instance()->account((*it_s).accountId());
0849                 } catch (const MyMoneyException &) {
0850                     qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " removed, because account '" << (*it_s).accountId() << "' does not exist.";
0851                     t.removeSplit(*it_s);
0852                     updated = true;
0853                 }
0854                 if ((*it_s).reconcileFlag() != eMyMoney::Split::State::NotReconciled) {
0855                     qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'";
0856                     MyMoneySplit split = *it_s;
0857                     split.setReconcileDate(QDate());
0858                     split.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
0859                     t.modifySplit(split);
0860                     updated = true;
0861                 }
0862                 // the schedule logic used to operate only on the value field.
0863                 // This is now obsolete.
0864                 if ((*it_s).shares().isZero() && !(*it_s).value().isZero()) {
0865                     MyMoneySplit split = *it_s;
0866                     split.setShares(split.value());
0867                     t.modifySplit(split);
0868                     updated = true;
0869                 }
0870             }
0871 
0872             // If there have been changes, update the schedule and
0873             // the engine data.
0874             if (updated) {
0875                 sched.setTransaction(t);
0876                 MyMoneyFile::instance()->modifySchedule(sched);
0877             }
0878         } catch (const MyMoneyException &e) {
0879             qWarning("Unable to update broken schedule: %s", e.what());
0880         }
0881     }
0882 
0883     void fixLoanAccount_0(MyMoneyAccount acc)
0884     {
0885         if (acc.value("final-payment").isEmpty() //
0886                 || acc.value("term").isEmpty() //
0887                 || acc.value("periodic-payment").isEmpty() //
0888                 || acc.value("loan-amount").isEmpty() //
0889                 || acc.value("interest-calculation").isEmpty() //
0890                 || acc.value("schedule").isEmpty() //
0891                 || acc.value("fixed-interest").isEmpty()) {
0892             KMessageBox::information(q,
0893                                      i18n("<p>The account \"%1\" was previously created as loan account but some information is missing.</p><p>The new loan wizard will be started to collect all relevant information.</p><p>Please use KMyMoney version 0.8.7 or later and earlier than version 0.9 to correct the problem.</p>"
0894                                           , acc.name()),
0895                                      i18n("Account problem"));
0896 
0897             throw MYMONEYEXCEPTION_CSTRING("Fix LoanAccount0 not supported anymore");
0898         }
0899     }
0900 
0901     void fixTransactions_0()
0902     {
0903         auto file = MyMoneyFile::instance();
0904 
0905         QList<MyMoneySchedule> scheduleList = file->scheduleList();
0906         MyMoneyTransactionFilter filter;
0907         filter.setReportAllSplits(false);
0908         QList<MyMoneyTransaction> transactionList;
0909         file->transactionList(transactionList, filter);
0910 
0911         QList<MyMoneySchedule>::Iterator it_x;
0912         QStringList interestAccounts;
0913 
0914         KMSTATUS(i18n("Fix transactions"));
0915         q->slotStatusProgressBar(0, scheduleList.count() + transactionList.count());
0916 
0917         int cnt = 0;
0918         // scan the schedules to find interest accounts
0919         for (it_x = scheduleList.begin(); it_x != scheduleList.end(); ++it_x) {
0920             MyMoneyTransaction t = (*it_x).transaction();
0921             QList<MyMoneySplit>::ConstIterator it_s;
0922             QStringList accounts;
0923             bool hasDuplicateAccounts = false;
0924 
0925             const auto splits = t.splits();
0926             for (const auto& split : splits) {
0927                 if (accounts.contains(split.accountId())) {
0928                     hasDuplicateAccounts = true;
0929                     qDebug() << Q_FUNC_INFO << " " << t.id() << " has multiple splits with account " << split.accountId();
0930                 } else {
0931                     accounts << split.accountId();
0932                 }
0933 
0934                 if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) {
0935                     if (interestAccounts.contains(split.accountId()) == 0) {
0936                         interestAccounts << split.accountId();
0937                     }
0938                 }
0939             }
0940             if (hasDuplicateAccounts) {
0941                 fixDuplicateAccounts_0(t);
0942             }
0943             ++cnt;
0944             if (!(cnt % 10))
0945                 q->slotStatusProgressBar(cnt);
0946         }
0947 
0948         // scan the transactions and modify loan transactions
0949         for (auto& transaction : transactionList) {
0950             QString defaultAction;
0951             const QList<MyMoneySplit> splits = transaction.splits();
0952             QStringList accounts;
0953 
0954             // check if base commodity is set. if not, set baseCurrency
0955             if (transaction.commodity().isEmpty()) {
0956                 qDebug() << Q_FUNC_INFO << " " << transaction.id() << " has no base currency";
0957                 transaction.setCommodity(file->baseCurrency().id());
0958                 file->modifyTransaction(transaction);
0959             }
0960 
0961             bool isLoan = false;
0962             // Determine default action
0963             if (transaction.splitCount() == 2) {
0964                 // check for transfer
0965                 int accountCount = 0;
0966                 MyMoneyMoney val;
0967                 for (const auto& split : splits) {
0968                     auto acc = file->account(split.accountId());
0969                     if (acc.accountGroup() == eMyMoney::Account::Type::Asset //
0970                             || acc.accountGroup() == eMyMoney::Account::Type::Liability) {
0971                         val = split.value();
0972                         accountCount++;
0973                         if (acc.accountType() == eMyMoney::Account::Type::Loan //
0974                                 || acc.accountType() == eMyMoney::Account::Type::AssetLoan)
0975                             isLoan = true;
0976                     } else
0977                         break;
0978                 }
0979                 if (accountCount == 2) {
0980                     if (isLoan)
0981                         defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization);
0982                     else
0983                         defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer);
0984                 } else {
0985                     if (val.isNegative())
0986                         defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal);
0987                     else
0988                         defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit);
0989                 }
0990             }
0991 
0992             for (const auto& split : splits) {
0993                 auto acc = file->account(split.accountId());
0994                 MyMoneyMoney val = split.value();
0995                 if (acc.accountGroup() == eMyMoney::Account::Type::Asset
0996                         || acc.accountGroup() == eMyMoney::Account::Type::Liability) {
0997                     if (!val.isPositive()) {
0998                         defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal);
0999                         break;
1000                     } else {
1001                         defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit);
1002                         break;
1003                     }
1004                 }
1005             }
1006 
1007 #if 0
1008             // Check for correct actions in transactions referencing credit cards
1009             bool needModify = false;
1010             // The action fields are actually not used anymore in the ledger view logic
1011             // so we might as well skip this whole thing here!
1012             for (it_s = splits.begin(); needModify == false && it_s != splits.end(); ++it_s) {
1013                 auto acc = file->account((*it_s).accountId());
1014                 MyMoneyMoney val = (*it_s).value();
1015                 if (acc.accountType() == Account::Type::CreditCard) {
1016                     if (val < 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer))
1017                         needModify = true;
1018                     if (val >= 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer))
1019                         needModify = true;
1020                 }
1021             }
1022 
1023             // (Ace) Extended the #endif down to cover this conditional, because as-written
1024             // it will ALWAYS be skipped.
1025 
1026             if (needModify == true) {
1027                 for (it_s = splits.begin(); it_s != splits.end(); ++it_s) {
1028                     (*it_s).setAction(defaultAction);
1029                     transaction.modifySplit(*it_s);
1030                     file->modifyTransaction(transaction);
1031                 }
1032                 splits = transaction.splits();    // update local copy
1033                 qDebug("Fixed credit card assignment in %s", transaction.id().data());
1034             }
1035 #endif
1036 
1037             // Check for correct assignment of ActionInterest in all splits
1038             // and check if there are any duplicates in this transactions
1039             for (auto split : splits) {
1040                 MyMoneyAccount splitAccount = file->account(split.accountId());
1041                 if (!accounts.contains(split.accountId())) {
1042                     accounts << split.accountId();
1043                 }
1044                 // if this split references an interest account, the action
1045                 // must be of type ActionInterest
1046                 if (interestAccounts.contains(split.accountId())) {
1047                     if (split.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) {
1048                         qDebug() << Q_FUNC_INFO << " " << transaction.id() << " contains an interest account (" << split.accountId() << ") but does not have ActionInterest";
1049                         split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest));
1050                         transaction.modifySplit(split);
1051                         file->modifyTransaction(transaction);
1052                         qDebug("Fixed interest action in %s", qPrintable(transaction.id()));
1053                     }
1054                     // if it does not reference an interest account, it must not be
1055                     // of type ActionInterest
1056                 } else {
1057                     if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) {
1058                         qDebug() << Q_FUNC_INFO << " " << transaction.id() << " does not contain an interest account so it should not have ActionInterest";
1059                         split.setAction(defaultAction);
1060                         transaction.modifySplit(split);
1061                         file->modifyTransaction(transaction);
1062                         qDebug("Fixed interest action in %s", qPrintable(transaction.id()));
1063                     }
1064                 }
1065 
1066                 // check that for splits referencing an account that has
1067                 // the same currency as the transactions commodity the value
1068                 // and shares field are the same.
1069                 if (transaction.commodity() == splitAccount.currencyId()
1070                         && split.value() != split.shares()) {
1071                     qDebug() << Q_FUNC_INFO << " " << transaction.id() << " " << split.id() << " uses the transaction currency, but shares != value";
1072                     split.setShares(split.value());
1073                     transaction.modifySplit(split);
1074                     file->modifyTransaction(transaction);
1075                 }
1076 
1077                 // fix the shares and values to have the correct fraction
1078                 if (!splitAccount.isInvest()) {
1079                     try {
1080                         int fract = splitAccount.fraction();
1081                         if (split.shares() != split.shares().convert(fract)) {
1082                             qDebug("adjusting fraction in %s,%s", qPrintable(transaction.id()), qPrintable(split.id()));
1083                             split.setShares(split.shares().convert(fract));
1084                             split.setValue(split.value().convert(fract));
1085                             transaction.modifySplit(split);
1086                             file->modifyTransaction(transaction);
1087                         }
1088                     } catch (const MyMoneyException &) {
1089                         qDebug("Missing security '%s', split not altered", qPrintable(splitAccount.currencyId()));
1090                     }
1091                 }
1092             }
1093 
1094             ++cnt;
1095             if (!(cnt % 10))
1096                 q->slotStatusProgressBar(cnt);
1097         }
1098 
1099         q->slotStatusProgressBar(-1, -1);
1100     }
1101 
1102     void fixDuplicateAccounts_0(MyMoneyTransaction& t)
1103     {
1104         qDebug("Duplicate account in transaction %s", qPrintable(t.id()));
1105     }
1106 
1107     void updateActions(const SelectedObjects& selections)
1108     {
1109         static const QVector<Action> actions = {
1110             Action::FilePersonalData,
1111             Action::FileInformation,
1112             Action::FileImportTemplate,
1113             Action::FileExportTemplate,
1114             Action::EditTabOrder,
1115 #ifdef KMM_DEBUG
1116             Action::FileDump,
1117             Action::NewFeature,
1118 #endif
1119             Action::EditFindTransaction,
1120             Action::NewCategory,
1121             Action::ToolCurrencies,
1122             Action::ToolPrices,
1123             Action::ToolUpdatePrices,
1124             Action::ToolConsistency,
1125             Action::ToolPerformance,
1126             Action::NewAccount,
1127             Action::NewInstitution,
1128             Action::NewSchedule,
1129             Action::ShowFilterWidget,
1130             Action::NewPayee,
1131             Action::NewTag,
1132             Action::Print,
1133             Action::PrintPreview,
1134         };
1135 
1136         for (const auto& action : actions)
1137             pActions[action]->setEnabled(m_storageInfo.isOpened);
1138 
1139         // make sure all shared actions of the New button have the right state
1140         if (m_sharedActionButtons[Action::FileNew].button) {
1141             for (const auto action : m_sharedActionButtons[Action::FileNew].button->actions()) {
1142                 action->setEnabled(m_storageInfo.isOpened);
1143             }
1144         }
1145         // except the New File/Book which is always enabled
1146         pActions[Action::FileNew]->setEnabled(true);
1147 
1148         pActions[Action::FileBackup]->setEnabled(m_storageInfo.isOpened && m_storageInfo.type == eKMyMoney::StorageType::XML);
1149 
1150         auto aC = q->actionCollection();
1151         aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(canFileSaveAs());
1152         aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Close)))->setEnabled(m_storageInfo.isOpened);
1153         pActions[Action::UpdateAllAccounts]->setEnabled(m_storageInfo.isOpened && KMyMoneyUtils::canUpdateAllAccounts());
1154 
1155         updateReconciliationTools(selections);
1156 
1157         // update actions in views and plugins
1158         m_myMoneyView->updateActions(selections);
1159         KMyMoneyPlugin::updateActions(pPlugins, selections);
1160     }
1161 
1162     void updateReconciliationTools(const SelectedObjects& selections)
1163     {
1164         // update reconciliation toolbar
1165         const bool inReconciliation = !selections.firstSelection(SelectedObjects::ReconciliationAccount).isEmpty();
1166         q->toolBar("reconcileToolBar")->setVisible(inReconciliation);
1167     }
1168 
1169     bool canFileSaveAs() const
1170     {
1171         return (m_storageInfo.isOpened
1172                 && (!pPlugins.storage.isEmpty() && !(pPlugins.storage.count() == 1 && pPlugins.storage.first()->storageType() == eKMyMoney::StorageType::GNC)));
1173     }
1174 
1175     /**
1176       * This method is used to update the caption of the application window.
1177       * It sets the caption to "filename [modified] - KMyMoney".
1178       */
1179     void updateCaption()
1180     {
1181         auto caption = m_storageInfo.url.isEmpty() && m_myMoneyView && m_storageInfo.isOpened ? i18n("Untitled") : m_storageInfo.url.fileName();
1182 
1183 #ifdef KMM_DEBUG
1184         caption += QString(" (%1 x %2)").arg(q->width()).arg(q->height());
1185 #endif
1186 
1187         q->setCaption(caption, MyMoneyFile::instance()->dirty());
1188     }
1189 
1190     // bool canUpdateAllAccounts() const;
1191     void fileAction(eKMyMoney::FileAction action)
1192     {
1193         AmountEdit amountEdit;
1194 
1195         switch (action) {
1196         case eKMyMoney::FileAction::Opened:
1197             q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false);
1198             updateAccountNames();
1199             updateCurrencyNames();
1200             selectBaseCurrency();
1201 
1202             // setup the standard precision
1203             amountEdit.setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction()));
1204 
1205             applyFileFixes();
1206             // setup internal data for which we need all models loaded
1207             MyMoneyFile::instance()->accountsModel()->setupAccountFractions();
1208 
1209             // clean undostack
1210             MyMoneyFile::instance()->undoStack()->clear();
1211 
1212             // inform everyone about new data
1213             MyMoneyFile::instance()->modelsReadyToUse();
1214             MyMoneyFile::instance()->forceDataChanged();
1215             // Enable save in case the fix changed the contents
1216             q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(dirty());
1217             updateActions(SelectedObjects());
1218             // inform views about new data source
1219             m_myMoneyView->executeAction(Action::FileNew, SelectedObjects());
1220 
1221             onlineJobAdministration::instance()->updateActions();
1222             m_myMoneyView->enableViewsIfFileOpen(m_storageInfo.isOpened);
1223             onlineJobAdministration::instance()->updateOnlineTaskProperties();
1224 
1225             q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
1226 
1227 #ifdef ENABLE_ACTIVITIES
1228             {
1229                 // make sure that we don't store the DB password in activity
1230                 QUrl url(m_storageInfo.url);
1231                 url.setPassword(QString());
1232                 m_activityResourceInstance->setUri(url);
1233             }
1234 #endif
1235             // start the check for scheduled transactions that need to be
1236             // entered as soon as the event loop becomes active.
1237             QMetaObject::invokeMethod(q, "slotCheckSchedules", Qt::QueuedConnection);
1238             break;
1239 
1240         case eKMyMoney::FileAction::Saved:
1241             // clear the dirty flag
1242             MyMoneyFile::instance()->setDirty(false);
1243             q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
1244             q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false);
1245             m_autoSaveTimer->stop();
1246             break;
1247 
1248         case eKMyMoney::FileAction::Closing:
1249             disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
1250             // make sure to not catch view activations anymore
1251             m_myMoneyView->executeAction(Action::FileClose, SelectedObjects());
1252             // notify the models that the file is going to be closed (we should have something like dataChanged that reaches the models first)
1253             MyMoneyFile::instance()->unload();
1254             break;
1255 
1256         case eKMyMoney::FileAction::Closed:
1257             q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
1258             q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false);
1259             m_myMoneyView->enableViewsIfFileOpen(m_storageInfo.isOpened);
1260             updateActions(SelectedObjects());
1261             break;
1262 
1263         case eKMyMoney::FileAction::Changed:
1264             q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
1265             q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(true && !m_storageInfo.url.isEmpty());
1266             // As this method is called every time the MyMoneyFile instance
1267             // notifies a modification, it's the perfect place to start the timer if needed
1268             if (m_autoSaveEnabled && !m_autoSaveTimer->isActive()) {
1269                 m_autoSaveTimer->setSingleShot(true);
1270                 m_autoSaveTimer->start(m_autoSavePeriod * 60 * 1000); // milliseconds
1271             }
1272             pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(KMyMoneyUtils::canUpdateAllAccounts());
1273             break;
1274 
1275         default:
1276             break;
1277         }
1278 
1279         updateCaption();
1280     }
1281 
1282     eMenu::Action qActionToId(const QAction* action)
1283     {
1284         if (action) {
1285             auto it = pActions.constBegin();
1286             for (; it != pActions.constEnd(); ++it) {
1287                 if (*it == action) {
1288                     return it.key();
1289                 }
1290             }
1291         }
1292         return eMenu::Action::None;
1293     }
1294 
1295     void matchTransaction()
1296     {
1297         if (m_selections.selection(SelectedObjects::JournalEntry).count() != 2)
1298             return;
1299 
1300         const auto priorSelection(m_selections);
1301 
1302         const auto file(MyMoneyFile::instance());
1303         QString toBeDeletedId;
1304         QString remainingId;
1305 
1306         for (const auto& journalEntryId : m_selections.selection(SelectedObjects::JournalEntry)) {
1307             const auto idx = file->journalModel()->indexById(journalEntryId);
1308             if (idx.data(eMyMoney::Model::TransactionIsImportedRole).toBool()) {
1309                 if (toBeDeletedId.isEmpty()) {
1310                     toBeDeletedId = journalEntryId;
1311                 } else {
1312                     // This is a second imported transaction, we still want to merge
1313                     remainingId = journalEntryId;
1314                 }
1315             } else if (!idx.data(eMyMoney::Model::JournalSplitIsMatchedRole).toBool()) {
1316                 if (remainingId.isEmpty()) {
1317                     remainingId = journalEntryId;
1318                 } else {
1319                     toBeDeletedId = journalEntryId;
1320                 }
1321             }
1322         }
1323 
1324         // the user selected two transactions but they might be
1325         // selected in the wrong order. We check here, if the
1326         // other way around makes more sense and simply exchange
1327         // the two. See bko #435512
1328         auto remaining = file->journalModel()->itemById(remainingId);
1329         auto toBeDeleted = file->journalModel()->itemById(toBeDeletedId);
1330 
1331         if ((remaining.transaction().splitCount() == 1) && (toBeDeleted.transaction().splitCount() > 1)) {
1332             swap(remaining, toBeDeleted);
1333         }
1334 
1335         QPointer<KTransactionMergeDlg> dlg(new KTransactionMergeDlg(q));
1336         dlg->addTransaction(remainingId);
1337         dlg->addTransaction(toBeDeletedId);
1338         const auto doMatch = ((dlg->exec() == QDialog::Accepted) && (dlg != nullptr));
1339 
1340         if (doMatch && (toBeDeleted.split().accountId() == remaining.split().accountId())) {
1341             remaining = file->journalModel()->itemById(dlg->remainingTransactionId());
1342             toBeDeleted = file->journalModel()->itemById(dlg->mergedTransactionId());
1343 
1344             MyMoneyFileTransaction ft;
1345             try {
1346                 if (remaining.transaction().id().isEmpty())
1347                     throw MYMONEYEXCEPTION(QString::fromLatin1("No manually entered transaction selected for matching"));
1348                 if (toBeDeleted.transaction().id().isEmpty())
1349                     throw MYMONEYEXCEPTION(QString::fromLatin1("No imported transaction selected for matching"));
1350 
1351                 TransactionMatcher matcher;
1352                 matcher.match(remaining.transaction(), remaining.split(), toBeDeleted.transaction(), toBeDeleted.split(), true);
1353                 ft.commit();
1354             } catch (const MyMoneyException& e) {
1355                 KMessageBox::detailedError(q, i18n("Unable to match the selected transactions"), e.what());
1356             }
1357 
1358             // inform views about the match (they may have to reselect some items)
1359             m_myMoneyView->executeAction(Action::MatchTransaction, priorSelection);
1360         }
1361 
1362         delete dlg;
1363     }
1364 
1365     void unmatchTransaction()
1366     {
1367         const auto file(MyMoneyFile::instance());
1368 
1369         MyMoneyFileTransaction ft;
1370         try {
1371             for (const auto& journalEntryId : m_selections.selection(SelectedObjects::JournalEntry)) {
1372                 const auto idx = file->journalModel()->indexById(journalEntryId);
1373                 if (idx.data(eMyMoney::Model::JournalSplitIsMatchedRole).toBool()) {
1374                     const auto journalEntry = file->journalModel()->itemByIndex(idx);
1375                     TransactionMatcher matcher;
1376                     matcher.unmatch(journalEntry.transaction(), journalEntry.split());
1377                 }
1378             }
1379             ft.commit();
1380 
1381         } catch (const MyMoneyException& e) {
1382             KMessageBox::detailedError(q, i18n("Unable to unmatch the selected transactions"), e.what());
1383         }
1384     }
1385 
1386     void executeAction(eMenu::Action actionId)
1387     {
1388         if (actionId != eMenu::Action::None) {
1389             // make sure to work on the current state
1390             const auto selections = m_selections;
1391             m_myMoneyView->executeAction(actionId, selections);
1392         }
1393     }
1394 
1395     void closeSubAccounts(const MyMoneyAccount& account)
1396     {
1397         MyMoneyFile* file = MyMoneyFile::instance();
1398         const auto accountList = account.accountList();
1399 
1400         for (const auto& subAccountId : accountList) {
1401             auto subAccount = file->account(subAccountId);
1402             closeSubAccounts(subAccount);
1403             subAccount.setClosed(true);
1404             file->modifyAccount(subAccount);
1405         }
1406     }
1407 
1408     LedgerViewSettings::ReconciliationHeader showReconciliationMarker() const
1409     {
1410         switch (KMyMoneySettings::showReconciliationMarker()) {
1411         case KMyMoneySettings::Off:
1412             return LedgerViewSettings::DontShowReconciliationHeader;
1413         case KMyMoneySettings::Last:
1414             return LedgerViewSettings::ShowLastReconciliationHeader;
1415         default:
1416             return LedgerViewSettings::ShowAllReconciliationHeader;
1417         }
1418     }
1419 };
1420 
1421 KMyMoneyApp::KMyMoneyApp(QWidget* parent) :
1422     KXmlGuiWindow(parent),
1423     d(new Private(this))
1424 {
1425 #ifdef KMM_DBUS
1426     new KmymoneyAdaptor(this);
1427     QDBusConnection::sessionBus().registerObject("/KMymoney", this);
1428     QDBusConnection::sessionBus().interface()->registerService(
1429         QStringLiteral("org.kde.kmymoney-%1").arg(QString::number(platformTools::processId()), QDBusConnectionInterface::DontQueueService));
1430 #endif
1431     // Register the main engine types used as meta-objects
1432     qRegisterMetaType<MyMoneyMoney>("MyMoneyMoney");
1433     qRegisterMetaType<MyMoneySecurity>("MyMoneySecurity");
1434 
1435 #ifdef ENABLE_SQLCIPHER
1436     /* Issues:
1437      * 1) libsqlite3 loads implicitly before libsqlcipher
1438      *  thus making the second one loaded but non-functional,
1439      * 2) libsqlite3 gets linked into kmymoney target implicitly
1440      *  and it's not possible to unload or unlink it explicitly
1441      *
1442      * Solution:
1443      * Use e.g. dummy sqlite3_key call, so that libsqlcipher gets loaded implicitly before libsqlite3
1444      * thus making the first one functional.
1445      *
1446      * Additional info:
1447      * 1) loading libsqlcipher explicitly doesn't solve the issue,
1448      * 2) using sqlite3_key only in sqlstorage plugin doesn't solve the issue,
1449      * 3) in a separate, minimal test case, loading libsqlite3 explicitly
1450      *  with QLibrary::ExportExternalSymbolsHint makes libsqlcipher non-functional
1451     */
1452     sqlite3_key(nullptr, nullptr, 0);
1453 #endif
1454 
1455     // preset the pointer because we need it during the course of this constructor
1456     kmymoney = this;
1457     d->m_config = KSharedConfig::openConfig();
1458 
1459     MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay());
1460 
1461     QFrame* frame = new QFrame;
1462     frame->setFrameStyle(QFrame::NoFrame);
1463     // values for margin (11) and spacing(6) taken from KDialog implementation
1464     QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, frame);
1465     layout->setContentsMargins(2, 2, 2, 2);
1466     layout->setSpacing(6);
1467 
1468     initIcons();
1469     initStatusBar();
1470     pActions = initActions();
1471     pMenus = initMenus();
1472 
1473     // overwrite the default text of the new file button
1474     pActions[Action::FileNew]->setText(i18nc("@action New file/database", "New book..."));
1475 
1476     d->m_myMoneyView = new KMyMoneyView;
1477     // we need to setup this connection so that setupSharedActions() has a callback
1478     connect(d->m_myMoneyView, &KMyMoneyView::addSharedActionButton, this, &KMyMoneyApp::slotAddSharedAction);
1479     d->m_myMoneyView->setupSharedActions();
1480 
1481     layout->addWidget(d->m_myMoneyView, 10);
1482     connect(d->m_myMoneyView, &KMyMoneyView::statusMsg, this, &KMyMoneyApp::slotStatusMsg);
1483     connect(d->m_myMoneyView, &KMyMoneyView::statusProgress, this, &KMyMoneyApp::slotStatusProgressBar);
1484     // connect view requests
1485     connect(d->m_myMoneyView, &KMyMoneyView::requestSelectionChange, this, &KMyMoneyApp::slotSelectionChanged);
1486 
1487     connect(d->m_myMoneyView, &KMyMoneyView::requestCustomContextMenu, this, [&](eMenu::Menu type, const QPoint& pos) {
1488         if (pMenus.contains(type)) {
1489             pMenus[type]->exec(pos);
1490         } else
1491             qDebug() << "Context menu for type" << static_cast<int>(type) << " not found";
1492     });
1493 
1494     connect(d->m_myMoneyView, &KMyMoneyView::requestActionTrigger, this, [&](eMenu::Action action) {
1495         if (pActions.contains(action)) {
1496             pActions[action]->trigger();
1497         }
1498     });
1499 
1500     // and setup the shared action dynamics
1501     connect(d->m_myMoneyView, &KMyMoneyView::selectSharedActionButton, this, [&](eMenu::Action action, QAction* newAction) {
1502         if (d->m_sharedActionButtons.contains(action) && newAction) {
1503             d->m_sharedActionButtons.value(action).button->setDefaultAction(newAction);
1504         } else {
1505             for (auto buttonInfo : d->m_sharedActionButtons) {
1506                 buttonInfo.button->setDefaultAction(buttonInfo.defaultAction);
1507             }
1508         }
1509     });
1510 
1511     // Initialize kactivities resource instance
1512 #ifdef ENABLE_ACTIVITIES
1513     d->m_activityResourceInstance = new KActivities::ResourceInstance(window()->winId(), this);
1514 #endif
1515 
1516     const auto viewActions = d->m_myMoneyView->actionsToBeConnected();
1517     actionCollection()->addActions(viewActions.values());
1518     for (auto it = viewActions.cbegin(); it != viewActions.cend(); ++it)
1519         pActions.insert(it.key(), it.value());
1520 
1521     ///////////////////////////////////////////////////////////////////
1522     // call inits to invoke all other construction parts
1523     readOptions();
1524 
1525     // now initialize the plugin structure
1526     createInterfaces();
1527     KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Load, pPlugins, this, guiFactory());
1528     onlineJobAdministration::instance()->setOnlinePlugins(pPlugins.extended);
1529     d->m_myMoneyView->setOnlinePlugins(&pPlugins.online);
1530 
1531     setCentralWidget(frame);
1532 
1533     connect(&d->m_proc, QOverload<int,QProcess::ExitStatus>::of(&KProcess::finished), this, &KMyMoneyApp::slotBackupHandleEvents);
1534 
1535     d->m_backupState = BACKUP_IDLE;
1536 
1537     QLocale locale;
1538     for (auto const& weekDay: locale.weekdays())
1539     {
1540         d->m_processingDays.setBit(static_cast<int>(weekDay));
1541     }
1542     d->m_autoSaveTimer = new QTimer(this);
1543     d->m_progressTimer = new QTimer(this);
1544 
1545     connect(d->m_autoSaveTimer, &QTimer::timeout, this, &KMyMoneyApp::slotAutoSave);
1546     connect(d->m_progressTimer, &QTimer::timeout, this, &KMyMoneyApp::slotStatusProgressDone);
1547 
1548     // connect the WebConnect server
1549     connect(d->m_webConnect, &WebConnect::gotUrl, this, &KMyMoneyApp::webConnectUrl);
1550 
1551     // connection to update caption
1552     connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, [&]() {
1553         d->updateCaption();
1554     });
1555 
1556     // setup the initial configuration
1557     slotUpdateConfiguration(QString());
1558 
1559     // kickstart date change timer
1560     slotDateChanged();
1561     d->fileAction(eKMyMoney::FileAction::Closed);
1562 
1563     connect(&d->m_actionCollectorTimer, &QTimer::timeout, this, [&]() {
1564         // update the actions in the views
1565         d->updateActions(d->m_selections);
1566     });
1567 }
1568 
1569 KMyMoneyApp::~KMyMoneyApp()
1570 {
1571     // delete cached objects since they are in the way
1572     // when unloading the plugins
1573     onlineJobAdministration::instance()->clearCaches();
1574 
1575     // we need to unload all plugins before we destroy anything else
1576     KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Unload, pPlugins, this, guiFactory());
1577     d->removeStorage();
1578 
1579 #ifdef ENABLE_HOLIDAYS
1580     delete d->m_holidayRegion;
1581 #endif
1582 
1583 #ifdef ENABLE_ACTIVITIES
1584     delete d->m_activityResourceInstance;
1585 #endif
1586 
1587     // destroy printer object
1588     KMyMoneyPrinter::cleanup();
1589 
1590     // destroy find transaction dialog
1591     delete d->m_searchDlg;
1592 
1593     // make sure all settings are written to disk
1594     KMyMoneySettings::self()->save();
1595     delete d;
1596 }
1597 
1598 QUrl KMyMoneyApp::lastOpenedURL()
1599 {
1600     QUrl url = d->m_startDialog ? QUrl() : d->m_storageInfo.url;
1601 
1602     if (!url.isValid()) {
1603         url = QUrl::fromUserInput(readLastUsedFile());
1604     }
1605 
1606     ready();
1607 
1608     return url;
1609 }
1610 
1611 void KMyMoneyApp::slotInstallConsistencyCheckContextMenu()
1612 {
1613     // this code relies on the implementation of KMessageBox::informationList to add a context menu to that list,
1614     // please adjust it if it's necessary or rewrite the way the consistency check results are displayed
1615     if (QWidget* dialog = QApplication::activeModalWidget()) {
1616         // allow the user to resize the dialog, since the contents might be large
1617         dialog->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
1618         dialog->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1619         if (QListWidget* widget = dialog->findChild<QListWidget *>()) {
1620             // give the user a hint that the data can be saved
1621             widget->setToolTip(i18n("This is the consistency check log, use the context menu to copy or save it."));
1622             widget->setWhatsThis(widget->toolTip());
1623             widget->setContextMenuPolicy(Qt::CustomContextMenu);
1624             connect(widget, &QListWidget::customContextMenuRequested, this, &KMyMoneyApp::slotShowContextMenuForConsistencyCheck);
1625         }
1626     }
1627 }
1628 
1629 void KMyMoneyApp::slotShowContextMenuForConsistencyCheck(const QPoint &pos)
1630 {
1631     // allow the user to save the consistency check results
1632     if (QWidget* widget = qobject_cast< QWidget* >(sender())) {
1633         QMenu contextMenu(widget);
1634         QAction* copy = new QAction(i18n("Copy to clipboard"), widget);
1635         QAction* save = new QAction(i18n("Save to file"), widget);
1636         contextMenu.addAction(copy);
1637         contextMenu.addAction(save);
1638         QAction *result = contextMenu.exec(widget->mapToGlobal(pos));
1639         if (result == copy) {
1640             // copy the consistency check results to the clipboard
1641             d->copyConsistencyCheckResults();
1642         } else if (result == save) {
1643             // save the consistency check results to a file
1644             d->saveConsistencyCheckResults();
1645         }
1646     }
1647 }
1648 
1649 QHash<eMenu::Menu, QMenu *> KMyMoneyApp::initMenus()
1650 {
1651     QHash<Menu, QMenu *> lutMenus;
1652     const QHash<Menu, QString> menuNames{
1653         {Menu::Institution, QStringLiteral("institution_context_menu")},
1654         {Menu::Account, QStringLiteral("account_context_menu")},
1655         {Menu::Schedule, QStringLiteral("schedule_context_menu")},
1656         {Menu::Category, QStringLiteral("category_context_menu")},
1657         {Menu::Tag, QStringLiteral("tag_context_menu")},
1658         {Menu::Payee, QStringLiteral("payee_context_menu")},
1659         {Menu::Investment, QStringLiteral("investment_context_menu")},
1660         {Menu::Security, QStringLiteral("security_context_menu")},
1661         {Menu::Transaction, QStringLiteral("transaction_context_menu")},
1662         {Menu::MoveTransaction, QStringLiteral("transaction_move_menu")},
1663         {Menu::MarkTransaction, QStringLiteral("transaction_mark_menu")},
1664         {Menu::MarkTransactionContext, QStringLiteral("transaction_context_mark_menu")},
1665     };
1666 
1667     for (auto it = menuNames.cbegin(); it != menuNames.cend(); ++it)
1668         lutMenus.insert(it.key(), qobject_cast<QMenu*>(factory()->container(it.value(), this)));
1669     return lutMenus;
1670 }
1671 
1672 void KMyMoneyApp::slotSelectionChanged(const SelectedObjects& selections)
1673 {
1674     if (!selections.isEmpty()) {
1675         qDebug() << "current selection";
1676 
1677         if (!selections.isEmpty(SelectedObjects::Institution))
1678             qDebug() << "Institutions:" << selections.selection(SelectedObjects::Institution);
1679         if (!selections.isEmpty(SelectedObjects::Account))
1680             qDebug() << "Accounts:" << selections.selection(SelectedObjects::Account);
1681         if (!selections.isEmpty(SelectedObjects::ReconciliationAccount))
1682             qDebug() << "ReconciliationAccounts:" << selections.selection(SelectedObjects::ReconciliationAccount);
1683         if (!selections.isEmpty(SelectedObjects::JournalEntry))
1684             qDebug() << "JournalEntries:" << selections.selection(SelectedObjects::JournalEntry);
1685         if (!selections.isEmpty(SelectedObjects::Payee))
1686             qDebug() << "Payees:" << selections.selection(SelectedObjects::Payee);
1687         if (!selections.isEmpty(SelectedObjects::Schedule))
1688             qDebug() << "Schedules:" << selections.selection(SelectedObjects::Schedule);
1689         if (!selections.isEmpty(SelectedObjects::Budget))
1690             qDebug() << "Budgets:" << selections.selection(SelectedObjects::Budget);
1691         if (!selections.isEmpty(SelectedObjects::OnlineJob))
1692             qDebug() << "OnlineJobs:" << selections.selection(SelectedObjects::OnlineJob);
1693         if (!selections.isEmpty(SelectedObjects::Tag))
1694             qDebug() << "Tags:" << selections.selection(SelectedObjects::Tag);
1695         if (!selections.isEmpty(SelectedObjects::Security))
1696             qDebug() << "Securities:" << selections.selection(SelectedObjects::Security);
1697         if (!selections.isEmpty(SelectedObjects::Report))
1698             qDebug() << "Reports:" << selections.selection(SelectedObjects::Report);
1699     } else {
1700         qDebug() << "No selections";
1701     }
1702 
1703     d->m_selections = selections;
1704 
1705     d->m_actionCollectorTimer.start();
1706 }
1707 
1708 QHash<Action, QAction *> KMyMoneyApp::initActions()
1709 {
1710     auto aC = actionCollection();
1711 
1712     /* Look-up table for all custom and standard actions.
1713     It's required for:
1714     1) building QList with QActions to be added to ActionCollection
1715     2) adding custom features to QActions like e.g. keyboard shortcut
1716     */
1717     QHash<Action, QAction *> lutActions;
1718 
1719     // *************
1720     // Adding standard actions
1721     // *************
1722     lutActions.insert(Action::FileNew, KStandardAction::openNew(this, &KMyMoneyApp::slotFileNew, aC));
1723     KStandardAction::open(this, &KMyMoneyApp::slotFileOpen, aC);
1724     d->m_recentFiles = KStandardAction::openRecent(this, &KMyMoneyApp::slotFileOpenRecent, aC);
1725     KStandardAction::save(this, &KMyMoneyApp::slotFileSave, aC);
1726     KStandardAction::saveAs(this, &KMyMoneyApp::slotFileSaveAs, aC);
1727     lutActions.insert(Action::FileClose, KStandardAction::close(this, &KMyMoneyApp::slotFileClose, aC));
1728     auto actionFilter = new ShortCutActionFilter(lutActions[Action::FileClose]);
1729     connect(actionFilter, &ShortCutActionFilter::shortCutDetected, this, &KMyMoneyApp::slotCloseViewOrFile, Qt::QueuedConnection);
1730     for (auto* w : qApp->topLevelWidgets()) {
1731         const auto mw = qobject_cast<QMainWindow*>(w);
1732         if (mw) {
1733             mw->installEventFilter(actionFilter);
1734         }
1735     }
1736     KStandardAction::quit(this, &KMyMoneyApp::slotFileQuit, aC);
1737     lutActions.insert(Action::Print, KStandardAction::print(this, &KMyMoneyApp::slotExecuteAction, aC));
1738     lutActions.insert(Action::PrintPreview, KStandardAction::printPreview(this, &KMyMoneyApp::slotExecuteAction, aC));
1739     KStandardAction::preferences(this, &KMyMoneyApp::slotSettings, aC);
1740 
1741     // *************
1742     // Adding all actions
1743     // *************
1744     {
1745         // struct for creating useless (unconnected) QAction
1746         struct actionInfo {
1747             Action  action;
1748             QString name;
1749             QString text;
1750             Icon    icon;
1751         };
1752 
1753         // clang-format off
1754         const QVector<actionInfo> actionInfos {
1755             // *************
1756             // The File menu
1757             // *************
1758             {Action::FileBackup,                    QStringLiteral("file_backup"),                    i18n("Backup..."),                                  Icon::Backup},
1759             {Action::FileImportStatement,           QStringLiteral("file_import_statement"),          i18n("Statement file..."),                          Icon::Empty},
1760             {Action::FileImportTemplate,            QStringLiteral("file_import_template"),           i18n("Account Template..."),                        Icon::Empty},
1761             {Action::FileExportTemplate,            QStringLiteral("file_export_template"),           i18n("Account Template..."),                        Icon::Empty},
1762             {Action::FilePersonalData,              QStringLiteral("view_personal_data"),             i18n("Personal Data..."),                           Icon::UserProperties},
1763 #ifdef KMM_DEBUG
1764             {Action::FileDump,                      QStringLiteral("file_dump"),                      i18n("Dump Memory"),                                Icon::Empty},
1765 #endif
1766             {Action::FileInformation,               QStringLiteral("view_file_info"),                 i18n("File-Information..."),                        Icon::DocumentProperties},
1767             // *************
1768             // The Edit menu
1769             // *************
1770             {Action::EditFindTransaction,           QStringLiteral("edit_find_transaction"),          i18n("Find transaction..."),                        Icon::Find},
1771             // *************
1772             // The View menu
1773             // *************
1774             {Action::ViewTransactionDetail,         QStringLiteral("view_show_transaction_detail"),   i18n("Show Transaction Detail"),                    Icon::TransactionDetails},
1775             {Action::ViewHideReconciled,            QStringLiteral("view_hide_reconciled_transactions"), i18n("Hide reconciled transactions"),            Icon::HideReconciled},
1776             {Action::ViewHideCategories,            QStringLiteral("view_hide_unused_categories"),    i18n("Hide unused categories"),                     Icon::HideCategories},
1777             {Action::ViewShowAll,                   QStringLiteral("view_show_all_accounts"),         i18n("Show all accounts"),                          Icon::Empty},
1778             // *********************
1779             // The institutions menu
1780             // *********************
1781             {Action::NewInstitution,                QStringLiteral("institution_new"),                i18n("New institution..."),                         Icon::InstitutionNew},
1782             {Action::EditInstitution,               QStringLiteral("institution_edit"),               i18n("Edit institution..."),                        Icon::InstitutionEdit},
1783             {Action::DeleteInstitution,             QStringLiteral("institution_delete"),             i18n("Delete institution..."),                      Icon::InstitutionRemove},
1784             // *****************
1785             // The accounts menu
1786             // *****************
1787             {Action::NewAccount,                    QStringLiteral("account_new"),                    i18n("New account..."),                             Icon::AccountNew},
1788             {Action::OpenAccount,                   QStringLiteral("account_open"),                   i18n("Open ledger"),                                Icon::Ledger},
1789             {Action::StartReconciliation,           QStringLiteral("account_reconcile"),              i18n("Reconcile..."),                               Icon::Reconcile},
1790             {Action::FinishReconciliation,          QStringLiteral("account_reconcile_finish"),       i18nc("Finish reconciliation", "Finish"),           Icon::Reconciled},
1791             {Action::PostponeReconciliation,        QStringLiteral("account_reconcile_postpone"),     i18nc("Postpone reconciliation", "Postpone"),       Icon::Pause},
1792             {Action::CancelReconciliation,          QStringLiteral("account_reconcile_cancel"),       i18nc("Cancel reconciliation", "Cancel"),           Icon::DialogCancel},
1793             {Action::ReconciliationReport,          QStringLiteral("account_reconcile_report"),       i18n("Report reconciliation"),                      Icon::Empty},
1794             {Action::EditAccount,                   QStringLiteral("account_edit"),                   i18n("Edit account..."),                            Icon::AccountEdit},
1795             {Action::DeleteAccount,                 QStringLiteral("account_delete"),                 i18n("Delete account..."),                          Icon::AccountRemove},
1796             {Action::CloseAccount,                  QStringLiteral("account_close"),                  i18n("Close account"),                              Icon::AccountClose},
1797             {Action::ReopenAccount,                 QStringLiteral("account_reopen"),                 i18n("Reopen account"),                             Icon::AccountReopen},
1798             {Action::ReportAccountTransactions,     QStringLiteral("account_transaction_report"),     i18n("Transaction report"),                         Icon::Report},
1799             {Action::ChartAccountBalance,           QStringLiteral("account_chart"),                  i18n("Show balance chart..."),                      Icon::OfficeCharBar},
1800             {Action::MapOnlineAccount,              QStringLiteral("account_online_map"),             i18n("Map account..."),                             Icon::MapOnlineAccount},
1801             {Action::UnmapOnlineAccount,            QStringLiteral("account_online_unmap"),           i18n("Unmap account..."),                           Icon::UnmapOnlineAccount},
1802             {Action::UpdateAccount,                 QStringLiteral("account_online_update"),          i18n("Update account..."),                          Icon::AccountUpdate},
1803             {Action::UpdateAllAccounts,             QStringLiteral("account_online_update_all"),      i18n("Update all accounts..."),                     Icon::AccountUpdateAll},
1804             // *******************
1805             // The categories menu
1806             // *******************
1807             {Action::NewCategory,                   QStringLiteral("category_new"),                   i18n("New category..."),                            Icon::FinancialCategoryNew},
1808             {Action::EditCategory,                  QStringLiteral("category_edit"),                  i18n("Edit category..."),                           Icon::FinancialCategoryEdit},
1809             {Action::DeleteCategory,                QStringLiteral("category_delete"),                i18n("Delete category..."),                         Icon::FinancialCategoryRemove},
1810             // **************
1811             // The tools menu
1812             // **************
1813             {Action::ToolCurrencies,                QStringLiteral("tools_currency_editor"),          i18n("Currencies..."),                              Icon::Currencies},
1814             {Action::ToolPrices,                    QStringLiteral("tools_price_editor"),             i18n("Prices..."),                                  Icon::Empty},
1815             {Action::ToolUpdatePrices,              QStringLiteral("tools_update_prices"),            i18n("Update Stock and Currency Prices..."),        Icon::OnlinePriceUpdate},
1816             {Action::ToolConsistency,               QStringLiteral("tools_consistency_check"),        i18n("Consistency Check"),                          Icon::Empty},
1817             {Action::ToolPerformance,               QStringLiteral("tools_performancetest"),          i18n("Performance-Test"),                           Icon::PerformanceTest},
1818             {Action::ToolCalculator,                QStringLiteral("tools_kcalc"),                    i18n("Calculator..."),                              Icon::Calculator},
1819             // *****************
1820             // The settings menu
1821             // *****************
1822             {Action::SettingsAllMessages,           QStringLiteral("settings_enable_messages"),       i18n("Enable all messages"),                        Icon::Empty},
1823             // *************
1824             // The help menu
1825             // *************
1826             {Action::GetOnlineHelp,                 QStringLiteral("help_get_online_help"),           i18n("Get help from our community"),                Icon::Community},
1827             {Action::WhatsNew,                      QStringLiteral("help_whats_new"),                 i18n("See what's new in this version"),             Icon::DialogInformation},
1828             {Action::VisitWebsite,                  QStringLiteral("help_visit_website"),             i18n("Visit our website"),                          Icon::Globe},
1829             // ***************************
1830             // Actions w/o main menu entry
1831             // ***************************
1832             {Action::NewTransaction,                QStringLiteral("transaction_new"),                i18nc("New transaction button", "New transaction"), Icon::DocumentNew},
1833             {Action::EditTransaction,               QStringLiteral("transaction_edit"),               i18nc("Edit transaction button", "Edit"),           Icon::DocumentEdit},
1834             {Action::EnterTransaction,              QStringLiteral("transaction_enter"),              i18nc("Enter transaction", "Enter"),                Icon::DialogOK},
1835             {Action::EditSplits,                    QStringLiteral("transaction_editsplits"),         i18nc("Edit split button", "Edit splits"),          Icon::Split},
1836             {Action::CancelTransaction,             QStringLiteral("transaction_cancel"),             i18nc("Cancel transaction edit", "Cancel"),         Icon::DialogCancel},
1837             {Action::DeleteTransaction,             QStringLiteral("transaction_delete"),             i18nc("Delete transaction", "Delete"),              Icon::EditRemove},
1838             {Action::DuplicateTransaction,          QStringLiteral("transaction_duplicate"),          i18nc("Duplicate transaction", "Duplicate"),        Icon::EditCopy},
1839             {Action::AddReversingTransaction,       QStringLiteral("transaction_add_reversing"),      i18nc("Add reversing transaction", "Add reversing"),Icon::Reverse},
1840             {Action::AcceptTransaction,             QStringLiteral("transaction_accept"),             i18nc("Accept 'imported' and 'matched' transaction", "Accept"), Icon::DialogOK},
1841             {Action::ToggleReconciliationFlag,      QStringLiteral("transaction_mark_toggle"),        i18nc("Toggle reconciliation flag", "Toggle"),      Icon::Empty},
1842             {Action::MarkCleared,                   QStringLiteral("transaction_mark_cleared"),       i18nc("Mark transaction cleared", "Cleared"),       Icon::Empty},
1843             {Action::MarkReconciled,                QStringLiteral("transaction_mark_reconciled"),    i18nc("Mark transaction reconciled", "Reconciled"), Icon::Empty},
1844             {Action::MarkNotReconciled,             QStringLiteral("transaction_mark_notreconciled"), i18nc("Mark transaction not reconciled", "Not reconciled"),     Icon::Empty},
1845             {Action::MoveTransactionTo,             QStringLiteral("transaction_move"),               i18nc("Move transaction", "Move transaction"),      Icon::Empty},     // not directly available in UI
1846             {Action::ShowTransaction,               QStringLiteral("transaction_show"),               i18nc("Show transaction", "Show transaction"),      Icon::Empty},     // not directly available in UI
1847         {Action::DisplayTransactionDetails,     QStringLiteral("transaction_display_details"),    i18nc("Display transaction details", "Show transaction details"),   Icon::DocumentProperties},
1848 
1849             {Action::SelectAllTransactions,         QStringLiteral("transaction_select_all"),         i18nc("Select all transactions", "Select all"),     Icon::SelectAll},
1850             {Action::GoToAccount,                   QStringLiteral("transaction_goto_account"),       i18n("Go to account"),                              Icon::BankAccount},
1851             {Action::GoToPayee,                     QStringLiteral("transaction_goto_payee"),         i18n("Go to payee"),                                Icon::Payee},
1852             {Action::NewScheduledTransaction,       QStringLiteral("transaction_create_schedule"),    i18n("Create scheduled transaction..."),            Icon::NewSchedule},
1853             {Action::AssignTransactionsNumber,      QStringLiteral("transaction_assign_number"),      i18n("Assign next number"),                         Icon::Empty},
1854             {Action::CombineTransactions,           QStringLiteral("transaction_combine"),            i18nc("Combine transactions", "Combine"),           Icon::Empty},
1855             {Action::MoveToToday,                   QStringLiteral("transaction_move_to_today"),      i18n("Move to today"),                              Icon::Empty},
1856             {Action::CopySplits,                    QStringLiteral("transaction_copy_splits"),        i18n("Copy splits"),                                Icon::Empty},
1857             {Action::ShowFilterWidget,              QStringLiteral("filter_show_widget"),             i18n("Show filter widget"),                         Icon::Empty},
1858             //Investment
1859             {Action::NewInvestment,                 QStringLiteral("investment_new"),                 i18n("New investment..."),                          Icon::InvestmentNew},
1860             {Action::EditInvestment,                QStringLiteral("investment_edit"),                i18n("Edit investment..."),                         Icon::InvestmentEdit},
1861             {Action::DeleteInvestment,              QStringLiteral("investment_delete"),              i18n("Delete investment..."),                       Icon::InvestmentRemove},
1862             {Action::UpdatePriceOnline,             QStringLiteral("investment_online_price_update"), i18n("Online price update..."),                     Icon::OnlinePriceUpdate},
1863             {Action::UpdatePriceManually,           QStringLiteral("investment_manual_price_update"), i18n("Manual price update..."),                     Icon::Empty},
1864             {Action::EditSecurity,                  QStringLiteral("security_edit"),                  i18n("Edit security..."),                           Icon::InvestmentEdit},
1865             {Action::DeleteSecurity,                QStringLiteral("security_delete"),                i18n("Delete security..."),                         Icon::InvestmentRemove},
1866 
1867             //Schedule
1868             {Action::NewSchedule,                   QStringLiteral("schedule_new"),                   i18n("New schedule..."),                            Icon::NewSchedule},
1869             {Action::EditSchedule,                  QStringLiteral("schedule_edit"),                  i18n("Edit scheduled transaction"),                 Icon::DocumentEdit},
1870             {Action::DeleteSchedule,                QStringLiteral("schedule_delete"),                i18n("Delete scheduled transaction"),               Icon::EditRemove},
1871             {Action::DuplicateSchedule,             QStringLiteral("schedule_duplicate"),             i18n("Duplicate scheduled transaction"),            Icon::EditCopy},
1872             {Action::EnterSchedule,                 QStringLiteral("schedule_enter"),                 i18n("Enter next transaction..."),                  Icon::KeyEnter},
1873             {Action::SkipSchedule,                  QStringLiteral("schedule_skip"),                  i18n("Skip next transaction..."),                   Icon::SeekForward},
1874             //Payees
1875             {Action::NewPayee,                      QStringLiteral("payee_new"),                      i18n("New payee..."),                               Icon::PayeeNew},
1876             {Action::RenamePayee,                   QStringLiteral("payee_rename"),                   i18n("Rename payee"),                               Icon::PayeeRename},
1877             {Action::DeletePayee,                   QStringLiteral("payee_delete"),                   i18n("Delete payee"),                               Icon::PayeeRemove},
1878             {Action::MergePayee,                    QStringLiteral("payee_merge"),                    i18n("Merge payees"),                               Icon::PayeeMerge},
1879             //Tags
1880             {Action::NewTag,                        QStringLiteral("tag_new"),                        i18n("New tag..."),                                 Icon::TagNew},
1881             {Action::RenameTag,                     QStringLiteral("tag_rename"),                     i18n("Rename tag"),                                 Icon::TagRename},
1882             {Action::DeleteTag,                     QStringLiteral("tag_delete"),                     i18n("Delete tag"),                                 Icon::TagRemove},
1883             //Reports
1884             {Action::ReportOpen,                    QStringLiteral("report_open"),                    i18n("Open report"),                                Icon::Report},
1885             {Action::ReportNew,                     QStringLiteral("report_new"),                     i18n("New report"),                                 Icon::DocumentNew},
1886             {Action::ReportCopy,                    QStringLiteral("report_copy"),                    i18n("Copy report"),                                Icon::EditCopy},
1887             {Action::ReportConfigure,               QStringLiteral("report_configure"),               i18n("Configure report"),                           Icon::DocumentProperties},
1888             {Action::ReportExport,                  QStringLiteral("report_export"),                  i18n("Export report"),                              Icon::Empty},
1889             {Action::ReportDelete,                  QStringLiteral("report_delete"),                  i18n("Delete report"),                              Icon::EditRemove},
1890             {Action::ReportClose,                   QStringLiteral("report_close"),                   i18n("Close report"),                               Icon::Close},
1891             {Action::ReportToggleChart,             QStringLiteral("report_toggle"),                  i18n("Toggle chart"),                               Icon::Close},
1892             {Action::EditTabOrder,                  QStringLiteral("edit_taborder"),                  i18n("Edit tab order"),                             Icon::Empty},
1893             //debug actions
1894 #ifdef KMM_DEBUG
1895             {Action::NewFeature,                    QStringLiteral("new feature"),                    i18n("Test new feature"),                           Icon::Empty},
1896             {Action::DebugTraces,                   QStringLiteral("debug_traces"),                   i18n("Debug Traces"),                               Icon::Empty},
1897 #endif
1898             {Action::DebugTimers,                   QStringLiteral("debug_timers"),                   i18n("Debug Timers"),                               Icon::Empty},
1899             // onlineJob actions
1900         };
1901         // clang-format on
1902 
1903         for (const auto& info : actionInfos) {
1904             auto a = new QAction(this);
1905             // KActionCollection::addAction by name sets object name anyways,
1906             // so, as better alternative, set it here right from the start
1907             a->setObjectName(info.name);
1908             a->setText(info.text);
1909             if (info.icon != Icon::Empty) // no need to set empty icon
1910                 a->setIcon(Icons::get(info.icon));
1911             a->setEnabled(false);
1912             lutActions.insert(info.action, a);  // store QAction's pointer for later processing
1913         }
1914 
1915         auto a = new KDualAction(this);
1916         a->setObjectName(QStringLiteral("transaction_match"));
1917         a->setActiveText(i18nc("Match transactions", "Match"));
1918         a->setInactiveText(i18nc("Unmatch transactions", "Unmatch"));
1919         a->setActiveIcon(Icons::get(Icon::Link));
1920         a->setInactiveIcon(Icons::get(Icon::Unlink));
1921         a->setActive(true);
1922         a->setAutoToggle(false);
1923         a->setEnabled(false);
1924         lutActions.insert(Action::MatchTransaction, a);
1925     }
1926 
1927     lutActions.insert(Action::EditUndo, KUndoActions::createUndoAction(MyMoneyFile::instance()->undoStack(), aC));
1928     lutActions.insert(Action::EditRedo, KUndoActions::createRedoAction(MyMoneyFile::instance()->undoStack(), aC));
1929 
1930     {
1931         // List with slots that get connected here. Other slots get connected in e.g. appropriate views
1932         // clang-format off
1933         typedef void(KMyMoneyApp::*KMyMoneyAppFunc)();
1934         const QHash<eMenu::Action, KMyMoneyAppFunc> actionConnections{
1935             // *************
1936             // The File menu
1937             // *************
1938             //      {Action::FileOpenDatabase,              &KMyMoneyApp::slotOpenDatabase},
1939             //      {Action::FileSaveAsDatabase,            &KMyMoneyApp::slotSaveAsDatabase},
1940             {Action::FileBackup,                    &KMyMoneyApp::slotBackupFile},
1941             {Action::FileImportTemplate,            &KMyMoneyApp::slotLoadAccountTemplates},
1942             {Action::FileExportTemplate,            &KMyMoneyApp::slotSaveAccountTemplates},
1943             {Action::FilePersonalData,              &KMyMoneyApp::slotFileViewPersonal},
1944 #ifdef KMM_DEBUG
1945             {Action::FileDump,                      &KMyMoneyApp::slotFileFileInfo},
1946 #endif
1947             {Action::FileInformation,               &KMyMoneyApp::slotFileInfoDialog},
1948             // *************
1949             // The View menu
1950             // *************
1951             {Action::ViewTransactionDetail,         &KMyMoneyApp::slotShowTransactionDetail},
1952             {Action::ViewHideReconciled,            &KMyMoneyApp::slotHideReconciledTransactions},
1953             {Action::ViewHideCategories,            &KMyMoneyApp::slotHideUnusedCategories},
1954             {Action::ViewShowAll,                   &KMyMoneyApp::slotShowAllAccounts},
1955             // *************
1956             // The Account menu
1957             // *************
1958             {Action::CloseAccount,                  &KMyMoneyApp::slotCloseAccount},
1959             {Action::ReopenAccount,                 &KMyMoneyApp::slotReopenAccount},
1960 
1961             // **************
1962             // The tools menu
1963             // **************
1964             {Action::ToolCurrencies,                &KMyMoneyApp::slotCurrencyDialog},
1965             {Action::ToolPrices,                    &KMyMoneyApp::slotPriceDialog},
1966             {Action::ToolUpdatePrices,              &KMyMoneyApp::slotEquityPriceUpdate},
1967             {Action::ToolConsistency,               &KMyMoneyApp::slotFileConsistencyCheck},
1968             {Action::ToolPerformance,               &KMyMoneyApp::slotPerformanceTest},
1969     //      {Action::ToolSQL,                       &KMyMoneyApp::slotGenerateSql},
1970             {Action::ToolCalculator,                &KMyMoneyApp::slotToolsStartKCalc},
1971             // *****************
1972             // The settings menu
1973             // *****************
1974             {Action::SettingsAllMessages,           &KMyMoneyApp::slotEnableMessages},
1975             {Action::EditTabOrder,                  &KMyMoneyApp::slotEditTabOrder},
1976             // *****************
1977             // The help menu
1978             // *****************
1979             {Action::GetOnlineHelp,                 &KMyMoneyApp::slotGetOnlineHelp},
1980             {Action::WhatsNew,                      &KMyMoneyApp::slotWhatsNew},
1981             {Action::VisitWebsite,                  &KMyMoneyApp::slotVisitWebsite},
1982             // ***************************
1983             // Actions w/o main menu entry
1984             // ***************************
1985             //debug actions
1986 #ifdef KMM_DEBUG
1987             {Action::NewFeature,                    &KMyMoneyApp::slotNewFeature},
1988             {Action::DebugTraces,                   &KMyMoneyApp::slotToggleTraces},
1989 #endif
1990             {Action::DebugTimers,                   &KMyMoneyApp::slotToggleTimers},
1991 
1992             {Action::OpenAccount,                   &KMyMoneyApp::slotExecuteAction},
1993             {Action::NewTransaction,                &KMyMoneyApp::slotExecuteAction},
1994             {Action::EditTransaction,               &KMyMoneyApp::slotExecuteAction},
1995             {Action::EditSplits,                    &KMyMoneyApp::slotExecuteAction},
1996             {Action::SelectAllTransactions,         &KMyMoneyApp::slotExecuteAction},
1997             {Action::DeleteTransaction,             &KMyMoneyApp::slotDeleteTransactions},
1998             {Action::DuplicateTransaction,          &KMyMoneyApp::slotDuplicateTransactions},
1999             {Action::AddReversingTransaction,       &KMyMoneyApp::slotDuplicateTransactions},
2000         {Action::DisplayTransactionDetails,     &KMyMoneyApp::slotDisplayTransactionDetails},
2001             {Action::CopySplits,                    &KMyMoneyApp::slotCopySplits},
2002             {Action::MarkCleared,                   &KMyMoneyApp::slotMarkTransactions},
2003             {Action::MarkReconciled,                &KMyMoneyApp::slotMarkTransactions},
2004             {Action::MarkNotReconciled,             &KMyMoneyApp::slotMarkTransactions},
2005             {Action::ToggleReconciliationFlag,      &KMyMoneyApp::slotMarkTransactions},
2006             {Action::MoveTransactionTo,             &KMyMoneyApp::slotMoveTransactionTo},
2007             {Action::MatchTransaction,              &KMyMoneyApp::slotMatchTransaction},
2008             {Action::AcceptTransaction,             &KMyMoneyApp::slotAcceptTransaction},
2009             {Action::ShowTransaction,               &KMyMoneyApp::slotExecuteAction},
2010 
2011             {Action::StartReconciliation,           &KMyMoneyApp::slotStartReconciliation},
2012             {Action::FinishReconciliation,          &KMyMoneyApp::slotExecuteAction},
2013             {Action::PostponeReconciliation,        &KMyMoneyApp::slotExecuteAction},
2014             {Action::CancelReconciliation,          &KMyMoneyApp::slotExecuteAction},
2015             {Action::ReconciliationReport,          &KMyMoneyApp::slotReportReconciliation},
2016 
2017             {Action::NewScheduledTransaction,       &KMyMoneyApp::slotCreateScheduledTransaction},
2018 
2019             {Action::GoToPayee,                     &KMyMoneyApp::slotExecuteActionWithData},
2020             {Action::GoToAccount,                   &KMyMoneyApp::slotExecuteActionWithData},
2021             {Action::ReportOpen,                    &KMyMoneyApp::slotExecuteActionWithData},
2022             {Action::ReportAccountTransactions,     &KMyMoneyApp::slotExecuteAction},
2023             {Action::ChartAccountBalance,           &KMyMoneyApp::slotExecuteAction},
2024 
2025             {Action::EditFindTransaction,           &KMyMoneyApp::slotFindTransaction},
2026             {Action::MoveToToday,                   &KMyMoneyApp::slotMoveToToday},
2027         };
2028         // clang-format off
2029 
2030         for (auto connection = actionConnections.cbegin(); connection != actionConnections.cend(); ++connection)
2031             connect(lutActions[connection.key()], &QAction::triggered, this, connection.value());
2032     }
2033 
2034     {
2035         // struct for adding tooltips to actions
2036         struct actionInfo {
2037             Action  action;
2038             QString tip;
2039         };
2040 
2041         // clang-format off
2042         const QVector<actionInfo> actionInfos {
2043             {Action::StartReconciliation,        i18nc("@info:tooltip", "Start reconciling the currently selected account")},
2044             {Action::PostponeReconciliation,     i18nc("@info:tooltip", "Postpone the current reconciliation")},
2045             {Action::FinishReconciliation,       i18nc("@info:tooltip", "Finish the current reconciliation")},
2046             {Action::CancelReconciliation,       i18nc("@info:tooltip", "Cancel the current reconciliation")},
2047         };
2048         // clang-format on
2049 
2050         for (const auto& actionInfo : actionInfos) {
2051             lutActions[actionInfo.action]->setToolTip(actionInfo.tip);
2052         }
2053     }
2054 
2055     // *************
2056     // Setting some of added actions checkable
2057     // *************
2058     {
2059         // Some actions are checkable,
2060         // so set them here
2061         const QVector<Action> checkableActions {
2062             Action::ViewTransactionDetail,
2063             Action::ViewHideReconciled,
2064             Action::ViewHideCategories,
2065 #ifdef KMM_DEBUG
2066             Action::DebugTraces,
2067             Action::DebugTimers,
2068 #endif
2069             Action::ViewShowAll
2070         };
2071 
2072         for (const auto& it : checkableActions) {
2073             lutActions[it]->setCheckable(true);
2074             lutActions[it]->setEnabled(true);
2075         }
2076     }
2077 
2078     // *************
2079     // Setting actions that are always enabled
2080     // *************
2081     {
2082         const QVector<eMenu::Action> alwaysEnabled {
2083             Action::GetOnlineHelp,
2084             Action::SettingsAllMessages,
2085             Action::ToolPerformance,
2086             Action::ToolCalculator,
2087         };
2088         for (const auto& action : alwaysEnabled) {
2089             lutActions[action]->setEnabled(true);
2090         }
2091     }
2092 
2093     // *************
2094     // Setting keyboard shortcuts for some of added actions
2095     // *************
2096     //
2097         {
2098         // clang-format off
2099             const QVector<QPair<Action, QKeySequence>> actionShortcuts{
2100             {qMakePair(Action::EditFindTransaction,         Qt::CTRL | Qt::SHIFT | Qt::Key_F)},
2101             {qMakePair(Action::ViewTransactionDetail,       Qt::CTRL | Qt::Key_T)},
2102             {qMakePair(Action::ViewHideReconciled,          Qt::CTRL | Qt::Key_R)},
2103             {qMakePair(Action::ViewHideCategories,          Qt::CTRL | Qt::Key_U)},
2104             {qMakePair(Action::ViewShowAll,                 Qt::CTRL | Qt::SHIFT | Qt::Key_A)},
2105             {qMakePair(Action::StartReconciliation,         Qt::CTRL | Qt::SHIFT | Qt::Key_R)},
2106             {qMakePair(Action::NewTransaction,              Qt::CTRL | Qt::Key_Insert)},
2107             {qMakePair(Action::DuplicateTransaction,        Qt::CTRL | Qt::Key_D)},
2108             {qMakePair(Action::DeleteTransaction,           Qt::CTRL | Qt::Key_Delete)},
2109             {qMakePair(Action::EditTransaction,             Qt::CTRL | Qt::Key_E)},
2110             {qMakePair(Action::EditSplits,                  Qt::CTRL | Qt::SHIFT | Qt::Key_E)},
2111             {qMakePair(Action::CopySplits,                  Qt::CTRL | Qt::SHIFT | Qt::Key_C)},
2112             {qMakePair(Action::AddReversingTransaction,     Qt::CTRL | Qt::SHIFT | Qt::Key_R)},
2113             {qMakePair(Action::AddReversingTransaction,     Qt::CTRL | Qt::SHIFT | Qt::Key_Backspace)},
2114             {qMakePair(Action::ToggleReconciliationFlag,    Qt::CTRL | Qt::Key_Space)},
2115             {qMakePair(Action::MarkCleared,                 Qt::CTRL | Qt::ALT | Qt::Key_Space)},
2116             {qMakePair(Action::MarkNotReconciled,           Qt::CTRL | Qt::SHIFT | Qt::ALT | Qt::Key_Space)},
2117             {qMakePair(Action::MarkReconciled,              Qt::CTRL | Qt::SHIFT | Qt::Key_Space)},
2118             {qMakePair(Action::MoveToToday,                 Qt::CTRL | Qt::SHIFT | Qt::Key_T)},
2119             {qMakePair(Action::GoToPayee,                   Qt::CTRL | Qt::SHIFT | Qt::Key_P)},
2120             {qMakePair(Action::SelectAllTransactions,       Qt::CTRL | Qt::Key_A)},
2121             {qMakePair(Action::EditTabOrder,                Qt::CTRL | Qt::SHIFT | Qt::Key_T)},
2122 #ifdef KMM_DEBUG
2123             {qMakePair(Action::NewFeature,                  Qt::CTRL | Qt::Key_G)},
2124 #endif
2125             {qMakePair(Action::AssignTransactionsNumber,    Qt::CTRL | Qt::SHIFT | Qt::Key_N)},
2126             {qMakePair(Action::ShowFilterWidget,            Qt::CTRL | Qt::Key_F)},
2127             };
2128         // clang-format on
2129 
2130         for (const auto& it : actionShortcuts) {
2131             actionCollection()->setDefaultShortcut(lutActions[it.first], it.second);
2132         }
2133     }
2134 
2135     // *************
2136     // Misc settings
2137     // *************
2138 
2139     // Setup transaction detail switch
2140     lutActions[Action::ViewTransactionDetail]->setChecked(KMyMoneySettings::showRegisterDetailed());
2141     lutActions[Action::ViewHideReconciled]->setChecked(KMyMoneySettings::hideReconciledTransactions());
2142     lutActions[Action::ViewHideCategories]->setChecked(KMyMoneySettings::hideUnusedCategory());
2143     lutActions[Action::ViewShowAll]->setChecked(KMyMoneySettings::showAllAccounts());
2144 
2145     // *************
2146     // Adding actions to ActionCollection
2147     // *************
2148     actionCollection()->addActions(lutActions.values());
2149 
2150     // ************************
2151     // Currently unused actions
2152     // ************************
2153 #if 0
2154     new KToolBarPopupAction(i18n("View back"), "go-previous", 0, this, SLOT(slotShowPreviousView()), actionCollection(), "go_back");
2155     new KToolBarPopupAction(i18n("View forward"), "go-next", 0, this, SLOT(slotShowNextView()), actionCollection(), "go_forward");
2156 
2157     action("go_back")->setEnabled(false);
2158     action("go_forward")->setEnabled(false);
2159 #endif
2160 
2161     // use the absolute path to your kmymoneyui.rc file for testing purpose in createGUI();
2162     setupGUI();
2163 
2164     // reconnect about app entry to dialog with full credits information
2165     auto aboutApp = aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::AboutApp)));
2166     aboutApp->disconnect();
2167     connect(aboutApp, &QAction::triggered, this, &KMyMoneyApp::slotShowCredits);
2168 
2169     QMenu *menuContainer;
2170     menuContainer = static_cast<QMenu*>(factory()->container(QStringLiteral("import"), this));
2171     menuContainer->setIcon(Icons::get(Icon::DocumentImport));
2172 
2173     menuContainer = static_cast<QMenu*>(factory()->container(QStringLiteral("export"), this));
2174     menuContainer->setIcon(Icons::get(Icon::DocumentExport));
2175 
2176     return lutActions;
2177 }
2178 
2179 void KMyMoneyApp::slotAddSharedAction(eMenu::Action action, QAction* defaultAction)
2180 {
2181     auto toolButton = d->m_sharedActionButtons.value(action).button;
2182     if (toolButton == nullptr) {
2183         auto actionObject = pActions.value(action, nullptr);
2184         if (actionObject) {
2185             for (auto* widget : actionObject->associatedWidgets()) {
2186                 toolButton = qobject_cast<QToolButton*>(widget);
2187                 if (toolButton) {
2188                     d->m_sharedActionButtons[action].button = toolButton;
2189                     d->m_sharedActionButtons[action].defaultAction = actionObject;
2190                     break;
2191                 }
2192             }
2193         }
2194     }
2195 
2196     if (toolButton) {
2197         auto currentAction = toolButton->defaultAction();
2198         toolButton->setDefaultAction(defaultAction);
2199         toolButton->setDefaultAction(currentAction);
2200     }
2201 }
2202 
2203 #ifdef KMM_DEBUG
2204 void KMyMoneyApp::dumpActions() const
2205 {
2206     const QList<QAction*> list = actionCollection()->actions();
2207     for (const auto& it : list)
2208         std::cout << qPrintable(it->objectName()) << ": " << qPrintable(it->text()) << std::endl;
2209 }
2210 #endif
2211 
2212 bool KMyMoneyApp::isActionToggled(const Action _a)
2213 {
2214     return pActions[_a]->isChecked();
2215 }
2216 
2217 void KMyMoneyApp::initStatusBar()
2218 {
2219     ///////////////////////////////////////////////////////////////////
2220     // STATUSBAR
2221 
2222     d->m_statusLabel = new QLabel;
2223     statusBar()->addWidget(d->m_statusLabel);
2224     ready();
2225 
2226     // Initialization of progress bar taken from KDevelop ;-)
2227     d->m_progressBar = new QProgressBar;
2228     statusBar()->addWidget(d->m_progressBar);
2229     d->m_progressBar->setFixedHeight(d->m_progressBar->sizeHint().height() - 8);
2230 
2231     // hide the progress bar for now
2232     slotStatusProgressBar(-1, -1);
2233 }
2234 
2235 void KMyMoneyApp::initIcons()
2236 {
2237     qDebug() << "System icon theme as reported by QT: " << QIcon::themeName();
2238 
2239     auto themeName = KMyMoneySettings::iconsTheme();
2240     qDebug() << "App icon theme as configured in KMyMoney: " << themeName;
2241 
2242 #if defined(Q_OS_WIN)
2243     // @todo add support for dark icons, e.g. https://www.thetopsites.net/article/51334674.shtml
2244 
2245     themeName = QStringLiteral("breeze");                      // only breeze is available for craft packages
2246     qDebug() << "Running under Windows, so will be forcing the icon theme to: " << themeName;
2247 #elif defined(Q_OS_MACOS)
2248     constexpr int OSX_LIGHT_MODE = 236;
2249 
2250     auto bg = palette().color(QPalette::Active, QPalette::Window);
2251 
2252     if (bg.lightness() == OSX_LIGHT_MODE) {
2253         themeName = QStringLiteral("breeze");
2254         qDebug() << "Detected macOS light mode, so will be forcing the icon theme to: " << themeName;
2255     }
2256     else {
2257         themeName = QStringLiteral("breeze-dark");
2258         qDebug() << "Detected macOS dark mode, so will be forcing the icon theme to: " << themeName;
2259     }
2260 #endif
2261 
2262     // if it isn't default theme then set it
2263     if (!themeName.isEmpty() && themeName != QStringLiteral("system")) {
2264         QIcon::setThemeName(themeName);
2265         qDebug() << "Setting icon theme to: " << themeName;
2266     }
2267     else {
2268         themeName = QIcon::themeName();
2269         qDebug() << "Obeying the system-wide icon theme, currently set to: " << themeName;
2270     }
2271 }
2272 
2273 void KMyMoneyApp::saveOptions()
2274 {
2275     KConfigGroup grp = d->m_config->group("General Options");
2276     grp.writeEntry("Geometry", size());
2277 
2278     grp.writeEntry("Show Statusbar", actionCollection()->action(KStandardAction::name(KStandardAction::ShowStatusbar))->isChecked());
2279 
2280     KConfigGroup toolbarGrp = d->m_config->group("mainToolBar");
2281     toolBar("mainToolBar")->saveSettings(toolbarGrp);
2282 
2283     d->m_recentFiles->saveEntries(d->m_config->group("Recent Files"));
2284 
2285 }
2286 
2287 
2288 void KMyMoneyApp::readOptions()
2289 {
2290     KConfigGroup grp = d->m_config->group("General Options");
2291 
2292 
2293     pActions[Action::ViewHideReconciled]->setChecked(KMyMoneySettings::hideReconciledTransactions());
2294     pActions[Action::ViewHideCategories]->setChecked(KMyMoneySettings::hideUnusedCategory());
2295 
2296     d->m_recentFiles->loadEntries(d->m_config->group("Recent Files"));
2297 
2298     // Startdialog is written in the settings dialog
2299     d->m_startDialog = grp.readEntry("StartDialog", true);
2300 }
2301 
2302 #ifdef KMM_DEBUG
2303 void KMyMoneyApp::resizeEvent(QResizeEvent* ev)
2304 {
2305     KMainWindow::resizeEvent(ev);
2306     d->updateCaption();
2307 }
2308 #endif
2309 
2310 bool KMyMoneyApp::queryClose()
2311 {
2312     if (!isReady())
2313         return false;
2314 
2315     if (!slotFileClose())
2316         return false;
2317 
2318     saveOptions();
2319     qApp->quit();
2320     return true;
2321 }
2322 
2323 /////////////////////////////////////////////////////////////////////
2324 // SLOT IMPLEMENTATION
2325 /////////////////////////////////////////////////////////////////////
2326 void KMyMoneyApp::slotFileInfoDialog()
2327 {
2328     QPointer<KMyMoneyFileInfoDlg> dlg = new KMyMoneyFileInfoDlg(0);
2329     dlg->exec();
2330     delete dlg;
2331 }
2332 
2333 void KMyMoneyApp::slotPerformanceTest()
2334 {
2335     // dump performance report to stderr
2336 
2337     int measurement[2];
2338     QElapsedTimer timer;
2339     MyMoneyAccount acc;
2340 
2341     qDebug("--- Starting performance tests ---");
2342 
2343     // AccountList
2344 //  MyMoneyFile::instance()->preloadCache();
2345     measurement[0] = measurement[1] = 0;
2346     timer.start();
2347     for (int i = 0; i < 1000; ++i) {
2348         QList<MyMoneyAccount> list;
2349 
2350         MyMoneyFile::instance()->accountList(list);
2351         measurement[i != 0] = timer.elapsed();
2352     }
2353     std::cerr << "accountList()" << std::endl;
2354     std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
2355     std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl;
2356     std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
2357 
2358     // Balance of asset account(s)
2359 //  MyMoneyFile::instance()->preloadCache();
2360     measurement[0] = measurement[1] = 0;
2361     acc = MyMoneyFile::instance()->asset();
2362     for (int i = 0; i < 1000; ++i) {
2363         timer.start();
2364         MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id());
2365         measurement[i != 0] += timer.elapsed();
2366     }
2367     std::cerr << "balance(Asset)" << std::endl;
2368     std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
2369     std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
2370 
2371     // total balance of asset account
2372 //  MyMoneyFile::instance()->preloadCache();
2373     measurement[0] = measurement[1] = 0;
2374     acc = MyMoneyFile::instance()->asset();
2375     for (int i = 0; i < 1000; ++i) {
2376         timer.start();
2377         MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id());
2378         measurement[i != 0] += timer.elapsed();
2379     }
2380     std::cerr << "totalBalance(Asset)" << std::endl;
2381     std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
2382     std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
2383 
2384     // Balance of expense account(s)
2385 //  MyMoneyFile::instance()->preloadCache();
2386     measurement[0] = measurement[1] = 0;
2387     acc = MyMoneyFile::instance()->expense();
2388     for (int i = 0; i < 1000; ++i) {
2389         timer.start();
2390         MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id());
2391         measurement[i != 0] += timer.elapsed();
2392     }
2393     std::cerr << "balance(Expense)" << std::endl;
2394     std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
2395     std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
2396 
2397     // total balance of expense account
2398 //  MyMoneyFile::instance()->preloadCache();
2399     measurement[0] = measurement[1] = 0;
2400     acc = MyMoneyFile::instance()->expense();
2401     timer.start();
2402     for (int i = 0; i < 1000; ++i) {
2403         MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id());
2404         measurement[i != 0] = timer.elapsed();
2405     }
2406     std::cerr << "totalBalance(Expense)" << std::endl;
2407     std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
2408     std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl;
2409     std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
2410 
2411     // transaction list
2412 //  MyMoneyFile::instance()->preloadCache();
2413     measurement[0] = measurement[1] = 0;
2414     if (MyMoneyFile::instance()->asset().accountCount()) {
2415         MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]);
2416         filter.setDateFilter(QDate(), QDate::currentDate());
2417         QList<MyMoneyTransaction> list;
2418 
2419         timer.start();
2420         for (int i = 0; i < 100; ++i) {
2421             MyMoneyFile::instance()->transactionList(list, filter);
2422             measurement[i != 0] = timer.elapsed();
2423         }
2424         std::cerr << "transactionList()" << std::endl;
2425         std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
2426         std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl;
2427         std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl;
2428     }
2429 
2430     // transaction list
2431 //  MyMoneyFile::instance()->preloadCache();
2432     measurement[0] = measurement[1] = 0;
2433     if (MyMoneyFile::instance()->asset().accountCount()) {
2434         MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]);
2435         filter.setDateFilter(QDate(), QDate::currentDate());
2436         QList<MyMoneyTransaction> list;
2437 
2438         timer.start();
2439         for (int i = 0; i < 100; ++i) {
2440             MyMoneyFile::instance()->transactionList(list, filter);
2441             measurement[i != 0] = timer.elapsed();
2442         }
2443         std::cerr << "transactionList(list)" << std::endl;
2444         std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
2445         std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl;
2446         std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl;
2447     }
2448 //  MyMoneyFile::instance()->preloadCache();
2449 }
2450 
2451 bool KMyMoneyApp::isDatabase()
2452 {
2453     return (d->m_storageInfo.isOpened && ((d->m_storageInfo.type == eKMyMoney::StorageType::SQL)));
2454 }
2455 
2456 bool KMyMoneyApp::isNativeFile()
2457 {
2458     return (d->m_storageInfo.isOpened && (d->m_storageInfo.type == eKMyMoney::StorageType::SQL || d->m_storageInfo.type == eKMyMoney::StorageType::XML));
2459 }
2460 
2461 bool KMyMoneyApp::fileOpen() const
2462 {
2463     return d->m_storageInfo.isOpened;
2464 }
2465 
2466 KMyMoneyAppCallback KMyMoneyApp::progressCallback()
2467 {
2468     return &KMyMoneyApp::progressCallback;
2469 }
2470 
2471 void KMyMoneyApp::consistencyCheck(bool alwaysDisplayResult)
2472 {
2473     d->consistencyCheck(alwaysDisplayResult);
2474 }
2475 
2476 bool KMyMoneyApp::isImportableFile(const QUrl &url)
2477 {
2478     bool result = false;
2479 
2480     // Iterate through the plugins and see if there's a loaded plugin who can handle it
2481     QMap<QString, KMyMoneyPlugin::ImporterPlugin*>::const_iterator it_plugin = pPlugins.importer.constBegin();
2482     while (it_plugin != pPlugins.importer.constEnd()) {
2483         if ((*it_plugin)->isMyFormat(url.toLocalFile())) {
2484             result = true;
2485             break;
2486         }
2487         ++it_plugin;
2488     }
2489 
2490     // If we did not find a match, try importing it as a KMM statement file,
2491     // which is really just for testing.  the statement file is not exposed
2492     // to users.
2493     if (it_plugin == pPlugins.importer.constEnd())
2494         if (MyMoneyStatement::isStatementFile(url.path()))
2495             result = true;
2496 
2497     // Place code here to test for QIF and other locally-supported formats
2498     // (i.e. not a plugin). If you add them here, be sure to add it to
2499     // the webConnect function.
2500 
2501     return result;
2502 }
2503 
2504 bool KMyMoneyApp::isFileOpenedInAnotherInstance(const QUrl &url)
2505 {
2506     const auto instances = instanceList();
2507 #ifdef KMM_DBUS
2508     // check if there are other instances which might have this file open
2509     for (const auto& instance : instances) {
2510         QDBusInterface remoteApp(instance, "/KMymoney", "org.kde.kmymoney");
2511         QDBusReply<QString> reply = remoteApp.call("filename");
2512         if (!reply.isValid())
2513             qDebug("D-Bus error while calling app->filename()");
2514         else if (reply.value() == url.url())
2515             return true;
2516     }
2517 #else
2518     Q_UNUSED(url)
2519 #endif
2520     return false;
2521 }
2522 
2523 void KMyMoneyApp::slotShowTransactionDetail()
2524 {
2525     const auto show = pActions[Action::ViewTransactionDetail]->isChecked();
2526     // make persistent
2527     KMyMoneySettings::setShowRegisterDetailed(show);
2528     // and inform the views
2529     LedgerViewSettings::instance()->setShowTransactionDetails(show);
2530 }
2531 
2532 void KMyMoneyApp::slotDeleteTransactions()
2533 {
2534     const auto file = MyMoneyFile::instance();
2535 
2536     // since we may jump here via code, we have to make sure to react only
2537     // if the action is enabled
2538     if (!pActions[Action::DeleteTransaction]->isEnabled())
2539         return;
2540 
2541     if (d->m_selections.selection(SelectedObjects::JournalEntry).isEmpty())
2542         return;
2543 
2544     if (MyMoneyUtils::transactionWarnLevel(d->m_selections.selection(SelectedObjects::JournalEntry)) == OneSplitReconciled) {
2545         if (KMessageBox::warningContinueCancel(this,
2546             i18n("At least one split of the selected transactions has been reconciled. "
2547             "Do you wish to delete the transactions anyway?"),
2548             i18n("Transaction already reconciled")) == KMessageBox::Cancel)
2549             return;
2550 
2551     } else {
2552         auto msg =
2553         i18np("Do you really want to delete the selected transaction?",
2554             "Do you really want to delete all %1 selected transactions?",
2555             d->m_selections.selection(SelectedObjects::JournalEntry).count());
2556 
2557         if (KMessageBox::questionTwoActions(this, msg, i18n("Delete transaction"), KMMYesNo::yes(), KMMYesNo::no()) == KMessageBox::SecondaryAction) {
2558             return;
2559         }
2560     }
2561 
2562     MyMoneyFileTransaction ft;
2563     for (const auto& journalEntryId : d->m_selections.selection(SelectedObjects::JournalEntry)) {
2564         const auto journalEntry = file->journalModel()->itemById(journalEntryId);
2565         if (!journalEntry.id().isEmpty()) {
2566             if (!journalEntry.transaction().id().isEmpty()) {
2567                 if (!file->referencesClosedAccount(journalEntry.transaction())) {
2568                     file->removeTransaction(journalEntry.transaction());
2569                 }
2570             }
2571         }
2572     }
2573     ft.commit();
2574 }
2575 
2576 void KMyMoneyApp::slotDuplicateTransactions()
2577 {
2578     auto action = qobject_cast<QAction*>(sender());
2579     const auto actionId = d->qActionToId(action);
2580 
2581     const auto reverse = (actionId == eMenu::Action::AddReversingTransaction);
2582     const auto accountId = d->m_selections.firstSelection(SelectedObjects::Account);
2583 
2584     if (d->m_selections.selection(SelectedObjects::JournalEntry).isEmpty()
2585         || accountId.isEmpty())
2586         return;
2587 
2588     MyMoneyFileTransaction ft;
2589     QString lastAddedTransactionId;
2590     const auto file = MyMoneyFile::instance();
2591 
2592     try {
2593         for (const auto& journalEntryId : d->m_selections.selection(SelectedObjects::JournalEntry)) {
2594             const auto journalEntry = file->journalModel()->itemById(journalEntryId);
2595             if (!journalEntry.id().isEmpty()) {
2596                 auto t = journalEntry.transaction();
2597                 if (!t.id().isEmpty()) {
2598                     // wipe out any reconciliation information
2599                     for (auto& split : t.splits()) {
2600                         split.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
2601                         split.setReconcileDate(QDate());
2602                         split.setBankID(QString());
2603                         split.removeMatch();
2604                     }
2605                     // clear invalid data
2606                     t.setEntryDate(QDate());
2607                     t.clearId();
2608 
2609                     if (reverse)
2610                         // reverse transaction
2611                         t.reverse();
2612                     else
2613                         // set the post date to today
2614                         t.setPostDate(QDate::currentDate());
2615 
2616                     file->addTransaction(t);
2617                     lastAddedTransactionId = t.id();
2618                 }
2619             }
2620         }
2621         ft.commit();
2622 
2623         // select the new transaction in the ledger
2624         auto selections = d->m_selections;
2625         const auto indeces = file->journalModel()->indexesByTransactionId(lastAddedTransactionId);
2626         for (const auto& idx : indeces) {
2627             if (idx.data(eMyMoney::Model::JournalSplitAccountIdRole).toString() == accountId) {
2628                 selections.setSelection(SelectedObjects::JournalEntry, idx.data(eMyMoney::Model::IdRole).toString());
2629             }
2630         }
2631         d->m_myMoneyView->executeAction(eMenu::Action::OpenAccount, selections);
2632 
2633     } catch (const MyMoneyException &e) {
2634         KMessageBox::detailedError(this, i18n("Unable to duplicate transaction(s)"), QString::fromLatin1(e.what()));
2635     }
2636 }
2637 
2638 void KMyMoneyApp::slotDisplayTransactionDetails()
2639 {
2640     // Display popup with full transaction details, since some are never displayed in the UI
2641     const auto file = MyMoneyFile::instance();
2642     QString head =
2643         "<html><head><style>table, tr, th, td {border: 1px solid black; border-collapse: collapse; padding: 4px} "
2644         ".spec {border-bottom-width: 3px}"
2645         "</style></head><body>";
2646     QString trans;
2647     QString kvpt;
2648     QString splits;
2649     QString kvps;
2650     for (const auto& journalEntryId : d->m_selections.selection(SelectedObjects::JournalEntry)) {
2651         const auto journalEntry = file->journalModel()->itemById(journalEntryId);
2652         if (!journalEntry.id().isEmpty()) {
2653             const auto t = journalEntry.transaction();
2654             const auto commodity = t.commodity();
2655             const auto safraction = MyMoneyFile::instance()->security(commodity).smallestAccountFraction();
2656             if (!t.id().isEmpty()) {
2657                 head += "<H1>Transaction: " + t.id() + "</H1>";
2658                 // transaction
2659                 trans = "<table>";
2660                 trans += "<tr><th>Entry date</th><td>" + t.entryDate().toString(Qt::ISODate) + "</td></tr>";
2661                 trans += "<tr><th>Post date</th><td>" + t.postDate().toString(Qt::ISODate) + "</td></tr>";
2662                 trans += "<tr><th>Currency</th><td>" + t.commodity() + "</td></tr>";
2663                 trans += "<tr><th>Memo</th><td>" + t.memo() + "</td></tr>";
2664                 trans += "</table>";
2665                 // Transaction KVPs
2666                 if (t.pairs().count() > 0) {
2667                     kvpt = "<p><b>Key Value Pairs</b></p><table><tr><th>Key</th><th>Value</th></tr>";
2668                     for (auto it = t.pairs().cbegin(); it != t.pairs().cend(); ++it) {
2669                         kvpt += "<tr><th>" + it.key() + "</th><th>" + it.value() + "</th></tr>";
2670                     }
2671                     kvpt += "</table><br/><br/>";
2672                 } else {
2673                     kvpt = "<br/>";
2674                 }
2675                 // splits: [id] payee account reconcileFlag reconcileDate action shares price value memo costCenter tagList number
2676                 splits =
2677                     "<table>"
2678                     "<tr><th>Split</th><th>Number</th><th>Payee</th><th>Account</th><th>_CR</th><th>ReconcileDate</th>"
2679                     "<th>Action</th><th>Shares</th><th>Price</th><th>Value</th>"
2680                     "<th>BankID</th></th></tr>"
2681                     "<tr><th>&nbsp;</th><th colspan=\"10\" align=\"left\">Memo</th></tr>"
2682                     "<tr><th>&nbsp;</th><th colspan=\"10\" align=\"left\">Tags</th></tr>";
2683                 for (const auto& split : t.splits()) {
2684                     // split: id payee account reconcileflag reconciledate activity shares price value memo
2685                     const auto splitAccount = MyMoneyFile::instance()->account(split.accountId());
2686                     const auto pairs = splitAccount.pairs();
2687                     const auto accopen = !splitAccount.isClosed();
2688                     const auto splitSymbol = splitAccount.accountType() == eMyMoney::Account::Type::Stock
2689                         ? MyMoneyFile::instance()->security(splitAccount.currencyId()).tradingSymbol()
2690                         : splitAccount.currencyId();
2691                     splits += "<tr></tr><tr><td>" + split.id() + "</td><td>";
2692                     splits += split.number() + "</td><td>";
2693                     splits += split.payeeId() + "<br/>" + MyMoneyFile::instance()->payee(split.payeeId()).name() + "</td><td>";
2694                     if (accopen == true) {
2695                         splits += split.accountId();
2696                     } else {
2697                         splits += "<s>" + split.accountId() + "</s> (Closed)";
2698                     }
2699                     splits += "<br/>" + splitAccount.name() + "</td><td>" + KMyMoneyUtils::reconcileStateToString(split.reconcileFlag());
2700                     splits += "</td><td>" + split.reconcileDate().toString(Qt::ISODate) + "</td><td>" + split.action() + "</td><td>";
2701                     splits += split.shares().formatMoney(splitAccount.fraction(), true) + "<br/>(" + splitSymbol + " ";
2702                     splits += QString::number(splitAccount.fraction()) + ")</td><td>";
2703                     splits +=
2704                         split.price().formatMoney(safraction > splitAccount.fraction() ? safraction / splitAccount.fraction() : splitAccount.fraction(), true);
2705                     splits += "</td><td>" + split.value().formatMoney(safraction, true) + "<br/>(" + t.commodity() + " " + QString::number(safraction);
2706                     splits += ")</td><td>" + split.bankID() + "</td></tr><tr><td>&nbsp;</td><td colspan=\"10\">" + split.memo() + "</td></tr>";
2707 
2708                     // collect tags and add them to the view
2709                     const auto tagIdList = split.tagIdList();
2710                     QString tagList;
2711                     for (const auto& tagId : qAsConst(tagIdList)) {
2712                         if (!tagList.isEmpty()) {
2713                             tagList += QLatin1String(", ");
2714                         }
2715                         const auto tag = MyMoneyFile::instance()->tag(tagId);
2716                         tagList += tag.name().simplified();
2717                     }
2718                     splits += "<tr><td>&nbsp;</td><td colspan=\"10\">" + tagList + "</td></tr>";
2719 
2720                     if (split.pairs().count() > 0) {
2721                         splits += "<tr><th>KVP</th><th colspan=\"2\">Key</th><th colspan=\"8\">Value</th></tr>";
2722                         for (auto it = split.pairs().cbegin(); it != split.pairs().cend(); ++it) {
2723                             splits += "<tr><td></td><td colspan=\"2\">" + it.key() + "</td><td colspan=\"8\">" + it.value() + "</td></tr>";
2724                         }
2725                     }
2726                 }
2727                 splits +=
2728                     "</table><p align=\"center\">The Value column uses the currency of the transaction, which may not be the currency of the account "
2729                     "of the split.<br/>Shown in parentheses are the currency or trading symbol of the split and the fraction used to display value.</p>";
2730             } else {
2731                 head += "<p>There is no selected Transaction.</p>";
2732             }
2733         } else {
2734             head += "<p>There appears to be nothing selected.</p>";
2735         }
2736         QString foot = "</body></html>";
2737         QString all = head + trans + kvpt + splits + foot;
2738 
2739         QDialog* txInfo = new QDialog;
2740         txInfo->setMinimumSize(700, 450);
2741         QTextBrowser* text = new QTextBrowser();
2742         text->setHtml(all);
2743         QPushButton* doneButton = new QPushButton("Done");
2744         doneButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
2745         connect(doneButton, SIGNAL(clicked()), txInfo, SLOT(accept()));
2746         QVBoxLayout* layout = new QVBoxLayout(txInfo);
2747         layout->addWidget(text);
2748         layout->addWidget(doneButton);
2749         layout->setAlignment(doneButton, Qt::AlignHCenter);
2750         txInfo->exec();
2751     }
2752 }
2753 
2754 void KMyMoneyApp::slotCopySplits()
2755 {
2756     const auto file = MyMoneyFile::instance();
2757     const auto accountId = d->m_selections.firstSelection(SelectedObjects::Account);
2758 
2759     if (!accountId.isEmpty() && (d->m_selections.selection(SelectedObjects::JournalEntry).count() >= 2)) {
2760         int singleSplitTransactions = 0;
2761         int multipleSplitTransactions = 0;
2762         MyMoneyTransaction selectedSourceTransaction;
2763 
2764         QString selectedSourceSplitId;
2765         for (const auto& journalEntryId : d->m_selections.selection(SelectedObjects::JournalEntry)) {
2766             const auto journalEntry = file->journalModel()->itemById(journalEntryId);
2767             if (!journalEntry.id().isEmpty()) {
2768                 const auto t = journalEntry.transaction();
2769                 switch (t.splitCount()) {
2770                     case 0:
2771                         break;
2772                     case 1:
2773                         singleSplitTransactions++;
2774                         break;
2775                     default:
2776                         selectedSourceTransaction = t;
2777                         selectedSourceSplitId = journalEntry.split().id();
2778                         multipleSplitTransactions++;
2779                         break;
2780                 }
2781             }
2782         }
2783         if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) {
2784             MyMoneyFileTransaction ft;
2785             try {
2786                 // remember the splitId that is used in the source transaction
2787                 // as the link
2788                 for (const auto& journalEntryId : d->m_selections.selection(SelectedObjects::JournalEntry)) {
2789                     const auto journalEntry = file->journalModel()->itemById(journalEntryId);
2790                     if (!journalEntry.id().isEmpty()) {
2791                         auto t = journalEntry.transaction();
2792                         // don't process the source transaction
2793                         if (selectedSourceTransaction.id() == t.id()) {
2794                             continue;
2795                         }
2796 
2797                         if (!t.id().isEmpty() && (t.splitCount() == 1)) {
2798                             // keep a copy of that one and only split
2799                             const auto baseSplit = t.splits().first();
2800 
2801                             for (const auto& split : selectedSourceTransaction.splits()) {
2802                                 // Don't copy the source split, as we already have that
2803                                 // as part of the destination transaction
2804                                 if (split.id() == selectedSourceSplitId) {
2805                                     continue;
2806                                 }
2807 
2808                                 MyMoneySplit sp(split);
2809                                 // clear the ID, reconciliation state, match information and data stored in KVP
2810                                 sp.clearId();
2811                                 sp.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
2812                                 sp.setReconcileDate(QDate());
2813                                 sp.removeMatch();
2814                                 sp.clear();
2815 
2816                                 // in case it is a simple transaction consisting of two splits,
2817                                 // we can adjust the share and value part of the second split we
2818                                 // just created. We need to keep a possible price in mind in case
2819                                 // of different currencies
2820                                 if (selectedSourceTransaction.splitCount() == 2) {
2821                                     sp.setValue(-baseSplit.value());
2822                                     sp.setShares(-(baseSplit.shares() * baseSplit.price()));
2823                                 }
2824                                 t.addSplit(sp);
2825                             }
2826                             // check if we need to add/update a VAT assignment
2827                             file->updateVAT(t);
2828 
2829                             // and store the modified transaction
2830                             file->modifyTransaction(t);
2831                         }
2832                     }
2833                 }
2834                 ft.commit();
2835             } catch (const MyMoneyException &) {
2836                 qDebug() << "transactionCopySplits() failed";
2837             }
2838         }
2839     }
2840 }
2841 
2842 void KMyMoneyApp::slotMarkTransactions()
2843 {
2844     auto action = qobject_cast<QAction*>(sender());
2845     const auto actionId = d->qActionToId(action);
2846     const auto file = MyMoneyFile::instance();
2847 
2848     static const QHash<eMenu::Action, eMyMoney::Split::State> action2state = {
2849         {eMenu::Action::MarkNotReconciled, eMyMoney::Split::State::NotReconciled},
2850         {eMenu::Action::MarkCleared, eMyMoney::Split::State::Cleared},
2851         {eMenu::Action::MarkReconciled, eMyMoney::Split::State::Reconciled},
2852         {eMenu::Action::ToggleReconciliationFlag, eMyMoney::Split::State::Unknown},
2853     };
2854 
2855     const auto flag = action2state.value(actionId, eMyMoney::Split::State::Unknown);
2856     if (actionId == eMenu::Action::None) {
2857         return;
2858     }
2859 
2860     auto cnt = d->m_selections.selection(SelectedObjects::JournalEntry).count();
2861 
2862     MyMoneyFileTransaction ft;
2863     try {
2864         const auto isReconciliationMode = !d->m_selections.firstSelection(SelectedObjects::ReconciliationAccount).isEmpty();
2865         for (const auto& journalEntryId : d->m_selections.selection(SelectedObjects::JournalEntry)) {
2866             // turn on signals before we modify the last entry in the list
2867             cnt--;
2868             MyMoneyFile::instance()->blockSignals(cnt != 0);
2869 
2870             // get a fresh copy
2871             auto journalEntry = file->journalModel()->itemById(journalEntryId);
2872             if (!journalEntry.id().isEmpty()) {
2873                 auto t = journalEntry.transaction();
2874                 auto sp = journalEntry.split();
2875                 if (sp.reconcileFlag() != flag) {
2876                     if (flag == eMyMoney::Split::State::Unknown) {
2877                         if (!isReconciliationMode) {
2878                             // in normal mode we cycle through all states
2879                             // except when reconciled transactions are hidden
2880                             switch (sp.reconcileFlag()) {
2881                                 case eMyMoney::Split::State::NotReconciled:
2882                                     sp.setReconcileFlag(eMyMoney::Split::State::Cleared);
2883                                     break;
2884                                 case eMyMoney::Split::State::Cleared:
2885                                     sp.setReconcileFlag(KMyMoneySettings::hideReconciledTransactions() ? eMyMoney::Split::State::NotReconciled
2886                                                                                                        : eMyMoney::Split::State::Reconciled);
2887                                     t.setImported(false);
2888                                     break;
2889                                 case eMyMoney::Split::State::Reconciled:
2890                                     sp.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
2891                                     break;
2892                                 default:
2893                                     break;
2894                             }
2895                         } else {
2896                             // in reconciliation mode we skip the reconciled state
2897                             switch (sp.reconcileFlag()) {
2898                                 case eMyMoney::Split::State::NotReconciled:
2899                                     sp.setReconcileFlag(eMyMoney::Split::State::Cleared);
2900                                     break;
2901                                 case eMyMoney::Split::State::Cleared:
2902                                     sp.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
2903                                     break;
2904                                 default:
2905                                     break;
2906                             }
2907                         }
2908                     } else {
2909                         sp.setReconcileFlag(flag);
2910                     }
2911 
2912                     t.modifySplit(sp);
2913                     t.setImported(false);
2914                     MyMoneyFile::instance()->modifyTransaction(t);
2915                 }
2916             }
2917         }
2918         ft.commit();
2919     } catch (const MyMoneyException &e) {
2920         KMessageBox::detailedError(this, i18n("Unable to modify transaction"), e.what());
2921     }
2922 }
2923 
2924 void KMyMoneyApp::slotMoveTransactionTo()
2925 {
2926     auto action = qobject_cast<QAction*>(sender());
2927     // const auto actionId = d->qActionToId(action);
2928     const auto file = MyMoneyFile::instance();
2929     const auto accountId = action->data().toString();
2930     const auto journalEntryList = d->m_selections.selection(SelectedObjects::JournalEntry);
2931 
2932     if (!journalEntryList.isEmpty()) {
2933         MyMoneyFileTransaction ft;
2934         try {
2935             for (const auto& journalId : journalEntryList) {
2936                 const auto journalIdx = file->journalModel()->indexById(journalId);
2937                 auto t = file->transaction(journalIdx.data(eMyMoney::Model::JournalTransactionIdRole).toString());
2938                 auto s = t.splitById(journalIdx.data(eMyMoney::Model::JournalSplitIdRole).toString());
2939                 const auto acc = file->accountsModel()->itemById(s.accountId());
2940                 if (acc.isInvest()) {
2941                     /// moving an investment transactions must make sure that the
2942                     //  necessary (security) accounts exist before the transaction is moved
2943                     auto toInvAcc = file->account(accountId);
2944                     // first determine which stock we are dealing with.
2945                     // fortunately, investment transactions have only one stock involved
2946                     QString stockAccountId;
2947                     QString stockSecurityId;
2948                     for (const auto& split : t.splits()) {
2949                         stockAccountId = split.accountId();
2950                         stockSecurityId = file->account(stockAccountId).currencyId();
2951                         if (!file->security(stockSecurityId).isCurrency()) {
2952                             s = split;
2953                             break;
2954                         }
2955                     }
2956                     // Now check the target investment account to see if it
2957                     // contains a stock with this id
2958                     QString newStockAccountId;
2959                     for (const auto& sAccountId : toInvAcc.accountList()) {
2960                         if (file->account(sAccountId).currencyId() == stockSecurityId) {
2961                             newStockAccountId = sAccountId;
2962                             break;
2963                         }
2964                     }
2965                     // if it doesn't exist, we need to add it as a copy of the old one
2966                     // no 'copyAccount()' function??
2967                     if (newStockAccountId.isEmpty()) {
2968                         MyMoneyAccount stockAccount = file->account(stockAccountId);
2969                         MyMoneyAccount newStock;
2970                         newStock.setName(stockAccount.name());
2971                         newStock.setNumber(stockAccount.number());
2972                         newStock.setDescription(stockAccount.description());
2973                         newStock.setInstitutionId(stockAccount.institutionId());
2974                         newStock.setOpeningDate(stockAccount.openingDate());
2975                         newStock.setAccountType(stockAccount.accountType());
2976                         newStock.setCurrencyId(stockAccount.currencyId());
2977                         newStock.setClosed(stockAccount.isClosed());
2978                         file->addAccount(newStock, toInvAcc);
2979                         newStockAccountId = newStock.id();
2980                     }
2981 
2982                     // now update the split and the transaction
2983                     s.setAccountId(newStockAccountId);
2984 
2985                 } else {
2986                     s.setAccountId(accountId);
2987                 }
2988                 t.modifySplit(s);
2989                 file->modifyTransaction(t);
2990             }
2991             ft.commit();
2992         } catch (const MyMoneyException& e) {
2993             qDebug() << e.what();
2994         }
2995     }
2996 }
2997 
2998 void KMyMoneyApp::slotMoveToToday()
2999 {
3000     const auto file = MyMoneyFile::instance();
3001 
3002     // since we may jump here via code, we have to make sure to react only
3003     // if the action is enabled
3004     if (!pActions[Action::MoveToToday]->isEnabled())
3005         return;
3006 
3007     if (d->m_selections.selection(SelectedObjects::JournalEntry).isEmpty())
3008         return;
3009 
3010     if (MyMoneyUtils::transactionWarnLevel(d->m_selections.selection(SelectedObjects::JournalEntry)) == OneSplitReconciled) {
3011         if (KMessageBox::warningContinueCancel(this,
3012                                                i18n("At least one split of the selected transactions has been reconciled. "
3013                                                     "Do you wish to change the transactions anyway?"),
3014                                                i18nc("@title:window Warning dialog", "Transaction already reconciled"))
3015             == KMessageBox::Cancel)
3016             return;
3017 
3018     } else {
3019         auto msg = i18np("Do you really want to change the selected transaction?",
3020                          "Do you really want to change all %1 selected transactions?",
3021                          d->m_selections.selection(SelectedObjects::JournalEntry).count());
3022 
3023         if (KMessageBox::questionTwoActions(this, msg, i18nc("@title:window Confirmation dialog", "Change transaction"), KMMYesNo::yes(), KMMYesNo::no())
3024             == KMessageBox::SecondaryAction) {
3025             return;
3026         }
3027     }
3028 
3029     MyMoneyFileTransaction ft;
3030     const QDate today = QDate::currentDate();
3031     for (const auto& journalEntryId : d->m_selections.selection(SelectedObjects::JournalEntry)) {
3032         const auto journalEntry = file->journalModel()->itemById(journalEntryId);
3033         if (!journalEntry.id().isEmpty()) {
3034             if (!journalEntry.transaction().id().isEmpty()) {
3035                 if (!file->referencesClosedAccount(journalEntry.transaction())) {
3036                     MyMoneyTransaction tr = file->transaction(journalEntry.transaction().id());
3037                     tr.setPostDate(today);
3038                     file->modifyTransaction(tr);
3039                 }
3040             }
3041         }
3042     }
3043     ft.commit();
3044 }
3045 
3046 void KMyMoneyApp::slotMatchTransaction()
3047 {
3048     auto action = qobject_cast<KDualAction*>(sender());
3049     if (action) {
3050         action->isActive() ? d->matchTransaction() : d->unmatchTransaction();
3051         d->updateActions(d->m_selections);
3052     }
3053 
3054 }
3055 
3056 void KMyMoneyApp::slotCreateScheduledTransaction()
3057 {
3058     if (d->m_selections.selection(SelectedObjects::JournalEntry).count() == 1) {
3059         const auto journalEntryId = d->m_selections.firstSelection(SelectedObjects::JournalEntry);
3060         const auto journalEntry = MyMoneyFile::instance()->journalModel()->itemById(journalEntryId);
3061         // make sure to have the current selected split as first split in the schedule
3062         MyMoneyTransaction t = journalEntry.transaction();
3063         MyMoneySplit s = journalEntry.split();
3064         const auto splitId = s.id();
3065         s.clearId();
3066         s.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
3067         s.setReconcileDate(QDate());
3068         t.removeSplits();
3069         t.setImported(false);
3070         t.addSplit(s);
3071         for (const auto& split : journalEntry.transaction().splits()) {
3072             if (split.id() != splitId) {
3073                 auto s0 = split;
3074                 s0.clearId();
3075                 s0.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
3076                 s0.setReconcileDate(QDate());
3077                 t.addSplit(s0);
3078             }
3079         }
3080         KEditScheduleDlg::newSchedule(t, eMyMoney::Schedule::Occurrence::Monthly);
3081     }
3082 }
3083 
3084 void KMyMoneyApp::slotAcceptTransaction()
3085 {
3086     const auto file = MyMoneyFile::instance();
3087     MyMoneyFileTransaction ft;
3088     try {
3089         for (const auto& journalEntryId : d->m_selections.selection(SelectedObjects::JournalEntry)) {
3090             const auto journalEntry = file->journalModel()->itemById(journalEntryId);
3091             auto t = journalEntry.transaction();
3092             auto s = journalEntry.split();
3093             if (t.isImported()) {
3094                 t.setImported(false);
3095                 if (s.reconcileFlag() < eMyMoney::Split::State::Reconciled) {
3096                     s.setReconcileFlag(eMyMoney::Split::State::Cleared);
3097                 }
3098                 t.modifySplit(s);
3099                 file->modifyTransaction(t);
3100             }
3101             TransactionMatcher matcher;
3102             matcher.accept(t, s);
3103         }
3104         ft.commit();
3105         d->updateActions(d->m_selections);
3106 
3107     } catch (const MyMoneyException &e) {
3108         KMessageBox::detailedError(this, i18n("Unable to accept transaction"), QString::fromLatin1(e.what()));
3109     }
3110 }
3111 
3112 void KMyMoneyApp::slotStartReconciliation()
3113 {
3114     slotEnterOverdueSchedules();
3115 
3116     d->executeAction(eMenu::Action::StartReconciliation);
3117 }
3118 
3119 void KMyMoneyApp::slotReportReconciliation()
3120 {
3121     auto action = qobject_cast<QAction*>(sender());
3122     const auto report = action->data().value<MyMoneyReconciliationReport>();
3123 
3124     const auto account = MyMoneyFile::instance()->accountsModel()->itemById(report.accountId);
3125     if (!account.id().isEmpty()) {
3126         KMyMoneyPlugin::pluginInterfaces().viewInterface->accountReconciled(account, report.statementDate, report.startingBalance, report.endingBalance, report.journalEntryIds);
3127     }
3128 }
3129 
3130 void KMyMoneyApp::slotEnterOverdueSchedules()
3131 {
3132     const auto accountId = d->m_selections.firstSelection(SelectedObjects::Account);
3133     if (accountId.isEmpty())
3134         return;
3135 
3136     const auto file = MyMoneyFile::instance();
3137     const auto accountIdx = file->accountsModel()->indexById(accountId);
3138     if (!accountIdx.isValid()) {
3139         qDebug() << "Invalid account id in slotEnterOverdueSchedules";
3140         return;
3141     }
3142 
3143     auto schedules = file->scheduleList(accountId, eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, QDate(), QDate(), true);
3144     if (!schedules.isEmpty()) {
3145         const auto accountName = accountIdx.data(eMyMoney::Model::AccountNameRole).toString();
3146         if (KMessageBox::questionTwoActions(
3147                 this,
3148                 i18n("KMyMoney has detected some overdue scheduled transactions for the account <b>%1</b>. Do you want to enter those "
3149                      "scheduled transactions now?",
3150                      accountName),
3151                 i18n("Scheduled transactions found"),
3152                 KMMYesNo::yes(),
3153                 KMMYesNo::no())
3154             == KMessageBox::PrimaryAction) {
3155             QSet<QString> skipMap;
3156             bool processedOne(false);
3157             auto rc = eDialogs::ScheduleResultCode::Enter;
3158 
3159             do {
3160                 processedOne = false;
3161                 QList<MyMoneySchedule>::const_iterator it_sch;
3162                 for (it_sch = schedules.constBegin(); (rc != eDialogs::ScheduleResultCode::Cancel) && (it_sch != schedules.constEnd()); ++it_sch) {
3163                     MyMoneySchedule sch(*(it_sch));
3164 
3165                     // and enter it if it is not on the skip list
3166                     if (!skipMap.contains((*it_sch).id())) {
3167                         rc = d->m_myMoneyView->enterSchedule(sch, false, true);
3168                         if (rc == eDialogs::ScheduleResultCode::Ignore) {
3169                             skipMap.insert((*it_sch).id());
3170                         } else {
3171                             processedOne = true;
3172                         }
3173                     }
3174                 }
3175 
3176                 // reload list (maybe this schedule needs to be added again)
3177                 schedules = file->scheduleList(accountId, eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, QDate(), QDate(), true);
3178             } while (processedOne);
3179         }
3180     }
3181 }
3182 
3183 void KMyMoneyApp::slotFindTransaction()
3184 {
3185     if (!d->m_searchDlg) {
3186         d->m_searchDlg = new KSearchTransactionDlg(this);
3187         connect(d->m_searchDlg, &QObject::destroyed, this, [&]() {
3188             d->m_searchDlg = nullptr;
3189         });
3190         connect(d->m_searchDlg, &KSearchTransactionDlg::requestSelectionChange, this, &KMyMoneyApp::slotSelectionChanged);
3191 
3192 #if 0
3193         connect(d->m_searchDlg, &KSearchTransactionDlg::selectTransaction,
3194                 this, [&]() {
3195                     qDebug() << "Jumping to the found transaction needs to be implemented";
3196                 });
3197 #endif
3198     }
3199     d->m_searchDlg->show();
3200     d->m_searchDlg->raise();
3201     d->m_searchDlg->activateWindow();
3202 }
3203 
3204 void KMyMoneyApp::slotCloseAccount()
3205 {
3206     MyMoneyFileTransaction ft;
3207     MyMoneyFile* file = MyMoneyFile::instance();
3208 
3209     try {
3210         const auto accountId = d->m_selections.firstSelection(SelectedObjects::Account);
3211         auto account = file->account(accountId);
3212         // in case of investment, try to close the sub-accounts first
3213         if (account.accountType() == eMyMoney::Account::Type::Investment) {
3214             d->closeSubAccounts(account);
3215         }
3216         account.setClosed(true);
3217         file->modifyAccount(account);
3218         ft.commit();
3219 
3220         // inform views about the closing of the account
3221         d->executeAction(eMenu::Action::CloseAccount);
3222 
3223         if (!KMyMoneySettings::showAllAccounts()) {
3224             KMessageBox::information(
3225                 this,
3226                 i18n("<qt>You have closed this account. It remains in the system because you have transactions which still refer to it, but it is not shown in "
3227                      "the views. You can make it visible again by going to the View menu and selecting <b>Show all accounts</b>.</qt>"),
3228                 i18n("Information"),
3229                 "CloseAccountInfo");
3230         }
3231         slotSelectionChanged(d->m_selections);
3232 
3233     } catch (const MyMoneyException& e) {
3234         qDebug() << e.what();
3235     }
3236 }
3237 
3238 void KMyMoneyApp::slotReopenAccount()
3239 {
3240     const auto file = MyMoneyFile::instance();
3241     MyMoneyFileTransaction ft;
3242     try {
3243         const auto accountId = d->m_selections.firstSelection(SelectedObjects::Account);
3244         auto account = file->account(accountId);
3245         while (account.isClosed()) {
3246             account.setClosed(false);
3247             file->modifyAccount(account);
3248             account = file->account(account.parentAccountId());
3249         }
3250         ft.commit();
3251         slotSelectionChanged(d->m_selections);
3252 
3253     } catch (const MyMoneyException& e) {
3254         qDebug() << e.what();
3255     }
3256 }
3257 
3258 #if 0
3259 void KMyMoneyApp::slotOpenAccount()
3260 {
3261     QString accountId, transactionId;
3262     if (!d->m_selections.isEmpty(SelectedObjects::Account)) {
3263         accountId = d->m_selections.selection(SelectedObjects::Account).at(0);
3264         if (!d->m_selections.isEmpty(SelectedObjects::Transaction)) {
3265             transactionId = d->m_selections.selection(SelectedObjects::Transaction).at(0);
3266         }
3267         d->m_myMoneyView->executeAction(eMenu::Action::OpenAccount, QVariantList{accountId, transactionId});
3268     }
3269 }
3270 
3271 void KMyMoneyApp::slotEditTransaction()
3272 {
3273     if (!d->m_selections.isEmpty(SelectedObjects::Account)) {
3274         const auto accountId = d->m_selections.selection(SelectedObjects::Account).at(0);
3275         if (!d->m_selections.isEmpty(SelectedObjects::Transaction)) {
3276             d->m_myMoneyView->executeAction(eMenu::Action::EditTransaction, QVariantList{accountId, d->m_selections.selection(SelectedObjects::Transaction).first()});
3277         }
3278     }
3279 }
3280 #endif
3281 
3282 void KMyMoneyApp::slotExecuteActionWithData()
3283 {
3284     static const QHash<eMenu::Action, SelectedObjects::Object_t> actionToType = {
3285         {Action::OpenAccount, SelectedObjects::Account},
3286         {Action::GoToAccount, SelectedObjects::Account},
3287         {Action::GoToPayee, SelectedObjects::Payee},
3288         {Action::ReportOpen, SelectedObjects::Report},
3289     };
3290     auto action = qobject_cast<QAction*>(sender());
3291     const auto actionId = d->qActionToId(action);
3292 
3293     if (actionId != eMenu::Action::None) {
3294         if (actionToType.contains(actionId)) {
3295             auto selections = d->m_selections;
3296             selections.setSelection(actionToType[actionId], action->data().toString());
3297             d->m_myMoneyView->executeAction(actionId, selections);
3298             action->setData(QVariant());
3299         } else {
3300             qDebug() << "Action" << static_cast<int>(actionId) << "missing in slotExecuteActionWithData";
3301         }
3302     }
3303 }
3304 
3305 void KMyMoneyApp::slotExecuteAction()
3306 {
3307     d->executeAction(d->qActionToId(qobject_cast<QAction*>(sender())));
3308 }
3309 
3310 void KMyMoneyApp::slotHideReconciledTransactions()
3311 {
3312     KMyMoneySettings::setHideReconciledTransactions(pActions[Action::ViewHideReconciled]->isChecked());
3313     LedgerViewSettings::instance()->setHideReconciledTransactions(KMyMoneySettings::hideReconciledTransactions());
3314 }
3315 
3316 void KMyMoneyApp::slotHideUnusedCategories()
3317 {
3318     KMyMoneySettings::setHideUnusedCategory(pActions[Action::ViewHideCategories]->isChecked());
3319     d->m_myMoneyView->slotSettingsChanged();
3320 }
3321 
3322 void KMyMoneyApp::slotShowAllAccounts()
3323 {
3324     KMyMoneySettings::setShowAllAccounts(pActions[Action::ViewShowAll]->isChecked());
3325     d->m_myMoneyView->slotSettingsChanged();
3326 }
3327 
3328 #ifdef KMM_DEBUG
3329 void KMyMoneyApp::slotFileFileInfo()
3330 {
3331     if (!d->m_storageInfo.isOpened) {
3332         KMessageBox::information(this, i18n("No KMyMoneyFile open"));
3333         return;
3334     }
3335 
3336     QFile g("kmymoney.dump");
3337     g.open(QIODevice::WriteOnly);
3338     QDataStream st(&g);
3339     MyMoneyStorageDump dumper;
3340     dumper.writeStream(st, MyMoneyFile::instance());
3341     g.close();
3342 }
3343 
3344 void KMyMoneyApp::slotToggleTraces()
3345 {
3346     MyMoneyTracer::onOff(pActions[Action::DebugTraces]->isChecked() ? 1 : 0);
3347 }
3348 #endif
3349 
3350 void KMyMoneyApp::slotToggleTimers()
3351 {
3352     extern bool timersOn; // main.cpp
3353 
3354     timersOn = pActions[Action::DebugTimers]->isChecked();
3355 }
3356 
3357 QString KMyMoneyApp::slotStatusMsg(const QString &text)
3358 {
3359     ///////////////////////////////////////////////////////////////////
3360     // change status message permanently
3361     QString previousMessage = d->m_statusLabel->text();
3362     d->m_applicationIsReady = false;
3363 
3364     QString currentMessage = text;
3365     if (currentMessage.isEmpty() || currentMessage == i18nc("Application is ready to use", "Ready.")) {
3366         d->m_applicationIsReady = true;
3367         currentMessage = i18nc("Application is ready to use", "Ready.");
3368     }
3369     statusBar()->clearMessage();
3370     d->m_statusLabel->setText(currentMessage);
3371     return previousMessage;
3372 }
3373 
3374 void KMyMoneyApp::ready()
3375 {
3376     slotStatusMsg(QString());
3377 }
3378 
3379 bool KMyMoneyApp::isReady()
3380 {
3381     return d->m_applicationIsReady;
3382 }
3383 
3384 void KMyMoneyApp::slotStatusProgressBar(int current, int total)
3385 {
3386     if (total == -1 && current == -1) {     // reset
3387         if (d->m_progressTimer) {
3388             d->m_progressTimer->start(500);     // remove from screen in 500 msec
3389             d->m_progressBar->setValue(d->m_progressBar->maximum());
3390         }
3391 
3392     } else if (total != 0) {                // init
3393         d->m_progressTimer->stop();
3394         d->m_progressBar->setMaximum(total);
3395         d->m_progressBar->setValue(0);
3396         d->m_progressBar->show();
3397         d->m_lastUpdate = QTime::currentTime();
3398 
3399     } else {                                // update
3400         const auto currentTime = QTime::currentTime();
3401         // only process painting if last update is at least 200 ms ago
3402         if (abs(d->m_lastUpdate.msecsTo(currentTime)) > 200) {
3403             d->m_progressBar->setValue(current);
3404             d->m_lastUpdate = currentTime;
3405         }
3406     }
3407 }
3408 
3409 void KMyMoneyApp::slotStatusProgressDone()
3410 {
3411     d->m_progressTimer->stop();
3412     d->m_progressBar->reset();
3413     d->m_progressBar->hide();
3414     d->m_progressBar->setValue(0);
3415 }
3416 
3417 void KMyMoneyApp::progressCallback(int current, int total, const QString& msg)
3418 {
3419     if (!msg.isEmpty())
3420         kmymoney->slotStatusMsg(msg);
3421 
3422     kmymoney->slotStatusProgressBar(current, total);
3423 }
3424 
3425 void KMyMoneyApp::slotFileViewPersonal()
3426 {
3427     if (!d->m_storageInfo.isOpened) {
3428         KMessageBox::information(this, i18n("No KMyMoneyFile open"));
3429         return;
3430     }
3431 
3432     KMSTATUS(i18n("Viewing personal data..."));
3433 
3434     MyMoneyFile* file = MyMoneyFile::instance();
3435     MyMoneyPayee user = file->user();
3436 
3437     QPointer<EditPersonalDataDlg> editPersonalDataDlg = new EditPersonalDataDlg(user.name(), user.address(),
3438             user.city(), user.state(), user.postcode(), user.telephone(),
3439             user.email(), this, i18n("Edit Personal Data"));
3440 
3441     if (editPersonalDataDlg->exec() == QDialog::Accepted && editPersonalDataDlg != 0) {
3442         user.setName(editPersonalDataDlg->userName());
3443         user.setAddress(editPersonalDataDlg->userStreet());
3444         user.setCity(editPersonalDataDlg->userTown());
3445         user.setState(editPersonalDataDlg->userCountry());
3446         user.setPostcode(editPersonalDataDlg->userPostcode());
3447         user.setTelephone(editPersonalDataDlg->userTelephone());
3448         user.setEmail(editPersonalDataDlg->userEmail());
3449         MyMoneyFileTransaction ft;
3450         try {
3451             file->setUser(user);
3452             ft.commit();
3453         } catch (const MyMoneyException &e) {
3454             KMessageBox::information(this, i18n("Unable to store user information: %1", QString::fromLatin1(e.what())));
3455         }
3456     }
3457     delete editPersonalDataDlg;
3458 }
3459 
3460 void KMyMoneyApp::slotLoadAccountTemplates()
3461 {
3462     KMSTATUS(i18n("Importing account templates."));
3463 
3464     QPointer<KLoadTemplateDlg> dlg = new KLoadTemplateDlg();
3465     if (dlg->exec() == QDialog::Accepted && dlg != 0) {
3466         MyMoneyFileTransaction ft;
3467         TemplateLoader loader(this);
3468         try {
3469             // import the account templates
3470             const auto templates = dlg->templates();
3471             for (const auto& tmpl : qAsConst(templates)) {
3472                 loader.importTemplate(tmpl);
3473             }
3474             ft.commit();
3475         } catch (const MyMoneyException &e) {
3476             KMessageBox::detailedError(this, i18n("Unable to import template(s)"), QString::fromLatin1(e.what()));
3477         }
3478     }
3479     delete dlg;
3480 }
3481 
3482 void KMyMoneyApp::slotSaveAccountTemplates()
3483 {
3484     KMSTATUS(i18n("Exporting account templates."));
3485 
3486     QString savePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/templates/" + QLocale().name();
3487     QDir templatesDir(savePath);
3488     if (!templatesDir.exists())
3489         templatesDir.mkpath(savePath);
3490     QString newName = QFileDialog::getSaveFileName(this, i18n("Save as..."), savePath,
3491                       i18n("KMyMoney template files (*.kmt);;All files (*)"));
3492 
3493     //
3494     // If there is no file extension, then append a .kmt at the end of the file name.
3495     // If there is a file extension, make sure it is .kmt, delete any others.
3496     //
3497     if (!newName.isEmpty()) {
3498         // find last . delimiter
3499         int nLoc = newName.lastIndexOf('.');
3500         if (nLoc != -1) {
3501             QString strExt, strTemp;
3502             strTemp = newName.left(nLoc + 1);
3503             strExt = newName.right(newName.length() - (nLoc + 1));
3504             if ((strExt.indexOf("kmt", 0, Qt::CaseInsensitive) == -1)) {
3505                 strTemp.append("kmt");
3506                 //append to make complete file name
3507                 newName = strTemp;
3508             }
3509         } else {
3510             newName.append(".kmt");
3511         }
3512 
3513         QPointer <KTemplateExportDlg> dlg = new KTemplateExportDlg(this);
3514         if ((dlg->exec() == QDialog::Accepted) && dlg) {
3515             MyMoneyTemplate tmpl;
3516             tmpl.setTitle(dlg->title());
3517             tmpl.setShortDescription(dlg->shortDescription());
3518             tmpl.setLongDescription(dlg->longDescription());
3519 
3520             TemplateWriter templateWriter(this);
3521             if (!templateWriter.exportTemplate(tmpl, QUrl::fromLocalFile(newName))) {
3522                 KMessageBox::error(this, templateWriter.errorMessage(), i18nc("@title:window Error display", "Template export"), QFlags<KMessageBox::Option>());
3523             }
3524         }
3525         delete dlg;
3526     }
3527 }
3528 
3529 bool KMyMoneyApp::okToWriteFile(const QUrl &url)
3530 {
3531     Q_UNUSED(url)
3532 
3533     // check if the file exists and warn the user
3534     bool reallySaveFile = true;
3535 
3536     if (KMyMoneyUtils::fileExists(url)) {
3537         if (KMessageBox::warningTwoActions(
3538                 this,
3539                 QLatin1String("<qt>")
3540                     + i18n("The file <b>%1</b> already exists. Do you really want to overwrite it?", url.toDisplayString(QUrl::PreferLocalFile))
3541                     + QLatin1String("</qt>"),
3542                 i18n("File already exists"),
3543                 KMMYesNo::yes(),
3544                 KMMYesNo::no())
3545             != KMessageBox::PrimaryAction)
3546             reallySaveFile = false;
3547     }
3548     return reallySaveFile;
3549 }
3550 
3551 void KMyMoneyApp::slotSettings()
3552 {
3553     // if we already have an instance of the settings dialog, then
3554     // make sure all widgets show correct state and use it
3555     auto dlg = KConfigDialog::exists("KMyMoney-Settings");
3556     if (dlg) {
3557         const auto managers = dlg->findChildren<KConfigDialogManager*>();
3558         for (const auto manager : managers) {
3559             manager->updateWidgets();
3560         }
3561         dlg->show();
3562         return;
3563     }
3564 
3565     // otherwise, we have to create it
3566     dlg = new KSettingsKMyMoney(this, "KMyMoney-Settings", KMyMoneySettings::self());
3567     connect(dlg, &KSettingsKMyMoney::settingsChanged, this, &KMyMoneyApp::slotUpdateConfiguration);
3568     dlg->show();
3569 }
3570 
3571 void KMyMoneyApp::slotShowCredits()
3572 {
3573     KAboutData aboutData = initializeCreditsData();
3574     KAboutApplicationDialog dlg(aboutData, this);
3575     dlg.exec();
3576 }
3577 
3578 void KMyMoneyApp::slotUpdateConfiguration(const QString &dialogName)
3579 {
3580     if(dialogName.compare(QLatin1String("Plugins")) == 0) {
3581         KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Reorganize, pPlugins, this, guiFactory());
3582         actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(d->canFileSaveAs());
3583         onlineJobAdministration::instance()->updateActions();
3584         onlineJobAdministration::instance()->setOnlinePlugins(pPlugins.extended);
3585         d->m_myMoneyView->setOnlinePlugins(&pPlugins.online);
3586         d->updateActions(d->m_selections);
3587     }
3588 
3589     MyMoneyUtils::clearFormatCaches();
3590     switch (KMyMoneySettings::initialDateFieldCursorPosition()) {
3591     case KMyMoneySettings::Day:
3592         KMyMoneyDateEdit().setInitialSection(QDateTimeEdit::DaySection);
3593         break;
3594     case KMyMoneySettings::Month:
3595         KMyMoneyDateEdit().setInitialSection(QDateTimeEdit::MonthSection);
3596         break;
3597     case KMyMoneySettings::Year:
3598         KMyMoneyDateEdit().setInitialSection(QDateTimeEdit::YearSection);
3599         break;
3600     }
3601 
3602     const auto ledgerViewSettings = LedgerViewSettings::instance();
3603     ledgerViewSettings->setHideReconciledTransactions(KMyMoneySettings::hideReconciledTransactions());
3604     ledgerViewSettings->setHideTransactionsBefore(KMyMoneySettings::startDate().date());
3605 
3606     MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay());
3607     MyMoneyFile::instance()->budgetsModel()->setFiscalYearStart(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay());
3608     MyMoneyReport::setLineWidth(KMyMoneySettings::lineWidth());
3609 
3610     const auto showHeaders = KMyMoneySettings::showFancyMarker();
3611     QDate firstFiscalDate;
3612     if (KMyMoneySettings::showFiscalMarker())
3613         firstFiscalDate = KMyMoneySettings::firstFiscalDate();
3614 
3615     MyMoneyFile::instance()->specialDatesModel()->setOptions(showHeaders, firstFiscalDate);
3616     MyMoneyFile::instance()->schedulesJournalModel()->setPreviewPeriod(KMyMoneySettings::schedulePreview());
3617     MyMoneyFile::instance()->schedulesJournalModel()->setShowPlannedDate(KMyMoneySettings::showPlannedScheduleDates());
3618 
3619     ledgerViewSettings->setShowLedgerLens(KMyMoneySettings::ledgerLens());
3620     ledgerViewSettings->setShowTransactionDetails(KMyMoneySettings::showRegisterDetailed());
3621     ledgerViewSettings->setShowAllSplits(KMyMoneySettings::showAllSplits());
3622     ledgerViewSettings->setSortOrder(LedgerViewSettings::SortOrderStd, KMyMoneySettings::sortNormalView());
3623     ledgerViewSettings->setSortOrder(LedgerViewSettings::SortOrderInvest, KMyMoneySettings::sortNormalView());
3624     ledgerViewSettings->setSortOrder(LedgerViewSettings::SortOrderReconcileStd, KMyMoneySettings::sortReconcileView());
3625     ledgerViewSettings->setSortOrder(LedgerViewSettings::SortOrderReconcileInvest, KMyMoneySettings::sortReconcileView());
3626     ledgerViewSettings->setSortOrder(LedgerViewSettings::SortOrderSearch, KMyMoneySettings::sortSearchView());
3627     ledgerViewSettings->setShowReconciliationEntries(d->showReconciliationMarker());
3628     ledgerViewSettings->flushChanges();
3629 
3630     MyMoneyFile::instance()->journalModel()->resetRowHeightInformation();
3631 
3632     pActions[Action::ViewTransactionDetail]->setChecked(KMyMoneySettings::showRegisterDetailed());
3633 
3634     // update the holiday region configuration
3635     setHolidayRegion(KMyMoneySettings::holidayRegion());
3636 
3637     d->m_myMoneyView->slotSettingsChanged();
3638     KMyMoneyPlugin::updateConfiguration(pPlugins);
3639 
3640     // re-read autosave configuration
3641     d->m_autoSaveEnabled = KMyMoneySettings::autoSaveFile();
3642     d->m_autoSavePeriod = KMyMoneySettings::autoSavePeriod();
3643 
3644     // stop timer if turned off but running
3645     if (d->m_autoSaveTimer->isActive() && !d->m_autoSaveEnabled) {
3646         d->m_autoSaveTimer->stop();
3647     }
3648     // start timer if turned on and needed but not running
3649     if (!d->m_autoSaveTimer->isActive() && d->m_autoSaveEnabled && d->dirty()) {
3650         d->m_autoSaveTimer->setSingleShot(true);
3651         d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000);
3652     }
3653 
3654     d->setThemedCSS();
3655 }
3656 
3657 void KMyMoneyApp::slotBackupFile()
3658 {
3659     // Save the file first so isLocalFile() works
3660     if (d->m_myMoneyView && d->dirty())
3661 
3662     {
3663         if (KMessageBox::questionTwoActions(this,
3664                                             i18n("The file must be saved first "
3665                                                  "before it can be backed up.  Do you want to continue?"),
3666                                             i18nc("@title:window", "Confirmation of backup"),
3667                                             KMMYesNo::yes(),
3668                                             KMMYesNo::no())
3669             == KMessageBox::SecondaryAction) {
3670             return;
3671         }
3672 
3673         slotFileSave();
3674     }
3675 
3676 
3677 
3678     if (d->m_storageInfo.url.isEmpty())
3679         return;
3680 
3681     if (!d->m_storageInfo.url.isLocalFile()) {
3682         KMessageBox::error(this,
3683                            i18n("The current implementation of the backup functionality only supports local files as source files. Your current source file is '%1'.", d->m_storageInfo.url.url()),
3684 
3685                            i18n("Local files only"));
3686         return;
3687     }
3688 
3689     QPointer<KBackupDlg> backupDlg = new KBackupDlg(this);
3690     int returncode = backupDlg->exec();
3691 
3692     if (returncode == QDialog::Accepted && backupDlg != 0) {
3693         d->m_backupMount = backupDlg->mountCheckBoxChecked();
3694         d->m_proc.clearProgram();
3695         d->m_backupState = BACKUP_MOUNTING;
3696         d->m_mountpoint = backupDlg->mountPoint();
3697 
3698         if (d->m_backupMount) {
3699             slotBackupMount();
3700         } else {
3701             progressCallback(0, 300, "");
3702 #ifdef Q_OS_WIN
3703             d->m_ignoreBackupExitCode = true;
3704             QTimer::singleShot(0, this, SLOT(slotBackupHandleEvents()));
3705 #else
3706             // If we don't have to mount a device, we just issue
3707             // a dummy command to start the copy operation
3708 
3709             // make sure to fix the LD_LIBRARY_PATH
3710             // to not include APPDIR subdirectories
3711             AlkEnvironment::removeAppImagePathFromLinkLoaderLibPath(&d->m_proc);
3712 
3713             d->m_proc.setProgram("true");
3714             d->m_proc.start();
3715 #endif
3716         }
3717     }
3718 
3719     delete backupDlg;
3720 }
3721 
3722 void KMyMoneyApp::slotBackupMount()
3723 {
3724     progressCallback(0, 300, i18n("Mounting %1", d->m_mountpoint));
3725     d->m_proc.setProgram("mount");
3726     d->m_proc << d->m_mountpoint;
3727 
3728     // make sure to fix the LD_LIBRARY_PATH
3729     // to not include APPDIR subdirectories
3730     AlkEnvironment::removeAppImagePathFromLinkLoaderLibPath(&d->m_proc);
3731 
3732     d->m_proc.start();
3733 }
3734 
3735 bool KMyMoneyApp::slotBackupWriteFile()
3736 {
3737     QFileInfo fi(d->m_storageInfo.url.fileName());
3738     QString today = QDate::currentDate().toString("-yyyy-MM-dd.") + fi.suffix();
3739     QString backupfile = d->m_mountpoint + '/' + d->m_storageInfo.url.fileName();
3740     KMyMoneyUtils::appendCorrectFileExt(backupfile, today);
3741 
3742 #ifdef Q_OS_WIN
3743     // on windows, a leading slash is a problem if a drive letter follows
3744     // eg. "/Z:/path". In case we detect such a pattern, we simply remove
3745     // the leading slash
3746     const QRegularExpression re(QStringLiteral("/(?<path>\\w+:/.+)"),
3747                                 QRegularExpression::CaseInsensitiveOption|QRegularExpression::UseUnicodePropertiesOption
3748                                );
3749     const auto match = re.match(backupfile);
3750     if (match.hasMatch() && !match.captured(QStringLiteral("path")).isEmpty()) {
3751         backupfile = match.captured(QStringLiteral("path"));
3752     }
3753 #endif
3754 
3755     // check if file already exists and ask what to do
3756     QFileInfo fileInfo(backupfile);
3757     if (fileInfo.exists()) {
3758         int answer = KMessageBox::warningContinueCancel(this, i18n("Backup file for today exists on that device. Replace?"), i18n("Backup"), KGuiItem(i18n("&Replace")));
3759         if (answer == KMessageBox::Cancel) {
3760             return false;
3761         }
3762     } else {
3763         // if it does not exist, make sure the path exists
3764         const auto path = fileInfo.absolutePath();
3765         if (!QDir().mkpath(path)) {
3766             KMessageBox::error(this, i18nc("@info Error during backup", "Unable to create backup directory '%1'.", path));
3767             return false;
3768         }
3769     }
3770 
3771     progressCallback(50, 0, i18n("Writing %1", backupfile));
3772     d->m_proc.clearProgram();
3773 #ifdef Q_OS_WIN
3774     d->m_proc << "cmd.exe" << "/c" << "copy" << "/b" << "/y";
3775     d->m_proc << QDir::toNativeSeparators(d->m_storageInfo.url.toLocalFile()) << "+" << "nul" << QDir::toNativeSeparators(backupfile);
3776 #else
3777     d->m_proc << "cp" << "-f";
3778     d->m_proc << d->m_storageInfo.url.toLocalFile() << backupfile;
3779 
3780     // make sure to fix the LD_LIBRARY_PATH
3781     // to not include APPDIR subdirectories
3782     AlkEnvironment::removeAppImagePathFromLinkLoaderLibPath(&d->m_proc);
3783 #endif
3784     d->m_backupState = BACKUP_COPYING;
3785     qDebug() << "Backup cmd:" << d->m_proc.program();
3786     d->m_proc.start();
3787     return true;
3788 }
3789 
3790 void KMyMoneyApp::slotBackupUnmount()
3791 {
3792     progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint));
3793     d->m_proc.clearProgram();
3794     d->m_proc.setProgram("umount");
3795     d->m_proc << d->m_mountpoint;
3796     d->m_backupState = BACKUP_UNMOUNTING;
3797 
3798     // make sure to fix the LD_LIBRARY_PATH
3799     // to not include APPDIR subdirectories
3800     AlkEnvironment::removeAppImagePathFromLinkLoaderLibPath(&d->m_proc);
3801 
3802     d->m_proc.start();
3803 }
3804 
3805 void KMyMoneyApp::slotBackupFinish()
3806 {
3807     d->m_backupState = BACKUP_IDLE;
3808     progressCallback(-1, -1, QString());
3809     ready();
3810 }
3811 
3812 void KMyMoneyApp::slotBackupHandleEvents()
3813 {
3814     switch (d->m_backupState) {
3815     case BACKUP_MOUNTING:
3816 
3817         if (d->m_ignoreBackupExitCode ||
3818                 (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0)) {
3819             d->m_ignoreBackupExitCode = false;
3820             d->m_backupResult = 0;
3821             if (!slotBackupWriteFile()) {
3822                 d->m_backupResult = 1;
3823                 if (d->m_backupMount)
3824                     slotBackupUnmount();
3825                 else
3826                     slotBackupFinish();
3827             }
3828         } else {
3829             KMessageBox::information(this, i18n("Error mounting device"), i18n("Backup"));
3830             d->m_backupResult = 1;
3831             if (d->m_backupMount)
3832                 slotBackupUnmount();
3833             else
3834                 slotBackupFinish();
3835         }
3836         break;
3837 
3838     case BACKUP_COPYING:
3839         if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) {
3840 
3841             if (d->m_backupMount) {
3842                 slotBackupUnmount();
3843             } else {
3844                 progressCallback(300, 0, i18nc("Backup done", "Done"));
3845                 KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup"));
3846                 slotBackupFinish();
3847             }
3848         } else {
3849             qDebug("copy exit code is %d", d->m_proc.exitCode());
3850             d->m_backupResult = 1;
3851             KMessageBox::information(this, i18n("Error copying file to device"), i18n("Backup"));
3852             if (d->m_backupMount)
3853                 slotBackupUnmount();
3854             else
3855                 slotBackupFinish();
3856         }
3857         break;
3858 
3859 
3860     case BACKUP_UNMOUNTING:
3861         if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) {
3862 
3863             progressCallback(300, 0, i18nc("Backup done", "Done"));
3864             if (d->m_backupResult == 0)
3865                 KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup"));
3866         } else {
3867             KMessageBox::information(this, i18n("Error unmounting device"), i18n("Backup"));
3868         }
3869         slotBackupFinish();
3870         break;
3871 
3872     default:
3873         qWarning("Unknown state for backup operation!");
3874         progressCallback(-1, -1, QString());
3875         ready();
3876         break;
3877     }
3878 }
3879 
3880 void KMyMoneyApp::slotGenerateSql()
3881 {
3882 //  QPointer<KGenerateSqlDlg> editor = new KGenerateSqlDlg(this);
3883 //  editor->setObjectName("Generate Database SQL");
3884 //  editor->exec();
3885 //  delete editor;
3886 }
3887 
3888 void KMyMoneyApp::slotToolsStartKCalc()
3889 {
3890     QString cmd = KMyMoneySettings::externalCalculator();
3891     // if none is present, we fall back to the default
3892     if (cmd.isEmpty()) {
3893 #if defined(Q_OS_WIN32)
3894         cmd = QLatin1String("calc");
3895 #elif defined(Q_OS_MAC)
3896         cmd = QLatin1String("open -a Calculator");
3897 #else
3898         cmd = QLatin1String("kcalc");
3899 #endif
3900     }
3901     auto *job = new KIO::CommandLauncherJob(cmd, this);
3902     job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
3903     job->setWorkingDirectory(QString());
3904     job->start();
3905 }
3906 
3907 void KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal)
3908 {
3909     MyMoneyFile *file = MyMoneyFile::instance();
3910     try {
3911         const MyMoneySecurity& sec = file->security(newAccount.currencyId());
3912         // Check the opening balance
3913         if (openingBal.isPositive() && newAccount.accountGroup() == eMyMoney::Account::Type::Liability) {
3914             QString message = i18n("This account is a liability and if the "
3915                                    "opening balance represents money owed, then it should be negative.  "
3916                                    "Negate the amount?\n\n"
3917                                    "Please click Yes to change the opening balance to %1,\n"
3918                                    "Please click No to leave the amount as %2,\n"
3919                                    "Please click Cancel to abort the account creation."
3920                                    , MyMoneyUtils::formatMoney(-openingBal, newAccount, sec)
3921                                    , MyMoneyUtils::formatMoney(openingBal, newAccount, sec));
3922 
3923             int ans =
3924                 KMessageBox::questionTwoActionsCancel(this, message, i18nc("@title:window", "Opening balance for liability"), KMMYesNo::yes(), KMMYesNo::no());
3925             if (ans == KMessageBox::PrimaryAction) {
3926                 openingBal = -openingBal;
3927 
3928             } else if (ans == KMessageBox::Cancel)
3929                 return;
3930         }
3931 
3932         file->createAccount(newAccount, parentAccount, brokerageAccount, openingBal);
3933 
3934     } catch (const MyMoneyException &e) {
3935         KMessageBox::information(this, i18n("Unable to add account: %1", QString::fromLatin1(e.what())));
3936     }
3937 }
3938 
3939 void KMyMoneyApp::slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent)
3940 {
3941     KNewInvestmentWizard::newInvestment(account, parent);
3942 }
3943 
3944 void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent)
3945 {
3946     KNewAccountDlg::newCategory(account, parent);
3947 }
3948 
3949 void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account)
3950 {
3951     KNewAccountDlg::newCategory(account, MyMoneyAccount());
3952 }
3953 
3954 void KMyMoneyApp::slotAccountNew(MyMoneyAccount& account)
3955 {
3956     NewAccountWizard::Wizard::newAccount(account);
3957 }
3958 
3959 void KMyMoneyApp::slotScheduleNew(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence)
3960 {
3961     KEditScheduleDlg::newSchedule(_t, occurrence);
3962 }
3963 
3964 void KMyMoneyApp::slotPayeeNew(const QString& newnameBase, QString& id)
3965 {
3966     bool ok;
3967     std::tie(ok, id) = KMyMoneyUtils::newPayee(newnameBase);
3968 }
3969 
3970 void KMyMoneyApp::slotEditTabOrder()
3971 {
3972     d->executeAction(d->qActionToId(qobject_cast<QAction*>(sender())));
3973 }
3974 
3975 void KMyMoneyApp::slotNewFeature()
3976 {
3977 }
3978 
3979 // move a stock transaction from one investment account to another
3980 void KMyMoneyApp::Private::moveInvestmentTransaction(const QString& /*fromId*/,
3981         const QString& toId,
3982         const MyMoneyTransaction& tx)
3983 {
3984     MyMoneyAccount toInvAcc = MyMoneyFile::instance()->account(toId);
3985     MyMoneyTransaction t(tx);
3986     // first determine which stock we are dealing with.
3987     // fortunately, investment transactions have only one stock involved
3988     QString stockAccountId;
3989     QString stockSecurityId;
3990     MyMoneySplit s;
3991     const auto splits = t.splits();
3992     for (const auto& split : splits) {
3993         stockAccountId = split.accountId();
3994         stockSecurityId =
3995             MyMoneyFile::instance()->account(stockAccountId).currencyId();
3996         if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) {
3997             s = split;
3998             break;
3999         }
4000     }
4001     // Now check the target investment account to see if it
4002     // contains a stock with this id
4003     QString newStockAccountId;
4004     const auto accountList = toInvAcc.accountList();
4005     for (const auto& sAccount : accountList) {
4006         if (MyMoneyFile::instance()->account(sAccount).currencyId() ==
4007                 stockSecurityId) {
4008             newStockAccountId = sAccount;
4009             break;
4010         }
4011     }
4012     // if it doesn't exist, we need to add it as a copy of the old one
4013     // no 'copyAccount()' function??
4014     if (newStockAccountId.isEmpty()) {
4015         MyMoneyAccount stockAccount =
4016             MyMoneyFile::instance()->account(stockAccountId);
4017         MyMoneyAccount newStock;
4018         newStock.setName(stockAccount.name());
4019         newStock.setNumber(stockAccount.number());
4020         newStock.setDescription(stockAccount.description());
4021         newStock.setInstitutionId(stockAccount.institutionId());
4022         newStock.setOpeningDate(stockAccount.openingDate());
4023         newStock.setAccountType(stockAccount.accountType());
4024         newStock.setCurrencyId(stockAccount.currencyId());
4025         newStock.setClosed(stockAccount.isClosed());
4026         MyMoneyFile::instance()->addAccount(newStock, toInvAcc);
4027         newStockAccountId = newStock.id();
4028     }
4029     // now update the split and the transaction
4030     s.setAccountId(newStockAccountId);
4031     t.modifySplit(s);
4032     MyMoneyFile::instance()->modifyTransaction(t);
4033 }
4034 
4035 void KMyMoneyApp::slotDataChanged()
4036 {
4037     d->fileAction(eKMyMoney::FileAction::Changed);
4038 }
4039 
4040 void KMyMoneyApp::slotCurrencyDialog()
4041 {
4042     QPointer<KCurrencyEditDlg> dlg = new KCurrencyEditDlg(this);
4043     dlg->exec();
4044     delete dlg;
4045 }
4046 
4047 void KMyMoneyApp::slotPriceDialog()
4048 {
4049     QPointer<KMyMoneyPriceDlg> dlg = new KMyMoneyPriceDlg(this);
4050     dlg->exec();
4051     delete dlg;
4052 }
4053 
4054 void KMyMoneyApp::slotFileConsistencyCheck()
4055 {
4056     d->consistencyCheck(true);
4057 }
4058 
4059 void KMyMoneyApp::Private::consistencyCheck(bool alwaysDisplayResult)
4060 {
4061     KMSTATUS(i18n("Running consistency check..."));
4062 
4063     MyMoneyFileTransaction ft;
4064     try {
4065         m_consistencyCheckResult = MyMoneyFile::instance()->consistencyCheck();
4066         ft.commit();
4067     } catch (const MyMoneyException &e) {
4068         m_consistencyCheckResult.append(i18n("Consistency check failed: %1", e.what()));
4069         // always display the result if the check failed
4070         alwaysDisplayResult = true;
4071     }
4072 
4073     // in case the consistency check was OK, we get a single line as result
4074     // in all erroneous cases, we get more than one line and force the
4075     // display of them.
4076 
4077     if (alwaysDisplayResult || m_consistencyCheckResult.size() > 1) {
4078         QString msg = i18n("The consistency check has found no issues in your data. Details are presented below.");
4079         if (m_consistencyCheckResult.size() > 1)
4080             msg = i18n("The consistency check has found some issues in your data. Details are presented below. Those issues that could not be corrected automatically need to be solved by the user.");
4081         // install a context menu for the list after the dialog is displayed
4082         QTimer::singleShot(500, q, SLOT(slotInstallConsistencyCheckContextMenu()));
4083         KMessageBox::informationList(0, msg, m_consistencyCheckResult, i18n("Consistency check result"));
4084     }
4085     // this data is no longer needed
4086     m_consistencyCheckResult.clear();
4087 }
4088 
4089 void KMyMoneyApp::Private::copyConsistencyCheckResults()
4090 {
4091     QClipboard *clipboard = QApplication::clipboard();
4092     clipboard->setText(m_consistencyCheckResult.join(QLatin1String("\n")));
4093 }
4094 
4095 void KMyMoneyApp::Private::saveConsistencyCheckResults()
4096 {
4097     QUrl fileUrl = QFileDialog::getSaveFileUrl(q);
4098 
4099     if (!fileUrl.isEmpty()) {
4100         QFile file(fileUrl.toLocalFile());
4101         if (file.open(QFile::WriteOnly | QFile::Append | QFile::Text)) {
4102             QTextStream out(&file);
4103             out << m_consistencyCheckResult.join(QLatin1String("\n"));
4104             file.close();
4105         }
4106     }
4107 }
4108 
4109 void KMyMoneyApp::Private::setThemedCSS()
4110 {
4111     const QStringList CSSnames {QStringLiteral("kmymoney.css")};
4112     const QString cssDir("/html/");
4113     const QString embeddedCSSPath = ":" + cssDir;
4114     // make sure we have the local directory where the themed version is stored
4115     const QString themedCSSPath  = QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation).first() + cssDir;
4116     QDir().mkpath(themedCSSPath);
4117 
4118     for (const auto& CSSname : CSSnames) {
4119         const QString defaultCSSFilename = embeddedCSSPath + CSSname;
4120         QFileInfo fileInfo(defaultCSSFilename);
4121         if (fileInfo.exists()) {
4122             const QString themedCSSFilename = themedCSSPath + CSSname;
4123             QFile::remove(themedCSSFilename);
4124             if (QFile::copy(defaultCSSFilename, themedCSSFilename)) {
4125                 QFile::setPermissions(themedCSSFilename, QFileDevice::ReadOwner|QFileDevice::WriteOwner);
4126                 QFile cssFile (themedCSSFilename);
4127                 if (cssFile.open(QIODevice::ReadWrite)) {
4128                     QTextStream cssStream(&cssFile);
4129                     auto cssText = cssStream.readAll();
4130                     cssText.replace(QLatin1String("WindowText"),    KMyMoneySettings::schemeColor(SchemeColor::WindowText).name(),        Qt::CaseSensitive);
4131                     cssText.replace(QLatin1String("Window"),        KMyMoneySettings::schemeColor(SchemeColor::WindowBackground).name(),  Qt::CaseSensitive);
4132                     cssText.replace(QLatin1String("HighlightText"), KMyMoneySettings::schemeColor(SchemeColor::ListHighlightText).name(), Qt::CaseSensitive);
4133                     cssText.replace(QLatin1String("Highlight"),     KMyMoneySettings::schemeColor(SchemeColor::ListHighlight).name(),     Qt::CaseSensitive);
4134                     cssText.replace(QLatin1String("black"),         KMyMoneySettings::schemeColor(SchemeColor::ListGrid).name(),          Qt::CaseSensitive);
4135                     cssStream.seek(0);
4136                     cssStream << cssText;
4137                     cssFile.resize(cssFile.pos());
4138                     cssFile.close();
4139                 }
4140             }
4141         }
4142     }
4143 }
4144 
4145 void KMyMoneyApp::slotCheckSchedules()
4146 {
4147     if (KMyMoneySettings::checkSchedule() == true) {
4148 
4149         KMSTATUS(i18n("Checking for overdue scheduled transactions..."));
4150         MyMoneyFile *file = MyMoneyFile::instance();
4151         QDate checkDate = QDate::currentDate().addDays(KMyMoneySettings::checkSchedulePreview());
4152 
4153         QList<MyMoneySchedule> scheduleList =  file->scheduleList();
4154         QList<MyMoneySchedule>::Iterator it;
4155 
4156         eDialogs::ScheduleResultCode rc = eDialogs::ScheduleResultCode::Enter;
4157         for (it = scheduleList.begin(); (it != scheduleList.end()) && (rc != eDialogs::ScheduleResultCode::Cancel); ++it) {
4158             // Get the copy in the file because it might be modified by commitTransaction
4159             MyMoneySchedule schedule = file->schedule((*it).id());
4160 
4161             if (schedule.autoEnter()) {
4162                 try {
4163                     while (!schedule.isFinished() && (schedule.adjustedNextDueDate() <= checkDate) //
4164                             && rc != eDialogs::ScheduleResultCode::Ignore //
4165                             && rc != eDialogs::ScheduleResultCode::Cancel) {
4166                         rc = d->m_myMoneyView->enterSchedule(schedule, true, true);
4167                         schedule = file->schedule((*it).id()); // get a copy of the modified schedule
4168                     }
4169                 } catch (const MyMoneyException &) {
4170                 }
4171             }
4172             if (rc == eDialogs::ScheduleResultCode::Ignore) {
4173                 // if the current schedule was ignored then we must make sure that the user can still enter the next scheduled transaction
4174                 rc = eDialogs::ScheduleResultCode::Enter;
4175             }
4176         }
4177     }
4178 }
4179 
4180 void KMyMoneyApp::writeLastUsedDir(const QString& directory)
4181 {
4182     //get global config object for our app.
4183     KSharedConfigPtr kconfig = KSharedConfig::openConfig();
4184     if (kconfig) {
4185         KConfigGroup grp = kconfig->group("General Options");
4186 
4187         //write path entry, no error handling since its void.
4188         grp.writeEntry("LastUsedDirectory", directory);
4189     }
4190 }
4191 
4192 void KMyMoneyApp::writeLastUsedFile(const QString& fileName)
4193 {
4194     //get global config object for our app.
4195     KSharedConfigPtr kconfig = KSharedConfig::openConfig();
4196     if (kconfig) {
4197         KConfigGroup grp = d->m_config->group("General Options");
4198 
4199         // write path entry, no error handling since its void.
4200         // use a standard string, as fileName could contain a protocol
4201         // e.g. file:/home/thb/....
4202         grp.writeEntry("LastUsedFile", fileName);
4203     }
4204 }
4205 
4206 QString KMyMoneyApp::readLastUsedDir() const
4207 {
4208     QString str;
4209 
4210     //get global config object for our app.
4211     KSharedConfigPtr kconfig = KSharedConfig::openConfig();
4212     if (kconfig) {
4213         KConfigGroup grp = d->m_config->group("General Options");
4214 
4215         //read path entry.  Second parameter is the default if the setting is not found, which will be the default document path.
4216         str = grp.readEntry("LastUsedDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
4217         // if the path stored is empty, we use the default nevertheless
4218         if (str.isEmpty())
4219             str = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
4220     }
4221 
4222     return str;
4223 }
4224 
4225 QString KMyMoneyApp::readLastUsedFile() const
4226 {
4227     QString str;
4228 
4229     // get global config object for our app.
4230     KSharedConfigPtr kconfig = KSharedConfig::openConfig();
4231     if (kconfig) {
4232         KConfigGroup grp = d->m_config->group("General Options");
4233 
4234         // read filename entry.
4235         str = grp.readEntry("LastUsedFile", "");
4236     }
4237 
4238     return str;
4239 }
4240 
4241 QString KMyMoneyApp::filename() const
4242 {
4243     return d->m_storageInfo.url.url();
4244 }
4245 
4246 QUrl KMyMoneyApp::filenameURL() const
4247 {
4248     return d->m_storageInfo.url;
4249 }
4250 
4251 void KMyMoneyApp::writeFilenameURL(const QUrl &url)
4252 {
4253     d->m_storageInfo.url = url;
4254 }
4255 
4256 void KMyMoneyApp::addToRecentFiles(const QUrl& url)
4257 {
4258     d->m_recentFiles->addUrl(url);
4259 }
4260 
4261 QTimer* KMyMoneyApp::autosaveTimer()
4262 {
4263     return d->m_autoSaveTimer;
4264 }
4265 
4266 WebConnect* KMyMoneyApp::webConnect() const
4267 {
4268     return d->m_webConnect;
4269 }
4270 
4271 QList<QString> KMyMoneyApp::instanceList() const
4272 {
4273     QList<QString> list;
4274 #ifdef KMM_DBUS
4275     QDBusReply<QStringList> reply = QDBusConnection::sessionBus().interface()->registeredServiceNames();
4276 
4277     if (reply.isValid()) {
4278         QStringList apps = reply.value();
4279         QStringList::ConstIterator it;
4280 
4281         // build a list of service names of all running kmymoney applications without this one
4282         for (it = apps.constBegin(); it != apps.constEnd(); ++it) {
4283             // please change this method of creating a list of 'all the other kmymoney instances that are running on the system'
4284             // since assuming that D-Bus creates service names with org.kde.kmymoney-PID is an observation I don't think that it's documented somewhere
4285             if ((*it).indexOf("org.kde.kmymoney-") == 0) {
4286                 uint thisProcPid = platformTools::processId();
4287                 if ((*it).indexOf(QString("org.kde.kmymoney-%1").arg(thisProcPid)) != 0)
4288                     list += (*it);
4289             }
4290         }
4291     } else {
4292         qDebug("D-Bus returned the following error while obtaining instances: %s", qPrintable(reply.error().message()));
4293     }
4294 #endif
4295     return list;
4296 }
4297 
4298 void KMyMoneyApp::slotEquityPriceUpdate()
4299 {
4300     QPointer<KEquityPriceUpdateDlg> dlg = new KEquityPriceUpdateDlg(this);
4301     if (dlg->exec() == QDialog::Accepted && dlg != 0)
4302         dlg->storePrices();
4303     delete dlg;
4304 }
4305 
4306 void KMyMoneyApp::webConnectUrl(const QUrl url)
4307 {
4308     QMetaObject::invokeMethod(this, "webConnect", Qt::QueuedConnection, Q_ARG(QString, url.toLocalFile()), Q_ARG(QByteArray, QByteArray()));
4309 }
4310 
4311 void KMyMoneyApp::webConnect(const QString& sourceUrl, const QByteArray& asn_id)
4312 {
4313     //
4314     // Web connect attempts to go through the known importers and see if the file
4315     // can be importing using that method.  If so, it will import it using that
4316     // plugin
4317     //
4318 
4319     Q_UNUSED(asn_id)
4320 
4321     d->m_importUrlsQueue.enqueue(sourceUrl);
4322 
4323     // only start processing if this is the only import so far
4324     if (d->m_importUrlsQueue.count() == 1) {
4325         MyMoneyStatementReader::clearResultMessages();
4326         auto statementCount = 0;
4327         while (!d->m_importUrlsQueue.isEmpty()) {
4328             ++statementCount;
4329             // get the value of the next item from the queue
4330             // but leave it on the queue for now
4331             QString url = d->m_importUrlsQueue.head();
4332 
4333             // Bring this window to the forefront.  This method was suggested by
4334             // Lubos Lunak <l.lunak@suse.cz> of the KDE core development team.
4335             //KStartupInfo::setNewStartupId(this, asn_id);
4336 
4337             // Make sure we have an open file
4338             if (! d->m_storageInfo.isOpened &&
4339                     KMessageBox::warningContinueCancel(this, i18n("You must first select a KMyMoney file before you can import a statement.")) == KMessageBox::Continue)
4340                 slotFileOpen();
4341 
4342             // only continue if the user really did open a file.
4343             if (d->m_storageInfo.isOpened) {
4344                 KMSTATUS(i18n("Importing a statement via Web Connect"));
4345 
4346                 // remove the statement files
4347                 d->unlinkStatementXML();
4348 
4349                 QMap<QString, KMyMoneyPlugin::ImporterPlugin*>::const_iterator it_plugin = pPlugins.importer.constBegin();
4350                 while (it_plugin != pPlugins.importer.constEnd()) {
4351                     if ((*it_plugin)->isMyFormat(url)) {
4352                         if (!(*it_plugin)->import(url) && !(*it_plugin)->lastError().isEmpty()) {
4353                             auto pluginName = QString();
4354                             if (pPlugins.standard.contains(it_plugin.key()))
4355                                 pluginName = pPlugins.standard.value(it_plugin.key())->componentDisplayName();
4356                             KMessageBox::error(this, i18nc("%1 file location, %2 plugin name", "Unable to import %1 using %2 plugin. The plugin returned the following error: %3", url, pluginName, (*it_plugin)->lastError()), i18n("Importing error"));
4357                         }
4358 
4359                         break;
4360                     }
4361                     ++it_plugin;
4362                 }
4363 
4364                 // If we did not find a match, try importing it as a KMM statement file,
4365                 // which is really just for testing.  the statement file is not exposed
4366                 // to users.
4367                 if (it_plugin == pPlugins.importer.constEnd())
4368                     if (MyMoneyStatement::isStatementFile(url))
4369                         MyMoneyStatementReader::importStatement(url, false, progressCallback);
4370 
4371             }
4372             // remove the current processed item from the queue
4373             d->m_importUrlsQueue.dequeue();
4374         }
4375 
4376         KMyMoneyUtils::showStatementImportResult(MyMoneyStatementReader::resultMessages(), statementCount);
4377     }
4378 }
4379 
4380 void KMyMoneyApp::slotEnableMessages()
4381 {
4382     KMessageBox::enableAllMessages();
4383     KMessageBox::information(this, i18n("All messages have been enabled."), i18n("All messages"));
4384 }
4385 
4386 void KMyMoneyApp::slotGetOnlineHelp()
4387 {
4388     QDesktopServices::openUrl(QUrl("https://kmymoney.org/support.html"));
4389 }
4390 
4391 void KMyMoneyApp::slotWhatsNew()
4392 {
4393     QDesktopServices::openUrl(QUrl("https://kmymoney.org/news/"));
4394 }
4395 
4396 void KMyMoneyApp::slotVisitWebsite()
4397 {
4398     QDesktopServices::openUrl(QUrl("https://kmymoney.org"));
4399 }
4400 
4401 void KMyMoneyApp::createInterfaces()
4402 {
4403     // Sets up the plugin interface
4404     KMyMoneyPlugin::pluginInterfaces().appInterface = new KMyMoneyPlugin::KMMAppInterface(this, this);
4405     KMyMoneyPlugin::pluginInterfaces().importInterface = new KMyMoneyPlugin::KMMImportInterface(this);
4406     KMyMoneyPlugin::pluginInterfaces().statementInterface = new KMyMoneyPlugin::KMMStatementInterface(this);
4407     KMyMoneyPlugin::pluginInterfaces().viewInterface = new KMyMoneyPlugin::KMMViewInterface(d->m_myMoneyView, this);
4408 
4409     // setup the calendar interface for schedules
4410     MyMoneySchedule::setProcessingCalendar(this);
4411 }
4412 
4413 void KMyMoneyApp::slotAutoSave()
4414 {
4415     if (!d->m_inAutoSaving) {
4416         // store the focus widget so we can restore it after save
4417         QPointer<QWidget> focusWidget = qApp->focusWidget();
4418         d->m_inAutoSaving = true;
4419         KMSTATUS(i18n("Auto saving..."));
4420 
4421         //calls slotFileSave if needed, and restart the timer
4422         //it the file is not saved, reinitializes the countdown.
4423         if (d->dirty() && d->m_autoSaveEnabled) {
4424             if (!slotFileSave() && d->m_autoSavePeriod > 0) {
4425                 d->m_autoSaveTimer->setSingleShot(true);
4426                 d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000);
4427             }
4428         }
4429 
4430         d->m_inAutoSaving = false;
4431         if (focusWidget && focusWidget != qApp->focusWidget()) {
4432             // we have a valid focus widget so restore it
4433             focusWidget->setFocus();
4434         }
4435     }
4436 }
4437 
4438 void KMyMoneyApp::slotDateChanged()
4439 {
4440     QDateTime dt = QDateTime::currentDateTime();
4441     QDateTime nextDay(QDate(dt.date().addDays(1)), QTime(0, 0, 0));
4442 
4443     // +1 is to make sure that we're already in the next day when the
4444     // signal is sent (this way we also avoid setting the timer to 0)
4445     QTimer::singleShot((static_cast<int>(dt.secsTo(nextDay)) + 1)*1000, this, SLOT(slotDateChanged()));
4446 }
4447 
4448 void KMyMoneyApp::setHolidayRegion(const QString& holidayRegion)
4449 {
4450 #ifdef ENABLE_HOLIDAYS
4451     //since the cost of updating the cache is now not negligible
4452     //check whether the region has been modified
4453     if (!d->m_holidayRegion || d->m_holidayRegion->regionCode() != holidayRegion) {
4454         // Delete the previous holidayRegion before creating a new one.
4455         delete d->m_holidayRegion;
4456         // Create a new holidayRegion.
4457         d->m_holidayRegion = new KHolidays::HolidayRegion(holidayRegion);
4458 
4459         //clear and update the holiday cache
4460         preloadHolidays();
4461     }
4462 #else
4463     Q_UNUSED(holidayRegion);
4464 #endif
4465 }
4466 
4467 bool KMyMoneyApp::isProcessingDate(const QDate& date) const
4468 {
4469     if (!d->m_processingDays.testBit(date.dayOfWeek()))
4470         return false;
4471 #ifdef ENABLE_HOLIDAYS
4472     if (!d->m_holidayRegion || !d->m_holidayRegion->isValid())
4473         return true;
4474 
4475     //check first whether it's already in cache
4476     if (d->m_holidayMap.contains(date)) {
4477         return d->m_holidayMap.value(date);
4478     } else {
4479         bool processingDay = !d->m_holidayRegion->isHoliday(date);
4480         d->m_holidayMap.insert(date, processingDay);
4481         return processingDay;
4482     }
4483 #else
4484     return true;
4485 #endif
4486 }
4487 
4488 void KMyMoneyApp::preloadHolidays()
4489 {
4490 #ifdef ENABLE_HOLIDAYS
4491     //clear the cache before loading
4492     d->m_holidayMap.clear();
4493     // only do this if it is a valid region
4494     if (d->m_holidayRegion && d->m_holidayRegion->isValid()) {
4495         // load holidays for the forecast days plus 1 cycle, to be on the safe side
4496         auto forecastDays = KMyMoneySettings::forecastDays() + KMyMoneySettings::forecastAccountCycle();
4497         QDate endDate = QDate::currentDate().addDays(forecastDays);
4498 
4499         // look for holidays for the next 2 years as a minimum. That should give a good margin for the cache
4500         if (endDate < QDate::currentDate().addYears(2))
4501             endDate = QDate::currentDate().addYears(2);
4502 
4503 #if KHOLIDAYS_VERSION >= QT_VERSION_CHECK(5, 95, 0)
4504         KHolidays::Holiday::List holidayList = d->m_holidayRegion->rawHolidaysWithAstroSeasons(QDate::currentDate(), endDate);
4505 #else
4506         KHolidays::Holiday::List holidayList = d->m_holidayRegion->holidays(QDate::currentDate(), endDate);
4507 #endif
4508         KHolidays::Holiday::List::const_iterator holiday_it;
4509         for (holiday_it = holidayList.constBegin(); holiday_it != holidayList.constEnd(); ++holiday_it) {
4510             for (QDate holidayDate = (*holiday_it).observedStartDate(); holidayDate <= (*holiday_it).observedEndDate(); holidayDate = holidayDate.addDays(1))
4511                 d->m_holidayMap.insert(holidayDate, (*holiday_it).dayType() == KHolidays::Holiday::Workday);
4512         }
4513 
4514         // prefill cache with all values of the forecast period
4515         for (QDate date = QDate::currentDate(); date <= endDate; date = date.addDays(1)) {
4516             // if it is not a processing day, set it to false
4517             if (!d->m_processingDays.testBit(date.dayOfWeek())) {
4518                 d->m_holidayMap.insert(date, false);
4519             } else if (!d->m_holidayMap.contains(date)) {
4520                 // if it is not a holiday nor a weekend, it is a processing day
4521                 d->m_holidayMap.insert(date, true);
4522             }
4523         }
4524     }
4525 #endif
4526 }
4527 
4528 bool KMyMoneyApp::event(QEvent * event) {
4529     if (event->type() == QEvent::PaletteChange) {
4530         this->initIcons();
4531         return true;
4532     }
4533 
4534     return KXmlGuiWindow::event(event);
4535 }
4536 
4537 bool KMyMoneyApp::slotFileNew()
4538 {
4539     KMSTATUS(i18n("Creating new document..."));
4540 
4541     if (!slotFileClose())
4542         return false;
4543 
4544     NewUserWizard::Wizard wizard;
4545     if (wizard.exec() != QDialog::Accepted)
4546         return false;
4547 
4548     d->m_storageInfo.isOpened = true;
4549     d->m_storageInfo.type = eKMyMoney::StorageType::None;
4550     d->m_storageInfo.url = QUrl();
4551 
4552     try {
4553         MyMoneyFileTransaction ft;
4554         auto file = MyMoneyFile::instance();
4555         file->unload();
4556 
4557         // store the user info
4558         file->setUser(wizard.user());
4559 
4560         // create and setup base currency
4561         file->addCurrency(wizard.baseCurrency());
4562         file->setBaseCurrency(wizard.baseCurrency());
4563 
4564         // create a possible institution
4565         MyMoneyInstitution inst = wizard.institution();
4566         if (inst.name().length()) {
4567             file->addInstitution(inst);
4568         }
4569 
4570         // create a possible checking account
4571         auto acc = wizard.account();
4572         if (acc.name().length()) {
4573             acc.setInstitutionId(inst.id());
4574             MyMoneyAccount asset = file->asset();
4575             file->addAccount(acc, asset);
4576 
4577             // create possible opening balance transaction
4578             if (!wizard.openingBalance().isZero()) {
4579                 file->createOpeningBalanceTransaction(acc, wizard.openingBalance());
4580             }
4581         }
4582 
4583         // import the account templates
4584         const auto templates = wizard.templates();
4585         TemplateLoader loader(this);
4586         for (const auto& tmpl : templates) {
4587             loader.importTemplate(tmpl);
4588         }
4589 
4590         ft.commit();
4591         KMyMoneySettings::setFirstTimeRun(false);
4592 
4593         d->fileAction(eKMyMoney::FileAction::Opened);
4594         slotFileSaveAs();
4595 
4596     } catch (const MyMoneyException & e) {
4597         slotFileClose();
4598         d->removeStorage();
4599         KMessageBox::detailedError(this, i18n("Couldn't create a new file."), e.what());
4600         return false;
4601     }
4602 
4603     if (wizard.startSettingsAfterFinished())
4604         slotSettings();
4605     return true;
4606 }
4607 
4608 void KMyMoneyApp::slotFileOpen()
4609 {
4610     KMSTATUS(i18n("Open a file."));
4611 
4612     const QVector<eKMyMoney::StorageType> desiredFileExtensions {eKMyMoney::StorageType::XML, eKMyMoney::StorageType::GNC};
4613     QString fileExtensions;
4614     for (const auto &extension : desiredFileExtensions) {
4615         for (const auto &plugin : pPlugins.storage) {
4616             if (plugin->storageType() == extension) {
4617                 fileExtensions += plugin->fileExtension() + QLatin1String(";;");
4618                 break;
4619             }
4620         }
4621     }
4622 
4623     if (fileExtensions.isEmpty()) {
4624         KMessageBox::error(this, i18n("Couldn't find any plugin for opening storage."));
4625         return;
4626     }
4627 
4628     fileExtensions.append(i18n("All files (*)"));
4629 
4630     QPointer<QFileDialog> dialog = new QFileDialog(this, QString(), readLastUsedDir(), fileExtensions);
4631     dialog->setFileMode(QFileDialog::ExistingFile);
4632     dialog->setAcceptMode(QFileDialog::AcceptOpen);
4633 
4634     if (dialog->exec() == QDialog::Accepted && dialog != nullptr)
4635         slotFileOpenRecent(dialog->selectedUrls().first());
4636     delete dialog;
4637 }
4638 
4639 bool KMyMoneyApp::slotFileOpenRecent(const QUrl &url)
4640 {
4641     KMSTATUS(i18n("Loading file..."));
4642 
4643     if (!url.isValid())
4644         throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid URL %1").arg(qPrintable(url.url())));
4645 
4646     if (isFileOpenedInAnotherInstance(url)) {
4647         KMessageBox::error(this, i18n("<p>File <b>%1</b> is already opened in another instance of KMyMoney</p>", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Duplicate open"));
4648         return false;
4649     }
4650 
4651     qDebug() << "Open file" << url;
4652 
4653     if (url.scheme() != QLatin1String("sql") && !KMyMoneyUtils::fileExists(url)) {
4654         KMessageBox::error(this, i18n("<p><b>%1</b> is either an invalid filename or the file does not exist. You can open another file or create a new one.</p>", url.toDisplayString(QUrl::PreferLocalFile)), i18n("File not found"));
4655         return false;
4656     }
4657 
4658     if (d->m_storageInfo.isOpened)
4659         if (!slotFileClose())
4660             return false;
4661 
4662     // open the database
4663     d->m_storageInfo.type = eKMyMoney::StorageType::None;
4664     for (auto &plugin : pPlugins.storage) {
4665         try {
4666             if (plugin->open(url)) {
4667                 d->m_storageInfo.type = plugin->storageType();
4668                 if (plugin->storageType() != eKMyMoney::StorageType::GNC) {
4669                     d->m_storageInfo.url = plugin->openUrl();
4670                     writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile));
4671                     /* Don't use url variable after KRecentFilesAction::addUrl
4672                     * as it might delete it.
4673                     * More in API reference to this method
4674                     */
4675                     d->m_recentFiles->addUrl(url);
4676                 }
4677                 d->m_storageInfo.isOpened = true;
4678                 break;
4679             }
4680         } catch (const MyMoneyException &e) {
4681             KMessageBox::detailedError(this, i18n("Cannot open file as requested."), QString::fromLatin1(e.what()));
4682             return false;
4683         }
4684     }
4685 
4686     if(d->m_storageInfo.type == eKMyMoney::StorageType::None) {
4687         KMessageBox::error(this, i18n("Could not read your data source. Please check the KMyMoney settings that the necessary plugin is enabled."));
4688         return false;
4689     }
4690 
4691     d->fileAction(eKMyMoney::FileAction::Opened);
4692     return true;
4693 }
4694 
4695 bool KMyMoneyApp::slotFileSave()
4696 {
4697     KMSTATUS(i18n("Saving file..."));
4698 
4699     for (const auto& plugin : pPlugins.storage) {
4700         if (plugin->storageType() == d->m_storageInfo.type) {
4701             d->consistencyCheck(false);
4702             try {
4703                 if (plugin->save(d->m_storageInfo.url)) {
4704                     d->fileAction(eKMyMoney::FileAction::Saved);
4705                     return true;
4706                 }
4707                 return false;
4708             } catch (const MyMoneyException &e) {
4709                 KMessageBox::detailedError(this, i18n("Failed to save your data."), e.what());
4710                 return false;
4711             }
4712         }
4713     }
4714 
4715     KMessageBox::error(this, i18n("Couldn't find suitable plugin to save your data."));
4716     return false;
4717 }
4718 
4719 bool KMyMoneyApp::slotFileSaveAs()
4720 {
4721     KMSTATUS(i18n("Saving file as...."));
4722 
4723     QVector<eKMyMoney::StorageType> availableFileTypes;
4724     for (const auto& plugin : pPlugins.storage) {
4725         switch (plugin->storageType()) {
4726         case eKMyMoney::StorageType::GNC:
4727             break;
4728         default:
4729             availableFileTypes.append(plugin->storageType());
4730             break;
4731         }
4732     }
4733 
4734     auto chosenFileType = eKMyMoney::StorageType::None;
4735     switch (availableFileTypes.count()) {
4736     case 0:
4737         KMessageBox::error(this, i18n("Couldn't find any plugin for saving data."));
4738         return false;
4739     case 1:
4740         chosenFileType = availableFileTypes.first();
4741         break;
4742     default:
4743     {
4744         QPointer<KSaveAsQuestion> dlg = new KSaveAsQuestion(availableFileTypes, this);
4745         auto rc = dlg->exec();
4746         if (dlg) {
4747             auto fileType = dlg->fileType();
4748             delete dlg;
4749             if (rc != QDialog::Accepted)
4750                 return false;
4751             chosenFileType = fileType;
4752         }
4753     }
4754     }
4755 
4756     for (const auto &plugin : pPlugins.storage) {
4757         if (chosenFileType == plugin->storageType()) {
4758             try {
4759                 d->consistencyCheck(false);
4760                 if (plugin->saveAs()) {
4761                     d->fileAction(eKMyMoney::FileAction::Saved);
4762                     d->m_storageInfo.type = plugin->storageType();
4763                     return true;
4764                 }
4765             } catch (const MyMoneyException &e) {
4766                 KMessageBox::detailedError(this, i18n("Failed to save your storage."), e.what());
4767             }
4768         }
4769     }
4770     return false;
4771 }
4772 
4773 bool KMyMoneyApp::slotCloseViewOrFile()
4774 {
4775     if (!d->m_storageInfo.isOpened)
4776         return true;
4777 
4778     // check if we have a closable view/tab
4779     if (d->m_myMoneyView && d->m_myMoneyView->hasClosableView()) {
4780         d->m_myMoneyView->closeCurrentView();
4781         return true;
4782     }
4783     return slotFileClose();
4784 }
4785 
4786 bool KMyMoneyApp::slotFileClose()
4787 {
4788     if (!d->m_storageInfo.isOpened)
4789         return true;
4790 
4791     if (!d->askAboutSaving())
4792         return false;
4793 
4794     // prevent starting action checks
4795     // when there is no file open
4796     d->m_actionCollectorTimer.stop();
4797 
4798     d->fileAction(eKMyMoney::FileAction::Closing);
4799 
4800     d->removeStorage();
4801 
4802     d->m_storageInfo = KMyMoneyApp::Private::storageInfo();
4803 
4804     d->fileAction(eKMyMoney::FileAction::Closed);
4805     return true;
4806 }
4807 
4808 void KMyMoneyApp::slotFileQuit()
4809 {
4810     // don't modify the status message here as this will prevent quit from working!!
4811     // See the beginning of queryClose() and isReady() why. Thomas Baumgart 2005-10-17
4812 
4813     bool quitApplication = true;
4814 
4815     QList<KMainWindow*> memberList = KMainWindow::memberList();
4816     if (!memberList.isEmpty()) {
4817 
4818         QList<KMainWindow*>::const_iterator w_it = memberList.constBegin();
4819         for (; w_it != memberList.constEnd(); ++w_it) {
4820             // only close the window if the closeEvent is accepted. If the user presses Cancel on the saveModified() dialog,
4821             // the window and the application stay open.
4822             if (!(*w_it)->close()) {
4823                 quitApplication = false;
4824                 break;
4825             }
4826         }
4827     }
4828 
4829     // We will only quit if all windows were processed and not cancelled
4830     if (quitApplication) {
4831         QCoreApplication::quit();
4832     }
4833 }
4834 
4835 KMStatus::KMStatus(const QString &text)
4836     : m_prevText(kmymoney->slotStatusMsg(text))
4837 {
4838 }
4839 
4840 KMStatus::~KMStatus()
4841 {
4842     kmymoney->slotStatusMsg(m_prevText);
4843 }
4844 
4845 void KMyMoneyApp::Private::unlinkStatementXML()
4846 {
4847     QDir dir(KMyMoneySettings::logPath(), "kmm-statement*");
4848     for (uint i = 0; i < dir.count(); ++i) {
4849         qDebug("Remove %s", qPrintable(dir[i]));
4850         dir.remove(KMyMoneySettings::logPath() + QString("/%1").arg(dir[i]));
4851     }
4852 }
4853 
4854 #include "kmymoney.moc"