File indexing completed on 2024-04-28 16:29:35

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-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 
0009 #include <config-kmymoney.h>
0010 
0011 #include "kmymoney.h"
0012 
0013 // ----------------------------------------------------------------------------
0014 // Std C++ / STL Includes
0015 
0016 #include <typeinfo>
0017 #include <iostream>
0018 #include <memory>
0019 
0020 // ----------------------------------------------------------------------------
0021 // QT Includes
0022 
0023 #include <QDir>
0024 #include <QDateTime>         // only for performance tests
0025 #include <QTimer>
0026 #include <QByteArray>
0027 #include <QBoxLayout>
0028 #include <QLabel>
0029 #include <QMenu>
0030 #include <QProgressBar>
0031 #include <QList>
0032 #include <QUrl>
0033 #include <QClipboard>
0034 #include <QKeySequence>
0035 #include <QIcon>
0036 #include <QInputDialog>
0037 #include <QStatusBar>
0038 #include <QPushButton>
0039 #include <QListWidget>
0040 #include <QApplication>
0041 
0042 // ----------------------------------------------------------------------------
0043 // KDE Includes
0044 
0045 #include <KAboutApplicationDialog>
0046 #include <KActionCollection>
0047 #include <KBackup>
0048 #include <KConfig>
0049 #include <KConfigDialog>
0050 #include <KLocalizedString>
0051 #include <KMessageBox>
0052 #include <KProcess>
0053 #include <KRecentDirs>
0054 #include <KRecentFilesAction>
0055 #include <KRun>
0056 #include <KStandardAction>
0057 #include <KTipDialog>
0058 #include <KToolBar>
0059 #include <KXMLGUIFactory>
0060 #include <kio_version.h>
0061 
0062 #ifdef ENABLE_HOLIDAYS
0063 #include <KHolidays/Holiday>
0064 #include <KHolidays/HolidayRegion>
0065 #endif
0066 
0067 #ifdef ENABLE_ACTIVITIES
0068 #include <KActivities/ResourceInstance>
0069 #endif
0070 
0071 #if KIO_VERSION < QT_VERSION_CHECK(5, 70, 0)
0072 #include <KRun>
0073 #else
0074 #include <KDialogJobUiDelegate>
0075 #include <KIO/CommandLauncherJob>
0076 #endif
0077 
0078 // ----------------------------------------------------------------------------
0079 // Project Includes
0080 
0081 #include "kmymoneysettings.h"
0082 #include "kmymoneyadaptor.h"
0083 
0084 #include "dialogs/settings/ksettingskmymoney.h"
0085 #include "dialogs/kbackupdlg.h"
0086 #include "dialogs/kconfirmmanualenterdlg.h"
0087 #include "dialogs/kmymoneypricedlg.h"
0088 #include "dialogs/kcurrencyeditdlg.h"
0089 #include "dialogs/kequitypriceupdatedlg.h"
0090 #include "dialogs/kmymoneyfileinfodlg.h"
0091 #include "dialogs/knewbankdlg.h"
0092 #include "dialogs/ksaveasquestion.h"
0093 #include "wizards/newinvestmentwizard/knewinvestmentwizard.h"
0094 #include "dialogs/knewaccountdlg.h"
0095 #include "dialogs/editpersonaldatadlg.h"
0096 #include "dialogs/kcurrencycalculator.h"
0097 #include "dialogs/keditscheduledlg.h"
0098 #include "wizards/newloanwizard/keditloanwizard.h"
0099 #include "dialogs/kpayeereassigndlg.h"
0100 #include "dialogs/kcategoryreassigndlg.h"
0101 #include "wizards/endingbalancedlg/kendingbalancedlg.h"
0102 #include "dialogs/kloadtemplatedlg.h"
0103 #include "dialogs/ktemplateexportdlg.h"
0104 #include "dialogs/transactionmatcher.h"
0105 #include "wizards/newuserwizard/knewuserwizard.h"
0106 #include "wizards/newaccountwizard/knewaccountwizard.h"
0107 #include "dialogs/kbalancewarning.h"
0108 #include "widgets/kmymoneyaccountselector.h"
0109 #include "widgets/kmymoneypayeecombo.h"
0110 #include "widgets/amountedit.h"
0111 #include "widgets/kmymoneymvccombo.h"
0112 
0113 #include "views/kmymoneyview.h"
0114 #include "models/models.h"
0115 #include "models/accountsmodel.h"
0116 #include "models/equitiesmodel.h"
0117 #include "models/securitiesmodel.h"
0118 #ifdef ENABLE_UNFINISHEDFEATURES
0119 #include "models/ledgermodel.h"
0120 #endif
0121 
0122 #include "mymoney/mymoneyobject.h"
0123 #include "mymoney/mymoneyfile.h"
0124 #include "mymoney/mymoneyinstitution.h"
0125 #include "mymoney/mymoneyaccount.h"
0126 #include "mymoney/mymoneyaccountloan.h"
0127 #include "mymoney/mymoneysecurity.h"
0128 #include "mymoney/mymoneypayee.h"
0129 #include "mymoney/mymoneyprice.h"
0130 #include "mymoney/mymoneytag.h"
0131 #include "mymoney/mymoneybudget.h"
0132 #include "mymoney/mymoneyreport.h"
0133 #include "mymoney/mymoneysplit.h"
0134 #include "mymoney/mymoneyutils.h"
0135 #include "mymoney/mymoneystatement.h"
0136 #include "mymoney/mymoneyforecast.h"
0137 #include "mymoney/mymoneytransactionfilter.h"
0138 #include "mymoneyexception.h"
0139 
0140 
0141 #include "converter/mymoneystatementreader.h"
0142 #include "converter/mymoneytemplate.h"
0143 
0144 #include "plugins/interfaces/kmmappinterface.h"
0145 #include "plugins/interfaces/kmmviewinterface.h"
0146 #include "plugins/interfaces/kmmstatementinterface.h"
0147 #include "plugins/interfaces/kmmimportinterface.h"
0148 #include "plugins/interfaceloader.h"
0149 #include "plugins/onlinepluginextended.h"
0150 #include "pluginloader.h"
0151 #include "kmymoneyplugin.h"
0152 
0153 #include "tasks/credittransfer.h"
0154 
0155 #include "icons/icons.h"
0156 
0157 #include "misc/webconnect.h"
0158 
0159 #include "storage/mymoneystoragemgr.h"
0160 #include "imymoneystorageformat.h"
0161 
0162 #include "transactioneditor.h"
0163 #include <QHBoxLayout>
0164 #include <QFileDialog>
0165 
0166 #include "kmymoneyutils.h"
0167 #include "kcreditswindow.h"
0168 
0169 #include "ledgerdelegate.h"
0170 #include "storageenums.h"
0171 #include "mymoneyenums.h"
0172 #include "dialogenums.h"
0173 #include "viewenums.h"
0174 #include "menuenums.h"
0175 #include "kmymoneyenums.h"
0176 
0177 #include "platformtools.h"
0178 #include "kmm_printer.h"
0179 
0180 #ifdef ENABLE_SQLCIPHER
0181 #include "sqlcipher/sqlite3.h"
0182 #endif
0183 
0184 #ifdef KMM_DEBUG
0185 #include "mymoney/storage/mymoneystoragedump.h"
0186 #include "mymoneytracer.h"
0187 #endif
0188 
0189 using namespace Icons;
0190 using namespace eMenu;
0191 
0192 enum backupStateE {
0193     BACKUP_IDLE = 0,
0194     BACKUP_MOUNTING,
0195     BACKUP_COPYING,
0196     BACKUP_UNMOUNTING
0197 };
0198 
0199 class KMyMoneyApp::Private
0200 {
0201 public:
0202     explicit Private(KMyMoneyApp *app) :
0203         q(app),
0204         m_backupState(backupStateE::BACKUP_IDLE),
0205         m_backupResult(0),
0206         m_backupMount(0),
0207         m_ignoreBackupExitCode(false),
0208         m_myMoneyView(nullptr),
0209         m_startDialog(false),
0210         m_progressBar(nullptr),
0211         m_statusLabel(nullptr),
0212         m_autoSaveEnabled(true),
0213         m_autoSaveTimer(nullptr),
0214         m_progressTimer(nullptr),
0215         m_autoSavePeriod(0),
0216         m_inAutoSaving(false),
0217         m_recentFiles(nullptr),
0218 #ifdef ENABLE_HOLIDAYS
0219         m_holidayRegion(nullptr),
0220 #endif
0221 #ifdef ENABLE_ACTIVITIES
0222         m_activityResourceInstance(nullptr),
0223 #endif
0224         m_applicationIsReady(true),
0225         m_webConnect(new WebConnect(app)) {
0226         // since the days of the week are from 1 to 7,
0227         // and a day of the week is used to index this bit array,
0228         // resize the array to 8 elements (element 0 is left unused)
0229         m_processingDays.resize(8);
0230 
0231     }
0232 
0233     void unlinkStatementXML();
0234     void moveInvestmentTransaction(const QString& fromId,
0235                                    const QString& toId,
0236                                    const MyMoneyTransaction& t);
0237 
0238     struct storageInfo {
0239         eKMyMoney::StorageType type {eKMyMoney::StorageType::None};
0240         bool isOpened {false};
0241         QUrl url;
0242     };
0243 
0244     storageInfo m_storageInfo;
0245     /**
0246       * The public interface.
0247       */
0248     KMyMoneyApp * const q;
0249 
0250     /** the configuration object of the application */
0251     KSharedConfigPtr m_config;
0252 
0253     /**
0254       * The following variable represents the state while crafting a backup.
0255       * It can have the following values
0256       *
0257       * - IDLE: the default value if not performing a backup
0258       * - MOUNTING: when a mount command has been issued
0259       * - COPYING:  when a copy command has been issued
0260       * - UNMOUNTING: when an unmount command has been issued
0261       */
0262     backupStateE   m_backupState;
0263 
0264     /**
0265       * This variable keeps the result of the backup operation.
0266       */
0267     int     m_backupResult;
0268 
0269     /**
0270       * This variable is set, when the user selected to mount/unmount
0271       * the backup volume.
0272       */
0273     bool    m_backupMount;
0274 
0275     /**
0276       * Flag for internal run control
0277       */
0278     bool    m_ignoreBackupExitCode;
0279 
0280     KProcess m_proc;
0281 
0282     /// A pointer to the view holding the tabs.
0283     KMyMoneyView *m_myMoneyView;
0284 
0285     bool m_startDialog;
0286     QString m_mountpoint;
0287 
0288     QProgressBar* m_progressBar;
0289     QTime         m_lastUpdate;
0290     QLabel*       m_statusLabel;
0291 
0292     // allows multiple imports to be launched trough web connect and to be executed sequentially
0293     QQueue<QString> m_importUrlsQueue;
0294 
0295     // This is Auto Saving related
0296     bool                  m_autoSaveEnabled;
0297     QTimer*               m_autoSaveTimer;
0298     QTimer*               m_progressTimer;
0299     int                   m_autoSavePeriod;
0300     bool                  m_inAutoSaving;
0301 
0302     // id's that need to be remembered
0303     QString               m_accountGoto, m_payeeGoto;
0304 
0305     KRecentFilesAction*   m_recentFiles;
0306 
0307 #ifdef ENABLE_HOLIDAYS
0308     // used by the calendar interface for schedules
0309     KHolidays::HolidayRegion* m_holidayRegion;
0310 #endif
0311 
0312 #ifdef ENABLE_ACTIVITIES
0313     KActivities::ResourceInstance * m_activityResourceInstance;
0314 #endif
0315 
0316     QBitArray             m_processingDays;
0317     QMap<QDate, bool>     m_holidayMap;
0318     QStringList           m_consistencyCheckResult;
0319     bool                  m_applicationIsReady;
0320 
0321     WebConnect*           m_webConnect;
0322 
0323     // methods
0324     void consistencyCheck(bool alwaysDisplayResults);
0325     static void setThemedCSS();
0326     void copyConsistencyCheckResults();
0327     void saveConsistencyCheckResults();
0328 
0329     void checkAccountName(const MyMoneyAccount& _acc, const QString& name) const
0330     {
0331         auto file = MyMoneyFile::instance();
0332         if (_acc.name() != name) {
0333             MyMoneyAccount acc(_acc);
0334             acc.setName(name);
0335             file->modifyAccount(acc);
0336         }
0337     }
0338 
0339     /**
0340       * This method updates names of currencies from file to localized names
0341       */
0342     void updateCurrencyNames()
0343     {
0344         auto file = MyMoneyFile::instance();
0345         MyMoneyFileTransaction ft;
0346 
0347         QList<MyMoneySecurity> storedCurrencies = MyMoneyFile::instance()->currencyList();
0348         QList<MyMoneySecurity> availableCurrencies = MyMoneyFile::instance()->availableCurrencyList();
0349         QStringList currencyIDs;
0350 
0351         foreach (auto currency, availableCurrencies)
0352             currencyIDs.append(currency.id());
0353 
0354         try {
0355             foreach (auto currency, storedCurrencies) {
0356                 int i = currencyIDs.indexOf(currency.id());
0357                 if (i != -1 && availableCurrencies.at(i).name() != currency.name()) {
0358                     currency.setName(availableCurrencies.at(i).name());
0359                     file->modifyCurrency(currency);
0360                 }
0361             }
0362             ft.commit();
0363         } catch (const MyMoneyException &e) {
0364             qDebug("Error %s updating currency names", e.what());
0365         }
0366     }
0367 
0368     void updateAccountNames()
0369     {
0370         // make sure we setup the name of the base accounts in translated form
0371         try {
0372             MyMoneyFileTransaction ft;
0373             const auto file = MyMoneyFile::instance();
0374             checkAccountName(file->asset(), i18n("Asset"));
0375             checkAccountName(file->liability(), i18n("Liability"));
0376             checkAccountName(file->income(), i18n("Income"));
0377             checkAccountName(file->expense(), i18n("Expense"));
0378             checkAccountName(file->equity(), i18n("Equity"));
0379             ft.commit();
0380         } catch (const MyMoneyException &) {
0381         }
0382     }
0383 
0384     void ungetString(QIODevice *qfile, char *buf, int len)
0385     {
0386         buf = &buf[len-1];
0387         while (len--) {
0388             qfile->ungetChar(*buf--);
0389         }
0390     }
0391 
0392     bool applyFileFixes()
0393     {
0394         const auto blocked = MyMoneyFile::instance()->blockSignals(true);
0395         KSharedConfigPtr config = KSharedConfig::openConfig();
0396 
0397         KConfigGroup grp = config->group("General Options");
0398 
0399         // For debugging purposes, we can turn off the automatic fix manually
0400         // by setting the entry in kmymoneyrc to true
0401         if (grp.readEntry("SkipFix", false) != true) {
0402             MyMoneyFileTransaction ft;
0403             try {
0404                 // Check if we have to modify the file before we allow to work with it
0405                 auto s = MyMoneyFile::instance()->storage();
0406                 while (s->fileFixVersion() < s->currentFixVersion()) {
0407                     qDebug("%s", qPrintable((QString("testing fileFixVersion %1 < %2").arg(s->fileFixVersion()).arg(s->currentFixVersion()))));
0408                     switch (s->fileFixVersion()) {
0409                     case 0:
0410                         fixFile_0();
0411                         s->setFileFixVersion(1);
0412                         break;
0413 
0414                     case 1:
0415                         fixFile_1();
0416                         s->setFileFixVersion(2);
0417                         break;
0418 
0419                     case 2:
0420                         fixFile_2();
0421                         s->setFileFixVersion(3);
0422                         break;
0423 
0424                     case 3:
0425                         fixFile_3();
0426                         s->setFileFixVersion(4);
0427                         break;
0428 
0429                     case 4:
0430                         fixFile_4();
0431                         s->setFileFixVersion(5);
0432                         break;
0433 
0434                     // add new levels above. Don't forget to increase currentFixVersion() for all
0435                     // the storage backends this fix applies to
0436                     default:
0437                         throw MYMONEYEXCEPTION(QString::fromLatin1("Unknown fix level in input file"));
0438                     }
0439                 }
0440                 ft.commit();
0441             } catch (const MyMoneyException &) {
0442                 MyMoneyFile::instance()->blockSignals(blocked);
0443                 return false;
0444             }
0445         } else {
0446             qDebug("Skipping automatic transaction fix!");
0447         }
0448         MyMoneyFile::instance()->blockSignals(blocked);
0449         return true;
0450     }
0451 
0452     void connectStorageToModels()
0453     {
0454         const auto file = MyMoneyFile::instance();
0455 
0456         const auto accountsModel = Models::instance()->accountsModel();
0457         q->connect(file, &MyMoneyFile::objectAdded,    accountsModel, &AccountsModel::slotObjectAdded);
0458         q->connect(file, &MyMoneyFile::objectModified, accountsModel, &AccountsModel::slotObjectModified);
0459         q->connect(file, &MyMoneyFile::objectRemoved,  accountsModel, &AccountsModel::slotObjectRemoved);
0460         q->connect(file, &MyMoneyFile::balanceChanged, accountsModel, &AccountsModel::slotBalanceOrValueChanged);
0461         q->connect(file, &MyMoneyFile::valueChanged,   accountsModel, &AccountsModel::slotBalanceOrValueChanged);
0462 
0463         const auto institutionsModel = Models::instance()->institutionsModel();
0464         q->connect(file, &MyMoneyFile::objectAdded,    institutionsModel, &InstitutionsModel::slotObjectAdded);
0465         q->connect(file, &MyMoneyFile::objectModified, institutionsModel, &InstitutionsModel::slotObjectModified);
0466         q->connect(file, &MyMoneyFile::objectRemoved,  institutionsModel, &InstitutionsModel::slotObjectRemoved);
0467         q->connect(file, &MyMoneyFile::balanceChanged, institutionsModel, &AccountsModel::slotBalanceOrValueChanged);
0468         q->connect(file, &MyMoneyFile::valueChanged,   institutionsModel, &AccountsModel::slotBalanceOrValueChanged);
0469 
0470         const auto equitiesModel = Models::instance()->equitiesModel();
0471         q->connect(file, &MyMoneyFile::objectAdded,    equitiesModel, &EquitiesModel::slotObjectAdded);
0472         q->connect(file, &MyMoneyFile::objectModified, equitiesModel, &EquitiesModel::slotObjectModified);
0473         q->connect(file, &MyMoneyFile::objectRemoved,  equitiesModel, &EquitiesModel::slotObjectRemoved);
0474         q->connect(file, &MyMoneyFile::balanceChanged, equitiesModel, &EquitiesModel::slotBalanceOrValueChanged);
0475         q->connect(file, &MyMoneyFile::valueChanged,   equitiesModel, &EquitiesModel::slotBalanceOrValueChanged);
0476 
0477         const auto securitiesModel = Models::instance()->securitiesModel();
0478         q->connect(file, &MyMoneyFile::objectAdded,    securitiesModel, &SecuritiesModel::slotObjectAdded);
0479         q->connect(file, &MyMoneyFile::objectModified, securitiesModel, &SecuritiesModel::slotObjectModified);
0480         q->connect(file, &MyMoneyFile::objectRemoved,  securitiesModel, &SecuritiesModel::slotObjectRemoved);
0481 
0482 #ifdef ENABLE_UNFINISHEDFEATURES
0483         const auto ledgerModel = Models::instance()->ledgerModel();
0484         q->connect(file, &MyMoneyFile::objectAdded,    ledgerModel, &LedgerModel::slotAddTransaction);
0485         q->connect(file, &MyMoneyFile::objectModified, ledgerModel, &LedgerModel::slotModifyTransaction);
0486         q->connect(file, &MyMoneyFile::objectRemoved,  ledgerModel, &LedgerModel::slotRemoveTransaction);
0487 
0488         q->connect(file, &MyMoneyFile::objectAdded,    ledgerModel, &LedgerModel::slotAddSchedule);
0489         q->connect(file, &MyMoneyFile::objectModified, ledgerModel, &LedgerModel::slotModifySchedule);
0490         q->connect(file, &MyMoneyFile::objectRemoved,  ledgerModel, &LedgerModel::slotRemoveSchedule);
0491 #endif
0492     }
0493 
0494     void disconnectStorageFromModels()
0495     {
0496         const auto file = MyMoneyFile::instance();
0497         q->disconnect(file, nullptr, Models::instance()->accountsModel(), nullptr);
0498         q->disconnect(file, nullptr, Models::instance()->institutionsModel(), nullptr);
0499         q->disconnect(file, nullptr, Models::instance()->equitiesModel(), nullptr);
0500         q->disconnect(file, nullptr, Models::instance()->securitiesModel(), nullptr);
0501 
0502 #ifdef ENABLE_UNFINISHEDFEATURES
0503         q->disconnect(file, nullptr, Models::instance()->ledgerModel(), nullptr);
0504 #endif
0505     }
0506 
0507     bool askAboutSaving()
0508     {
0509         const auto isFileNotSaved = q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->isEnabled();
0510         const auto isNewFileNotSaved = m_storageInfo.isOpened && m_storageInfo.url.isEmpty();
0511         auto fileNeedsToBeSaved = false;
0512 
0513         if (isFileNotSaved && KMyMoneySettings::autoSaveOnClose()) {
0514             fileNeedsToBeSaved = true;
0515         } else if (isFileNotSaved || isNewFileNotSaved) {
0516             switch (KMessageBox::warningYesNoCancel(q, i18n("The file has been changed, save it?"))) {
0517             case KMessageBox::ButtonCode::Yes:
0518                 fileNeedsToBeSaved = true;
0519                 break;
0520             case KMessageBox::ButtonCode::No:
0521                 fileNeedsToBeSaved = false;
0522                 break;
0523             case KMessageBox::ButtonCode::Cancel:
0524             default:
0525                 return false;
0526                 break;
0527             }
0528         }
0529         if (fileNeedsToBeSaved) {
0530             if (isFileNotSaved)
0531                 return q->slotFileSave();
0532             else if (isNewFileNotSaved)
0533                 return q->slotFileSaveAs();
0534         }
0535         return true;
0536     }
0537 
0538     /**
0539       * This method attaches an empty storage object to the MyMoneyFile
0540       * object. It calls removeStorage() to remove a possibly attached
0541       * storage object.
0542       */
0543     void newStorage()
0544     {
0545         removeStorage();
0546         auto file = MyMoneyFile::instance();
0547         file->attachStorage(new MyMoneyStorageMgr);
0548     }
0549 
0550     /**
0551       * This method removes an attached storage from the MyMoneyFile
0552       * object.
0553       */
0554     void removeStorage()
0555     {
0556         auto file = MyMoneyFile::instance();
0557         auto p = file->storage();
0558         if (p) {
0559             file->detachStorage(p);
0560             delete p;
0561         }
0562     }
0563 
0564     /**
0565       * if no base currency is defined, start the dialog and force it to be set
0566       */
0567     void selectBaseCurrency()
0568     {
0569         auto file = MyMoneyFile::instance();
0570 
0571         // check if we have a base currency. If not, we need to select one
0572         QString baseId;
0573         try {
0574             baseId = MyMoneyFile::instance()->baseCurrency().id();
0575         } catch (const MyMoneyException &e) {
0576             qDebug("%s", e.what());
0577         }
0578 
0579         if (baseId.isEmpty()) {
0580             QPointer<KCurrencyEditDlg> dlg = new KCurrencyEditDlg(q);
0581             //    connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotSetBaseCurrency(MyMoneySecurity)));
0582             dlg->exec();
0583             delete dlg;
0584         }
0585 
0586         try {
0587             baseId = MyMoneyFile::instance()->baseCurrency().id();
0588         } catch (const MyMoneyException &e) {
0589             qDebug("%s", e.what());
0590         }
0591 
0592         if (!baseId.isEmpty()) {
0593             // check that all accounts have a currency
0594             QList<MyMoneyAccount> list;
0595             file->accountList(list);
0596             QList<MyMoneyAccount>::Iterator it;
0597 
0598             // don't forget those standard accounts
0599             list << file->asset();
0600             list << file->liability();
0601             list << file->income();
0602             list << file->expense();
0603             list << file->equity();
0604 
0605 
0606             for (it = list.begin(); it != list.end(); ++it) {
0607                 QString cid;
0608                 try {
0609                     if (!(*it).currencyId().isEmpty() || (*it).currencyId().length() != 0)
0610                         cid = MyMoneyFile::instance()->currency((*it).currencyId()).id();
0611                 } catch (const MyMoneyException &e) {
0612                     qDebug() << QLatin1String("Account") << (*it).id() << (*it).name() << e.what();
0613                 }
0614 
0615                 if (cid.isEmpty()) {
0616                     (*it).setCurrencyId(baseId);
0617                     MyMoneyFileTransaction ft;
0618                     try {
0619                         file->modifyAccount(*it);
0620                         ft.commit();
0621                     } catch (const MyMoneyException &e) {
0622                         qDebug("Unable to setup base currency in account %s (%s): %s", qPrintable((*it).name()), qPrintable((*it).id()), e.what());
0623                     }
0624                 }
0625             }
0626         }
0627     }
0628 
0629     /**
0630       * Call this to see if the MyMoneyFile contains any unsaved data.
0631       *
0632       * @retval true if any data has been modified but not saved
0633       * @retval false otherwise
0634       */
0635     bool dirty()
0636     {
0637         if (!m_storageInfo.isOpened)
0638             return false;
0639 
0640         return MyMoneyFile::instance()->dirty();
0641     }
0642 
0643 
0644     /* DO NOT ADD code to this function or any of it's called ones.
0645        Instead, create a new function, fixFile_n, and modify the initializeStorage()
0646        logic above to call it */
0647 
0648     void fixFile_4()
0649     {
0650         auto file = MyMoneyFile::instance();
0651         QList<MyMoneySecurity> currencies = file->currencyList();
0652         static const QStringList symbols = {  QStringLiteral("XAU"),
0653                                               QStringLiteral("XPD"),
0654                                               QStringLiteral("XPT"),
0655                                               QStringLiteral("XAG"),
0656                                            };
0657 
0658 
0659         foreach(auto currency, currencies) {
0660             if (symbols.contains(currency.id())) {
0661                 if (currency.smallestAccountFraction() != currency.smallestCashFraction()) {
0662                     currency.setSmallestAccountFraction(currency.smallestCashFraction());
0663                     file->modifyCurrency(currency);
0664                 }
0665             }
0666         }
0667     }
0668 
0669     void fixFile_3()
0670     {
0671         // make sure each storage object contains a (unique) id
0672         MyMoneyFile::instance()->storageId();
0673     }
0674 
0675     void fixFile_2()
0676     {
0677         auto file = MyMoneyFile::instance();
0678         MyMoneyTransactionFilter filter;
0679         filter.setReportAllSplits(false);
0680         QList<MyMoneyTransaction> transactionList;
0681         file->transactionList(transactionList, filter);
0682 
0683         // scan the transactions and modify transactions with two splits
0684         // which reference an account and a category to have the memo text
0685         // of the account.
0686         auto count = 0;
0687         foreach (const auto transaction, transactionList) {
0688             if (transaction.splitCount() == 2) {
0689                 QString accountId;
0690                 QString categoryId;
0691                 QString accountMemo;
0692                 QString categoryMemo;
0693                 foreach (const auto split, transaction.splits()) {
0694                     auto acc = file->account(split.accountId());
0695                     if (acc.isIncomeExpense()) {
0696                         categoryId = split.id();
0697                         categoryMemo = split.memo();
0698                     } else {
0699                         accountId = split.id();
0700                         accountMemo = split.memo();
0701                     }
0702                 }
0703 
0704                 if (!accountId.isEmpty() && !categoryId.isEmpty()
0705                         && accountMemo != categoryMemo) {
0706                     MyMoneyTransaction t(transaction);
0707                     MyMoneySplit s(t.splitById(categoryId));
0708                     s.setMemo(accountMemo);
0709                     t.modifySplit(s);
0710                     file->modifyTransaction(t);
0711                     ++count;
0712                 }
0713             }
0714         }
0715         qDebug("%d transactions fixed in fixFile_2", count);
0716     }
0717 
0718     void fixFile_1()
0719     {
0720         // we need to fix reports. If the account filter list contains
0721         // investment accounts, we need to add the stock accounts to the list
0722         // as well if we don't have the expert mode enabled
0723         if (!KMyMoneySettings::expertMode()) {
0724             try {
0725                 QList<MyMoneyReport> reports = MyMoneyFile::instance()->reportList();
0726                 QList<MyMoneyReport>::iterator it_r;
0727                 for (it_r = reports.begin(); it_r != reports.end(); ++it_r) {
0728                     QStringList list;
0729                     (*it_r).accounts(list);
0730                     QStringList missing;
0731                     QStringList::const_iterator it_a, it_b;
0732                     for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) {
0733                         auto acc = MyMoneyFile::instance()->account(*it_a);
0734                         if (acc.accountType() == eMyMoney::Account::Type::Investment) {
0735                             foreach (const auto accountID, acc.accountList()) {
0736                                 if (!list.contains(accountID)) {
0737                                     missing.append(accountID);
0738                                 }
0739                             }
0740                         }
0741                     }
0742                     if (!missing.isEmpty()) {
0743                         (*it_r).addAccount(missing);
0744                         MyMoneyFile::instance()->modifyReport(*it_r);
0745                     }
0746                 }
0747             } catch (const MyMoneyException &) {
0748             }
0749         }
0750     }
0751 
0752 #if 0
0753     if (!m_accountsView->allItemsSelected())
0754     {
0755         // retrieve a list of selected accounts
0756         QStringList list;
0757         m_accountsView->selectedItems(list);
0758 
0759         // if we're not in expert mode, we need to make sure
0760         // that all stock accounts for the selected investment
0761         // account are also selected
0762         if (!KMyMoneySettings::expertMode()) {
0763             QStringList missing;
0764             QStringList::const_iterator it_a, it_b;
0765             for (it_a = list.begin(); it_a != list.end(); ++it_a) {
0766                 auto acc = MyMoneyFile::instance()->account(*it_a);
0767                 if (acc.accountType() == Account::Type::Investment) {
0768                     foreach (const auto accountID, acc.accountList()) {
0769                         if (!list.contains(accountID)) {
0770                             missing.append(accountID);
0771                         }
0772                     }
0773                 }
0774             }
0775             list += missing;
0776         }
0777 
0778         m_filter.addAccount(list);
0779     }
0780 
0781 #endif
0782 
0783 
0784 
0785 
0786 
0787     void fixFile_0()
0788     {
0789         /* (Ace) I am on a crusade against file fixups.  Whenever we have to fix the
0790          * file, it is really a warning.  So I'm going to print a debug warning, and
0791          * then go track them down when I see them to figure out how they got saved
0792          * out needing fixing anyway.
0793          */
0794 
0795         auto file = MyMoneyFile::instance();
0796         QList<MyMoneyAccount> accountList;
0797         file->accountList(accountList);
0798         QList<MyMoneyAccount>::Iterator it_a;
0799         QList<MyMoneySchedule> scheduleList = file->scheduleList();
0800         QList<MyMoneySchedule>::Iterator it_s;
0801 
0802         MyMoneyAccount equity = file->equity();
0803         MyMoneyAccount asset = file->asset();
0804         bool equityListEmpty = equity.accountList().count() == 0;
0805 
0806         for (it_a = accountList.begin(); it_a != accountList.end(); ++it_a) {
0807             if ((*it_a).accountType() == eMyMoney::Account::Type::Loan
0808                     || (*it_a).accountType() == eMyMoney::Account::Type::AssetLoan) {
0809                 fixLoanAccount_0(*it_a);
0810             }
0811             // until early before 0.8 release, the equity account was not saved to
0812             // the file. If we have an equity account with no sub-accounts but
0813             // find and equity account that has equity() as it's parent, we reparent
0814             // this account. Need to move it to asset() first, because otherwise
0815             // MyMoneyFile::reparent would act as NOP.
0816             if (equityListEmpty && (*it_a).accountType() == eMyMoney::Account::Type::Equity) {
0817                 if ((*it_a).parentAccountId() == equity.id()) {
0818                     auto acc = *it_a;
0819                     // tricky, force parent account to be empty so that we really
0820                     // can re-parent it
0821                     acc.setParentAccountId(QString());
0822                     file->reparentAccount(acc, equity);
0823                     qDebug() << Q_FUNC_INFO << " fixed account " << acc.id() << " reparented to " << equity.id();
0824                 }
0825             }
0826         }
0827 
0828         for (it_s = scheduleList.begin(); it_s != scheduleList.end(); ++it_s) {
0829             fixSchedule_0(*it_s);
0830         }
0831 
0832         fixTransactions_0();
0833     }
0834 
0835     void fixSchedule_0(MyMoneySchedule sched)
0836     {
0837         MyMoneyTransaction t = sched.transaction();
0838         QList<MyMoneySplit> splitList = t.splits();
0839         QList<MyMoneySplit>::ConstIterator it_s;
0840 
0841         try {
0842             bool updated = false;
0843             // Check if the splits contain valid data and set it to
0844             // be valid.
0845             for (it_s = splitList.constBegin(); it_s != splitList.constEnd(); ++it_s) {
0846                 // the first split is always the account on which this transaction operates
0847                 // and if the transaction commodity is not set, we take this
0848                 if (it_s == splitList.constBegin() && t.commodity().isEmpty()) {
0849                     qDebug() << Q_FUNC_INFO << " " << t.id() << " has no commodity";
0850                     try {
0851                         auto acc = MyMoneyFile::instance()->account((*it_s).accountId());
0852                         t.setCommodity(acc.currencyId());
0853                         updated = true;
0854                     } catch (const MyMoneyException &) {
0855                     }
0856                 }
0857                 // make sure the account exists. If not, remove the split
0858                 try {
0859                     MyMoneyFile::instance()->account((*it_s).accountId());
0860                 } catch (const MyMoneyException &) {
0861                     qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " removed, because account '" << (*it_s).accountId() << "' does not exist.";
0862                     t.removeSplit(*it_s);
0863                     updated = true;
0864                 }
0865                 if ((*it_s).reconcileFlag() != eMyMoney::Split::State::NotReconciled) {
0866                     qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'";
0867                     MyMoneySplit split = *it_s;
0868                     split.setReconcileDate(QDate());
0869                     split.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
0870                     t.modifySplit(split);
0871                     updated = true;
0872                 }
0873                 // the schedule logic used to operate only on the value field.
0874                 // This is now obsolete.
0875                 if ((*it_s).shares().isZero() && !(*it_s).value().isZero()) {
0876                     MyMoneySplit split = *it_s;
0877                     split.setShares(split.value());
0878                     t.modifySplit(split);
0879                     updated = true;
0880                 }
0881             }
0882 
0883             // If there have been changes, update the schedule and
0884             // the engine data.
0885             if (updated) {
0886                 sched.setTransaction(t);
0887                 MyMoneyFile::instance()->modifySchedule(sched);
0888             }
0889         } catch (const MyMoneyException &e) {
0890             qWarning("Unable to update broken schedule: %s", e.what());
0891         }
0892     }
0893 
0894     void fixLoanAccount_0(MyMoneyAccount acc)
0895     {
0896         if (acc.value("final-payment").isEmpty() //
0897                 || acc.value("term").isEmpty() //
0898                 || acc.value("periodic-payment").isEmpty() //
0899                 || acc.value("loan-amount").isEmpty() //
0900                 || acc.value("interest-calculation").isEmpty() //
0901                 || acc.value("schedule").isEmpty() //
0902                 || acc.value("fixed-interest").isEmpty()) {
0903             KMessageBox::information(q,
0904                                      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>"
0905                                           , acc.name()),
0906                                      i18n("Account problem"));
0907 
0908             throw MYMONEYEXCEPTION_CSTRING("Fix LoanAccount0 not supported anymore");
0909         }
0910     }
0911 
0912     void fixTransactions_0()
0913     {
0914         auto file = MyMoneyFile::instance();
0915 
0916         QList<MyMoneySchedule> scheduleList = file->scheduleList();
0917         MyMoneyTransactionFilter filter;
0918         filter.setReportAllSplits(false);
0919         QList<MyMoneyTransaction> transactionList;
0920         file->transactionList(transactionList, filter);
0921 
0922         QList<MyMoneySchedule>::Iterator it_x;
0923         QStringList interestAccounts;
0924 
0925         KMSTATUS(i18n("Fix transactions"));
0926         q->slotStatusProgressBar(0, scheduleList.count() + transactionList.count());
0927 
0928         int cnt = 0;
0929         // scan the schedules to find interest accounts
0930         for (it_x = scheduleList.begin(); it_x != scheduleList.end(); ++it_x) {
0931             MyMoneyTransaction t = (*it_x).transaction();
0932             QList<MyMoneySplit>::ConstIterator it_s;
0933             QStringList accounts;
0934             bool hasDuplicateAccounts = false;
0935 
0936             foreach (const auto split, t.splits()) {
0937                 if (accounts.contains(split.accountId())) {
0938                     hasDuplicateAccounts = true;
0939                     qDebug() << Q_FUNC_INFO << " " << t.id() << " has multiple splits with account " << split.accountId();
0940                 } else {
0941                     accounts << split.accountId();
0942                 }
0943 
0944                 if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) {
0945                     if (interestAccounts.contains(split.accountId()) == 0) {
0946                         interestAccounts << split.accountId();
0947                     }
0948                 }
0949             }
0950             if (hasDuplicateAccounts) {
0951                 fixDuplicateAccounts_0(t);
0952             }
0953             ++cnt;
0954             if (!(cnt % 10))
0955                 q->slotStatusProgressBar(cnt);
0956         }
0957 
0958         // scan the transactions and modify loan transactions
0959         for (auto& transaction : transactionList) {
0960             QString defaultAction;
0961             QList<MyMoneySplit> splits = transaction.splits();
0962             QStringList accounts;
0963 
0964             // check if base commodity is set. if not, set baseCurrency
0965             if (transaction.commodity().isEmpty()) {
0966                 qDebug() << Q_FUNC_INFO << " " << transaction.id() << " has no base currency";
0967                 transaction.setCommodity(file->baseCurrency().id());
0968                 file->modifyTransaction(transaction);
0969             }
0970 
0971             bool isLoan = false;
0972             // Determine default action
0973             if (transaction.splitCount() == 2) {
0974                 // check for transfer
0975                 int accountCount = 0;
0976                 MyMoneyMoney val;
0977                 foreach (const auto split, splits) {
0978                     auto acc = file->account(split.accountId());
0979                     if (acc.accountGroup() == eMyMoney::Account::Type::Asset //
0980                             || acc.accountGroup() == eMyMoney::Account::Type::Liability) {
0981                         val = split.value();
0982                         accountCount++;
0983                         if (acc.accountType() == eMyMoney::Account::Type::Loan //
0984                                 || acc.accountType() == eMyMoney::Account::Type::AssetLoan)
0985                             isLoan = true;
0986                     } else
0987                         break;
0988                 }
0989                 if (accountCount == 2) {
0990                     if (isLoan)
0991                         defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization);
0992                     else
0993                         defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer);
0994                 } else {
0995                     if (val.isNegative())
0996                         defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal);
0997                     else
0998                         defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit);
0999                 }
1000             }
1001 
1002             isLoan = false;
1003             foreach (const auto split, splits) {
1004                 auto acc = file->account(split.accountId());
1005                 MyMoneyMoney val = split.value();
1006                 if (acc.accountGroup() == eMyMoney::Account::Type::Asset
1007                         || acc.accountGroup() == eMyMoney::Account::Type::Liability) {
1008                     if (!val.isPositive()) {
1009                         defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal);
1010                         break;
1011                     } else {
1012                         defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit);
1013                         break;
1014                     }
1015                 }
1016             }
1017 
1018 #if 0
1019             // Check for correct actions in transactions referencing credit cards
1020             bool needModify = false;
1021             // The action fields are actually not used anymore in the ledger view logic
1022             // so we might as well skip this whole thing here!
1023             for (it_s = splits.begin(); needModify == false && it_s != splits.end(); ++it_s) {
1024                 auto acc = file->account((*it_s).accountId());
1025                 MyMoneyMoney val = (*it_s).value();
1026                 if (acc.accountType() == Account::Type::CreditCard) {
1027                     if (val < 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer))
1028                         needModify = true;
1029                     if (val >= 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer))
1030                         needModify = true;
1031                 }
1032             }
1033 
1034             // (Ace) Extended the #endif down to cover this conditional, because as-written
1035             // it will ALWAYS be skipped.
1036 
1037             if (needModify == true) {
1038                 for (it_s = splits.begin(); it_s != splits.end(); ++it_s) {
1039                     (*it_s).setAction(defaultAction);
1040                     transaction.modifySplit(*it_s);
1041                     file->modifyTransaction(transaction);
1042                 }
1043                 splits = transaction.splits();    // update local copy
1044                 qDebug("Fixed credit card assignment in %s", transaction.id().data());
1045             }
1046 #endif
1047 
1048             // Check for correct assignment of ActionInterest in all splits
1049             // and check if there are any duplicates in this transactions
1050             for (auto& split : splits) {
1051                 MyMoneyAccount splitAccount = file->account(split.accountId());
1052                 if (!accounts.contains(split.accountId())) {
1053                     accounts << split.accountId();
1054                 }
1055                 // if this split references an interest account, the action
1056                 // must be of type ActionInterest
1057                 if (interestAccounts.contains(split.accountId())) {
1058                     if (split.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) {
1059                         qDebug() << Q_FUNC_INFO << " " << transaction.id() << " contains an interest account (" << split.accountId() << ") but does not have ActionInterest";
1060                         split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest));
1061                         transaction.modifySplit(split);
1062                         file->modifyTransaction(transaction);
1063                         qDebug("Fixed interest action in %s", qPrintable(transaction.id()));
1064                     }
1065                     // if it does not reference an interest account, it must not be
1066                     // of type ActionInterest
1067                 } else {
1068                     if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) {
1069                         qDebug() << Q_FUNC_INFO << " " << transaction.id() << " does not contain an interest account so it should not have ActionInterest";
1070                         split.setAction(defaultAction);
1071                         transaction.modifySplit(split);
1072                         file->modifyTransaction(transaction);
1073                         qDebug("Fixed interest action in %s", qPrintable(transaction.id()));
1074                     }
1075                 }
1076 
1077                 // check that for splits referencing an account that has
1078                 // the same currency as the transactions commodity the value
1079                 // and shares field are the same.
1080                 if (transaction.commodity() == splitAccount.currencyId()
1081                         && split.value() != split.shares()) {
1082                     qDebug() << Q_FUNC_INFO << " " << transaction.id() << " " << split.id() << " uses the transaction currency, but shares != value";
1083                     split.setShares(split.value());
1084                     transaction.modifySplit(split);
1085                     file->modifyTransaction(transaction);
1086                 }
1087 
1088                 // fix the shares and values to have the correct fraction
1089                 if (!splitAccount.isInvest()) {
1090                     try {
1091                         int fract = splitAccount.fraction();
1092                         if (split.shares() != split.shares().convert(fract)) {
1093                             qDebug("adjusting fraction in %s,%s", qPrintable(transaction.id()), qPrintable(split.id()));
1094                             split.setShares(split.shares().convert(fract));
1095                             split.setValue(split.value().convert(fract));
1096                             transaction.modifySplit(split);
1097                             file->modifyTransaction(transaction);
1098                         }
1099                     } catch (const MyMoneyException &) {
1100                         qDebug("Missing security '%s', split not altered", qPrintable(splitAccount.currencyId()));
1101                     }
1102                 }
1103             }
1104 
1105             ++cnt;
1106             if (!(cnt % 10))
1107                 q->slotStatusProgressBar(cnt);
1108         }
1109 
1110         q->slotStatusProgressBar(-1, -1);
1111     }
1112 
1113     void fixDuplicateAccounts_0(MyMoneyTransaction& t)
1114     {
1115         qDebug("Duplicate account in transaction %s", qPrintable(t.id()));
1116     }
1117 
1118     /**
1119       * This method is used to update the caption of the application window.
1120       * It sets the caption to "filename [modified] - KMyMoney".
1121       *
1122       * @param skipActions if true, the actions will not be updated. This
1123       *                    is usually onyl required by some early calls when
1124       *                    these widgets are not yet created (the default is false).
1125       */
1126     void updateCaption();
1127     void updateActions();
1128     bool canFileSaveAs() const;
1129     bool canUpdateAllAccounts() const;
1130     void fileAction(eKMyMoney::FileAction action);
1131 };
1132 
1133 KMyMoneyApp::KMyMoneyApp(QWidget* parent) :
1134     KXmlGuiWindow(parent),
1135     d(new Private(this))
1136 {
1137 #ifdef KMM_DBUS
1138     new KmymoneyAdaptor(this);
1139     QDBusConnection::sessionBus().registerObject("/KMymoney", this);
1140     QDBusConnection::sessionBus().interface()->registerService(
1141         "org.kde.kmymoney-" + QString::number(platformTools::processId()), QDBusConnectionInterface::DontQueueService);
1142 #endif
1143     // Register the main engine types used as meta-objects
1144     qRegisterMetaType<MyMoneyMoney>("MyMoneyMoney");
1145     qRegisterMetaType<MyMoneySecurity>("MyMoneySecurity");
1146 
1147 #ifdef ENABLE_SQLCIPHER
1148     /* Issues:
1149      * 1) libsqlite3 loads implicitly before libsqlcipher
1150      *  thus making the second one loaded but non-functional,
1151      * 2) libsqlite3 gets linked into kmymoney target implicitly
1152      *  and it's not possible to unload or unlink it explicitly
1153      *
1154      * Solution:
1155      * Use e.g. dummy sqlite3_key call, so that libsqlcipher gets loaded implicitly before libsqlite3
1156      * thus making the first one functional.
1157      *
1158      * Additional info:
1159      * 1) loading libsqlcipher explicitly doesn't solve the issue,
1160      * 2) using sqlite3_key only in sqlstorage plugin doesn't solve the issue,
1161      * 3) in a separate, minimal test case, loading libsqlite3 explicitly
1162      *  with QLibrary::ExportExternalSymbolsHint makes libsqlcipher non-functional
1163     */
1164     sqlite3_key(nullptr, nullptr, 0);
1165 #endif
1166 
1167     // preset the pointer because we need it during the course of this constructor
1168     kmymoney = this;
1169     d->m_config = KSharedConfig::openConfig();
1170 
1171     d->setThemedCSS();
1172 
1173     MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay());
1174 
1175     QFrame* frame = new QFrame;
1176     frame->setFrameStyle(QFrame::NoFrame);
1177     // values for margin (11) and spacing(6) taken from KDialog implementation
1178     QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, frame);
1179     layout->setContentsMargins(2, 2, 2, 2);
1180     layout->setSpacing(6);
1181 
1182     initIcons();
1183     initStatusBar();
1184     pActions = initActions();
1185     pMenus = initMenus();
1186 
1187     d->m_myMoneyView = new KMyMoneyView;
1188     layout->addWidget(d->m_myMoneyView, 10);
1189     connect(d->m_myMoneyView, &KMyMoneyView::statusMsg, this, &KMyMoneyApp::slotStatusMsg);
1190     connect(d->m_myMoneyView, &KMyMoneyView::statusProgress, this, &KMyMoneyApp::slotStatusProgressBar);
1191 
1192     // Initialize kactivities resource instance
1193 #ifdef ENABLE_ACTIVITIES
1194     d->m_activityResourceInstance = new KActivities::ResourceInstance(window()->winId(), this);
1195 #endif
1196 
1197     const auto viewActions = d->m_myMoneyView->actionsToBeConnected();
1198     actionCollection()->addActions(viewActions.values());
1199     for (auto it = viewActions.cbegin(); it != viewActions.cend(); ++it)
1200         pActions.insert(it.key(), it.value());
1201 
1202     ///////////////////////////////////////////////////////////////////
1203     // call inits to invoke all other construction parts
1204     readOptions();
1205 
1206     // now initialize the plugin structure
1207     createInterfaces();
1208     KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Load, pPlugins, this, guiFactory());
1209     onlineJobAdministration::instance()->setOnlinePlugins(pPlugins.extended);
1210     d->m_myMoneyView->setOnlinePlugins(pPlugins.online);
1211 
1212     setCentralWidget(frame);
1213 
1214     connect(&d->m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotBackupHandleEvents()));
1215 
1216     // force to show the home page if the file is closed
1217     connect(pActions[Action::ViewTransactionDetail], &QAction::toggled, d->m_myMoneyView, &KMyMoneyView::slotShowTransactionDetail);
1218 
1219     d->m_backupState = BACKUP_IDLE;
1220 
1221     QLocale locale;
1222     for (auto const& weekDay: locale.weekdays())
1223     {
1224         d->m_processingDays.setBit(static_cast<int>(weekDay));
1225     }
1226     d->m_autoSaveTimer = new QTimer(this);
1227     d->m_progressTimer = new QTimer(this);
1228 
1229     connect(d->m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
1230     connect(d->m_progressTimer, SIGNAL(timeout()), this, SLOT(slotStatusProgressDone()));
1231 
1232     // connect the WebConnect server
1233     connect(d->m_webConnect, &WebConnect::gotUrl, this, &KMyMoneyApp::webConnectUrl);
1234 
1235     // setup the initial configuration
1236     slotUpdateConfiguration(QString());
1237 
1238     // kickstart date change timer
1239     slotDateChanged();
1240     d->fileAction(eKMyMoney::FileAction::Closed);
1241 }
1242 
1243 KMyMoneyApp::~KMyMoneyApp()
1244 {
1245     // delete cached objects since they are in the way
1246     // when unloading the plugins
1247     onlineJobAdministration::instance()->clearCaches();
1248 
1249     // we need to unload all plugins before we destroy anything else
1250     KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Unload, pPlugins, this, guiFactory());
1251     d->removeStorage();
1252 
1253 #ifdef ENABLE_HOLIDAYS
1254     delete d->m_holidayRegion;
1255 #endif
1256 
1257 #ifdef ENABLE_ACTIVITIES
1258     delete d->m_activityResourceInstance;
1259 #endif
1260 
1261     // destroy printer object
1262     KMyMoneyPrinter::cleanup();
1263 
1264     // make sure all settings are written to disk
1265     KMyMoneySettings::self()->save();
1266     delete d;
1267 }
1268 
1269 QUrl KMyMoneyApp::lastOpenedURL()
1270 {
1271     QUrl url = d->m_startDialog ? QUrl() : d->m_storageInfo.url;
1272 
1273     if (!url.isValid()) {
1274         url = QUrl::fromUserInput(readLastUsedFile());
1275     }
1276 
1277     ready();
1278 
1279     return url;
1280 }
1281 
1282 void KMyMoneyApp::slotInstallConsistencyCheckContextMenu()
1283 {
1284     // this code relies on the implementation of KMessageBox::informationList to add a context menu to that list,
1285     // please adjust it if it's necessary or rewrite the way the consistency check results are displayed
1286     if (QWidget* dialog = QApplication::activeModalWidget()) {
1287         if (QListWidget* widget = dialog->findChild<QListWidget *>()) {
1288             // give the user a hint that the data can be saved
1289             widget->setToolTip(i18n("This is the consistency check log, use the context menu to copy or save it."));
1290             widget->setWhatsThis(widget->toolTip());
1291             widget->setContextMenuPolicy(Qt::CustomContextMenu);
1292             connect(widget, SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotShowContextMenuForConsistencyCheck(QPoint)));
1293         }
1294     }
1295 }
1296 
1297 void KMyMoneyApp::slotShowContextMenuForConsistencyCheck(const QPoint &pos)
1298 {
1299     // allow the user to save the consistency check results
1300     if (QWidget* widget = qobject_cast< QWidget* >(sender())) {
1301         QMenu contextMenu(widget);
1302         QAction* copy = new QAction(i18n("Copy to clipboard"), widget);
1303         QAction* save = new QAction(i18n("Save to file"), widget);
1304         contextMenu.addAction(copy);
1305         contextMenu.addAction(save);
1306         QAction *result = contextMenu.exec(widget->mapToGlobal(pos));
1307         if (result == copy) {
1308             // copy the consistency check results to the clipboard
1309             d->copyConsistencyCheckResults();
1310         } else if (result == save) {
1311             // save the consistency check results to a file
1312             d->saveConsistencyCheckResults();
1313         }
1314     }
1315 }
1316 
1317 QHash<eMenu::Menu, QMenu *> KMyMoneyApp::initMenus()
1318 {
1319     QHash<Menu, QMenu *> lutMenus;
1320     const QHash<Menu, QString> menuNames {
1321         {Menu::Institution,             QStringLiteral("institution_context_menu")},
1322         {Menu::Account,                 QStringLiteral("account_context_menu")},
1323         {Menu::Schedule,                QStringLiteral("schedule_context_menu")},
1324         {Menu::Category,                QStringLiteral("category_context_menu")},
1325         {Menu::Tag,                     QStringLiteral("tag_context_menu")},
1326         {Menu::Payee,                   QStringLiteral("payee_context_menu")},
1327         {Menu::Investment,              QStringLiteral("investment_context_menu")},
1328         {Menu::Transaction,             QStringLiteral("transaction_context_menu")},
1329         {Menu::MoveTransaction,         QStringLiteral("transaction_move_menu")},
1330         {Menu::MarkTransaction,         QStringLiteral("transaction_mark_menu")},
1331         {Menu::MarkTransactionContext,  QStringLiteral("transaction_context_mark_menu")},
1332         {Menu::OnlineJob,               QStringLiteral("onlinejob_context_menu")}
1333     };
1334 
1335     for (auto it = menuNames.cbegin(); it != menuNames.cend(); ++it)
1336         lutMenus.insert(it.key(), qobject_cast<QMenu*>(factory()->container(it.value(), this)));
1337     return lutMenus;
1338 }
1339 
1340 QHash<Action, QAction *> KMyMoneyApp::initActions()
1341 {
1342     auto aC = actionCollection();
1343 
1344     /* Look-up table for all custom and standard actions.
1345     It's required for:
1346     1) building QList with QActions to be added to ActionCollection
1347     2) adding custom features to QActions like e.g. keyboard shortcut
1348     */
1349     QHash<Action, QAction *> lutActions;
1350 
1351     // *************
1352     // Adding standard actions
1353     // *************
1354     KStandardAction::openNew(this, &KMyMoneyApp::slotFileNew, aC);
1355     KStandardAction::open(this, &KMyMoneyApp::slotFileOpen, aC);
1356     d->m_recentFiles = KStandardAction::openRecent(this, &KMyMoneyApp::slotFileOpenRecent, aC);
1357     KStandardAction::save(this, &KMyMoneyApp::slotFileSave, aC);
1358     KStandardAction::saveAs(this, &KMyMoneyApp::slotFileSaveAs, aC);
1359     KStandardAction::close(this, &KMyMoneyApp::slotFileClose, aC);
1360     KStandardAction::quit(this, &KMyMoneyApp::slotFileQuit, aC);
1361     lutActions.insert(Action::Print, KStandardAction::print(this, &KMyMoneyApp::slotPrintView, aC));
1362     KStandardAction::preferences(this, &KMyMoneyApp::slotSettings, aC);
1363 
1364     // *************
1365     // Adding all actions
1366     // *************
1367     {
1368         // struct for creating useless (unconnected) QAction
1369         struct actionInfo {
1370             Action  action;
1371             QString name;
1372             QString text;
1373             Icon    icon;
1374         };
1375 
1376         // clang-format off
1377         const QVector<actionInfo> actionInfos {
1378             // *************
1379             // The File menu
1380             // *************
1381             {Action::FileBackup,                    QStringLiteral("file_backup"),                    i18n("Backup..."),                                  Icon::Empty},
1382             {Action::FileImportStatement,           QStringLiteral("file_import_statement"),          i18n("Statement file..."),                          Icon::Empty},
1383             {Action::FileImportTemplate,            QStringLiteral("file_import_template"),           i18n("Account Template..."),                        Icon::Empty},
1384             {Action::FileExportTemplate,            QStringLiteral("file_export_template"),           i18n("Account Template..."),                        Icon::Empty},
1385             {Action::FilePersonalData,              QStringLiteral("view_personal_data"),             i18n("Personal Data..."),                           Icon::UserProperties},
1386 #ifdef KMM_DEBUG
1387             {Action::FileDump,                      QStringLiteral("file_dump"),                      i18n("Dump Memory"),                                Icon::Empty},
1388 #endif
1389             {Action::FileInformation,               QStringLiteral("view_file_info"),                 i18n("File-Information..."),                        Icon::DocumentProperties},
1390             // *************
1391             // The Edit menu
1392             // *************
1393             {Action::EditFindTransaction,           QStringLiteral("edit_find_transaction"),          i18n("Find transaction..."),                        Icon::Find},
1394             // *************
1395             // The View menu
1396             // *************
1397             {Action::ViewTransactionDetail,         QStringLiteral("view_show_transaction_detail"),   i18n("Show Transaction Detail"),                    Icon::TransactionDetails},
1398             {Action::ViewHideReconciled,            QStringLiteral("view_hide_reconciled_transactions"), i18n("Hide reconciled transactions"),            Icon::HideReconciled},
1399             {Action::ViewShowReconciledBalances,    QStringLiteral("view_show_reconciled_balances"),  i18n("Show reconciled balances"),                   Icon::ShowReconciledBalances},
1400             {Action::ViewHideCategories,            QStringLiteral("view_hide_unused_categories"),    i18n("Hide unused categories"),                     Icon::HideCategories},
1401             {Action::ViewShowAll,                   QStringLiteral("view_show_all_accounts"),         i18n("Show all accounts"),                          Icon::Empty},
1402             // *********************
1403             // The institutions menu
1404             // *********************
1405             {Action::NewInstitution,                QStringLiteral("institution_new"),                i18n("New institution..."),                         Icon::InstitutionNew},
1406             {Action::EditInstitution,               QStringLiteral("institution_edit"),               i18n("Edit institution..."),                        Icon::InstitutionEdit},
1407             {Action::DeleteInstitution,             QStringLiteral("institution_delete"),             i18n("Delete institution..."),                      Icon::InstitutionDelete},
1408             // *****************
1409             // The accounts menu
1410             // *****************
1411             {Action::NewAccount,                    QStringLiteral("account_new"),                    i18n("New account..."),                             Icon::AccountNew},
1412             {Action::OpenAccount,                   QStringLiteral("account_open"),                   i18n("Open ledger"),                                Icon::Ledger},
1413             {Action::StartReconciliation,           QStringLiteral("account_reconcile"),              i18n("Reconcile..."),                               Icon::Reconcile},
1414             {Action::FinishReconciliation,          QStringLiteral("account_reconcile_finish"),       i18nc("Finish reconciliation", "Finish"),    Icon::AccountFinishReconciliation},
1415             {Action::PostponeReconciliation,        QStringLiteral("account_reconcile_postpone"),     i18n("Postpone reconciliation"),                    Icon::Pause},
1416             {Action::EditAccount,                   QStringLiteral("account_edit"),                   i18n("Edit account..."),                            Icon::AccountEdit},
1417             {Action::DeleteAccount,                 QStringLiteral("account_delete"),                 i18n("Delete account..."),                          Icon::AccountDelete},
1418             {Action::CloseAccount,                  QStringLiteral("account_close"),                  i18n("Close account"),                              Icon::AccountClose},
1419             {Action::ReopenAccount,                 QStringLiteral("account_reopen"),                 i18n("Reopen account"),                             Icon::AccountReopen},
1420             {Action::ReportAccountTransactions,     QStringLiteral("account_transaction_report"),     i18n("Transaction report"),                         Icon::Report},
1421             {Action::ChartAccountBalance,           QStringLiteral("account_chart"),                  i18n("Show balance chart..."),                      Icon::OfficeChartLine},
1422             {Action::MapOnlineAccount,              QStringLiteral("account_online_map"),             i18n("Map account..."),                             Icon::MapOnlineAccount},
1423             {Action::UnmapOnlineAccount,            QStringLiteral("account_online_unmap"),           i18n("Unmap account..."),                           Icon::UnmapOnlineAccount},
1424             {Action::UpdateAccount,                 QStringLiteral("account_online_update"),          i18n("Update account..."),                          Icon::AccountUpdate},
1425             {Action::UpdateAllAccounts,             QStringLiteral("account_online_update_all"),      i18n("Update all accounts..."),                     Icon::AccountUpdateAll},
1426             {Action::AccountCreditTransfer,         QStringLiteral("account_online_new_credit_transfer"), i18n("New credit transfer"),                    Icon::AccountCreditTransfer},
1427             // *******************
1428             // The categories menu
1429             // *******************
1430             {Action::NewCategory,                   QStringLiteral("category_new"),                   i18n("New category..."),                            Icon::CategoryNew},
1431             {Action::EditCategory,                  QStringLiteral("category_edit"),                  i18n("Edit category..."),                           Icon::CategoryEdit},
1432             {Action::DeleteCategory,                QStringLiteral("category_delete"),                i18n("Delete category..."),                         Icon::CategoryDelete},
1433             // **************
1434             // The tools menu
1435             // **************
1436             {Action::ToolCurrencies,                QStringLiteral("tools_currency_editor"),          i18n("Currencies..."),                              Icon::Currencies},
1437             {Action::ToolPrices,                    QStringLiteral("tools_price_editor"),             i18n("Prices..."),                                  Icon::Empty},
1438             {Action::ToolUpdatePrices,              QStringLiteral("tools_update_prices"),            i18n("Update Stock and Currency Prices..."),        Icon::InvestmentOnlinePriceAll},
1439             {Action::ToolConsistency,               QStringLiteral("tools_consistency_check"),        i18n("Consistency Check"),                          Icon::Empty},
1440             {Action::ToolPerformance,               QStringLiteral("tools_performancetest"),          i18n("Performance-Test"),                           Icon::PerformanceTest},
1441             {Action::ToolCalculator,                QStringLiteral("tools_kcalc"),                    i18n("Calculator..."),                              Icon::Calculator},
1442             // *****************
1443             // The settings menu
1444             // *****************
1445             {Action::SettingsAllMessages,           QStringLiteral("settings_enable_messages"),       i18n("Enable all messages"),                        Icon::Empty},
1446             // *************
1447             // The help menu
1448             // *************
1449             {Action::HelpShow,                      QStringLiteral("help_show_tip"),                  i18n("&Show tip of the day"),                       Icon::Tip},
1450             // ***************************
1451             // Actions w/o main menu entry
1452             // ***************************
1453             {Action::NewTransaction,                QStringLiteral("transaction_new"),                i18nc("New transaction button", "New"),             Icon::TransactionNew},
1454             {Action::EditTransaction,               QStringLiteral("transaction_edit"),               i18nc("Edit transaction button", "Edit"),           Icon::TransactionEdit},
1455             {Action::EnterTransaction,              QStringLiteral("transaction_enter"),              i18nc("Enter transaction", "Enter"),                Icon::DialogOK},
1456             {Action::EditSplits,                    QStringLiteral("transaction_editsplits"),         i18nc("Edit split button", "Edit splits"),          Icon::Split},
1457             {Action::CancelTransaction,             QStringLiteral("transaction_cancel"),             i18nc("Cancel transaction edit", "Cancel"),         Icon::DialogCancel},
1458             {Action::DeleteTransaction,             QStringLiteral("transaction_delete"),             i18nc("Delete transaction", "Delete"),              Icon::EditDelete},
1459             {Action::DuplicateTransaction,          QStringLiteral("transaction_duplicate"),          i18nc("Duplicate transaction", "Duplicate"),        Icon::EditCopy},
1460             {Action::AddReversingTransaction,       QStringLiteral("transaction_add_reversing"),      i18nc("Add reversing transaction", "Add reversing"),Icon::Reverse},
1461             {Action::MatchTransaction,              QStringLiteral("transaction_match"),              i18nc("Button text for match transaction", "Match"),Icon::TransactionMatch},
1462             {Action::AcceptTransaction,             QStringLiteral("transaction_accept"),             i18nc("Accept 'imported' and 'matched' transaction", "Accept"), Icon::TransactionAccept},
1463             {Action::ToggleReconciliationFlag,      QStringLiteral("transaction_mark_toggle"),        i18nc("Toggle reconciliation flag", "Toggle"),     Icon::Empty},
1464             {Action::MarkCleared,                   QStringLiteral("transaction_mark_cleared"),       i18nc("Mark transaction cleared", "Cleared"),       Icon::Empty},
1465             {Action::MarkReconciled,                QStringLiteral("transaction_mark_reconciled"),    i18nc("Mark transaction reconciled", "Reconciled"), Icon::Empty},
1466             {Action::MarkNotReconciled,             QStringLiteral("transaction_mark_notreconciled"), i18nc("Mark transaction not reconciled", "Not reconciled"),     Icon::Empty},
1467             {Action::SelectAllTransactions,         QStringLiteral("transaction_select_all"),         i18nc("Select all transactions", "Select all"),     Icon::SelectAll},
1468             {Action::GoToAccount,                   QStringLiteral("transaction_goto_account"),       i18n("Go to account"),                              Icon::Accounts},
1469             {Action::GoToPayee,                     QStringLiteral("transaction_goto_payee"),         i18n("Go to payee"),                                Icon::Payees},
1470             {Action::NewScheduledTransaction,       QStringLiteral("transaction_create_schedule"),    i18n("Create scheduled transaction..."),            Icon::NewSchedule},
1471             {Action::AssignTransactionsNumber,      QStringLiteral("transaction_assign_number"),      i18n("Assign next number"),                         Icon::Empty},
1472             {Action::CombineTransactions,           QStringLiteral("transaction_combine"),            i18nc("Combine transactions", "Combine"),    Icon::Empty},
1473             {Action::CopySplits,                    QStringLiteral("transaction_copy_splits"),        i18n("Copy splits"),                                Icon::Empty},
1474             //Investment
1475             {Action::NewInvestment,                 QStringLiteral("investment_new"),                 i18n("New investment..."),                          Icon::InvestmentNew},
1476             {Action::EditInvestment,                QStringLiteral("investment_edit"),                i18n("Edit investment..."),                         Icon::InvestmentEdit},
1477             {Action::DeleteInvestment,              QStringLiteral("investment_delete"),              i18n("Delete investment..."),                       Icon::InvestmentDelete},
1478             {Action::UpdatePriceOnline,             QStringLiteral("investment_online_price_update"), i18n("Online price update..."),                     Icon::InvestmentOnlinePrice},
1479             {Action::UpdatePriceManually,           QStringLiteral("investment_manual_price_update"), i18n("Manual price update..."),                     Icon::Empty},
1480             //Schedule
1481             {Action::NewSchedule,                   QStringLiteral("schedule_new"),                   i18n("New scheduled transaction"),                  Icon::NewSchedule},
1482             {Action::EditSchedule,                  QStringLiteral("schedule_edit"),                  i18n("Edit scheduled transaction"),                 Icon::DocumentEdit},
1483             {Action::DeleteSchedule,                QStringLiteral("schedule_delete"),                i18n("Delete scheduled transaction"),               Icon::EditDelete},
1484             {Action::DuplicateSchedule,             QStringLiteral("schedule_duplicate"),             i18n("Duplicate scheduled transaction"),            Icon::EditCopy},
1485             {Action::EnterSchedule,                 QStringLiteral("schedule_enter"),                 i18n("Enter next transaction..."),                  Icon::KeyEnter},
1486             {Action::SkipSchedule,                  QStringLiteral("schedule_skip"),                  i18n("Skip next transaction..."),                   Icon::SeekForward},
1487             //Payees
1488             {Action::NewPayee,                      QStringLiteral("payee_new"),                      i18n("New payee"),                                  Icon::ListAddUser},
1489             {Action::RenamePayee,                   QStringLiteral("payee_rename"),                   i18n("Rename payee"),                               Icon::PayeeRename},
1490             {Action::DeletePayee,                   QStringLiteral("payee_delete"),                   i18n("Delete payee"),                               Icon::ListRemoveUser},
1491             {Action::MergePayee,                    QStringLiteral("payee_merge"),                    i18n("Merge payees"),                               Icon::PayeeMerge},
1492             //Tags
1493             {Action::NewTag,                        QStringLiteral("tag_new"),                        i18n("New tag"),                                    Icon::ListAddTag},
1494             {Action::RenameTag,                     QStringLiteral("tag_rename"),                     i18n("Rename tag"),                                 Icon::TagRename},
1495             {Action::DeleteTag,                     QStringLiteral("tag_delete"),                     i18n("Delete tag"),                                 Icon::ListRemoveTag},
1496             //debug actions
1497 #ifdef KMM_DEBUG
1498             {Action::WizardNewUser,                 QStringLiteral("new_user_wizard"),                i18n("Test new feature"),                           Icon::Empty},
1499             {Action::DebugTraces,                   QStringLiteral("debug_traces"),                   i18n("Debug Traces"),                               Icon::Empty},
1500 #endif
1501             {Action::DebugTimers,                   QStringLiteral("debug_timers"),                   i18n("Debug Timers"),                               Icon::Empty},
1502             // onlineJob actions
1503             {Action::DeleteOnlineJob,               QStringLiteral("onlinejob_delete"),               i18n("Remove credit transfer"),                     Icon::EditDelete},
1504             {Action::EditOnlineJob,                 QStringLiteral("onlinejob_edit"),                 i18n("Edit credit transfer"),                       Icon::DocumentEdit},
1505             {Action::LogOnlineJob,                  QStringLiteral("onlinejob_log"),                  i18n("Show log"),                                   Icon::Empty},
1506         };
1507         // clang-format on
1508 
1509         for (const auto& info : actionInfos) {
1510             auto a = new QAction(this);
1511             // KActionCollection::addAction by name sets object name anyways,
1512             // so, as better alternative, set it here right from the start
1513             a->setObjectName(info.name);
1514             a->setText(info.text);
1515             if (info.icon != Icon::Empty) // no need to set empty icon
1516                 a->setIcon(Icons::get(info.icon));
1517             a->setEnabled(false);
1518             lutActions.insert(info.action, a);  // store QAction's pointer for later processing
1519         }
1520     }
1521 
1522     {
1523         // List with slots that get connected here. Other slots get connected in e.g. appropriate views
1524         typedef void(KMyMoneyApp::*KMyMoneyAppFunc)();
1525         const QHash<eMenu::Action, KMyMoneyAppFunc> actionConnections {
1526             // *************
1527             // The File menu
1528             // *************
1529 //      {Action::FileOpenDatabase,              &KMyMoneyApp::slotOpenDatabase},
1530 //      {Action::FileSaveAsDatabase,            &KMyMoneyApp::slotSaveAsDatabase},
1531             {Action::FileBackup,                    &KMyMoneyApp::slotBackupFile},
1532             {Action::FileImportTemplate,            &KMyMoneyApp::slotLoadAccountTemplates},
1533             {Action::FileExportTemplate,            &KMyMoneyApp::slotSaveAccountTemplates},
1534             {Action::FilePersonalData,              &KMyMoneyApp::slotFileViewPersonal},
1535 #ifdef KMM_DEBUG
1536             {Action::FileDump,                      &KMyMoneyApp::slotFileFileInfo},
1537 #endif
1538             {Action::FileInformation,               &KMyMoneyApp::slotFileInfoDialog},
1539             // *************
1540             // The View menu
1541             // *************
1542             {Action::ViewTransactionDetail,         &KMyMoneyApp::slotShowTransactionDetail},
1543             {Action::ViewHideReconciled,            &KMyMoneyApp::slotHideReconciledTransactions},
1544             {Action::ViewShowReconciledBalances,    &KMyMoneyApp::slotShowReconciledBalances},
1545             {Action::ViewHideCategories,            &KMyMoneyApp::slotHideUnusedCategories},
1546             {Action::ViewShowAll,                   &KMyMoneyApp::slotShowAllAccounts},
1547             // **************
1548             // The tools menu
1549             // **************
1550             {Action::ToolCurrencies,                &KMyMoneyApp::slotCurrencyDialog},
1551             {Action::ToolPrices,                    &KMyMoneyApp::slotPriceDialog},
1552             {Action::ToolUpdatePrices,              &KMyMoneyApp::slotEquityPriceUpdate},
1553             {Action::ToolConsistency,               &KMyMoneyApp::slotFileConsistencyCheck},
1554             {Action::ToolPerformance,               &KMyMoneyApp::slotPerformanceTest},
1555 //      {Action::ToolSQL,                       &KMyMoneyApp::slotGenerateSql},
1556             {Action::ToolCalculator,                &KMyMoneyApp::slotToolsStartKCalc},
1557             // *****************
1558             // The settings menu
1559             // *****************
1560             {Action::SettingsAllMessages,           &KMyMoneyApp::slotEnableMessages},
1561             // *************
1562             // The help menu
1563             // *************
1564             {Action::HelpShow,                      &KMyMoneyApp::slotShowTipOfTheDay},
1565             // ***************************
1566             // Actions w/o main menu entry
1567             // ***************************
1568             //debug actions
1569 #ifdef KMM_DEBUG
1570             {Action::WizardNewUser,                 &KMyMoneyApp::slotNewFeature},
1571             {Action::DebugTraces,                   &KMyMoneyApp::slotToggleTraces},
1572 #endif
1573             {Action::DebugTimers,                   &KMyMoneyApp::slotToggleTimers},
1574         };
1575 
1576         for (auto connection = actionConnections.cbegin(); connection != actionConnections.cend(); ++connection)
1577             connect(lutActions[connection.key()], &QAction::triggered, this, connection.value());
1578     }
1579 
1580     // *************
1581     // Setting some of added actions checkable
1582     // *************
1583     {
1584         // Some actions are checkable,
1585         // so set them here
1586         const QVector<Action> checkableActions {
1587             Action::ViewTransactionDetail, Action::ViewHideReconciled, Action::ViewHideCategories,
1588             Action::ViewShowReconciledBalances, Action::ViewHideCategories,
1589 #ifdef KMM_DEBUG
1590             Action::DebugTraces,
1591             Action::DebugTimers,
1592 #endif
1593             Action::ViewShowAll
1594         };
1595 
1596         for (const auto& it : checkableActions) {
1597             lutActions[it]->setCheckable(true);
1598             lutActions[it]->setEnabled(true);
1599         }
1600     }
1601 
1602     // *************
1603     // Setting actions that are always enabled
1604     // *************
1605     {
1606         const QVector<eMenu::Action> alwaysEnabled {
1607             Action::HelpShow,
1608             Action::SettingsAllMessages,
1609             Action::ToolPerformance,
1610             Action::ToolCalculator,
1611         };
1612         for (const auto& action : alwaysEnabled) {
1613             lutActions[action]->setEnabled(true);
1614         }
1615     }
1616 
1617     // *************
1618     // Setting keyboard shortcuts for some of added actions
1619     // *************
1620     {
1621         const QVector<QPair<Action, QKeySequence>> actionShortcuts {
1622             {qMakePair(Action::EditFindTransaction,         Qt::CTRL + Qt::Key_F)},
1623             {qMakePair(Action::ViewTransactionDetail,       Qt::CTRL + Qt::Key_T)},
1624             {qMakePair(Action::ViewHideReconciled,          Qt::CTRL + Qt::Key_R)},
1625             {qMakePair(Action::ViewHideCategories,          Qt::CTRL + Qt::Key_U)},
1626             {qMakePair(Action::ViewShowAll,                 Qt::CTRL + Qt::SHIFT + Qt::Key_A)},
1627             {qMakePair(Action::StartReconciliation,         Qt::CTRL + Qt::SHIFT + Qt::Key_R)},
1628             {qMakePair(Action::NewTransaction,              Qt::CTRL + Qt::Key_Insert)},
1629             {qMakePair(Action::ToggleReconciliationFlag,    Qt::CTRL + Qt::Key_Space)},
1630             {qMakePair(Action::MarkCleared,                 Qt::CTRL + Qt::ALT+ Qt::Key_Space)},
1631             {qMakePair(Action::MarkReconciled,              Qt::CTRL + Qt::SHIFT + Qt::Key_Space)},
1632             {qMakePair(Action::SelectAllTransactions,       Qt::CTRL + Qt::Key_A)},
1633 #ifdef KMM_DEBUG
1634             {qMakePair(Action::WizardNewUser,               Qt::CTRL + Qt::Key_G)},
1635 #endif
1636             {qMakePair(Action::AssignTransactionsNumber,    Qt::CTRL + Qt::SHIFT + Qt::Key_N)}
1637         };
1638 
1639         for(const auto& it : actionShortcuts)
1640             lutActions[it.first]->setShortcut(it.second);
1641     }
1642 
1643     // *************
1644     // Misc settings
1645     // *************
1646     connect(onlineJobAdministration::instance(), &onlineJobAdministration::canSendCreditTransferChanged,  lutActions.value(Action::AccountCreditTransfer), &QAction::setEnabled);
1647 
1648     // Setup transaction detail switch
1649     lutActions[Action::ViewTransactionDetail]->setChecked(KMyMoneySettings::showRegisterDetailed());
1650     lutActions[Action::ViewHideReconciled]->setChecked(KMyMoneySettings::hideReconciledTransactions());
1651     lutActions[Action::ViewShowReconciledBalances]->setChecked(KMyMoneySettings::showReconciledBalances());
1652     lutActions[Action::ViewHideCategories]->setChecked(KMyMoneySettings::hideUnusedCategory());
1653     lutActions[Action::ViewShowAll]->setChecked(KMyMoneySettings::showAllAccounts());
1654 
1655     // *************
1656     // Adding actions to ActionCollection
1657     // *************
1658     actionCollection()->addActions(lutActions.values());
1659 
1660     // ************************
1661     // Currently unused actions
1662     // ************************
1663 #if 0
1664     new KToolBarPopupAction(i18n("View back"), "go-previous", 0, this, SLOT(slotShowPreviousView()), actionCollection(), "go_back");
1665     new KToolBarPopupAction(i18n("View forward"), "go-next", 0, this, SLOT(slotShowNextView()), actionCollection(), "go_forward");
1666 
1667     action("go_back")->setEnabled(false);
1668     action("go_forward")->setEnabled(false);
1669 #endif
1670 
1671     // use the absolute path to your kmymoneyui.rc file for testing purpose in createGUI();
1672     setupGUI();
1673 
1674     // reconnect about app entry to dialog with full credits information
1675     auto aboutApp = aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::AboutApp)));
1676     aboutApp->disconnect();
1677     connect(aboutApp, &QAction::triggered, this, &KMyMoneyApp::slotShowCredits);
1678 
1679     QMenu *menuContainer;
1680     menuContainer = static_cast<QMenu*>(factory()->container(QStringLiteral("import"), this));
1681     menuContainer->setIcon(Icons::get(Icon::DocumentImport));
1682 
1683     menuContainer = static_cast<QMenu*>(factory()->container(QStringLiteral("export"), this));
1684     menuContainer->setIcon(Icons::get(Icon::DocumentExport));
1685     return lutActions;
1686 }
1687 
1688 #ifdef KMM_DEBUG
1689 void KMyMoneyApp::dumpActions() const
1690 {
1691     const QList<QAction*> list = actionCollection()->actions();
1692     foreach (const auto it, list)
1693         std::cout << qPrintable(it->objectName()) << ": " << qPrintable(it->text()) << std::endl;
1694 }
1695 #endif
1696 
1697 bool KMyMoneyApp::isActionToggled(const Action _a)
1698 {
1699     return pActions[_a]->isChecked();
1700 }
1701 
1702 void KMyMoneyApp::initStatusBar()
1703 {
1704     ///////////////////////////////////////////////////////////////////
1705     // STATUSBAR
1706 
1707     d->m_statusLabel = new QLabel;
1708     statusBar()->addWidget(d->m_statusLabel);
1709     ready();
1710 
1711     // Initialization of progress bar taken from KDevelop ;-)
1712     d->m_progressBar = new QProgressBar;
1713     statusBar()->addWidget(d->m_progressBar);
1714     d->m_progressBar->setFixedHeight(d->m_progressBar->sizeHint().height() - 8);
1715 
1716     // hide the progress bar for now
1717     slotStatusProgressBar(-1, -1);
1718 }
1719 
1720 void KMyMoneyApp::initIcons()
1721 {
1722 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1723     const auto appDataIconsLocation = QStringLiteral("kmymoney/icons");
1724 #else
1725     const auto appDataIconsLocation = QStringLiteral("icons");
1726 #endif
1727 
1728     const QString customIconRelativePath = appDataIconsLocation + QStringLiteral("/hicolor/16x16/actions/account-add.png");
1729     QString customIconAbsolutePath;
1730     if (!MyMoneyUtils::isRunningAsAppImage()) {
1731         // find where our custom icons were installed based on an custom icon that we know should exist after installation
1732         customIconAbsolutePath = QStandardPaths::locate(QStandardPaths::AppDataLocation, customIconRelativePath);
1733         if (customIconAbsolutePath.isEmpty()) {
1734             qWarning("Custom icons were not found in any of the following QStandardPaths::AppDataLocation:");
1735             for (const auto& standardPath : QStandardPaths::standardLocations(QStandardPaths::AppDataLocation))
1736                 qWarning() << standardPath;
1737         }
1738     } else {
1739         // according to https://docs.appimage.org/packaging-guide/ingredients.html#open-source-applications
1740         // QStandardPaths::AppDataLocation is unreliable on AppImages, so apply workaround here in case we fail to find icons
1741         const auto appImageAppDataLocation =
1742             QString("%1%2%3").arg(QCoreApplication::applicationDirPath(), QString("/../share/kmymoney/"), customIconRelativePath);
1743         if (QFile::exists(appImageAppDataLocation)) {
1744             customIconAbsolutePath = appImageAppDataLocation;
1745         } else {
1746             qWarning("Custom icons were not found in the following location:");
1747             qWarning() << appImageAppDataLocation;
1748         }
1749     }
1750 
1751     // add our custom icons path to icons search path
1752     if (!customIconAbsolutePath.isEmpty()) {
1753         customIconAbsolutePath.chop(customIconRelativePath.length());
1754         customIconAbsolutePath.append(appDataIconsLocation);
1755         auto paths = QIcon::themeSearchPaths();
1756         paths.append(customIconAbsolutePath);
1757         QIcon::setThemeSearchPaths(paths);
1758     }
1759 
1760     qDebug() << "System icon theme as reported by QT: " << QIcon::themeName();
1761 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1762     auto themeName = QStringLiteral("breeze");                      // only breeze is available for craft packages
1763 #else
1764     auto themeName = KMyMoneySettings::iconsTheme();                        // get theme user wants
1765     if (!themeName.isEmpty() && themeName != QStringLiteral("system"))  // if it isn't default theme then set it
1766         QIcon::setThemeName(themeName);
1767     else
1768         themeName = QIcon::themeName();
1769 #endif
1770 
1771     setUpMappings(themeName);
1772 }
1773 
1774 void KMyMoneyApp::saveOptions()
1775 {
1776     KConfigGroup grp = d->m_config->group("General Options");
1777     grp.writeEntry("Geometry", size());
1778 
1779     grp.writeEntry("Show Statusbar", actionCollection()->action(KStandardAction::name(KStandardAction::ShowStatusbar))->isChecked());
1780 
1781     KConfigGroup toolbarGrp = d->m_config->group("mainToolBar");
1782     toolBar("mainToolBar")->saveSettings(toolbarGrp);
1783 
1784     d->m_recentFiles->saveEntries(d->m_config->group("Recent Files"));
1785 
1786 }
1787 
1788 
1789 void KMyMoneyApp::readOptions()
1790 {
1791     KConfigGroup grp = d->m_config->group("General Options");
1792 
1793 
1794     pActions[Action::ViewHideReconciled]->setChecked(KMyMoneySettings::hideReconciledTransactions());
1795     pActions[Action::ViewShowReconciledBalances]->setChecked(KMyMoneySettings::showReconciledBalances());
1796     pActions[Action::ViewHideCategories]->setChecked(KMyMoneySettings::hideUnusedCategory());
1797 
1798     d->m_recentFiles->loadEntries(d->m_config->group("Recent Files"));
1799 
1800     // Startdialog is written in the settings dialog
1801     d->m_startDialog = grp.readEntry("StartDialog", true);
1802 }
1803 
1804 #ifdef KMM_DEBUG
1805 void KMyMoneyApp::resizeEvent(QResizeEvent* ev)
1806 {
1807     KMainWindow::resizeEvent(ev);
1808     d->updateCaption();
1809 }
1810 #endif
1811 
1812 bool KMyMoneyApp::queryClose()
1813 {
1814     if (!isReady())
1815         return false;
1816 
1817     if (!slotFileClose())
1818         return false;
1819 
1820     saveOptions();
1821     qApp->quit();
1822     return true;
1823 }
1824 
1825 /////////////////////////////////////////////////////////////////////
1826 // SLOT IMPLEMENTATION
1827 /////////////////////////////////////////////////////////////////////
1828 void KMyMoneyApp::slotFileInfoDialog()
1829 {
1830     QPointer<KMyMoneyFileInfoDlg> dlg = new KMyMoneyFileInfoDlg(0);
1831     dlg->exec();
1832     delete dlg;
1833 }
1834 
1835 void KMyMoneyApp::slotPerformanceTest()
1836 {
1837     // dump performance report to stderr
1838 
1839     int measurement[2];
1840     QTime timer;
1841     MyMoneyAccount acc;
1842 
1843     qDebug("--- Starting performance tests ---");
1844 
1845     // AccountList
1846 //  MyMoneyFile::instance()->preloadCache();
1847     measurement[0] = measurement[1] = 0;
1848     timer.start();
1849     for (int i = 0; i < 1000; ++i) {
1850         QList<MyMoneyAccount> list;
1851 
1852         MyMoneyFile::instance()->accountList(list);
1853         measurement[i != 0] = timer.elapsed();
1854     }
1855     std::cerr << "accountList()" << std::endl;
1856     std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1857     std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl;
1858     std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
1859 
1860     // Balance of asset account(s)
1861 //  MyMoneyFile::instance()->preloadCache();
1862     measurement[0] = measurement[1] = 0;
1863     acc = MyMoneyFile::instance()->asset();
1864     for (int i = 0; i < 1000; ++i) {
1865         timer.start();
1866         MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id());
1867         measurement[i != 0] += timer.elapsed();
1868     }
1869     std::cerr << "balance(Asset)" << std::endl;
1870     std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1871     std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
1872 
1873     // total balance of asset account
1874 //  MyMoneyFile::instance()->preloadCache();
1875     measurement[0] = measurement[1] = 0;
1876     acc = MyMoneyFile::instance()->asset();
1877     for (int i = 0; i < 1000; ++i) {
1878         timer.start();
1879         MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id());
1880         measurement[i != 0] += timer.elapsed();
1881     }
1882     std::cerr << "totalBalance(Asset)" << std::endl;
1883     std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1884     std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
1885 
1886     // Balance of expense account(s)
1887 //  MyMoneyFile::instance()->preloadCache();
1888     measurement[0] = measurement[1] = 0;
1889     acc = MyMoneyFile::instance()->expense();
1890     for (int i = 0; i < 1000; ++i) {
1891         timer.start();
1892         MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id());
1893         measurement[i != 0] += timer.elapsed();
1894     }
1895     std::cerr << "balance(Expense)" << std::endl;
1896     std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1897     std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
1898 
1899     // total balance of expense account
1900 //  MyMoneyFile::instance()->preloadCache();
1901     measurement[0] = measurement[1] = 0;
1902     acc = MyMoneyFile::instance()->expense();
1903     timer.start();
1904     for (int i = 0; i < 1000; ++i) {
1905         MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id());
1906         measurement[i != 0] = timer.elapsed();
1907     }
1908     std::cerr << "totalBalance(Expense)" << std::endl;
1909     std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1910     std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl;
1911     std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
1912 
1913     // transaction list
1914 //  MyMoneyFile::instance()->preloadCache();
1915     measurement[0] = measurement[1] = 0;
1916     if (MyMoneyFile::instance()->asset().accountCount()) {
1917         MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]);
1918         filter.setDateFilter(QDate(), QDate::currentDate());
1919         QList<MyMoneyTransaction> list;
1920 
1921         timer.start();
1922         for (int i = 0; i < 100; ++i) {
1923             list = MyMoneyFile::instance()->transactionList(filter);
1924             measurement[i != 0] = timer.elapsed();
1925         }
1926         std::cerr << "transactionList()" << std::endl;
1927         std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1928         std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl;
1929         std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl;
1930     }
1931 
1932     // transaction list
1933 //  MyMoneyFile::instance()->preloadCache();
1934     measurement[0] = measurement[1] = 0;
1935     if (MyMoneyFile::instance()->asset().accountCount()) {
1936         MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]);
1937         filter.setDateFilter(QDate(), QDate::currentDate());
1938         QList<MyMoneyTransaction> list;
1939 
1940         timer.start();
1941         for (int i = 0; i < 100; ++i) {
1942             MyMoneyFile::instance()->transactionList(list, filter);
1943             measurement[i != 0] = timer.elapsed();
1944         }
1945         std::cerr << "transactionList(list)" << std::endl;
1946         std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1947         std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl;
1948         std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl;
1949     }
1950 //  MyMoneyFile::instance()->preloadCache();
1951 }
1952 
1953 bool KMyMoneyApp::isDatabase()
1954 {
1955     return (d->m_storageInfo.isOpened && ((d->m_storageInfo.type == eKMyMoney::StorageType::SQL)));
1956 }
1957 
1958 bool KMyMoneyApp::isNativeFile()
1959 {
1960     return (d->m_storageInfo.isOpened && (d->m_storageInfo.type == eKMyMoney::StorageType::SQL || d->m_storageInfo.type == eKMyMoney::StorageType::XML));
1961 }
1962 
1963 bool KMyMoneyApp::fileOpen() const
1964 {
1965     return d->m_storageInfo.isOpened;
1966 }
1967 
1968 KMyMoneyAppCallback KMyMoneyApp::progressCallback()
1969 {
1970     return &KMyMoneyApp::progressCallback;
1971 }
1972 
1973 void KMyMoneyApp::consistencyCheck(bool alwaysDisplayResult)
1974 {
1975     d->consistencyCheck(alwaysDisplayResult);
1976 }
1977 
1978 bool KMyMoneyApp::isImportableFile(const QUrl &url)
1979 {
1980     bool result = false;
1981 
1982     // Iterate through the plugins and see if there's a loaded plugin who can handle it
1983     QMap<QString, KMyMoneyPlugin::ImporterPlugin*>::const_iterator it_plugin = pPlugins.importer.constBegin();
1984     while (it_plugin != pPlugins.importer.constEnd()) {
1985         if ((*it_plugin)->isMyFormat(url.toLocalFile())) {
1986             result = true;
1987             break;
1988         }
1989         ++it_plugin;
1990     }
1991 
1992     // If we did not find a match, try importing it as a KMM statement file,
1993     // which is really just for testing.  the statement file is not exposed
1994     // to users.
1995     if (it_plugin == pPlugins.importer.constEnd())
1996         if (MyMoneyStatement::isStatementFile(url.path()))
1997             result = true;
1998 
1999     // Place code here to test for QIF and other locally-supported formats
2000     // (i.e. not a plugin). If you add them here, be sure to add it to
2001     // the webConnect function.
2002 
2003     return result;
2004 }
2005 
2006 bool KMyMoneyApp::isFileOpenedInAnotherInstance(const QUrl &url)
2007 {
2008     const auto instances = instanceList();
2009 #ifdef KMM_DBUS
2010     // check if there are other instances which might have this file open
2011     for (const auto& instance : instances) {
2012         QDBusInterface remoteApp(instance, "/KMymoney", "org.kde.kmymoney");
2013         QDBusReply<QString> reply = remoteApp.call("filename");
2014         if (!reply.isValid())
2015             qDebug("D-Bus error while calling app->filename()");
2016         else if (reply.value() == url.url())
2017             return true;
2018     }
2019 #else
2020     Q_UNUSED(url)
2021 #endif
2022     return false;
2023 }
2024 
2025 void KMyMoneyApp::slotShowTransactionDetail()
2026 {
2027 
2028 }
2029 
2030 void KMyMoneyApp::slotHideReconciledTransactions()
2031 {
2032     KMyMoneySettings::setHideReconciledTransactions(pActions[Action::ViewHideReconciled]->isChecked());
2033     d->m_myMoneyView->slotRefreshViews();
2034 }
2035 
2036 void KMyMoneyApp::slotShowReconciledBalances()
2037 {
2038     KMyMoneySettings::setShowReconciledBalances(pActions[Action::ViewShowReconciledBalances]->isChecked());
2039     d->m_myMoneyView->slotRefreshViews();
2040 }
2041 
2042 void KMyMoneyApp::slotHideUnusedCategories()
2043 {
2044     KMyMoneySettings::setHideUnusedCategory(pActions[Action::ViewHideCategories]->isChecked());
2045     d->m_myMoneyView->slotRefreshViews();
2046 }
2047 
2048 void KMyMoneyApp::slotShowAllAccounts()
2049 {
2050     KMyMoneySettings::setShowAllAccounts(pActions[Action::ViewShowAll]->isChecked());
2051     d->m_myMoneyView->slotRefreshViews();
2052 }
2053 
2054 #ifdef KMM_DEBUG
2055 void KMyMoneyApp::slotFileFileInfo()
2056 {
2057     if (!d->m_storageInfo.isOpened) {
2058         KMessageBox::information(this, i18n("No KMyMoneyFile open"));
2059         return;
2060     }
2061 
2062     QFile g("kmymoney.dump");
2063     g.open(QIODevice::WriteOnly);
2064     QDataStream st(&g);
2065     MyMoneyStorageDump dumper;
2066     dumper.writeStream(st, MyMoneyFile::instance()->storage());
2067     g.close();
2068 }
2069 
2070 void KMyMoneyApp::slotToggleTraces()
2071 {
2072     MyMoneyTracer::onOff(pActions[Action::DebugTraces]->isChecked() ? 1 : 0);
2073 }
2074 #endif
2075 
2076 void KMyMoneyApp::slotToggleTimers()
2077 {
2078     extern bool timersOn; // main.cpp
2079 
2080     timersOn = pActions[Action::DebugTimers]->isChecked();
2081 }
2082 
2083 QString KMyMoneyApp::slotStatusMsg(const QString &text)
2084 {
2085     ///////////////////////////////////////////////////////////////////
2086     // change status message permanently
2087     QString previousMessage = d->m_statusLabel->text();
2088     d->m_applicationIsReady = false;
2089 
2090     QString currentMessage = text;
2091     if (currentMessage.isEmpty() || currentMessage == i18nc("Application is ready to use", "Ready.")) {
2092         d->m_applicationIsReady = true;
2093         currentMessage = i18nc("Application is ready to use", "Ready.");
2094     }
2095     statusBar()->clearMessage();
2096     d->m_statusLabel->setText(currentMessage);
2097     return previousMessage;
2098 }
2099 
2100 void KMyMoneyApp::ready()
2101 {
2102     slotStatusMsg(QString());
2103 }
2104 
2105 bool KMyMoneyApp::isReady()
2106 {
2107     return d->m_applicationIsReady;
2108 }
2109 
2110 void KMyMoneyApp::slotStatusProgressBar(int current, int total)
2111 {
2112     if (total == -1 && current == -1) {     // reset
2113         if (d->m_progressTimer) {
2114             d->m_progressTimer->start(500);     // remove from screen in 500 msec
2115             d->m_progressBar->setValue(d->m_progressBar->maximum());
2116         }
2117 
2118     } else if (total != 0) {                // init
2119         d->m_progressTimer->stop();
2120         d->m_progressBar->setMaximum(total);
2121         d->m_progressBar->setValue(0);
2122         d->m_progressBar->show();
2123         d->m_lastUpdate = QTime::currentTime();
2124 
2125     } else {                                // update
2126         const auto currentTime = QTime::currentTime();
2127         // only process painting if last update is at least 200 ms ago
2128         if (abs(d->m_lastUpdate.msecsTo(currentTime)) > 200) {
2129             d->m_progressBar->setValue(current);
2130             d->m_lastUpdate = currentTime;
2131         }
2132     }
2133 }
2134 
2135 void KMyMoneyApp::slotStatusProgressDone()
2136 {
2137     d->m_progressTimer->stop();
2138     d->m_progressBar->reset();
2139     d->m_progressBar->hide();
2140     d->m_progressBar->setValue(0);
2141 }
2142 
2143 void KMyMoneyApp::progressCallback(int current, int total, const QString& msg)
2144 {
2145     if (!msg.isEmpty())
2146         kmymoney->slotStatusMsg(msg);
2147 
2148     kmymoney->slotStatusProgressBar(current, total);
2149 }
2150 
2151 void KMyMoneyApp::slotFileViewPersonal()
2152 {
2153     if (!d->m_storageInfo.isOpened) {
2154         KMessageBox::information(this, i18n("No KMyMoneyFile open"));
2155         return;
2156     }
2157 
2158     KMSTATUS(i18n("Viewing personal data..."));
2159 
2160     MyMoneyFile* file = MyMoneyFile::instance();
2161     MyMoneyPayee user = file->user();
2162 
2163     QPointer<EditPersonalDataDlg> editPersonalDataDlg = new EditPersonalDataDlg(user.name(), user.address(),
2164             user.city(), user.state(), user.postcode(), user.telephone(),
2165             user.email(), this, i18n("Edit Personal Data"));
2166 
2167     if (editPersonalDataDlg->exec() == QDialog::Accepted && editPersonalDataDlg != 0) {
2168         user.setName(editPersonalDataDlg->userName());
2169         user.setAddress(editPersonalDataDlg->userStreet());
2170         user.setCity(editPersonalDataDlg->userTown());
2171         user.setState(editPersonalDataDlg->userCountry());
2172         user.setPostcode(editPersonalDataDlg->userPostcode());
2173         user.setTelephone(editPersonalDataDlg->userTelephone());
2174         user.setEmail(editPersonalDataDlg->userEmail());
2175         MyMoneyFileTransaction ft;
2176         try {
2177             file->setUser(user);
2178             ft.commit();
2179         } catch (const MyMoneyException &e) {
2180             KMessageBox::information(this, i18n("Unable to store user information: %1", QString::fromLatin1(e.what())));
2181         }
2182     }
2183     delete editPersonalDataDlg;
2184 }
2185 
2186 void KMyMoneyApp::slotLoadAccountTemplates()
2187 {
2188     KMSTATUS(i18n("Importing account templates."));
2189 
2190     QPointer<KLoadTemplateDlg> dlg = new KLoadTemplateDlg();
2191     if (dlg->exec() == QDialog::Accepted && dlg != 0) {
2192         MyMoneyFileTransaction ft;
2193         try {
2194             // import the account templates
2195             QList<MyMoneyTemplate> templates = dlg->templates();
2196             QList<MyMoneyTemplate>::iterator it_t;
2197             for (it_t = templates.begin(); it_t != templates.end(); ++it_t) {
2198                 (*it_t).importTemplate(progressCallback);
2199             }
2200             ft.commit();
2201         } catch (const MyMoneyException &e) {
2202             KMessageBox::detailedSorry(this, i18n("Unable to import template(s)"), QString::fromLatin1(e.what()));
2203         }
2204     }
2205     delete dlg;
2206 }
2207 
2208 void KMyMoneyApp::slotSaveAccountTemplates()
2209 {
2210     KMSTATUS(i18n("Exporting account templates."));
2211 
2212     QString savePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/templates/" + QLocale().name();
2213     QDir templatesDir(savePath);
2214     if (!templatesDir.exists())
2215         templatesDir.mkpath(savePath);
2216     QString newName = QFileDialog::getSaveFileName(this, i18n("Save as..."), savePath,
2217                       i18n("KMyMoney template files (*.kmt);;All files (*)"));
2218 
2219     //
2220     // If there is no file extension, then append a .kmt at the end of the file name.
2221     // If there is a file extension, make sure it is .kmt, delete any others.
2222     //
2223     if (!newName.isEmpty()) {
2224         // find last . delimiter
2225         int nLoc = newName.lastIndexOf('.');
2226         if (nLoc != -1) {
2227             QString strExt, strTemp;
2228             strTemp = newName.left(nLoc + 1);
2229             strExt = newName.right(newName.length() - (nLoc + 1));
2230             if ((strExt.indexOf("kmt", 0, Qt::CaseInsensitive) == -1)) {
2231                 strTemp.append("kmt");
2232                 //append to make complete file name
2233                 newName = strTemp;
2234             }
2235         } else {
2236             newName.append(".kmt");
2237         }
2238 
2239         if (okToWriteFile(QUrl::fromLocalFile(newName))) {
2240             QPointer <KTemplateExportDlg> dlg = new KTemplateExportDlg(this);
2241             if (dlg->exec() == QDialog::Accepted && dlg) {
2242                 MyMoneyTemplate templ;
2243                 templ.setTitle(dlg->title());
2244                 templ.setShortDescription(dlg->shortDescription());
2245                 templ.setLongDescription(dlg->longDescription());
2246                 templ.exportTemplate(progressCallback);
2247                 templ.saveTemplate(QUrl::fromLocalFile(newName));
2248             }
2249             delete dlg;
2250         }
2251     }
2252 }
2253 
2254 bool KMyMoneyApp::okToWriteFile(const QUrl &url)
2255 {
2256     Q_UNUSED(url)
2257 
2258     // check if the file exists and warn the user
2259     bool reallySaveFile = true;
2260 
2261     if (KMyMoneyUtils::fileExists(url)) {
2262         if (KMessageBox::warningYesNo(this, QLatin1String("<qt>") + i18n("The file <b>%1</b> already exists. Do you really want to overwrite it?", url.toDisplayString(QUrl::PreferLocalFile)) + QLatin1String("</qt>"), i18n("File already exists")) != KMessageBox::Yes)
2263             reallySaveFile = false;
2264     }
2265     return reallySaveFile;
2266 }
2267 
2268 void KMyMoneyApp::slotSettings()
2269 {
2270     // if we already have an instance of the settings dialog, then use it
2271     if (KConfigDialog::showDialog("KMyMoney-Settings"))
2272         return;
2273 
2274     // otherwise, we have to create it
2275     auto dlg = new KSettingsKMyMoney(this, "KMyMoney-Settings", KMyMoneySettings::self());
2276     connect(dlg, &KSettingsKMyMoney::settingsChanged, this, &KMyMoneyApp::slotUpdateConfiguration);
2277     dlg->show();
2278 }
2279 
2280 void KMyMoneyApp::slotShowCredits()
2281 {
2282     KAboutData aboutData = initializeCreditsData();
2283     KAboutApplicationDialog dlg(aboutData, this);
2284     dlg.exec();
2285 }
2286 
2287 void KMyMoneyApp::slotUpdateConfiguration(const QString &dialogName)
2288 {
2289     if(dialogName.compare(QLatin1String("Plugins")) == 0) {
2290         KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Reorganize, pPlugins, this, guiFactory());
2291         actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(d->canFileSaveAs());
2292         onlineJobAdministration::instance()->updateActions();
2293         onlineJobAdministration::instance()->setOnlinePlugins(pPlugins.extended);
2294         d->m_myMoneyView->setOnlinePlugins(pPlugins.online);
2295         d->updateActions();
2296         d->m_myMoneyView->slotRefreshViews();
2297         return;
2298     }
2299     MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay());
2300 
2301 #ifdef ENABLE_UNFINISHEDFEATURES
2302     LedgerSeparator::setFirstFiscalDate(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay());
2303 #endif
2304 
2305     d->m_myMoneyView->updateViewType();
2306 
2307     // update the sql storage module settings
2308 //  MyMoneyStorageSql::setStartDate(KMyMoneySettings::startDate().date());
2309 
2310     // update the report module settings
2311     MyMoneyReport::setLineWidth(KMyMoneySettings::lineWidth());
2312 
2313     // update the holiday region configuration
2314     setHolidayRegion(KMyMoneySettings::holidayRegion());
2315 
2316     // Update view menu entries
2317     pActions[Action::ViewHideReconciled]->setChecked(KMyMoneySettings::hideReconciledTransactions());
2318     pActions[Action::ViewHideCategories]->setChecked(KMyMoneySettings::hideUnusedCategory());
2319     // calls slotRefreshViews()
2320     slotHideReconciledTransactions();
2321     slotHideUnusedCategories();
2322 
2323     // re-read autosave configuration
2324     d->m_autoSaveEnabled = KMyMoneySettings::autoSaveFile();
2325     d->m_autoSavePeriod = KMyMoneySettings::autoSavePeriod();
2326 
2327     // stop timer if turned off but running
2328     if (d->m_autoSaveTimer->isActive() && !d->m_autoSaveEnabled) {
2329         d->m_autoSaveTimer->stop();
2330     }
2331     // start timer if turned on and needed but not running
2332     if (!d->m_autoSaveTimer->isActive() && d->m_autoSaveEnabled && d->dirty()) {
2333         d->m_autoSaveTimer->setSingleShot(true);
2334         d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000);
2335     }
2336 
2337     d->setThemedCSS();
2338 }
2339 
2340 void KMyMoneyApp::slotBackupFile()
2341 {
2342     // Save the file first so isLocalFile() works
2343     if (d->m_myMoneyView && d->dirty())
2344 
2345     {
2346         if (KMessageBox::questionYesNo(this, i18n("The file must be saved first "
2347                                        "before it can be backed up.  Do you want to continue?")) == KMessageBox::No) {
2348             return;
2349 
2350         }
2351 
2352         slotFileSave();
2353     }
2354 
2355 
2356 
2357     if (d->m_storageInfo.url.isEmpty())
2358         return;
2359 
2360     if (!d->m_storageInfo.url.isLocalFile()) {
2361         KMessageBox::sorry(this,
2362                            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()),
2363 
2364                            i18n("Local files only"));
2365         return;
2366     }
2367 
2368     QPointer<KBackupDlg> backupDlg = new KBackupDlg(this);
2369     int returncode = backupDlg->exec();
2370 
2371     if (returncode == QDialog::Accepted && backupDlg != 0) {
2372         d->m_backupMount = backupDlg->mountCheckBoxChecked();
2373         d->m_proc.clearProgram();
2374         d->m_backupState = BACKUP_MOUNTING;
2375         d->m_mountpoint = backupDlg->mountPoint();
2376 
2377         if (d->m_backupMount) {
2378             slotBackupMount();
2379         } else {
2380             progressCallback(0, 300, "");
2381 #ifdef Q_OS_WIN
2382             d->m_ignoreBackupExitCode = true;
2383             QTimer::singleShot(0, this, SLOT(slotBackupHandleEvents()));
2384 #else
2385             // If we don't have to mount a device, we just issue
2386             // a dummy command to start the copy operation
2387             d->m_proc.setProgram("true");
2388             d->m_proc.start();
2389 #endif
2390         }
2391 
2392     }
2393 
2394     delete backupDlg;
2395 }
2396 
2397 void KMyMoneyApp::slotBackupMount()
2398 {
2399     progressCallback(0, 300, i18n("Mounting %1", d->m_mountpoint));
2400     d->m_proc.setProgram("mount");
2401     d->m_proc << d->m_mountpoint;
2402     d->m_proc.start();
2403 }
2404 
2405 bool KMyMoneyApp::slotBackupWriteFile()
2406 {
2407     QFileInfo fi(d->m_storageInfo.url.fileName());
2408     QString today = QDate::currentDate().toString("-yyyy-MM-dd.") + fi.suffix();
2409     QString backupfile = d->m_mountpoint + '/' + d->m_storageInfo.url.fileName();
2410     KMyMoneyUtils::appendCorrectFileExt(backupfile, today);
2411 
2412 #ifdef Q_OS_WIN
2413     // on windows, a leading slash is a problem if a drive letter follows
2414     // eg. "/Z:/path". In case we detect such a pattern, we simply remove
2415     // the leading slash
2416     const QRegularExpression re(QStringLiteral("/(?<path>\\w+:/.+)"),
2417                                 QRegularExpression::CaseInsensitiveOption|QRegularExpression::UseUnicodePropertiesOption
2418                                );
2419     const auto match = re.match(backupfile);
2420     if (match.hasMatch() && !match.captured(QStringLiteral("path")).isEmpty()) {
2421         backupfile = match.captured(QStringLiteral("path"));
2422     }
2423 #endif
2424 
2425     // check if file already exists and ask what to do
2426     QFileInfo fileInfo(backupfile);
2427     if (fileInfo.exists()) {
2428         int answer = KMessageBox::warningContinueCancel(this, i18n("Backup file for today exists on that device. Replace?"), i18n("Backup"), KGuiItem(i18n("&Replace")));
2429         if (answer == KMessageBox::Cancel) {
2430             return false;
2431         }
2432     } else {
2433         // if it does not exist, make sure the path exists
2434         const auto path = fileInfo.absolutePath();
2435         if (!QDir().mkpath(path)) {
2436             KMessageBox::error(this, i18nc("@info Error during backup", "Unable to create backup directory '%1'.", path));
2437             return false;
2438         }
2439     }
2440 
2441     progressCallback(50, 0, i18n("Writing %1", backupfile));
2442     d->m_proc.clearProgram();
2443 #ifdef Q_OS_WIN
2444     d->m_proc << "cmd.exe" << "/c" << "copy" << "/b" << "/y";
2445     d->m_proc << QDir::toNativeSeparators(d->m_storageInfo.url.toLocalFile()) << "+" << "nul" << QDir::toNativeSeparators(backupfile);
2446 #else
2447     d->m_proc << "cp" << "-f";
2448     d->m_proc << d->m_storageInfo.url.toLocalFile() << backupfile;
2449 #endif
2450     d->m_backupState = BACKUP_COPYING;
2451     qDebug() << "Backup cmd:" << d->m_proc.program();
2452     d->m_proc.start();
2453     return true;
2454 }
2455 
2456 void KMyMoneyApp::slotBackupUnmount()
2457 {
2458     progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint));
2459     d->m_proc.clearProgram();
2460     d->m_proc.setProgram("umount");
2461     d->m_proc << d->m_mountpoint;
2462     d->m_backupState = BACKUP_UNMOUNTING;
2463     d->m_proc.start();
2464 }
2465 
2466 void KMyMoneyApp::slotBackupFinish()
2467 {
2468     d->m_backupState = BACKUP_IDLE;
2469     progressCallback(-1, -1, QString());
2470     ready();
2471 }
2472 
2473 void KMyMoneyApp::slotBackupHandleEvents()
2474 {
2475     switch (d->m_backupState) {
2476     case BACKUP_MOUNTING:
2477 
2478         if (d->m_ignoreBackupExitCode ||
2479                 (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0)) {
2480             d->m_ignoreBackupExitCode = false;
2481             d->m_backupResult = 0;
2482             if (!slotBackupWriteFile()) {
2483                 d->m_backupResult = 1;
2484                 if (d->m_backupMount)
2485                     slotBackupUnmount();
2486                 else
2487                     slotBackupFinish();
2488             }
2489         } else {
2490             KMessageBox::information(this, i18n("Error mounting device"), i18n("Backup"));
2491             d->m_backupResult = 1;
2492             if (d->m_backupMount)
2493                 slotBackupUnmount();
2494             else
2495                 slotBackupFinish();
2496         }
2497         break;
2498 
2499     case BACKUP_COPYING:
2500         if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) {
2501 
2502             if (d->m_backupMount) {
2503                 slotBackupUnmount();
2504             } else {
2505                 progressCallback(300, 0, i18nc("Backup done", "Done"));
2506                 KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup"));
2507                 slotBackupFinish();
2508             }
2509         } else {
2510             qDebug("copy exit code is %d", d->m_proc.exitCode());
2511             d->m_backupResult = 1;
2512             KMessageBox::information(this, i18n("Error copying file to device"), i18n("Backup"));
2513             if (d->m_backupMount)
2514                 slotBackupUnmount();
2515             else
2516                 slotBackupFinish();
2517         }
2518         break;
2519 
2520 
2521     case BACKUP_UNMOUNTING:
2522         if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) {
2523 
2524             progressCallback(300, 0, i18nc("Backup done", "Done"));
2525             if (d->m_backupResult == 0)
2526                 KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup"));
2527         } else {
2528             KMessageBox::information(this, i18n("Error unmounting device"), i18n("Backup"));
2529         }
2530         slotBackupFinish();
2531         break;
2532 
2533     default:
2534         qWarning("Unknown state for backup operation!");
2535         progressCallback(-1, -1, QString());
2536         ready();
2537         break;
2538     }
2539 }
2540 
2541 void KMyMoneyApp::slotShowTipOfTheDay()
2542 {
2543     KTipDialog::showTip(d->m_myMoneyView, "", true);
2544 }
2545 
2546 void KMyMoneyApp::slotShowPreviousView()
2547 {
2548 
2549 }
2550 
2551 void KMyMoneyApp::slotShowNextView()
2552 {
2553 
2554 }
2555 
2556 void KMyMoneyApp::slotViewSelected(View view)
2557 {
2558     KMyMoneySettings::setLastViewSelected(static_cast<int>(view));
2559 }
2560 
2561 void KMyMoneyApp::slotGenerateSql()
2562 {
2563 //  QPointer<KGenerateSqlDlg> editor = new KGenerateSqlDlg(this);
2564 //  editor->setObjectName("Generate Database SQL");
2565 //  editor->exec();
2566 //  delete editor;
2567 }
2568 
2569 void KMyMoneyApp::slotToolsStartKCalc()
2570 {
2571     QString cmd = KMyMoneySettings::externalCalculator();
2572     // if none is present, we fall back to the default
2573     if (cmd.isEmpty()) {
2574 #if defined(Q_OS_WIN32)
2575         cmd = QLatin1String("calc");
2576 #elif defined(Q_OS_MAC)
2577         cmd = QLatin1String("open -a Calculator");
2578 #else
2579         cmd = QLatin1String("kcalc");
2580 #endif
2581     }
2582 #if KIO_VERSION < QT_VERSION_CHECK(5, 70, 0)
2583     KRun::runCommand(cmd, this);
2584 #else
2585     auto* job = new KIO::CommandLauncherJob(cmd, this);
2586     job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
2587     job->setWorkingDirectory(QString());
2588     job->start();
2589 #endif
2590 }
2591 
2592 void KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal)
2593 {
2594     MyMoneyFile *file = MyMoneyFile::instance();
2595     try {
2596         const MyMoneySecurity& sec = file->security(newAccount.currencyId());
2597         // Check the opening balance
2598         if (openingBal.isPositive() && newAccount.accountGroup() == eMyMoney::Account::Type::Liability) {
2599             QString message = i18n("This account is a liability and if the "
2600                                    "opening balance represents money owed, then it should be negative.  "
2601                                    "Negate the amount?\n\n"
2602                                    "Please click Yes to change the opening balance to %1,\n"
2603                                    "Please click No to leave the amount as %2,\n"
2604                                    "Please click Cancel to abort the account creation."
2605                                    , MyMoneyUtils::formatMoney(-openingBal, newAccount, sec)
2606                                    , MyMoneyUtils::formatMoney(openingBal, newAccount, sec));
2607 
2608             int ans = KMessageBox::questionYesNoCancel(this, message);
2609             if (ans == KMessageBox::Yes) {
2610                 openingBal = -openingBal;
2611 
2612             } else if (ans == KMessageBox::Cancel)
2613                 return;
2614         }
2615 
2616         file->createAccount(newAccount, parentAccount, brokerageAccount, openingBal);
2617 
2618     } catch (const MyMoneyException &e) {
2619         KMessageBox::information(this, i18n("Unable to add account: %1", QString::fromLatin1(e.what())));
2620     }
2621 }
2622 
2623 void KMyMoneyApp::slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent)
2624 {
2625     KNewInvestmentWizard::newInvestment(account, parent);
2626 }
2627 
2628 void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent)
2629 {
2630     KNewAccountDlg::newCategory(account, parent);
2631 }
2632 
2633 void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account)
2634 {
2635     KNewAccountDlg::newCategory(account, MyMoneyAccount());
2636 }
2637 
2638 void KMyMoneyApp::slotAccountNew(MyMoneyAccount& account)
2639 {
2640     NewAccountWizard::Wizard::newAccount(account);
2641 }
2642 
2643 void KMyMoneyApp::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount)
2644 {
2645     MyMoneyFile* file = MyMoneyFile::instance();
2646     // Add the schedule only if one exists
2647     //
2648     // Remember to modify the first split to reference the newly created account
2649     if (!newSchedule.name().isEmpty()) {
2650         try {
2651             // We assume at least 2 splits in the transaction
2652             MyMoneyTransaction t = newSchedule.transaction();
2653             if (t.splitCount() < 2) {
2654                 throw MYMONEYEXCEPTION_CSTRING("Transaction for schedule has less than 2 splits!");
2655             }
2656 
2657             MyMoneyFileTransaction ft;
2658             try {
2659                 file->addSchedule(newSchedule);
2660 
2661                 // in case of a loan account, we keep a reference to this
2662                 // schedule in the account
2663                 if (newAccount.accountType() == eMyMoney::Account::Type::Loan
2664                         || newAccount.accountType() == eMyMoney::Account::Type::AssetLoan) {
2665                     newAccount.setValue("schedule", newSchedule.id());
2666                     file->modifyAccount(newAccount);
2667                 }
2668                 ft.commit();
2669             } catch (const MyMoneyException &e) {
2670                 KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", QString::fromLatin1(e.what())));
2671             }
2672         } catch (const MyMoneyException &e) {
2673             KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", QString::fromLatin1(e.what())));
2674         }
2675     }
2676 }
2677 
2678 void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyInstitution& _dst)
2679 {
2680     MyMoneyAccount src(_src);
2681     src.setInstitutionId(_dst.id());
2682     MyMoneyFileTransaction ft;
2683     try {
2684         MyMoneyFile::instance()->modifyAccount(src);
2685         ft.commit();
2686     } catch (const MyMoneyException &e) {
2687         KMessageBox::sorry(this, i18n("<p><b>%1</b> cannot be moved to institution <b>%2</b>. Reason: %3</p>", src.name(), _dst.name(), QString::fromLatin1(e.what())));
2688     }
2689 }
2690 
2691 void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyAccount& _dst)
2692 {
2693     MyMoneyAccount src(_src);
2694     MyMoneyAccount dst(_dst);
2695     MyMoneyFileTransaction ft;
2696     try {
2697         MyMoneyFile::instance()->reparentAccount(src, dst);
2698         ft.commit();
2699     } catch (const MyMoneyException &e) {
2700         KMessageBox::sorry(this, i18n("<p><b>%1</b> cannot be moved to <b>%2</b>. Reason: %3</p>", src.name(), dst.name(), QString::fromLatin1(e.what())));
2701     }
2702 }
2703 
2704 void KMyMoneyApp::slotScheduleNew(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence)
2705 {
2706     KEditScheduleDlg::newSchedule(_t, occurrence);
2707 }
2708 
2709 void KMyMoneyApp::slotPayeeNew(const QString& newnameBase, QString& id)
2710 {
2711     KMyMoneyUtils::newPayee(newnameBase, id);
2712 }
2713 
2714 void KMyMoneyApp::slotNewFeature()
2715 {
2716 }
2717 
2718 // move a stock transaction from one investment account to another
2719 void KMyMoneyApp::Private::moveInvestmentTransaction(const QString& /*fromId*/,
2720         const QString& toId,
2721         const MyMoneyTransaction& tx)
2722 {
2723     MyMoneyAccount toInvAcc = MyMoneyFile::instance()->account(toId);
2724     MyMoneyTransaction t(tx);
2725     // first determine which stock we are dealing with.
2726     // fortunately, investment transactions have only one stock involved
2727     QString stockAccountId;
2728     QString stockSecurityId;
2729     MyMoneySplit s;
2730     foreach (const auto split, t.splits()) {
2731         stockAccountId = split.accountId();
2732         stockSecurityId =
2733             MyMoneyFile::instance()->account(stockAccountId).currencyId();
2734         if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) {
2735             s = split;
2736             break;
2737         }
2738     }
2739     // Now check the target investment account to see if it
2740     // contains a stock with this id
2741     QString newStockAccountId;
2742     foreach (const auto sAccount, toInvAcc.accountList()) {
2743         if (MyMoneyFile::instance()->account(sAccount).currencyId() ==
2744                 stockSecurityId) {
2745             newStockAccountId = sAccount;
2746             break;
2747         }
2748     }
2749     // if it doesn't exist, we need to add it as a copy of the old one
2750     // no 'copyAccount()' function??
2751     if (newStockAccountId.isEmpty()) {
2752         MyMoneyAccount stockAccount =
2753             MyMoneyFile::instance()->account(stockAccountId);
2754         MyMoneyAccount newStock;
2755         newStock.setName(stockAccount.name());
2756         newStock.setNumber(stockAccount.number());
2757         newStock.setDescription(stockAccount.description());
2758         newStock.setInstitutionId(stockAccount.institutionId());
2759         newStock.setOpeningDate(stockAccount.openingDate());
2760         newStock.setAccountType(stockAccount.accountType());
2761         newStock.setCurrencyId(stockAccount.currencyId());
2762         newStock.setClosed(stockAccount.isClosed());
2763         MyMoneyFile::instance()->addAccount(newStock, toInvAcc);
2764         newStockAccountId = newStock.id();
2765     }
2766     // now update the split and the transaction
2767     s.setAccountId(newStockAccountId);
2768     t.modifySplit(s);
2769     MyMoneyFile::instance()->modifyTransaction(t);
2770 }
2771 
2772 void KMyMoneyApp::showContextMenu(const QString& containerName)
2773 {
2774     QWidget* w = factory()->container(containerName, this);
2775     if (auto menu = dynamic_cast<QMenu*>(w))
2776         menu->exec(QCursor::pos());
2777     else
2778         qDebug("menu '%s' not found: w = %p, menu = %p", qPrintable(containerName), w, menu);
2779 }
2780 
2781 void KMyMoneyApp::slotPrintView()
2782 {
2783     d->m_myMoneyView->slotPrintView();
2784 }
2785 
2786 void KMyMoneyApp::Private::updateCaption()
2787 {
2788     auto caption = m_storageInfo.url.isEmpty() && m_myMoneyView && m_storageInfo.isOpened  ?
2789                    i18n("Untitled") :
2790                    m_storageInfo.url.fileName();
2791 
2792 #ifdef KMM_DEBUG
2793     caption += QString(" (%1 x %2)").arg(q->width()).arg(q->height());
2794 #endif
2795 
2796     q->setCaption(caption, MyMoneyFile::instance()->dirty());
2797 }
2798 
2799 void KMyMoneyApp::Private::updateActions()
2800 {
2801     const QVector<Action> actions
2802     {
2803         Action::FilePersonalData, Action::FileInformation, Action::FileImportTemplate, Action::FileExportTemplate,
2804 #ifdef KMM_DEBUG
2805         Action::FileDump,
2806 #endif
2807         Action::EditFindTransaction, Action::NewCategory, Action::ToolCurrencies, Action::ToolPrices, Action::ToolUpdatePrices,
2808         Action::ToolConsistency, Action::ToolPerformance, Action::NewAccount, Action::NewInstitution, Action::NewSchedule,
2809     };
2810 
2811     for (const auto &action : actions)
2812         pActions[action]->setEnabled(m_storageInfo.isOpened);
2813     pActions[Action::FileBackup]->setEnabled(m_storageInfo.isOpened && m_storageInfo.type == eKMyMoney::StorageType::XML);
2814 
2815     auto aC = q->actionCollection();
2816     aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(canFileSaveAs());
2817     aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Close)))->setEnabled(m_storageInfo.isOpened);
2818     pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(KMyMoneyUtils::canUpdateAllAccounts());
2819 }
2820 
2821 bool KMyMoneyApp::Private::canFileSaveAs() const
2822 {
2823     return (m_storageInfo.isOpened &&
2824             (!pPlugins.storage.isEmpty() &&
2825              !(pPlugins.storage.count() == 1 && pPlugins.storage.first()->storageType() == eKMyMoney::StorageType::GNC)));
2826 }
2827 
2828 void KMyMoneyApp::slotDataChanged()
2829 {
2830     d->fileAction(eKMyMoney::FileAction::Changed);
2831 }
2832 
2833 void KMyMoneyApp::slotCurrencyDialog()
2834 {
2835     QPointer<KCurrencyEditDlg> dlg = new KCurrencyEditDlg(this);
2836     dlg->exec();
2837     delete dlg;
2838 }
2839 
2840 void KMyMoneyApp::slotPriceDialog()
2841 {
2842     QPointer<KMyMoneyPriceDlg> dlg = new KMyMoneyPriceDlg(this);
2843     dlg->exec();
2844     delete dlg;
2845 }
2846 
2847 void KMyMoneyApp::slotFileConsistencyCheck()
2848 {
2849     d->consistencyCheck(true);
2850 }
2851 
2852 void KMyMoneyApp::Private::consistencyCheck(bool alwaysDisplayResult)
2853 {
2854     KMSTATUS(i18n("Running consistency check..."));
2855 
2856     MyMoneyFileTransaction ft;
2857     try {
2858         m_consistencyCheckResult = MyMoneyFile::instance()->consistencyCheck();
2859         ft.commit();
2860     } catch (const MyMoneyException &e) {
2861         m_consistencyCheckResult.append(i18n("Consistency check failed: %1", e.what()));
2862         // always display the result if the check failed
2863         alwaysDisplayResult = true;
2864     }
2865 
2866     // in case the consistency check was OK, we get a single line as result
2867     // in all erroneous cases, we get more than one line and force the
2868     // display of them.
2869 
2870     if (alwaysDisplayResult || m_consistencyCheckResult.size() > 1) {
2871         QString msg = i18n("The consistency check has found no issues in your data. Details are presented below.");
2872         if (m_consistencyCheckResult.size() > 1)
2873             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.");
2874         // install a context menu for the list after the dialog is displayed
2875         QTimer::singleShot(500, q, SLOT(slotInstallConsistencyCheckContextMenu()));
2876         KMessageBox::informationList(0, msg, m_consistencyCheckResult, i18n("Consistency check result"));
2877     }
2878     // this data is no longer needed
2879     m_consistencyCheckResult.clear();
2880 }
2881 
2882 void KMyMoneyApp::Private::copyConsistencyCheckResults()
2883 {
2884     QClipboard *clipboard = QApplication::clipboard();
2885     clipboard->setText(m_consistencyCheckResult.join(QLatin1String("\n")));
2886 }
2887 
2888 void KMyMoneyApp::Private::saveConsistencyCheckResults()
2889 {
2890     QUrl fileUrl = QFileDialog::getSaveFileUrl(q);
2891 
2892     if (!fileUrl.isEmpty()) {
2893         QFile file(fileUrl.toLocalFile());
2894         if (file.open(QFile::WriteOnly | QFile::Append | QFile::Text)) {
2895             QTextStream out(&file);
2896             out << m_consistencyCheckResult.join(QLatin1String("\n"));
2897             file.close();
2898         }
2899     }
2900 }
2901 
2902 void KMyMoneyApp::Private::setThemedCSS()
2903 {
2904     const QStringList CSSnames {QStringLiteral("kmymoney.css"), QStringLiteral("welcome.css")};
2905 #if defined(Q_OS_MAC)
2906     const auto rcDir = QStringLiteral("/kmymoney/html/");
2907 #else
2908     const QString rcDir("/html/");
2909 #endif
2910     QStringList defaultCSSDirs;
2911     if (!MyMoneyUtils::isRunningAsAppImage()) {
2912         defaultCSSDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, rcDir, QStandardPaths::LocateDirectory);
2913         if (defaultCSSDirs.isEmpty()) {
2914             qWarning("the 'html' folder was not found in any of the following QStandardPaths::AppDataLocation:");
2915             for (const auto& standardPath : QStandardPaths::standardLocations(QStandardPaths::AppDataLocation))
2916                 qWarning() << standardPath;
2917         } else {
2918             qDebug() << "Found html dir(s):" << defaultCSSDirs;
2919         }
2920     } else {
2921         // according to https://docs.appimage.org/packaging-guide/ingredients.html#open-source-applications
2922         // QStandardPaths::AppDataLocation is unreliable on AppImages, so apply workaround here in case we fail to find icons
2923         // watch out for QStringBuilder here; for yet unknown reason it causes segmentation fault on startup
2924         const auto appImageAppDataLocation = QString("%1%2%3").arg(QCoreApplication::applicationDirPath(), QString("/../share/kmymoney"), rcDir);
2925         if (QFile::exists(appImageAppDataLocation + CSSnames.first())) {
2926             defaultCSSDirs.append(appImageAppDataLocation);
2927         } else {
2928             qWarning("CSS file was not found in the following location:");
2929             qWarning() << appImageAppDataLocation;
2930         }
2931     }
2932 
2933     // scan the list of directories to find the ones that really
2934     // contains all files we look for
2935     QString defaultCSSDir;
2936     foreach (const auto dir, defaultCSSDirs) {
2937         defaultCSSDir = dir;
2938         foreach (const auto CSSname, CSSnames) {
2939             QFileInfo fileInfo(defaultCSSDir + CSSname);
2940             if (!fileInfo.exists()) {
2941                 qDebug() << "Tried " << fileInfo.absoluteFilePath() << "but it doesn't exist";
2942                 defaultCSSDir.clear();
2943                 break;
2944             }
2945         }
2946         if (!defaultCSSDir.isEmpty()) {
2947             qDebug() << "Found an 'html' folder with CSS files:" << defaultCSSDir;
2948             break;
2949         }
2950     }
2951 
2952     // make sure we have the local directory where the themed version is stored
2953     const QString themedCSSDir = QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation).first() + "/html/";
2954     QDir().mkpath(themedCSSDir);
2955 
2956     foreach (const auto CSSname, CSSnames) {
2957         const QString defaultCSSFilename = defaultCSSDir + CSSname;
2958         QFileInfo fileInfo(defaultCSSFilename);
2959         if (fileInfo.exists()) {
2960             const QString themedCSSFilename = themedCSSDir + CSSname;
2961             QFile::remove(themedCSSFilename);
2962             if (QFile::copy(defaultCSSFilename, themedCSSFilename)) {
2963                 QFile cssFile (themedCSSFilename);
2964                 if (cssFile.open(QIODevice::ReadWrite)) {
2965                     QTextStream cssStream(&cssFile);
2966                     auto cssText = cssStream.readAll();
2967                     cssText.replace(QLatin1String("./"), defaultCSSDir, Qt::CaseSensitive);
2968                     cssText.replace(QLatin1String("WindowText"),    KMyMoneySettings::schemeColor(SchemeColor::WindowText).name(),        Qt::CaseSensitive);
2969                     cssText.replace(QLatin1String("Window"),        KMyMoneySettings::schemeColor(SchemeColor::WindowBackground).name(),  Qt::CaseSensitive);
2970                     cssText.replace(QLatin1String("HighlightText"), KMyMoneySettings::schemeColor(SchemeColor::ListHighlightText).name(), Qt::CaseSensitive);
2971                     cssText.replace(QLatin1String("Highlight"),     KMyMoneySettings::schemeColor(SchemeColor::ListHighlight).name(),     Qt::CaseSensitive);
2972                     cssText.replace(QLatin1String("black"),         KMyMoneySettings::schemeColor(SchemeColor::ListGrid).name(),          Qt::CaseSensitive);
2973                     cssStream.seek(0);
2974                     cssStream << cssText;
2975                     cssFile.close();
2976                 }
2977             }
2978         }
2979     }
2980 }
2981 
2982 void KMyMoneyApp::slotCheckSchedules()
2983 {
2984     if (KMyMoneySettings::checkSchedule() == true) {
2985 
2986         KMSTATUS(i18n("Checking for overdue scheduled transactions..."));
2987         MyMoneyFile *file = MyMoneyFile::instance();
2988         QDate checkDate = QDate::currentDate().addDays(KMyMoneySettings::checkSchedulePreview());
2989 
2990         QList<MyMoneySchedule> scheduleList =  file->scheduleList();
2991         QList<MyMoneySchedule>::Iterator it;
2992 
2993         eDialogs::ScheduleResultCode rc = eDialogs::ScheduleResultCode::Enter;
2994         for (it = scheduleList.begin(); (it != scheduleList.end()) && (rc != eDialogs::ScheduleResultCode::Cancel); ++it) {
2995             // Get the copy in the file because it might be modified by commitTransaction
2996             MyMoneySchedule schedule = file->schedule((*it).id());
2997 
2998             if (schedule.autoEnter()) {
2999                 try {
3000                     while (!schedule.isFinished() && (schedule.adjustedNextDueDate() <= checkDate) //
3001                             && rc != eDialogs::ScheduleResultCode::Ignore //
3002                             && rc != eDialogs::ScheduleResultCode::Cancel) {
3003                         rc = d->m_myMoneyView->enterSchedule(schedule, true, true);
3004                         schedule = file->schedule((*it).id()); // get a copy of the modified schedule
3005                     }
3006                 } catch (const MyMoneyException &) {
3007                 }
3008             }
3009             if (rc == eDialogs::ScheduleResultCode::Ignore) {
3010                 // if the current schedule was ignored then we must make sure that the user can still enter the next scheduled transaction
3011                 rc = eDialogs::ScheduleResultCode::Enter;
3012             }
3013         }
3014     }
3015 }
3016 
3017 void KMyMoneyApp::writeLastUsedDir(const QString& directory)
3018 {
3019     //get global config object for our app.
3020     KSharedConfigPtr kconfig = KSharedConfig::openConfig();
3021     if (kconfig) {
3022         KConfigGroup grp = kconfig->group("General Options");
3023 
3024         //write path entry, no error handling since its void.
3025         grp.writeEntry("LastUsedDirectory", directory);
3026     }
3027 }
3028 
3029 void KMyMoneyApp::writeLastUsedFile(const QString& fileName)
3030 {
3031     //get global config object for our app.
3032     KSharedConfigPtr kconfig = KSharedConfig::openConfig();
3033     if (kconfig) {
3034         KConfigGroup grp = d->m_config->group("General Options");
3035 
3036         // write path entry, no error handling since its void.
3037         // use a standard string, as fileName could contain a protocol
3038         // e.g. file:/home/thb/....
3039         grp.writeEntry("LastUsedFile", fileName);
3040     }
3041 }
3042 
3043 QString KMyMoneyApp::readLastUsedDir() const
3044 {
3045     QString str;
3046 
3047     //get global config object for our app.
3048     KSharedConfigPtr kconfig = KSharedConfig::openConfig();
3049     if (kconfig) {
3050         KConfigGroup grp = d->m_config->group("General Options");
3051 
3052         //read path entry.  Second parameter is the default if the setting is not found, which will be the default document path.
3053         str = grp.readEntry("LastUsedDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
3054         // if the path stored is empty, we use the default nevertheless
3055         if (str.isEmpty())
3056             str = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
3057     }
3058 
3059     return str;
3060 }
3061 
3062 QString KMyMoneyApp::readLastUsedFile() const
3063 {
3064     QString str;
3065 
3066     // get global config object for our app.
3067     KSharedConfigPtr kconfig = KSharedConfig::openConfig();
3068     if (kconfig) {
3069         KConfigGroup grp = d->m_config->group("General Options");
3070 
3071         // read filename entry.
3072         str = grp.readEntry("LastUsedFile", "");
3073     }
3074 
3075     return str;
3076 }
3077 
3078 QString KMyMoneyApp::filename() const
3079 {
3080     return d->m_storageInfo.url.url();
3081 }
3082 
3083 QUrl KMyMoneyApp::filenameURL() const
3084 {
3085     return d->m_storageInfo.url;
3086 }
3087 
3088 void KMyMoneyApp::writeFilenameURL(const QUrl &url)
3089 {
3090     d->m_storageInfo.url = url;
3091 }
3092 
3093 void KMyMoneyApp::addToRecentFiles(const QUrl& url)
3094 {
3095     d->m_recentFiles->addUrl(url);
3096 }
3097 
3098 QTimer* KMyMoneyApp::autosaveTimer()
3099 {
3100     return d->m_autoSaveTimer;
3101 }
3102 
3103 WebConnect* KMyMoneyApp::webConnect() const
3104 {
3105     return d->m_webConnect;
3106 }
3107 
3108 QList<QString> KMyMoneyApp::instanceList() const
3109 {
3110     QList<QString> list;
3111 #ifdef KMM_DBUS
3112     QDBusReply<QStringList> reply = QDBusConnection::sessionBus().interface()->registeredServiceNames();
3113 
3114     if (reply.isValid()) {
3115         QStringList apps = reply.value();
3116         QStringList::ConstIterator it;
3117 
3118         // build a list of service names of all running kmymoney applications without this one
3119         for (it = apps.constBegin(); it != apps.constEnd(); ++it) {
3120             // please change this method of creating a list of 'all the other kmymoney instances that are running on the system'
3121             // 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
3122             if ((*it).indexOf("org.kde.kmymoney-") == 0) {
3123                 uint thisProcPid = platformTools::processId();
3124                 if ((*it).indexOf(QString("org.kde.kmymoney-%1").arg(thisProcPid)) != 0)
3125                     list += (*it);
3126             }
3127         }
3128     } else {
3129         qDebug("D-Bus returned the following error while obtaining instances: %s", qPrintable(reply.error().message()));
3130     }
3131 #endif
3132     return list;
3133 }
3134 
3135 void KMyMoneyApp::slotEquityPriceUpdate()
3136 {
3137     QPointer<KEquityPriceUpdateDlg> dlg = new KEquityPriceUpdateDlg(this);
3138     if (dlg->exec() == QDialog::Accepted && dlg != 0)
3139         dlg->storePrices();
3140     delete dlg;
3141 }
3142 
3143 void KMyMoneyApp::webConnectUrl(const QUrl url)
3144 {
3145     QMetaObject::invokeMethod(this, "webConnect", Qt::QueuedConnection, Q_ARG(QString, url.toLocalFile()), Q_ARG(QByteArray, QByteArray()));
3146 }
3147 
3148 void KMyMoneyApp::webConnect(const QString& sourceUrl, const QByteArray& asn_id)
3149 {
3150     //
3151     // Web connect attempts to go through the known importers and see if the file
3152     // can be importing using that method.  If so, it will import it using that
3153     // plugin
3154     //
3155 
3156     Q_UNUSED(asn_id)
3157 
3158     d->m_importUrlsQueue.enqueue(sourceUrl);
3159 
3160     // only start processing if this is the only import so far
3161     if (d->m_importUrlsQueue.count() == 1) {
3162         MyMoneyStatementReader::clearResultMessages();
3163         auto statementCount = 0;
3164         while (!d->m_importUrlsQueue.isEmpty()) {
3165             ++statementCount;
3166             // get the value of the next item from the queue
3167             // but leave it on the queue for now
3168             QString url = d->m_importUrlsQueue.head();
3169 
3170             // Bring this window to the forefront.  This method was suggested by
3171             // Lubos Lunak <l.lunak@suse.cz> of the KDE core development team.
3172             //KStartupInfo::setNewStartupId(this, asn_id);
3173 
3174             // Make sure we have an open file
3175             if (! d->m_storageInfo.isOpened &&
3176                     KMessageBox::warningContinueCancel(this, i18n("You must first select a KMyMoney file before you can import a statement.")) == KMessageBox::Continue)
3177                 slotFileOpen();
3178 
3179             // only continue if the user really did open a file.
3180             if (d->m_storageInfo.isOpened) {
3181                 KMSTATUS(i18n("Importing a statement via Web Connect"));
3182 
3183                 // remove the statement files
3184                 d->unlinkStatementXML();
3185 
3186                 QMap<QString, KMyMoneyPlugin::ImporterPlugin*>::const_iterator it_plugin = pPlugins.importer.constBegin();
3187                 while (it_plugin != pPlugins.importer.constEnd()) {
3188                     if ((*it_plugin)->isMyFormat(url)) {
3189                         if (!(*it_plugin)->import(url) && !(*it_plugin)->lastError().isEmpty()) {
3190                             KMessageBox::error(this, i18n("Unable to import %1 using %2 plugin.  The plugin returned the following error: %3", url, (*it_plugin)->formatName(), (*it_plugin)->lastError()), i18n("Importing error"));
3191                         }
3192 
3193                         break;
3194                     }
3195                     ++it_plugin;
3196                 }
3197 
3198                 // If we did not find a match, try importing it as a KMM statement file,
3199                 // which is really just for testing.  the statement file is not exposed
3200                 // to users.
3201                 if (it_plugin == pPlugins.importer.constEnd())
3202                     if (MyMoneyStatement::isStatementFile(url))
3203                         MyMoneyStatementReader::importStatement(url, false, progressCallback);
3204 
3205             }
3206             // remove the current processed item from the queue
3207             d->m_importUrlsQueue.dequeue();
3208         }
3209 
3210         KMyMoneyUtils::showStatementImportResult(MyMoneyStatementReader::resultMessages(), statementCount);
3211     }
3212 }
3213 
3214 void KMyMoneyApp::slotEnableMessages()
3215 {
3216     KMessageBox::enableAllMessages();
3217     KMessageBox::information(this, i18n("All messages have been enabled."), i18n("All messages"));
3218 }
3219 
3220 void KMyMoneyApp::createInterfaces()
3221 {
3222     // Sets up the plugin interface
3223     KMyMoneyPlugin::pluginInterfaces().appInterface = new KMyMoneyPlugin::KMMAppInterface(this, this);
3224     KMyMoneyPlugin::pluginInterfaces().importInterface = new KMyMoneyPlugin::KMMImportInterface(this);
3225     KMyMoneyPlugin::pluginInterfaces().statementInterface = new KMyMoneyPlugin::KMMStatementInterface(this);
3226     KMyMoneyPlugin::pluginInterfaces().viewInterface = new KMyMoneyPlugin::KMMViewInterface(d->m_myMoneyView, this);
3227 
3228     // setup the calendar interface for schedules
3229     MyMoneySchedule::setProcessingCalendar(this);
3230 }
3231 
3232 void KMyMoneyApp::slotAutoSave()
3233 {
3234     if (!d->m_inAutoSaving) {
3235         // store the focus widget so we can restore it after save
3236         QPointer<QWidget> focusWidget = qApp->focusWidget();
3237         d->m_inAutoSaving = true;
3238         KMSTATUS(i18n("Auto saving..."));
3239 
3240         //calls slotFileSave if needed, and restart the timer
3241         //it the file is not saved, reinitializes the countdown.
3242         if (d->dirty() && d->m_autoSaveEnabled) {
3243             if (!slotFileSave() && d->m_autoSavePeriod > 0) {
3244                 d->m_autoSaveTimer->setSingleShot(true);
3245                 d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000);
3246             }
3247         }
3248 
3249         d->m_inAutoSaving = false;
3250         if (focusWidget && focusWidget != qApp->focusWidget()) {
3251             // we have a valid focus widget so restore it
3252             focusWidget->setFocus();
3253         }
3254     }
3255 }
3256 
3257 void KMyMoneyApp::slotDateChanged()
3258 {
3259     QDateTime dt = QDateTime::currentDateTime();
3260     QDateTime nextDay(QDate(dt.date().addDays(1)), QTime(0, 0, 0));
3261 
3262     // +1 is to make sure that we're already in the next day when the
3263     // signal is sent (this way we also avoid setting the timer to 0)
3264     QTimer::singleShot((static_cast<int>(dt.secsTo(nextDay)) + 1)*1000, this, SLOT(slotDateChanged()));
3265     d->m_myMoneyView->slotRefreshViews();
3266 }
3267 
3268 void KMyMoneyApp::setHolidayRegion(const QString& holidayRegion)
3269 {
3270 #ifdef ENABLE_HOLIDAYS
3271     //since the cost of updating the cache is now not negligible
3272     //check whether the region has been modified
3273     if (!d->m_holidayRegion || d->m_holidayRegion->regionCode() != holidayRegion) {
3274         // Delete the previous holidayRegion before creating a new one.
3275         delete d->m_holidayRegion;
3276         // Create a new holidayRegion.
3277         d->m_holidayRegion = new KHolidays::HolidayRegion(holidayRegion);
3278 
3279         //clear and update the holiday cache
3280         preloadHolidays();
3281     }
3282 #else
3283     Q_UNUSED(holidayRegion);
3284 #endif
3285 }
3286 
3287 bool KMyMoneyApp::isProcessingDate(const QDate& date) const
3288 {
3289     if (!d->m_processingDays.testBit(date.dayOfWeek()))
3290         return false;
3291 #ifdef ENABLE_HOLIDAYS
3292     if (!d->m_holidayRegion || !d->m_holidayRegion->isValid())
3293         return true;
3294 
3295     //check first whether it's already in cache
3296     if (d->m_holidayMap.contains(date)) {
3297         return d->m_holidayMap.value(date);
3298     } else {
3299         bool processingDay = !d->m_holidayRegion->isHoliday(date);
3300         d->m_holidayMap.insert(date, processingDay);
3301         return processingDay;
3302     }
3303 #else
3304     return true;
3305 #endif
3306 }
3307 
3308 void KMyMoneyApp::preloadHolidays()
3309 {
3310 #ifdef ENABLE_HOLIDAYS
3311     //clear the cache before loading
3312     d->m_holidayMap.clear();
3313     // only do this if it is a valid region
3314     if (d->m_holidayRegion && d->m_holidayRegion->isValid()) {
3315         // load holidays for the forecast days plus 1 cycle, to be on the safe side
3316         auto forecastDays = KMyMoneySettings::forecastDays() + KMyMoneySettings::forecastAccountCycle();
3317         QDate endDate = QDate::currentDate().addDays(forecastDays);
3318 
3319         // look for holidays for the next 2 years as a minimum. That should give a good margin for the cache
3320         if (endDate < QDate::currentDate().addYears(2))
3321             endDate = QDate::currentDate().addYears(2);
3322 
3323         KHolidays::Holiday::List holidayList = d->m_holidayRegion->holidays(QDate::currentDate(), endDate);
3324         KHolidays::Holiday::List::const_iterator holiday_it;
3325         for (holiday_it = holidayList.constBegin(); holiday_it != holidayList.constEnd(); ++holiday_it) {
3326             for (QDate holidayDate = (*holiday_it).observedStartDate(); holidayDate <= (*holiday_it).observedEndDate(); holidayDate = holidayDate.addDays(1))
3327                 d->m_holidayMap.insert(holidayDate, (*holiday_it).dayType() == KHolidays::Holiday::Workday);
3328         }
3329 
3330         // prefill cache with all values of the forecast period
3331         for (QDate date = QDate::currentDate(); date <= endDate; date = date.addDays(1)) {
3332             // if it is not a processing day, set it to false
3333             if (!d->m_processingDays.testBit(date.dayOfWeek())) {
3334                 d->m_holidayMap.insert(date, false);
3335             } else if (!d->m_holidayMap.contains(date)) {
3336                 // if it is not a holiday nor a weekend, it is a processing day
3337                 d->m_holidayMap.insert(date, true);
3338             }
3339         }
3340     }
3341 #endif
3342 }
3343 
3344 bool KMyMoneyApp::slotFileNew()
3345 {
3346     KMSTATUS(i18n("Creating new document..."));
3347 
3348     if (!slotFileClose())
3349         return false;
3350 
3351     NewUserWizard::Wizard wizard;
3352     if (wizard.exec() != QDialog::Accepted)
3353         return false;
3354 
3355     d->m_storageInfo.isOpened = true;
3356     d->m_storageInfo.type = eKMyMoney::StorageType::None;
3357     d->m_storageInfo.url = QUrl();
3358 
3359     try {
3360         auto storage = new MyMoneyStorageMgr;
3361         MyMoneyFile::instance()->attachStorage(storage);
3362 
3363         MyMoneyFileTransaction ft;
3364         auto file = MyMoneyFile::instance();
3365         // store the user info
3366         file->setUser(wizard.user());
3367 
3368         // create and setup base currency
3369         file->addCurrency(wizard.baseCurrency());
3370         file->setBaseCurrency(wizard.baseCurrency());
3371 
3372         // create a possible institution
3373         MyMoneyInstitution inst = wizard.institution();
3374         if (inst.name().length()) {
3375             file->addInstitution(inst);
3376         }
3377 
3378         // create a possible checking account
3379         auto acc = wizard.account();
3380         if (acc.name().length()) {
3381             acc.setInstitutionId(inst.id());
3382             MyMoneyAccount asset = file->asset();
3383             file->addAccount(acc, asset);
3384 
3385             // create possible opening balance transaction
3386             if (!wizard.openingBalance().isZero()) {
3387                 file->createOpeningBalanceTransaction(acc, wizard.openingBalance());
3388             }
3389         }
3390 
3391         // import the account templates
3392         for (auto &tmpl : wizard.templates())
3393             tmpl.importTemplate(progressCallback);
3394 
3395         ft.commit();
3396         KMyMoneySettings::setFirstTimeRun(false);
3397 
3398         d->fileAction(eKMyMoney::FileAction::Opened);
3399         if (actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->isEnabled())
3400             slotFileSaveAs();
3401     } catch (const MyMoneyException & e) {
3402         slotFileClose();
3403         d->removeStorage();
3404         KMessageBox::detailedError(this, i18n("Couldn't create a new file."), e.what());
3405         return false;
3406     }
3407 
3408     if (wizard.startSettingsAfterFinished())
3409         slotSettings();
3410     return true;
3411 }
3412 
3413 void KMyMoneyApp::slotFileOpen()
3414 {
3415     KMSTATUS(i18n("Open a file."));
3416 
3417     const QVector<eKMyMoney::StorageType> desiredFileExtensions {eKMyMoney::StorageType::XML, eKMyMoney::StorageType::GNC};
3418     QString fileExtensions;
3419     for (const auto &extension : desiredFileExtensions) {
3420         for (const auto &plugin : pPlugins.storage) {
3421             if (plugin->storageType() == extension) {
3422                 fileExtensions += plugin->fileExtension() + QLatin1String(";;");
3423                 break;
3424             }
3425         }
3426     }
3427 
3428     if (fileExtensions.isEmpty()) {
3429         KMessageBox::error(this, i18n("Couldn't find any plugin for opening storage."));
3430         return;
3431     }
3432 
3433     fileExtensions.append(i18n("All files (*)"));
3434 
3435     QPointer<QFileDialog> dialog = new QFileDialog(this, QString(), readLastUsedDir(), fileExtensions);
3436     dialog->setFileMode(QFileDialog::ExistingFile);
3437     dialog->setAcceptMode(QFileDialog::AcceptOpen);
3438 
3439     if (dialog->exec() == QDialog::Accepted && dialog != nullptr)
3440         slotFileOpenRecent(dialog->selectedUrls().first());
3441     delete dialog;
3442 }
3443 
3444 bool KMyMoneyApp::slotFileOpenRecent(const QUrl &url)
3445 {
3446     KMSTATUS(i18n("Loading file..."));
3447 
3448     if (!url.isValid())
3449         throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid URL %1").arg(qPrintable(url.url())));
3450 
3451     if (isFileOpenedInAnotherInstance(url)) {
3452         KMessageBox::sorry(this, i18n("<p>File <b>%1</b> is already opened in another instance of KMyMoney</p>", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Duplicate open"));
3453         return false;
3454     }
3455 
3456     if (url.scheme() != QLatin1String("sql") && !KMyMoneyUtils::fileExists(url)) {
3457         KMessageBox::sorry(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"));
3458         return false;
3459     }
3460 
3461     if (d->m_storageInfo.isOpened)
3462         if (!slotFileClose())
3463             return false;
3464 
3465     // open the database
3466     d->m_storageInfo.type = eKMyMoney::StorageType::None;
3467     for (auto &plugin : pPlugins.storage) {
3468         try {
3469             if (auto pStorage = plugin->open(url)) {
3470                 MyMoneyFile::instance()->attachStorage(pStorage);
3471                 d->m_storageInfo.type = plugin->storageType();
3472                 if (plugin->storageType() != eKMyMoney::StorageType::GNC) {
3473                     d->m_storageInfo.url = plugin->openUrl();
3474                     writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile));
3475                     /* Don't use url variable after KRecentFilesAction::addUrl
3476                     * as it might delete it.
3477                     * More in API reference to this method
3478                     */
3479                     d->m_recentFiles->addUrl(url);
3480                 }
3481                 d->m_storageInfo.isOpened = true;
3482                 break;
3483             }
3484         } catch (const MyMoneyException &e) {
3485             KMessageBox::detailedError(this, i18n("Cannot open file as requested."), QString::fromLatin1(e.what()));
3486             return false;
3487         }
3488     }
3489 
3490     if(d->m_storageInfo.type == eKMyMoney::StorageType::None) {
3491         KMessageBox::error(this, i18n("Could not read your data source. Please check the KMyMoney settings that the necessary plugin is enabled."));
3492         return false;
3493     }
3494 
3495     d->fileAction(eKMyMoney::FileAction::Opened);
3496     return true;
3497 }
3498 
3499 bool KMyMoneyApp::slotFileSave()
3500 {
3501     KMSTATUS(i18n("Saving file..."));
3502 
3503     for (const auto& plugin : pPlugins.storage) {
3504         if (plugin->storageType() == d->m_storageInfo.type) {
3505             d->consistencyCheck(false);
3506             try {
3507                 if (plugin->save(d->m_storageInfo.url)) {
3508                     d->fileAction(eKMyMoney::FileAction::Saved);
3509                     return true;
3510                 }
3511                 return false;
3512             } catch (const MyMoneyException &e) {
3513                 KMessageBox::detailedError(this, i18n("Failed to save your data."), e.what());
3514                 return false;
3515             }
3516         }
3517     }
3518 
3519     KMessageBox::error(this, i18n("Couldn't find suitable plugin to save your data."));
3520     return false;
3521 }
3522 
3523 bool KMyMoneyApp::slotFileSaveAs()
3524 {
3525     KMSTATUS(i18n("Saving file as...."));
3526 
3527     QVector<eKMyMoney::StorageType> availableFileTypes;
3528     for (const auto& plugin : pPlugins.storage) {
3529         switch (plugin->storageType()) {
3530         case eKMyMoney::StorageType::GNC:
3531             break;
3532         default:
3533             availableFileTypes.append(plugin->storageType());
3534             break;
3535         }
3536     }
3537 
3538     auto chosenFileType = eKMyMoney::StorageType::None;
3539     switch (availableFileTypes.count()) {
3540     case 0:
3541         KMessageBox::error(this, i18n("Couldn't find any plugin for saving data."));
3542         return false;
3543     case 1:
3544         chosenFileType = availableFileTypes.first();
3545         break;
3546     default:
3547     {
3548         QPointer<KSaveAsQuestion> dlg = new KSaveAsQuestion(availableFileTypes, this);
3549         auto rc = dlg->exec();
3550         if (dlg) {
3551             auto fileType = dlg->fileType();
3552             delete dlg;
3553             if (rc != QDialog::Accepted)
3554                 return false;
3555             chosenFileType = fileType;
3556         }
3557     }
3558     }
3559 
3560     for (const auto &plugin : pPlugins.storage) {
3561         if (chosenFileType == plugin->storageType()) {
3562             try {
3563                 d->consistencyCheck(false);
3564                 if (plugin->saveAs()) {
3565                     d->fileAction(eKMyMoney::FileAction::Saved);
3566                     d->m_storageInfo.type = plugin->storageType();
3567                     return true;
3568                 }
3569             } catch (const MyMoneyException &e) {
3570                 KMessageBox::detailedError(this, i18n("Failed to save your storage."), e.what());
3571             }
3572         }
3573     }
3574     return false;
3575 }
3576 
3577 bool KMyMoneyApp::slotFileClose()
3578 {
3579     if (!d->m_storageInfo.isOpened)
3580         return true;
3581 
3582     if (!d->askAboutSaving())
3583         return false;
3584 
3585     d->fileAction(eKMyMoney::FileAction::Closing);
3586 
3587     d->removeStorage();
3588 
3589     d->m_storageInfo = KMyMoneyApp::Private::storageInfo();
3590 
3591     d->fileAction(eKMyMoney::FileAction::Closed);
3592     return true;
3593 }
3594 
3595 void KMyMoneyApp::slotFileQuit()
3596 {
3597     // don't modify the status message here as this will prevent quit from working!!
3598     // See the beginning of queryClose() and isReady() why. Thomas Baumgart 2005-10-17
3599 
3600     bool quitApplication = true;
3601 
3602     QList<KMainWindow*> memberList = KMainWindow::memberList();
3603     if (!memberList.isEmpty()) {
3604 
3605         QList<KMainWindow*>::const_iterator w_it = memberList.constBegin();
3606         for (; w_it != memberList.constEnd(); ++w_it) {
3607             // only close the window if the closeEvent is accepted. If the user presses Cancel on the saveModified() dialog,
3608             // the window and the application stay open.
3609             if (!(*w_it)->close()) {
3610                 quitApplication = false;
3611                 break;
3612             }
3613         }
3614     }
3615 
3616     // We will only quit if all windows were processed and not cancelled
3617     if (quitApplication) {
3618         QCoreApplication::quit();
3619     }
3620 }
3621 
3622 void KMyMoneyApp::Private::fileAction(eKMyMoney::FileAction action)
3623 {
3624     switch(action) {
3625     case eKMyMoney::FileAction::Opened:
3626         q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false);
3627         updateAccountNames();
3628         updateCurrencyNames();
3629         selectBaseCurrency();
3630 
3631         // setup the standard precision
3632         AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction()));
3633 
3634         applyFileFixes();
3635         Models::instance()->fileOpened();
3636         connectStorageToModels();
3637         // inform everyone about new data
3638         MyMoneyFile::instance()->forceDataChanged();
3639         // Enable save in case the fix changed the contents
3640         q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(dirty());
3641         updateActions();
3642         m_myMoneyView->slotFileOpened();
3643         onlineJobAdministration::instance()->updateActions();
3644         m_myMoneyView->enableViewsIfFileOpen(m_storageInfo.isOpened);
3645         m_myMoneyView->slotRefreshViews();
3646         onlineJobAdministration::instance()->updateOnlineTaskProperties();
3647         q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
3648 
3649 #ifdef ENABLE_ACTIVITIES
3650         {
3651             // make sure that we don't store the DB password in activity
3652             QUrl url(m_storageInfo.url);
3653             url.setPassword(QString());
3654             m_activityResourceInstance->setUri(url);
3655         }
3656 #endif
3657         // start the check for scheduled transactions that need to be
3658         // entered as soon as the event loop becomes active.
3659         QMetaObject::invokeMethod(q, "slotCheckSchedules",  Qt::QueuedConnection);
3660 
3661         // make sure to catch view activations
3662         connect(m_myMoneyView, &KMyMoneyView::viewActivated, q, &KMyMoneyApp::slotViewSelected);
3663         break;
3664 
3665     case eKMyMoney::FileAction::Saved:
3666         q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
3667         q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false);
3668         m_autoSaveTimer->stop();
3669         break;
3670 
3671     case eKMyMoney::FileAction::Closing:
3672         disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
3673         // make sure to not catch view activations anymore
3674         disconnect(m_myMoneyView, &KMyMoneyView::viewActivated, q, &KMyMoneyApp::slotViewSelected);
3675         m_myMoneyView->slotFileClosed();
3676         // notify the models that the file is going to be closed (we should have something like dataChanged that reaches the models first)
3677         Models::instance()->fileClosed();
3678         break;
3679 
3680     case eKMyMoney::FileAction::Closed:
3681         q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
3682         disconnectStorageFromModels();
3683         q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false);
3684         m_myMoneyView->enableViewsIfFileOpen(m_storageInfo.isOpened);
3685         updateActions();
3686         break;
3687 
3688     case eKMyMoney::FileAction::Changed:
3689         q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
3690         q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(true && !m_storageInfo.url.isEmpty());
3691         // As this method is called every time the MyMoneyFile instance
3692         // notifies a modification, it's the perfect place to start the timer if needed
3693         if (m_autoSaveEnabled && !m_autoSaveTimer->isActive()) {
3694             m_autoSaveTimer->setSingleShot(true);
3695             m_autoSaveTimer->start(m_autoSavePeriod * 60 * 1000);  //miliseconds
3696         }
3697         pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(KMyMoneyUtils::canUpdateAllAccounts());
3698         break;
3699 
3700     default:
3701         break;
3702     }
3703 
3704     updateCaption();
3705 }
3706 
3707 KMStatus::KMStatus(const QString &text)
3708     : m_prevText(kmymoney->slotStatusMsg(text))
3709 {
3710 }
3711 
3712 KMStatus::~KMStatus()
3713 {
3714     kmymoney->slotStatusMsg(m_prevText);
3715 }
3716 
3717 void KMyMoneyApp::Private::unlinkStatementXML()
3718 {
3719     QDir d(KMyMoneySettings::logPath(), "kmm-statement*");
3720     for (uint i = 0; i < d.count(); ++i) {
3721         qDebug("Remove %s", qPrintable(d[i]));
3722         d.remove(KMyMoneySettings::logPath() + QString("/%1").arg(d[i]));
3723     }
3724 }