1 /***************************************************************************
2                           kmymoney.cpp
3                              -------------------
4     copyright            : (C) 2000 by Michael Edwardes <mte@users.sourceforge.net>
5                            (C) 2002-2020 by Thomas Baumgart <tbaumgart@kde.org>
6                            (C) 2017-2018 by Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
7 
8 ****************************************************************************/
9 
10 /***************************************************************************
11  *                                                                         *
12  *   This program is free software; you can redistribute it and/or modify  *
13  *   it under the terms of the GNU General Public License as published by  *
14  *   the Free Software Foundation; either version 2 of the License, or     *
15  *   (at your option) any later version.                                   *
16  *                                                                         *
17  ***************************************************************************/
18 
19 
20 #include <config-kmymoney.h>
21 
22 #include "kmymoney.h"
23 
24 // ----------------------------------------------------------------------------
25 // Std C++ / STL Includes
26 
27 #include <typeinfo>
28 #include <iostream>
29 #include <memory>
30 
31 // ----------------------------------------------------------------------------
32 // QT Includes
33 
34 #include <QDir>
35 #include <QDateTime>         // only for performance tests
36 #include <QTimer>
37 #include <QByteArray>
38 #include <QBoxLayout>
39 #include <QLabel>
40 #include <QMenu>
41 #include <QProgressBar>
42 #include <QList>
43 #include <QUrl>
44 #include <QClipboard>
45 #include <QKeySequence>
46 #include <QIcon>
47 #include <QInputDialog>
48 #include <QStatusBar>
49 #include <QPushButton>
50 #include <QListWidget>
51 #include <QApplication>
52 
53 // ----------------------------------------------------------------------------
54 // KDE Includes
55 
56 #include <KToolBar>
57 #include <KMessageBox>
58 #include <KLocalizedString>
59 #include <KConfig>
60 #include <KStandardAction>
61 #include <KActionCollection>
62 #include <KTipDialog>
63 #include <KRun>
64 #include <KConfigDialog>
65 #include <KXMLGUIFactory>
66 #include <KRecentFilesAction>
67 #include <KRecentDirs>
68 #include <KProcess>
69 #include <KAboutApplicationDialog>
70 #include <KBackup>
71 #ifdef ENABLE_HOLIDAYS
72 #include <KHolidays/Holiday>
73 #include <KHolidays/HolidayRegion>
74 #endif
75 #ifdef ENABLE_ACTIVITIES
76 #include <KActivities/ResourceInstance>
77 #endif
78 
79 // ----------------------------------------------------------------------------
80 // Project Includes
81 
82 #include "kmymoneysettings.h"
83 #include "kmymoneyadaptor.h"
84 
85 #include "dialogs/settings/ksettingskmymoney.h"
86 #include "dialogs/kbackupdlg.h"
87 #include "dialogs/kconfirmmanualenterdlg.h"
88 #include "dialogs/kmymoneypricedlg.h"
89 #include "dialogs/kcurrencyeditdlg.h"
90 #include "dialogs/kequitypriceupdatedlg.h"
91 #include "dialogs/kmymoneyfileinfodlg.h"
92 #include "dialogs/knewbankdlg.h"
93 #include "dialogs/ksaveasquestion.h"
94 #include "wizards/newinvestmentwizard/knewinvestmentwizard.h"
95 #include "dialogs/knewaccountdlg.h"
96 #include "dialogs/editpersonaldatadlg.h"
97 #include "dialogs/kcurrencycalculator.h"
98 #include "dialogs/keditscheduledlg.h"
99 #include "wizards/newloanwizard/keditloanwizard.h"
100 #include "dialogs/kpayeereassigndlg.h"
101 #include "dialogs/kcategoryreassigndlg.h"
102 #include "wizards/endingbalancedlg/kendingbalancedlg.h"
103 #include "dialogs/kloadtemplatedlg.h"
104 #include "dialogs/ktemplateexportdlg.h"
105 #include "dialogs/transactionmatcher.h"
106 #include "wizards/newuserwizard/knewuserwizard.h"
107 #include "wizards/newaccountwizard/knewaccountwizard.h"
108 #include "dialogs/kbalancewarning.h"
109 #include "widgets/kmymoneyaccountselector.h"
110 #include "widgets/kmymoneypayeecombo.h"
111 #include "widgets/amountedit.h"
112 #include "widgets/kmymoneymvccombo.h"
113 
114 #include "views/kmymoneyview.h"
115 #include "models/models.h"
116 #include "models/accountsmodel.h"
117 #include "models/equitiesmodel.h"
118 #include "models/securitiesmodel.h"
119 #ifdef ENABLE_UNFINISHEDFEATURES
120 #include "models/ledgermodel.h"
121 #endif
122 
123 #include "mymoney/mymoneyobject.h"
124 #include "mymoney/mymoneyfile.h"
125 #include "mymoney/mymoneyinstitution.h"
126 #include "mymoney/mymoneyaccount.h"
127 #include "mymoney/mymoneyaccountloan.h"
128 #include "mymoney/mymoneysecurity.h"
129 #include "mymoney/mymoneypayee.h"
130 #include "mymoney/mymoneyprice.h"
131 #include "mymoney/mymoneytag.h"
132 #include "mymoney/mymoneybudget.h"
133 #include "mymoney/mymoneyreport.h"
134 #include "mymoney/mymoneysplit.h"
135 #include "mymoney/mymoneyutils.h"
136 #include "mymoney/mymoneystatement.h"
137 #include "mymoney/mymoneyforecast.h"
138 #include "mymoney/mymoneytransactionfilter.h"
139 #include "mymoneyexception.h"
140 
141 
142 #include "converter/mymoneystatementreader.h"
143 #include "converter/mymoneytemplate.h"
144 
145 #include "plugins/interfaces/kmmappinterface.h"
146 #include "plugins/interfaces/kmmviewinterface.h"
147 #include "plugins/interfaces/kmmstatementinterface.h"
148 #include "plugins/interfaces/kmmimportinterface.h"
149 #include "plugins/interfaceloader.h"
150 #include "plugins/onlinepluginextended.h"
151 #include "pluginloader.h"
152 #include "kmymoneyplugin.h"
153 
154 #include "tasks/credittransfer.h"
155 
156 #include "icons/icons.h"
157 
158 #include "misc/webconnect.h"
159 
160 #include "storage/mymoneystoragemgr.h"
161 #include "imymoneystorageformat.h"
162 
163 #include "transactioneditor.h"
164 #include <QHBoxLayout>
165 #include <QFileDialog>
166 
167 #include "kmymoneyutils.h"
168 #include "kcreditswindow.h"
169 
170 #include "ledgerdelegate.h"
171 #include "storageenums.h"
172 #include "mymoneyenums.h"
173 #include "dialogenums.h"
174 #include "viewenums.h"
175 #include "menuenums.h"
176 #include "kmymoneyenums.h"
177 
178 #include "platformtools.h"
179 #include "kmm_printer.h"
180 
181 #ifdef ENABLE_SQLCIPHER
182 #include "sqlcipher/sqlite3.h"
183 #endif
184 
185 #ifdef KMM_DEBUG
186 #include "mymoney/storage/mymoneystoragedump.h"
187 #include "mymoneytracer.h"
188 #endif
189 
190 using namespace Icons;
191 using namespace eMenu;
192 
193 enum backupStateE {
194   BACKUP_IDLE = 0,
195   BACKUP_MOUNTING,
196   BACKUP_COPYING,
197   BACKUP_UNMOUNTING
198 };
199 
200 class KMyMoneyApp::Private
201 {
202 public:
Private(KMyMoneyApp * app)203   explicit Private(KMyMoneyApp *app) :
204       q(app),
205       m_backupState(backupStateE::BACKUP_IDLE),
206       m_backupResult(0),
207       m_backupMount(0),
208       m_ignoreBackupExitCode(false),
209       m_myMoneyView(nullptr),
210       m_startDialog(false),
211       m_progressBar(nullptr),
212       m_statusLabel(nullptr),
213       m_autoSaveEnabled(true),
214       m_autoSaveTimer(nullptr),
215       m_progressTimer(nullptr),
216       m_autoSavePeriod(0),
217       m_inAutoSaving(false),
218       m_recentFiles(nullptr),
219 #ifdef ENABLE_HOLIDAYS
220       m_holidayRegion(nullptr),
221 #endif
222 #ifdef ENABLE_ACTIVITIES
223       m_activityResourceInstance(nullptr),
224 #endif
225       m_applicationIsReady(true),
226       m_webConnect(new WebConnect(app)) {
227     // since the days of the week are from 1 to 7,
228     // and a day of the week is used to index this bit array,
229     // resize the array to 8 elements (element 0 is left unused)
230     m_processingDays.resize(8);
231 
232   }
233 
234   void unlinkStatementXML();
235   void moveInvestmentTransaction(const QString& fromId,
236                                  const QString& toId,
237                                  const MyMoneyTransaction& t);
238 
239   struct storageInfo {
240     eKMyMoney::StorageType type {eKMyMoney::StorageType::None};
241     bool isOpened {false};
242     QUrl url;
243   };
244 
245   storageInfo m_storageInfo;
246   /**
247     * The public interface.
248     */
249   KMyMoneyApp * const q;
250 
251   /** the configuration object of the application */
252   KSharedConfigPtr m_config;
253 
254   /**
255     * The following variable represents the state while crafting a backup.
256     * It can have the following values
257     *
258     * - IDLE: the default value if not performing a backup
259     * - MOUNTING: when a mount command has been issued
260     * - COPYING:  when a copy command has been issued
261     * - UNMOUNTING: when an unmount command has been issued
262     */
263   backupStateE   m_backupState;
264 
265   /**
266     * This variable keeps the result of the backup operation.
267     */
268   int     m_backupResult;
269 
270   /**
271     * This variable is set, when the user selected to mount/unmount
272     * the backup volume.
273     */
274   bool    m_backupMount;
275 
276   /**
277     * Flag for internal run control
278     */
279   bool    m_ignoreBackupExitCode;
280 
281   KProcess m_proc;
282 
283   /// A pointer to the view holding the tabs.
284   KMyMoneyView *m_myMoneyView;
285 
286   bool m_startDialog;
287   QString m_mountpoint;
288 
289   QProgressBar* m_progressBar;
290   QTime         m_lastUpdate;
291   QLabel*       m_statusLabel;
292 
293   // allows multiple imports to be launched trough web connect and to be executed sequentially
294   QQueue<QString> m_importUrlsQueue;
295 
296   // This is Auto Saving related
297   bool                  m_autoSaveEnabled;
298   QTimer*               m_autoSaveTimer;
299   QTimer*               m_progressTimer;
300   int                   m_autoSavePeriod;
301   bool                  m_inAutoSaving;
302 
303   // id's that need to be remembered
304   QString               m_accountGoto, m_payeeGoto;
305 
306   KRecentFilesAction*   m_recentFiles;
307 
308 #ifdef ENABLE_HOLIDAYS
309   // used by the calendar interface for schedules
310   KHolidays::HolidayRegion* m_holidayRegion;
311 #endif
312 
313 #ifdef ENABLE_ACTIVITIES
314   KActivities::ResourceInstance * m_activityResourceInstance;
315 #endif
316 
317   QBitArray             m_processingDays;
318   QMap<QDate, bool>     m_holidayMap;
319   QStringList           m_consistencyCheckResult;
320   bool                  m_applicationIsReady;
321 
322   WebConnect*           m_webConnect;
323 
324   // methods
325   void consistencyCheck(bool alwaysDisplayResults);
326   static void setThemedCSS();
327   void copyConsistencyCheckResults();
328   void saveConsistencyCheckResults();
329 
checkAccountName(const MyMoneyAccount & _acc,const QString & name) const330   void checkAccountName(const MyMoneyAccount& _acc, const QString& name) const
331   {
332     auto file = MyMoneyFile::instance();
333     if (_acc.name() != name) {
334       MyMoneyAccount acc(_acc);
335       acc.setName(name);
336       file->modifyAccount(acc);
337     }
338   }
339 
340   /**
341     * This method updates names of currencies from file to localized names
342     */
updateCurrencyNames()343   void updateCurrencyNames()
344   {
345     auto file = MyMoneyFile::instance();
346     MyMoneyFileTransaction ft;
347 
348     QList<MyMoneySecurity> storedCurrencies = MyMoneyFile::instance()->currencyList();
349     QList<MyMoneySecurity> availableCurrencies = MyMoneyFile::instance()->availableCurrencyList();
350     QStringList currencyIDs;
351 
352     foreach (auto currency, availableCurrencies)
353       currencyIDs.append(currency.id());
354 
355     try {
356       foreach (auto currency, storedCurrencies) {
357         int i = currencyIDs.indexOf(currency.id());
358         if (i != -1 && availableCurrencies.at(i).name() != currency.name()) {
359           currency.setName(availableCurrencies.at(i).name());
360           file->modifyCurrency(currency);
361         }
362       }
363       ft.commit();
364     } catch (const MyMoneyException &e) {
365       qDebug("Error %s updating currency names", e.what());
366     }
367   }
368 
updateAccountNames()369   void updateAccountNames()
370   {
371     // make sure we setup the name of the base accounts in translated form
372     try {
373       MyMoneyFileTransaction ft;
374       const auto file = MyMoneyFile::instance();
375       checkAccountName(file->asset(), i18n("Asset"));
376       checkAccountName(file->liability(), i18n("Liability"));
377       checkAccountName(file->income(), i18n("Income"));
378       checkAccountName(file->expense(), i18n("Expense"));
379       checkAccountName(file->equity(), i18n("Equity"));
380       ft.commit();
381     } catch (const MyMoneyException &) {
382     }
383   }
384 
ungetString(QIODevice * qfile,char * buf,int len)385   void ungetString(QIODevice *qfile, char *buf, int len)
386   {
387     buf = &buf[len-1];
388     while (len--) {
389       qfile->ungetChar(*buf--);
390     }
391   }
392 
applyFileFixes()393   bool applyFileFixes()
394   {
395     const auto blocked = MyMoneyFile::instance()->blockSignals(true);
396     KSharedConfigPtr config = KSharedConfig::openConfig();
397 
398     KConfigGroup grp = config->group("General Options");
399 
400     // For debugging purposes, we can turn off the automatic fix manually
401     // by setting the entry in kmymoneyrc to true
402     if (grp.readEntry("SkipFix", false) != true) {
403       MyMoneyFileTransaction ft;
404       try {
405         // Check if we have to modify the file before we allow to work with it
406         auto s = MyMoneyFile::instance()->storage();
407         while (s->fileFixVersion() < s->currentFixVersion()) {
408           qDebug("%s", qPrintable((QString("testing fileFixVersion %1 < %2").arg(s->fileFixVersion()).arg(s->currentFixVersion()))));
409           switch (s->fileFixVersion()) {
410             case 0:
411               fixFile_0();
412               s->setFileFixVersion(1);
413               break;
414 
415             case 1:
416               fixFile_1();
417               s->setFileFixVersion(2);
418               break;
419 
420             case 2:
421               fixFile_2();
422               s->setFileFixVersion(3);
423               break;
424 
425             case 3:
426               fixFile_3();
427               s->setFileFixVersion(4);
428               break;
429 
430             case 4:
431               fixFile_4();
432               s->setFileFixVersion(5);
433               break;
434 
435               // add new levels above. Don't forget to increase currentFixVersion() for all
436               // the storage backends this fix applies to
437             default:
438               throw MYMONEYEXCEPTION(QString::fromLatin1("Unknown fix level in input file"));
439           }
440         }
441         ft.commit();
442       } catch (const MyMoneyException &) {
443         MyMoneyFile::instance()->blockSignals(blocked);
444         return false;
445       }
446     } else {
447       qDebug("Skipping automatic transaction fix!");
448     }
449     MyMoneyFile::instance()->blockSignals(blocked);
450     return true;
451   }
452 
connectStorageToModels()453   void connectStorageToModels()
454   {
455     const auto file = MyMoneyFile::instance();
456 
457     const auto accountsModel = Models::instance()->accountsModel();
458     q->connect(file, &MyMoneyFile::objectAdded,    accountsModel, &AccountsModel::slotObjectAdded);
459     q->connect(file, &MyMoneyFile::objectModified, accountsModel, &AccountsModel::slotObjectModified);
460     q->connect(file, &MyMoneyFile::objectRemoved,  accountsModel, &AccountsModel::slotObjectRemoved);
461     q->connect(file, &MyMoneyFile::balanceChanged, accountsModel, &AccountsModel::slotBalanceOrValueChanged);
462     q->connect(file, &MyMoneyFile::valueChanged,   accountsModel, &AccountsModel::slotBalanceOrValueChanged);
463 
464     const auto institutionsModel = Models::instance()->institutionsModel();
465     q->connect(file, &MyMoneyFile::objectAdded,    institutionsModel, &InstitutionsModel::slotObjectAdded);
466     q->connect(file, &MyMoneyFile::objectModified, institutionsModel, &InstitutionsModel::slotObjectModified);
467     q->connect(file, &MyMoneyFile::objectRemoved,  institutionsModel, &InstitutionsModel::slotObjectRemoved);
468     q->connect(file, &MyMoneyFile::balanceChanged, institutionsModel, &AccountsModel::slotBalanceOrValueChanged);
469     q->connect(file, &MyMoneyFile::valueChanged,   institutionsModel, &AccountsModel::slotBalanceOrValueChanged);
470 
471     const auto equitiesModel = Models::instance()->equitiesModel();
472     q->connect(file, &MyMoneyFile::objectAdded,    equitiesModel, &EquitiesModel::slotObjectAdded);
473     q->connect(file, &MyMoneyFile::objectModified, equitiesModel, &EquitiesModel::slotObjectModified);
474     q->connect(file, &MyMoneyFile::objectRemoved,  equitiesModel, &EquitiesModel::slotObjectRemoved);
475     q->connect(file, &MyMoneyFile::balanceChanged, equitiesModel, &EquitiesModel::slotBalanceOrValueChanged);
476     q->connect(file, &MyMoneyFile::valueChanged,   equitiesModel, &EquitiesModel::slotBalanceOrValueChanged);
477 
478     const auto securitiesModel = Models::instance()->securitiesModel();
479     q->connect(file, &MyMoneyFile::objectAdded,    securitiesModel, &SecuritiesModel::slotObjectAdded);
480     q->connect(file, &MyMoneyFile::objectModified, securitiesModel, &SecuritiesModel::slotObjectModified);
481     q->connect(file, &MyMoneyFile::objectRemoved,  securitiesModel, &SecuritiesModel::slotObjectRemoved);
482 
483 #ifdef ENABLE_UNFINISHEDFEATURES
484     const auto ledgerModel = Models::instance()->ledgerModel();
485     q->connect(file, &MyMoneyFile::objectAdded,    ledgerModel, &LedgerModel::slotAddTransaction);
486     q->connect(file, &MyMoneyFile::objectModified, ledgerModel, &LedgerModel::slotModifyTransaction);
487     q->connect(file, &MyMoneyFile::objectRemoved,  ledgerModel, &LedgerModel::slotRemoveTransaction);
488 
489     q->connect(file, &MyMoneyFile::objectAdded,    ledgerModel, &LedgerModel::slotAddSchedule);
490     q->connect(file, &MyMoneyFile::objectModified, ledgerModel, &LedgerModel::slotModifySchedule);
491     q->connect(file, &MyMoneyFile::objectRemoved,  ledgerModel, &LedgerModel::slotRemoveSchedule);
492 #endif
493   }
494 
disconnectStorageFromModels()495   void disconnectStorageFromModels()
496   {
497     const auto file = MyMoneyFile::instance();
498     q->disconnect(file, nullptr, Models::instance()->accountsModel(), nullptr);
499     q->disconnect(file, nullptr, Models::instance()->institutionsModel(), nullptr);
500     q->disconnect(file, nullptr, Models::instance()->equitiesModel(), nullptr);
501     q->disconnect(file, nullptr, Models::instance()->securitiesModel(), nullptr);
502 
503 #ifdef ENABLE_UNFINISHEDFEATURES
504     q->disconnect(file, nullptr, Models::instance()->ledgerModel(), nullptr);
505 #endif
506   }
507 
askAboutSaving()508   bool askAboutSaving()
509   {
510     const auto isFileNotSaved = q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->isEnabled();
511     const auto isNewFileNotSaved = m_storageInfo.isOpened && m_storageInfo.url.isEmpty();
512     auto fileNeedsToBeSaved = false;
513 
514     if (isFileNotSaved && KMyMoneySettings::autoSaveOnClose()) {
515       fileNeedsToBeSaved = true;
516     } else if (isFileNotSaved || isNewFileNotSaved) {
517       switch (KMessageBox::warningYesNoCancel(q, i18n("The file has been changed, save it?"))) {
518         case KMessageBox::ButtonCode::Yes:
519           fileNeedsToBeSaved = true;
520           break;
521         case KMessageBox::ButtonCode::No:
522           fileNeedsToBeSaved = false;
523           break;
524         case KMessageBox::ButtonCode::Cancel:
525         default:
526           return false;
527           break;
528       }
529     }
530     if (fileNeedsToBeSaved) {
531       if (isFileNotSaved)
532         return q->slotFileSave();
533       else if (isNewFileNotSaved)
534         return q->slotFileSaveAs();
535     }
536     return true;
537   }
538 
539   /**
540     * This method attaches an empty storage object to the MyMoneyFile
541     * object. It calls removeStorage() to remove a possibly attached
542     * storage object.
543     */
newStorage()544   void newStorage()
545   {
546     removeStorage();
547     auto file = MyMoneyFile::instance();
548     file->attachStorage(new MyMoneyStorageMgr);
549   }
550 
551   /**
552     * This method removes an attached storage from the MyMoneyFile
553     * object.
554     */
removeStorage()555   void removeStorage()
556   {
557     auto file = MyMoneyFile::instance();
558     auto p = file->storage();
559     if (p) {
560       file->detachStorage(p);
561       delete p;
562     }
563   }
564 
565   /**
566     * if no base currency is defined, start the dialog and force it to be set
567     */
selectBaseCurrency()568   void selectBaseCurrency()
569   {
570     auto file = MyMoneyFile::instance();
571 
572     // check if we have a base currency. If not, we need to select one
573     QString baseId;
574     try {
575       baseId = MyMoneyFile::instance()->baseCurrency().id();
576     } catch (const MyMoneyException &e) {
577       qDebug("%s", e.what());
578     }
579 
580     if (baseId.isEmpty()) {
581       QPointer<KCurrencyEditDlg> dlg = new KCurrencyEditDlg(q);
582   //    connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotSetBaseCurrency(MyMoneySecurity)));
583       dlg->exec();
584       delete dlg;
585     }
586 
587     try {
588       baseId = MyMoneyFile::instance()->baseCurrency().id();
589     } catch (const MyMoneyException &e) {
590       qDebug("%s", e.what());
591     }
592 
593     if (!baseId.isEmpty()) {
594       // check that all accounts have a currency
595       QList<MyMoneyAccount> list;
596       file->accountList(list);
597       QList<MyMoneyAccount>::Iterator it;
598 
599       // don't forget those standard accounts
600       list << file->asset();
601       list << file->liability();
602       list << file->income();
603       list << file->expense();
604       list << file->equity();
605 
606 
607       for (it = list.begin(); it != list.end(); ++it) {
608         QString cid;
609         try {
610           if (!(*it).currencyId().isEmpty() || (*it).currencyId().length() != 0)
611             cid = MyMoneyFile::instance()->currency((*it).currencyId()).id();
612         } catch (const MyMoneyException &e) {
613           qDebug() << QLatin1String("Account") << (*it).id() << (*it).name() << e.what();
614         }
615 
616         if (cid.isEmpty()) {
617           (*it).setCurrencyId(baseId);
618           MyMoneyFileTransaction ft;
619           try {
620             file->modifyAccount(*it);
621             ft.commit();
622           } catch (const MyMoneyException &e) {
623             qDebug("Unable to setup base currency in account %s (%s): %s", qPrintable((*it).name()), qPrintable((*it).id()), e.what());
624           }
625         }
626       }
627     }
628   }
629 
630   /**
631     * Call this to see if the MyMoneyFile contains any unsaved data.
632     *
633     * @retval true if any data has been modified but not saved
634     * @retval false otherwise
635     */
dirty()636   bool dirty()
637   {
638     if (!m_storageInfo.isOpened)
639       return false;
640 
641     return MyMoneyFile::instance()->dirty();
642   }
643 
644 
645   /* DO NOT ADD code to this function or any of it's called ones.
646      Instead, create a new function, fixFile_n, and modify the initializeStorage()
647      logic above to call it */
648 
fixFile_4()649   void fixFile_4()
650   {
651     auto file = MyMoneyFile::instance();
652     QList<MyMoneySecurity> currencies = file->currencyList();
653     static const QStringList symbols = {  QStringLiteral("XAU"),
654                                           QStringLiteral("XPD"),
655                                           QStringLiteral("XPT"),
656                                           QStringLiteral("XAG") };
657 
658 
659     foreach(auto currency, currencies) {
660       if (symbols.contains(currency.id())) {
661         if (currency.smallestAccountFraction() != currency.smallestCashFraction()) {
662           currency.setSmallestAccountFraction(currency.smallestCashFraction());
663           file->modifyCurrency(currency);
664         }
665       }
666     }
667   }
668 
fixFile_3()669   void fixFile_3()
670   {
671     // make sure each storage object contains a (unique) id
672     MyMoneyFile::instance()->storageId();
673   }
674 
fixFile_2()675   void fixFile_2()
676   {
677     auto file = MyMoneyFile::instance();
678     MyMoneyTransactionFilter filter;
679     filter.setReportAllSplits(false);
680     QList<MyMoneyTransaction> transactionList;
681     file->transactionList(transactionList, filter);
682 
683     // scan the transactions and modify transactions with two splits
684     // which reference an account and a category to have the memo text
685     // of the account.
686     auto count = 0;
687     foreach (const auto transaction, transactionList) {
688       if (transaction.splitCount() == 2) {
689         QString accountId;
690         QString categoryId;
691         QString accountMemo;
692         QString categoryMemo;
693         foreach (const auto split, transaction.splits()) {
694           auto acc = file->account(split.accountId());
695           if (acc.isIncomeExpense()) {
696             categoryId = split.id();
697             categoryMemo = split.memo();
698           } else {
699             accountId = split.id();
700             accountMemo = split.memo();
701           }
702         }
703 
704         if (!accountId.isEmpty() && !categoryId.isEmpty()
705             && accountMemo != categoryMemo) {
706           MyMoneyTransaction t(transaction);
707           MyMoneySplit s(t.splitById(categoryId));
708           s.setMemo(accountMemo);
709           t.modifySplit(s);
710           file->modifyTransaction(t);
711           ++count;
712         }
713       }
714     }
715     qDebug("%d transactions fixed in fixFile_2", count);
716   }
717 
fixFile_1()718   void fixFile_1()
719   {
720     // we need to fix reports. If the account filter list contains
721     // investment accounts, we need to add the stock accounts to the list
722     // as well if we don't have the expert mode enabled
723     if (!KMyMoneySettings::expertMode()) {
724       try {
725         QList<MyMoneyReport> reports = MyMoneyFile::instance()->reportList();
726         QList<MyMoneyReport>::iterator it_r;
727         for (it_r = reports.begin(); it_r != reports.end(); ++it_r) {
728           QStringList list;
729           (*it_r).accounts(list);
730           QStringList missing;
731           QStringList::const_iterator it_a, it_b;
732           for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) {
733             auto acc = MyMoneyFile::instance()->account(*it_a);
734             if (acc.accountType() == eMyMoney::Account::Type::Investment) {
735               foreach (const auto accountID, acc.accountList()) {
736                 if (!list.contains(accountID)) {
737                   missing.append(accountID);
738                 }
739               }
740             }
741           }
742           if (!missing.isEmpty()) {
743             (*it_r).addAccount(missing);
744             MyMoneyFile::instance()->modifyReport(*it_r);
745           }
746         }
747       } catch (const MyMoneyException &) {
748       }
749     }
750   }
751 
752   #if 0
753   if (!m_accountsView->allItemsSelected())
754   {
755     // retrieve a list of selected accounts
756     QStringList list;
757     m_accountsView->selectedItems(list);
758 
759     // if we're not in expert mode, we need to make sure
760     // that all stock accounts for the selected investment
761     // account are also selected
762     if (!KMyMoneySettings::expertMode()) {
763       QStringList missing;
764       QStringList::const_iterator it_a, it_b;
765       for (it_a = list.begin(); it_a != list.end(); ++it_a) {
766         auto acc = MyMoneyFile::instance()->account(*it_a);
767         if (acc.accountType() == Account::Type::Investment) {
768           foreach (const auto accountID, acc.accountList()) {
769             if (!list.contains(accountID)) {
770               missing.append(accountID);
771             }
772           }
773         }
774       }
775       list += missing;
776     }
777 
778     m_filter.addAccount(list);
779   }
780 
781   #endif
782 
783 
784 
785 
786 
fixFile_0()787   void fixFile_0()
788   {
789     /* (Ace) I am on a crusade against file fixups.  Whenever we have to fix the
790      * file, it is really a warning.  So I'm going to print a debug warning, and
791      * then go track them down when I see them to figure out how they got saved
792      * out needing fixing anyway.
793      */
794 
795     auto file = MyMoneyFile::instance();
796     QList<MyMoneyAccount> accountList;
797     file->accountList(accountList);
798     QList<MyMoneyAccount>::Iterator it_a;
799     QList<MyMoneySchedule> scheduleList = file->scheduleList();
800     QList<MyMoneySchedule>::Iterator it_s;
801 
802     MyMoneyAccount equity = file->equity();
803     MyMoneyAccount asset = file->asset();
804     bool equityListEmpty = equity.accountList().count() == 0;
805 
806     for (it_a = accountList.begin(); it_a != accountList.end(); ++it_a) {
807       if ((*it_a).accountType() == eMyMoney::Account::Type::Loan
808           || (*it_a).accountType() == eMyMoney::Account::Type::AssetLoan) {
809         fixLoanAccount_0(*it_a);
810       }
811       // until early before 0.8 release, the equity account was not saved to
812       // the file. If we have an equity account with no sub-accounts but
813       // find and equity account that has equity() as it's parent, we reparent
814       // this account. Need to move it to asset() first, because otherwise
815       // MyMoneyFile::reparent would act as NOP.
816       if (equityListEmpty && (*it_a).accountType() == eMyMoney::Account::Type::Equity) {
817         if ((*it_a).parentAccountId() == equity.id()) {
818           auto acc = *it_a;
819           // tricky, force parent account to be empty so that we really
820           // can re-parent it
821           acc.setParentAccountId(QString());
822           file->reparentAccount(acc, equity);
823           qDebug() << Q_FUNC_INFO << " fixed account " << acc.id() << " reparented to " << equity.id();
824         }
825       }
826     }
827 
828     for (it_s = scheduleList.begin(); it_s != scheduleList.end(); ++it_s) {
829       fixSchedule_0(*it_s);
830     }
831 
832     fixTransactions_0();
833   }
834 
fixSchedule_0(MyMoneySchedule sched)835   void fixSchedule_0(MyMoneySchedule sched)
836   {
837     MyMoneyTransaction t = sched.transaction();
838     QList<MyMoneySplit> splitList = t.splits();
839     QList<MyMoneySplit>::ConstIterator it_s;
840 
841     try {
842       bool updated = false;
843       // Check if the splits contain valid data and set it to
844       // be valid.
845       for (it_s = splitList.constBegin(); it_s != splitList.constEnd(); ++it_s) {
846         // the first split is always the account on which this transaction operates
847         // and if the transaction commodity is not set, we take this
848         if (it_s == splitList.constBegin() && t.commodity().isEmpty()) {
849           qDebug() << Q_FUNC_INFO << " " << t.id() << " has no commodity";
850           try {
851             auto acc = MyMoneyFile::instance()->account((*it_s).accountId());
852             t.setCommodity(acc.currencyId());
853             updated = true;
854           } catch (const MyMoneyException &) {
855           }
856         }
857         // make sure the account exists. If not, remove the split
858         try {
859           MyMoneyFile::instance()->account((*it_s).accountId());
860         } catch (const MyMoneyException &) {
861           qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " removed, because account '" << (*it_s).accountId() << "' does not exist.";
862           t.removeSplit(*it_s);
863           updated = true;
864         }
865         if ((*it_s).reconcileFlag() != eMyMoney::Split::State::NotReconciled) {
866           qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'";
867           MyMoneySplit split = *it_s;
868           split.setReconcileDate(QDate());
869           split.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
870           t.modifySplit(split);
871           updated = true;
872         }
873         // the schedule logic used to operate only on the value field.
874         // This is now obsolete.
875         if ((*it_s).shares().isZero() && !(*it_s).value().isZero()) {
876           MyMoneySplit split = *it_s;
877           split.setShares(split.value());
878           t.modifySplit(split);
879           updated = true;
880         }
881       }
882 
883       // If there have been changes, update the schedule and
884       // the engine data.
885       if (updated) {
886         sched.setTransaction(t);
887         MyMoneyFile::instance()->modifySchedule(sched);
888       }
889     } catch (const MyMoneyException &e) {
890       qWarning("Unable to update broken schedule: %s", e.what());
891     }
892   }
893 
fixLoanAccount_0(MyMoneyAccount acc)894   void fixLoanAccount_0(MyMoneyAccount acc)
895   {
896     if (acc.value("final-payment").isEmpty()
897         || acc.value("term").isEmpty()
898         || acc.value("periodic-payment").isEmpty()
899         || acc.value("loan-amount").isEmpty()
900         || acc.value("interest-calculation").isEmpty()
901         || acc.value("schedule").isEmpty()
902         || acc.value("fixed-interest").isEmpty()) {
903       KMessageBox::information(q,
904                                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>"
905                                     , acc.name()),
906                                i18n("Account problem"));
907 
908       throw MYMONEYEXCEPTION_CSTRING("Fix LoanAccount0 not supported anymore");
909     }
910   }
911 
fixTransactions_0()912   void fixTransactions_0()
913   {
914     auto file = MyMoneyFile::instance();
915 
916     QList<MyMoneySchedule> scheduleList = file->scheduleList();
917     MyMoneyTransactionFilter filter;
918     filter.setReportAllSplits(false);
919     QList<MyMoneyTransaction> transactionList;
920     file->transactionList(transactionList, filter);
921 
922     QList<MyMoneySchedule>::Iterator it_x;
923     QStringList interestAccounts;
924 
925     KMSTATUS(i18n("Fix transactions"));
926     q->slotStatusProgressBar(0, scheduleList.count() + transactionList.count());
927 
928     int cnt = 0;
929     // scan the schedules to find interest accounts
930     for (it_x = scheduleList.begin(); it_x != scheduleList.end(); ++it_x) {
931       MyMoneyTransaction t = (*it_x).transaction();
932       QList<MyMoneySplit>::ConstIterator it_s;
933       QStringList accounts;
934       bool hasDuplicateAccounts = false;
935 
936       foreach (const auto split, t.splits()) {
937         if (accounts.contains(split.accountId())) {
938           hasDuplicateAccounts = true;
939           qDebug() << Q_FUNC_INFO << " " << t.id() << " has multiple splits with account " << split.accountId();
940         } else {
941           accounts << split.accountId();
942         }
943 
944         if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) {
945           if (interestAccounts.contains(split.accountId()) == 0) {
946             interestAccounts << split.accountId();
947           }
948         }
949       }
950       if (hasDuplicateAccounts) {
951         fixDuplicateAccounts_0(t);
952       }
953       ++cnt;
954       if (!(cnt % 10))
955         q->slotStatusProgressBar(cnt);
956     }
957 
958     // scan the transactions and modify loan transactions
959     for (auto& transaction : transactionList) {
960       QString defaultAction;
961       QList<MyMoneySplit> splits = transaction.splits();
962       QStringList accounts;
963 
964       // check if base commodity is set. if not, set baseCurrency
965       if (transaction.commodity().isEmpty()) {
966         qDebug() << Q_FUNC_INFO << " " << transaction.id() << " has no base currency";
967         transaction.setCommodity(file->baseCurrency().id());
968         file->modifyTransaction(transaction);
969       }
970 
971       bool isLoan = false;
972       // Determine default action
973       if (transaction.splitCount() == 2) {
974         // check for transfer
975         int accountCount = 0;
976         MyMoneyMoney val;
977         foreach (const auto split, splits) {
978           auto acc = file->account(split.accountId());
979           if (acc.accountGroup() == eMyMoney::Account::Type::Asset
980               || acc.accountGroup() == eMyMoney::Account::Type::Liability) {
981             val = split.value();
982             accountCount++;
983             if (acc.accountType() == eMyMoney::Account::Type::Loan
984                 || acc.accountType() == eMyMoney::Account::Type::AssetLoan)
985               isLoan = true;
986           } else
987             break;
988         }
989         if (accountCount == 2) {
990           if (isLoan)
991             defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization);
992           else
993             defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer);
994         } else {
995           if (val.isNegative())
996             defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal);
997           else
998             defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit);
999         }
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 
fixDuplicateAccounts_0(MyMoneyTransaction & t)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 
KMyMoneyApp(QWidget * parent)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 
~KMyMoneyApp()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 
lastOpenedURL()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 
slotInstallConsistencyCheckContextMenu()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 
slotShowContextMenuForConsistencyCheck(const QPoint & pos)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 
initMenus()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 
initActions()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     const QVector<actionInfo> actionInfos {
1377       // *************
1378       // The File menu
1379       // *************
1380       {Action::FileBackup,                    QStringLiteral("file_backup"),                    i18n("Backup..."),                                  Icon::Empty},
1381       {Action::FileImportStatement,           QStringLiteral("file_import_statement"),          i18n("Statement file..."),                          Icon::Empty},
1382       {Action::FileImportTemplate,            QStringLiteral("file_import_template"),           i18n("Account Template..."),                        Icon::Empty},
1383       {Action::FileExportTemplate,            QStringLiteral("file_export_template"),           i18n("Account Template..."),                        Icon::Empty},
1384       {Action::FilePersonalData,              QStringLiteral("view_personal_data"),             i18n("Personal Data..."),                           Icon::UserProperties},
1385 #ifdef KMM_DEBUG
1386       {Action::FileDump,                      QStringLiteral("file_dump"),                      i18n("Dump Memory"),                                Icon::Empty},
1387 #endif
1388       {Action::FileInformation,               QStringLiteral("view_file_info"),                 i18n("File-Information..."),                        Icon::DocumentProperties},
1389       // *************
1390       // The Edit menu
1391       // *************
1392       {Action::EditFindTransaction,           QStringLiteral("edit_find_transaction"),          i18n("Find transaction..."),                        Icon::Find},
1393       // *************
1394       // The View menu
1395       // *************
1396       {Action::ViewTransactionDetail,         QStringLiteral("view_show_transaction_detail"),   i18n("Show Transaction Detail"),                    Icon::TransactionDetails},
1397       {Action::ViewHideReconciled,            QStringLiteral("view_hide_reconciled_transactions"), i18n("Hide reconciled transactions"),            Icon::HideReconciled},
1398       {Action::ViewHideCategories,            QStringLiteral("view_hide_unused_categories"),    i18n("Hide unused categories"),                     Icon::HideCategories},
1399       {Action::ViewShowAll,                   QStringLiteral("view_show_all_accounts"),         i18n("Show all accounts"),                          Icon::Empty},
1400       // *********************
1401       // The institutions menu
1402       // *********************
1403       {Action::NewInstitution,                QStringLiteral("institution_new"),                i18n("New institution..."),                         Icon::InstitutionNew},
1404       {Action::EditInstitution,               QStringLiteral("institution_edit"),               i18n("Edit institution..."),                        Icon::InstitutionEdit},
1405       {Action::DeleteInstitution,             QStringLiteral("institution_delete"),             i18n("Delete institution..."),                      Icon::InstitutionDelete},
1406       // *****************
1407       // The accounts menu
1408       // *****************
1409       {Action::NewAccount,                    QStringLiteral("account_new"),                    i18n("New account..."),                             Icon::AccountNew},
1410       {Action::OpenAccount,                   QStringLiteral("account_open"),                   i18n("Open ledger"),                                Icon::Ledger},
1411       {Action::StartReconciliation,           QStringLiteral("account_reconcile"),              i18n("Reconcile..."),                               Icon::Reconcile},
1412       {Action::FinishReconciliation,          QStringLiteral("account_reconcile_finish"),       i18nc("Finish reconciliation", "Finish"),    Icon::AccountFinishReconciliation},
1413       {Action::PostponeReconciliation,        QStringLiteral("account_reconcile_postpone"),     i18n("Postpone reconciliation"),                    Icon::Pause},
1414       {Action::EditAccount,                   QStringLiteral("account_edit"),                   i18n("Edit account..."),                            Icon::AccountEdit},
1415       {Action::DeleteAccount,                 QStringLiteral("account_delete"),                 i18n("Delete account..."),                          Icon::AccountDelete},
1416       {Action::CloseAccount,                  QStringLiteral("account_close"),                  i18n("Close account"),                              Icon::AccountClose},
1417       {Action::ReopenAccount,                 QStringLiteral("account_reopen"),                 i18n("Reopen account"),                             Icon::AccountReopen},
1418       {Action::ReportAccountTransactions,     QStringLiteral("account_transaction_report"),     i18n("Transaction report"),                         Icon::Report},
1419       {Action::ChartAccountBalance,           QStringLiteral("account_chart"),                  i18n("Show balance chart..."),                      Icon::OfficeChartLine},
1420       {Action::MapOnlineAccount,              QStringLiteral("account_online_map"),             i18n("Map account..."),                             Icon::MapOnlineAccount},
1421       {Action::UnmapOnlineAccount,            QStringLiteral("account_online_unmap"),           i18n("Unmap account..."),                           Icon::UnmapOnlineAccount},
1422       {Action::UpdateAccount,                 QStringLiteral("account_online_update"),          i18n("Update account..."),                          Icon::AccountUpdate},
1423       {Action::UpdateAllAccounts,             QStringLiteral("account_online_update_all"),      i18n("Update all accounts..."),                     Icon::AccountUpdateAll},
1424       {Action::AccountCreditTransfer,         QStringLiteral("account_online_new_credit_transfer"), i18n("New credit transfer"),                    Icon::AccountCreditTransfer},
1425       // *******************
1426       // The categories menu
1427       // *******************
1428       {Action::NewCategory,                   QStringLiteral("category_new"),                   i18n("New category..."),                            Icon::CategoryNew},
1429       {Action::EditCategory,                  QStringLiteral("category_edit"),                  i18n("Edit category..."),                           Icon::CategoryEdit},
1430       {Action::DeleteCategory,                QStringLiteral("category_delete"),                i18n("Delete category..."),                         Icon::CategoryDelete},
1431       // **************
1432       // The tools menu
1433       // **************
1434       {Action::ToolCurrencies,                QStringLiteral("tools_currency_editor"),          i18n("Currencies..."),                              Icon::Currencies},
1435       {Action::ToolPrices,                    QStringLiteral("tools_price_editor"),             i18n("Prices..."),                                  Icon::Empty},
1436       {Action::ToolUpdatePrices,              QStringLiteral("tools_update_prices"),            i18n("Update Stock and Currency Prices..."),        Icon::InvestmentOnlinePriceAll},
1437       {Action::ToolConsistency,               QStringLiteral("tools_consistency_check"),        i18n("Consistency Check"),                          Icon::Empty},
1438       {Action::ToolPerformance,               QStringLiteral("tools_performancetest"),          i18n("Performance-Test"),                           Icon::PerformanceTest},
1439       {Action::ToolCalculator,                QStringLiteral("tools_kcalc"),                    i18n("Calculator..."),                              Icon::Calculator},
1440       // *****************
1441       // The settings menu
1442       // *****************
1443       {Action::SettingsAllMessages,           QStringLiteral("settings_enable_messages"),       i18n("Enable all messages"),                        Icon::Empty},
1444       // *************
1445       // The help menu
1446       // *************
1447       {Action::HelpShow,                      QStringLiteral("help_show_tip"),                  i18n("&Show tip of the day"),                       Icon::Tip},
1448       // ***************************
1449       // Actions w/o main menu entry
1450       // ***************************
1451       {Action::NewTransaction,                QStringLiteral("transaction_new"),                i18nc("New transaction button", "New"),             Icon::TransactionNew},
1452       {Action::EditTransaction,               QStringLiteral("transaction_edit"),               i18nc("Edit transaction button", "Edit"),           Icon::TransactionEdit},
1453       {Action::EnterTransaction,              QStringLiteral("transaction_enter"),              i18nc("Enter transaction", "Enter"),                Icon::DialogOK},
1454       {Action::EditSplits,                    QStringLiteral("transaction_editsplits"),         i18nc("Edit split button", "Edit splits"),          Icon::Split},
1455       {Action::CancelTransaction,             QStringLiteral("transaction_cancel"),             i18nc("Cancel transaction edit", "Cancel"),         Icon::DialogCancel},
1456       {Action::DeleteTransaction,             QStringLiteral("transaction_delete"),             i18nc("Delete transaction", "Delete"),              Icon::EditDelete},
1457       {Action::DuplicateTransaction,          QStringLiteral("transaction_duplicate"),          i18nc("Duplicate transaction", "Duplicate"),        Icon::EditCopy},
1458       {Action::AddReversingTransaction,       QStringLiteral("transaction_add_reversing"),      i18nc("Add reversing transaction", "Add reversing"),Icon::Reverse},
1459       {Action::MatchTransaction,              QStringLiteral("transaction_match"),              i18nc("Button text for match transaction", "Match"),Icon::TransactionMatch},
1460       {Action::AcceptTransaction,             QStringLiteral("transaction_accept"),             i18nc("Accept 'imported' and 'matched' transaction", "Accept"), Icon::TransactionAccept},
1461       {Action::ToggleReconciliationFlag,      QStringLiteral("transaction_mark_toggle"),        i18nc("Toggle reconciliation flag", "Toggle"),     Icon::Empty},
1462       {Action::MarkCleared,                   QStringLiteral("transaction_mark_cleared"),       i18nc("Mark transaction cleared", "Cleared"),       Icon::Empty},
1463       {Action::MarkReconciled,                QStringLiteral("transaction_mark_reconciled"),    i18nc("Mark transaction reconciled", "Reconciled"), Icon::Empty},
1464       {Action::MarkNotReconciled,             QStringLiteral("transaction_mark_notreconciled"), i18nc("Mark transaction not reconciled", "Not reconciled"),     Icon::Empty},
1465       {Action::SelectAllTransactions,         QStringLiteral("transaction_select_all"),         i18nc("Select all transactions", "Select all"),     Icon::SelectAll},
1466       {Action::GoToAccount,                   QStringLiteral("transaction_goto_account"),       i18n("Go to account"),                              Icon::GoTo},
1467       {Action::GoToPayee,                     QStringLiteral("transaction_goto_payee"),         i18n("Go to payee"),                                Icon::GoTo},
1468       {Action::NewScheduledTransaction,       QStringLiteral("transaction_create_schedule"),    i18n("Create scheduled transaction..."),            Icon::NewSchedule},
1469       {Action::AssignTransactionsNumber,      QStringLiteral("transaction_assign_number"),      i18n("Assign next number"),                         Icon::Empty},
1470       {Action::CombineTransactions,           QStringLiteral("transaction_combine"),            i18nc("Combine transactions", "Combine"),    Icon::Empty},
1471       {Action::CopySplits,                    QStringLiteral("transaction_copy_splits"),        i18n("Copy splits"),                                Icon::Empty},
1472       //Investment
1473       {Action::NewInvestment,                 QStringLiteral("investment_new"),                 i18n("New investment..."),                          Icon::InvestmentNew},
1474       {Action::EditInvestment,                QStringLiteral("investment_edit"),                i18n("Edit investment..."),                         Icon::InvestmentEdit},
1475       {Action::DeleteInvestment,              QStringLiteral("investment_delete"),              i18n("Delete investment..."),                       Icon::InvestmentDelete},
1476       {Action::UpdatePriceOnline,             QStringLiteral("investment_online_price_update"), i18n("Online price update..."),                     Icon::InvestmentOnlinePrice},
1477       {Action::UpdatePriceManually,           QStringLiteral("investment_manual_price_update"), i18n("Manual price update..."),                     Icon::Empty},
1478       //Schedule
1479       {Action::NewSchedule,                   QStringLiteral("schedule_new"),                   i18n("New scheduled transaction"),                  Icon::NewSchedule},
1480       {Action::EditSchedule,                  QStringLiteral("schedule_edit"),                  i18n("Edit scheduled transaction"),                 Icon::DocumentEdit},
1481       {Action::DeleteSchedule,                QStringLiteral("schedule_delete"),                i18n("Delete scheduled transaction"),               Icon::EditDelete},
1482       {Action::DuplicateSchedule,             QStringLiteral("schedule_duplicate"),             i18n("Duplicate scheduled transaction"),            Icon::EditCopy},
1483       {Action::EnterSchedule,                 QStringLiteral("schedule_enter"),                 i18n("Enter next transaction..."),                  Icon::KeyEnter},
1484       {Action::SkipSchedule,                  QStringLiteral("schedule_skip"),                  i18n("Skip next transaction..."),                   Icon::SeekForward},
1485       //Payees
1486       {Action::NewPayee,                      QStringLiteral("payee_new"),                      i18n("New payee"),                                  Icon::ListAddUser},
1487       {Action::RenamePayee,                   QStringLiteral("payee_rename"),                   i18n("Rename payee"),                               Icon::PayeeRename},
1488       {Action::DeletePayee,                   QStringLiteral("payee_delete"),                   i18n("Delete payee"),                               Icon::ListRemoveUser},
1489       {Action::MergePayee,                    QStringLiteral("payee_merge"),                    i18n("Merge payees"),                               Icon::PayeeMerge},
1490       //Tags
1491       {Action::NewTag,                        QStringLiteral("tag_new"),                        i18n("New tag"),                                    Icon::ListAddTag},
1492       {Action::RenameTag,                     QStringLiteral("tag_rename"),                     i18n("Rename tag"),                                 Icon::TagRename},
1493       {Action::DeleteTag,                     QStringLiteral("tag_delete"),                     i18n("Delete tag"),                                 Icon::ListRemoveTag},
1494       //debug actions
1495 #ifdef KMM_DEBUG
1496       {Action::WizardNewUser,                 QStringLiteral("new_user_wizard"),                i18n("Test new feature"),                           Icon::Empty},
1497       {Action::DebugTraces,                   QStringLiteral("debug_traces"),                   i18n("Debug Traces"),                               Icon::Empty},
1498 #endif
1499       {Action::DebugTimers,                   QStringLiteral("debug_timers"),                   i18n("Debug Timers"),                               Icon::Empty},
1500       // onlineJob actions
1501       {Action::DeleteOnlineJob,               QStringLiteral("onlinejob_delete"),               i18n("Remove credit transfer"),                     Icon::EditDelete},
1502       {Action::EditOnlineJob,                 QStringLiteral("onlinejob_edit"),                 i18n("Edit credit transfer"),                       Icon::DocumentEdit},
1503       {Action::LogOnlineJob,                  QStringLiteral("onlinejob_log"),                  i18n("Show log"),                                   Icon::Empty},
1504     };
1505 
1506     for (const auto& info : actionInfos) {
1507       auto a = new QAction(this);
1508       // KActionCollection::addAction by name sets object name anyways,
1509       // so, as better alternative, set it here right from the start
1510       a->setObjectName(info.name);
1511       a->setText(info.text);
1512       if (info.icon != Icon::Empty) // no need to set empty icon
1513         a->setIcon(Icons::get(info.icon));
1514       a->setEnabled(false);
1515       lutActions.insert(info.action, a);  // store QAction's pointer for later processing
1516     }
1517   }
1518 
1519   {
1520     // List with slots that get connected here. Other slots get connected in e.g. appropriate views
1521     typedef void(KMyMoneyApp::*KMyMoneyAppFunc)();
1522     const QHash<eMenu::Action, KMyMoneyAppFunc> actionConnections {
1523       // *************
1524       // The File menu
1525       // *************
1526 //      {Action::FileOpenDatabase,              &KMyMoneyApp::slotOpenDatabase},
1527 //      {Action::FileSaveAsDatabase,            &KMyMoneyApp::slotSaveAsDatabase},
1528       {Action::FileBackup,                    &KMyMoneyApp::slotBackupFile},
1529       {Action::FileImportTemplate,            &KMyMoneyApp::slotLoadAccountTemplates},
1530       {Action::FileExportTemplate,            &KMyMoneyApp::slotSaveAccountTemplates},
1531       {Action::FilePersonalData,              &KMyMoneyApp::slotFileViewPersonal},
1532 #ifdef KMM_DEBUG
1533       {Action::FileDump,                      &KMyMoneyApp::slotFileFileInfo},
1534 #endif
1535       {Action::FileInformation,               &KMyMoneyApp::slotFileInfoDialog},
1536       // *************
1537       // The View menu
1538       // *************
1539       {Action::ViewTransactionDetail,         &KMyMoneyApp::slotShowTransactionDetail},
1540       {Action::ViewHideReconciled,            &KMyMoneyApp::slotHideReconciledTransactions},
1541       {Action::ViewHideCategories,            &KMyMoneyApp::slotHideUnusedCategories},
1542       {Action::ViewShowAll,                   &KMyMoneyApp::slotShowAllAccounts},
1543       // **************
1544       // The tools menu
1545       // **************
1546       {Action::ToolCurrencies,                &KMyMoneyApp::slotCurrencyDialog},
1547       {Action::ToolPrices,                    &KMyMoneyApp::slotPriceDialog},
1548       {Action::ToolUpdatePrices,              &KMyMoneyApp::slotEquityPriceUpdate},
1549       {Action::ToolConsistency,               &KMyMoneyApp::slotFileConsistencyCheck},
1550       {Action::ToolPerformance,               &KMyMoneyApp::slotPerformanceTest},
1551 //      {Action::ToolSQL,                       &KMyMoneyApp::slotGenerateSql},
1552       {Action::ToolCalculator,                &KMyMoneyApp::slotToolsStartKCalc},
1553       // *****************
1554       // The settings menu
1555       // *****************
1556       {Action::SettingsAllMessages,           &KMyMoneyApp::slotEnableMessages},
1557       // *************
1558       // The help menu
1559       // *************
1560       {Action::HelpShow,                      &KMyMoneyApp::slotShowTipOfTheDay},
1561       // ***************************
1562       // Actions w/o main menu entry
1563       // ***************************
1564       //debug actions
1565 #ifdef KMM_DEBUG
1566       {Action::WizardNewUser,                 &KMyMoneyApp::slotNewFeature},
1567       {Action::DebugTraces,                   &KMyMoneyApp::slotToggleTraces},
1568 #endif
1569       {Action::DebugTimers,                   &KMyMoneyApp::slotToggleTimers},
1570     };
1571 
1572     for (auto connection = actionConnections.cbegin(); connection != actionConnections.cend(); ++connection)
1573       connect(lutActions[connection.key()], &QAction::triggered, this, connection.value());
1574   }
1575 
1576   // *************
1577   // Setting some of added actions checkable
1578   // *************
1579   {
1580     // Some actions are checkable,
1581     // so set them here
1582     const QVector<Action> checkableActions {
1583       Action::ViewTransactionDetail, Action::ViewHideReconciled, Action::ViewHideCategories,
1584     #ifdef KMM_DEBUG
1585           Action::DebugTraces,
1586           Action::DebugTimers,
1587     #endif
1588           Action::ViewShowAll
1589     };
1590 
1591     for (const auto& it : checkableActions) {
1592       lutActions[it]->setCheckable(true);
1593       lutActions[it]->setEnabled(true);
1594     }
1595   }
1596 
1597   // *************
1598   // Setting actions that are always enabled
1599   // *************
1600   {
1601     const QVector<eMenu::Action> alwaysEnabled {
1602       Action::HelpShow,
1603       Action::SettingsAllMessages,
1604       Action::ToolPerformance,
1605       Action::ToolCalculator
1606     };
1607     for (const auto& action : alwaysEnabled) {
1608       lutActions[action]->setEnabled(true);
1609     }
1610   }
1611 
1612   // *************
1613   // Setting keyboard shortcuts for some of added actions
1614   // *************
1615   {
1616     const QVector<QPair<Action, QKeySequence>> actionShortcuts {
1617       {qMakePair(Action::EditFindTransaction,         Qt::CTRL + Qt::Key_F)},
1618       {qMakePair(Action::ViewTransactionDetail,       Qt::CTRL + Qt::Key_T)},
1619       {qMakePair(Action::ViewHideReconciled,          Qt::CTRL + Qt::Key_R)},
1620       {qMakePair(Action::ViewHideCategories,          Qt::CTRL + Qt::Key_U)},
1621       {qMakePair(Action::ViewShowAll,                 Qt::CTRL + Qt::SHIFT + Qt::Key_A)},
1622       {qMakePair(Action::StartReconciliation,         Qt::CTRL + Qt::SHIFT + Qt::Key_R)},
1623       {qMakePair(Action::NewTransaction,              Qt::CTRL + Qt::Key_Insert)},
1624       {qMakePair(Action::ToggleReconciliationFlag,    Qt::CTRL + Qt::Key_Space)},
1625       {qMakePair(Action::MarkCleared,                 Qt::CTRL + Qt::ALT+ Qt::Key_Space)},
1626       {qMakePair(Action::MarkReconciled,              Qt::CTRL + Qt::SHIFT + Qt::Key_Space)},
1627       {qMakePair(Action::SelectAllTransactions,       Qt::CTRL + Qt::Key_A)},
1628 #ifdef KMM_DEBUG
1629       {qMakePair(Action::WizardNewUser,               Qt::CTRL + Qt::Key_G)},
1630 #endif
1631       {qMakePair(Action::AssignTransactionsNumber,    Qt::CTRL + Qt::SHIFT + Qt::Key_N)}
1632     };
1633 
1634     for(const auto& it : actionShortcuts)
1635       aC->setDefaultShortcut(lutActions[it.first], it.second);
1636   }
1637 
1638   // *************
1639   // Misc settings
1640   // *************
1641   connect(onlineJobAdministration::instance(), &onlineJobAdministration::canSendCreditTransferChanged,  lutActions.value(Action::AccountCreditTransfer), &QAction::setEnabled);
1642 
1643   // Setup transaction detail switch
1644   lutActions[Action::ViewTransactionDetail]->setChecked(KMyMoneySettings::showRegisterDetailed());
1645   lutActions[Action::ViewHideReconciled]->setChecked(KMyMoneySettings::hideReconciledTransactions());
1646   lutActions[Action::ViewHideCategories]->setChecked(KMyMoneySettings::hideUnusedCategory());
1647   lutActions[Action::ViewShowAll]->setChecked(KMyMoneySettings::showAllAccounts());
1648 
1649   // *************
1650   // Adding actions to ActionCollection
1651   // *************
1652   actionCollection()->addActions(lutActions.values());
1653 
1654   // ************************
1655   // Currently unused actions
1656   // ************************
1657 #if 0
1658   new KToolBarPopupAction(i18n("View back"), "go-previous", 0, this, SLOT(slotShowPreviousView()), actionCollection(), "go_back");
1659   new KToolBarPopupAction(i18n("View forward"), "go-next", 0, this, SLOT(slotShowNextView()), actionCollection(), "go_forward");
1660 
1661   action("go_back")->setEnabled(false);
1662   action("go_forward")->setEnabled(false);
1663 #endif
1664 
1665   // use the absolute path to your kmymoneyui.rc file for testing purpose in createGUI();
1666   setupGUI();
1667 
1668   // reconnect about app entry to dialog with full credits information
1669   auto aboutApp = aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::AboutApp)));
1670   aboutApp->disconnect();
1671   connect(aboutApp, &QAction::triggered, this, &KMyMoneyApp::slotShowCredits);
1672 
1673   QMenu *menuContainer;
1674   menuContainer = static_cast<QMenu*>(factory()->container(QStringLiteral("import"), this));
1675   menuContainer->setIcon(Icons::get(Icon::DocumentImport));
1676 
1677   menuContainer = static_cast<QMenu*>(factory()->container(QStringLiteral("export"), this));
1678   menuContainer->setIcon(Icons::get(Icon::DocumentExport));
1679   return lutActions;
1680 }
1681 
1682 #ifdef KMM_DEBUG
dumpActions() const1683 void KMyMoneyApp::dumpActions() const
1684 {
1685   const QList<QAction*> list = actionCollection()->actions();
1686   foreach (const auto it, list)
1687     std::cout << qPrintable(it->objectName()) << ": " << qPrintable(it->text()) << std::endl;
1688 }
1689 #endif
1690 
isActionToggled(const Action _a)1691 bool KMyMoneyApp::isActionToggled(const Action _a)
1692 {
1693   return pActions[_a]->isChecked();
1694 }
1695 
initStatusBar()1696 void KMyMoneyApp::initStatusBar()
1697 {
1698   ///////////////////////////////////////////////////////////////////
1699   // STATUSBAR
1700 
1701   d->m_statusLabel = new QLabel;
1702   statusBar()->addWidget(d->m_statusLabel);
1703   ready();
1704 
1705   // Initialization of progress bar taken from KDevelop ;-)
1706   d->m_progressBar = new QProgressBar;
1707   statusBar()->addWidget(d->m_progressBar);
1708   d->m_progressBar->setFixedHeight(d->m_progressBar->sizeHint().height() - 8);
1709 
1710   // hide the progress bar for now
1711   slotStatusProgressBar(-1, -1);
1712 }
1713 
initIcons()1714 void KMyMoneyApp::initIcons()
1715 {
1716 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1717   const auto appDataIconsLocation = QStringLiteral("kmymoney/icons");
1718 #else
1719   const auto appDataIconsLocation = QStringLiteral("icons");
1720 #endif
1721 
1722   const QString customIconRelativePath = appDataIconsLocation + QStringLiteral("/hicolor/16x16/actions/account-add.png");
1723 #ifndef IS_APPIMAGE
1724   // find where our custom icons were installed based on an custom icon that we know should exist after installation
1725   auto customIconAbsolutePath = QStandardPaths::locate(QStandardPaths::AppDataLocation, customIconRelativePath);
1726   if (customIconAbsolutePath.isEmpty()) {
1727     qWarning("Custom icons were not found in any of the following QStandardPaths::AppDataLocation:");
1728     for (const auto &standardPath : QStandardPaths::standardLocations(QStandardPaths::AppDataLocation))
1729       qWarning() << standardPath;
1730   }
1731 #else
1732   // according to https://docs.appimage.org/packaging-guide/ingredients.html#open-source-applications
1733   // QStandardPaths::AppDataLocation is unreliable on AppImages, so apply workaround here in case we fail to find icons
1734   QString customIconAbsolutePath;
1735   const auto appImageAppDataLocation = QString("%1%2%3").arg(QCoreApplication::applicationDirPath(), QString("/../share/kmymoney/"), customIconRelativePath);
1736   if (QFile::exists(appImageAppDataLocation )) {
1737     customIconAbsolutePath = appImageAppDataLocation ;
1738   } else {
1739     qWarning("Custom icons were not found in the following location:");
1740     qWarning() << appImageAppDataLocation ;
1741   }
1742 #endif
1743 
1744   // add our custom icons path to icons search path
1745   if (!customIconAbsolutePath.isEmpty()) {
1746     customIconAbsolutePath.chop(customIconRelativePath.length());
1747     customIconAbsolutePath.append(appDataIconsLocation);
1748     auto paths = QIcon::themeSearchPaths();
1749     paths.append(customIconAbsolutePath);
1750     QIcon::setThemeSearchPaths(paths);
1751   }
1752 
1753   qDebug() << "System icon theme as reported by QT: " << QIcon::themeName();
1754 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1755   auto themeName = QStringLiteral("breeze");                      // only breeze is available for craft packages
1756 #else
1757   auto themeName = KMyMoneySettings::iconsTheme();                        // get theme user wants
1758   if (!themeName.isEmpty() && themeName != QStringLiteral("system"))  // if it isn't default theme then set it
1759     QIcon::setThemeName(themeName);
1760   else
1761     themeName = QIcon::themeName();
1762 #endif
1763 
1764   setUpMappings(themeName);
1765 }
1766 
saveOptions()1767 void KMyMoneyApp::saveOptions()
1768 {
1769   KConfigGroup grp = d->m_config->group("General Options");
1770   grp.writeEntry("Geometry", size());
1771 
1772   grp.writeEntry("Show Statusbar", actionCollection()->action(KStandardAction::name(KStandardAction::ShowStatusbar))->isChecked());
1773 
1774   KConfigGroup toolbarGrp = d->m_config->group("mainToolBar");
1775   toolBar("mainToolBar")->saveSettings(toolbarGrp);
1776 
1777   d->m_recentFiles->saveEntries(d->m_config->group("Recent Files"));
1778 
1779 }
1780 
1781 
readOptions()1782 void KMyMoneyApp::readOptions()
1783 {
1784   KConfigGroup grp = d->m_config->group("General Options");
1785 
1786 
1787   pActions[Action::ViewHideReconciled]->setChecked(KMyMoneySettings::hideReconciledTransactions());
1788   pActions[Action::ViewHideCategories]->setChecked(KMyMoneySettings::hideUnusedCategory());
1789 
1790   d->m_recentFiles->loadEntries(d->m_config->group("Recent Files"));
1791 
1792   // Startdialog is written in the settings dialog
1793   d->m_startDialog = grp.readEntry("StartDialog", true);
1794 }
1795 
1796 #ifdef KMM_DEBUG
resizeEvent(QResizeEvent * ev)1797 void KMyMoneyApp::resizeEvent(QResizeEvent* ev)
1798 {
1799   KMainWindow::resizeEvent(ev);
1800   d->updateCaption();
1801 }
1802 #endif
1803 
queryClose()1804 bool KMyMoneyApp::queryClose()
1805 {
1806   if (!isReady())
1807     return false;
1808 
1809   if (!slotFileClose())
1810     return false;
1811 
1812   saveOptions();
1813   return true;
1814 }
1815 
1816 /////////////////////////////////////////////////////////////////////
1817 // SLOT IMPLEMENTATION
1818 /////////////////////////////////////////////////////////////////////
slotFileInfoDialog()1819 void KMyMoneyApp::slotFileInfoDialog()
1820 {
1821   QPointer<KMyMoneyFileInfoDlg> dlg = new KMyMoneyFileInfoDlg(0);
1822   dlg->exec();
1823   delete dlg;
1824 }
1825 
slotPerformanceTest()1826 void KMyMoneyApp::slotPerformanceTest()
1827 {
1828   // dump performance report to stderr
1829 
1830   int measurement[2];
1831   QTime timer;
1832   MyMoneyAccount acc;
1833 
1834   qDebug("--- Starting performance tests ---");
1835 
1836   // AccountList
1837 //  MyMoneyFile::instance()->preloadCache();
1838   measurement[0] = measurement[1] = 0;
1839   timer.start();
1840   for (int i = 0; i < 1000; ++i) {
1841     QList<MyMoneyAccount> list;
1842 
1843     MyMoneyFile::instance()->accountList(list);
1844     measurement[i != 0] = timer.elapsed();
1845   }
1846   std::cerr << "accountList()" << std::endl;
1847   std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1848   std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl;
1849   std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
1850 
1851   // Balance of asset account(s)
1852 //  MyMoneyFile::instance()->preloadCache();
1853   measurement[0] = measurement[1] = 0;
1854   acc = MyMoneyFile::instance()->asset();
1855   for (int i = 0; i < 1000; ++i) {
1856     timer.start();
1857     MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id());
1858     measurement[i != 0] += timer.elapsed();
1859   }
1860   std::cerr << "balance(Asset)" << std::endl;
1861   std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1862   std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
1863 
1864   // total balance of asset account
1865 //  MyMoneyFile::instance()->preloadCache();
1866   measurement[0] = measurement[1] = 0;
1867   acc = MyMoneyFile::instance()->asset();
1868   for (int i = 0; i < 1000; ++i) {
1869     timer.start();
1870     MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id());
1871     measurement[i != 0] += timer.elapsed();
1872   }
1873   std::cerr << "totalBalance(Asset)" << std::endl;
1874   std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1875   std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
1876 
1877   // Balance of expense account(s)
1878 //  MyMoneyFile::instance()->preloadCache();
1879   measurement[0] = measurement[1] = 0;
1880   acc = MyMoneyFile::instance()->expense();
1881   for (int i = 0; i < 1000; ++i) {
1882     timer.start();
1883     MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id());
1884     measurement[i != 0] += timer.elapsed();
1885   }
1886   std::cerr << "balance(Expense)" << std::endl;
1887   std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1888   std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
1889 
1890   // total balance of expense account
1891 //  MyMoneyFile::instance()->preloadCache();
1892   measurement[0] = measurement[1] = 0;
1893   acc = MyMoneyFile::instance()->expense();
1894   timer.start();
1895   for (int i = 0; i < 1000; ++i) {
1896     MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id());
1897     measurement[i != 0] = timer.elapsed();
1898   }
1899   std::cerr << "totalBalance(Expense)" << std::endl;
1900   std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1901   std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl;
1902   std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl;
1903 
1904   // transaction list
1905 //  MyMoneyFile::instance()->preloadCache();
1906   measurement[0] = measurement[1] = 0;
1907   if (MyMoneyFile::instance()->asset().accountCount()) {
1908     MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]);
1909     filter.setDateFilter(QDate(), QDate::currentDate());
1910     QList<MyMoneyTransaction> list;
1911 
1912     timer.start();
1913     for (int i = 0; i < 100; ++i) {
1914       list = MyMoneyFile::instance()->transactionList(filter);
1915       measurement[i != 0] = timer.elapsed();
1916     }
1917     std::cerr << "transactionList()" << std::endl;
1918     std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1919     std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl;
1920     std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl;
1921   }
1922 
1923   // transaction list
1924 //  MyMoneyFile::instance()->preloadCache();
1925   measurement[0] = measurement[1] = 0;
1926   if (MyMoneyFile::instance()->asset().accountCount()) {
1927     MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]);
1928     filter.setDateFilter(QDate(), QDate::currentDate());
1929     QList<MyMoneyTransaction> list;
1930 
1931     timer.start();
1932     for (int i = 0; i < 100; ++i) {
1933       MyMoneyFile::instance()->transactionList(list, filter);
1934       measurement[i != 0] = timer.elapsed();
1935     }
1936     std::cerr << "transactionList(list)" << std::endl;
1937     std::cerr << "First time: " << measurement[0] << " msec" << std::endl;
1938     std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl;
1939     std::cerr << "Average   : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl;
1940   }
1941 //  MyMoneyFile::instance()->preloadCache();
1942 }
1943 
isDatabase()1944 bool KMyMoneyApp::isDatabase()
1945 {
1946   return (d->m_storageInfo.isOpened && ((d->m_storageInfo.type == eKMyMoney::StorageType::SQL)));
1947 }
1948 
isNativeFile()1949 bool KMyMoneyApp::isNativeFile()
1950 {
1951   return (d->m_storageInfo.isOpened && (d->m_storageInfo.type == eKMyMoney::StorageType::SQL || d->m_storageInfo.type == eKMyMoney::StorageType::XML));
1952 }
1953 
fileOpen() const1954 bool KMyMoneyApp::fileOpen() const
1955 {
1956   return d->m_storageInfo.isOpened;
1957 }
1958 
progressCallback()1959 KMyMoneyAppCallback KMyMoneyApp::progressCallback()
1960 {
1961   return &KMyMoneyApp::progressCallback;
1962 }
1963 
consistencyCheck(bool alwaysDisplayResult)1964 void KMyMoneyApp::consistencyCheck(bool alwaysDisplayResult)
1965 {
1966   d->consistencyCheck(alwaysDisplayResult);
1967 }
1968 
isImportableFile(const QUrl & url)1969 bool KMyMoneyApp::isImportableFile(const QUrl &url)
1970 {
1971   bool result = false;
1972 
1973   // Iterate through the plugins and see if there's a loaded plugin who can handle it
1974   QMap<QString, KMyMoneyPlugin::ImporterPlugin*>::const_iterator it_plugin = pPlugins.importer.constBegin();
1975   while (it_plugin != pPlugins.importer.constEnd()) {
1976     if ((*it_plugin)->isMyFormat(url.toLocalFile())) {
1977       result = true;
1978       break;
1979     }
1980     ++it_plugin;
1981   }
1982 
1983   // If we did not find a match, try importing it as a KMM statement file,
1984   // which is really just for testing.  the statement file is not exposed
1985   // to users.
1986   if (it_plugin == pPlugins.importer.constEnd())
1987     if (MyMoneyStatement::isStatementFile(url.path()))
1988       result = true;
1989 
1990   // Place code here to test for QIF and other locally-supported formats
1991   // (i.e. not a plugin). If you add them here, be sure to add it to
1992   // the webConnect function.
1993 
1994   return result;
1995 }
1996 
isFileOpenedInAnotherInstance(const QUrl & url)1997 bool KMyMoneyApp::isFileOpenedInAnotherInstance(const QUrl &url)
1998 {
1999   const auto instances = instanceList();
2000 #ifdef KMM_DBUS
2001   // check if there are other instances which might have this file open
2002   for (const auto& instance : instances) {
2003     QDBusInterface remoteApp(instance, "/KMymoney", "org.kde.kmymoney");
2004     QDBusReply<QString> reply = remoteApp.call("filename");
2005     if (!reply.isValid())
2006       qDebug("D-Bus error while calling app->filename()");
2007     else if (reply.value() == url.url())
2008       return true;
2009   }
2010 #else
2011   Q_UNUSED(url)
2012 #endif
2013   return false;
2014 }
2015 
slotShowTransactionDetail()2016 void KMyMoneyApp::slotShowTransactionDetail()
2017 {
2018 
2019 }
2020 
slotHideReconciledTransactions()2021 void KMyMoneyApp::slotHideReconciledTransactions()
2022 {
2023   KMyMoneySettings::setHideReconciledTransactions(pActions[Action::ViewHideReconciled]->isChecked());
2024   d->m_myMoneyView->slotRefreshViews();
2025 }
2026 
slotHideUnusedCategories()2027 void KMyMoneyApp::slotHideUnusedCategories()
2028 {
2029   KMyMoneySettings::setHideUnusedCategory(pActions[Action::ViewHideCategories]->isChecked());
2030   d->m_myMoneyView->slotRefreshViews();
2031 }
2032 
slotShowAllAccounts()2033 void KMyMoneyApp::slotShowAllAccounts()
2034 {
2035   KMyMoneySettings::setShowAllAccounts(pActions[Action::ViewShowAll]->isChecked());
2036   d->m_myMoneyView->slotRefreshViews();
2037 }
2038 
2039 #ifdef KMM_DEBUG
slotFileFileInfo()2040 void KMyMoneyApp::slotFileFileInfo()
2041 {
2042   if (!d->m_storageInfo.isOpened) {
2043     KMessageBox::information(this, i18n("No KMyMoneyFile open"));
2044     return;
2045   }
2046 
2047   QFile g("kmymoney.dump");
2048   g.open(QIODevice::WriteOnly);
2049   QDataStream st(&g);
2050   MyMoneyStorageDump dumper;
2051   dumper.writeStream(st, MyMoneyFile::instance()->storage());
2052   g.close();
2053 }
2054 
slotToggleTraces()2055 void KMyMoneyApp::slotToggleTraces()
2056 {
2057   MyMoneyTracer::onOff(pActions[Action::DebugTraces]->isChecked() ? 1 : 0);
2058 }
2059 #endif
2060 
slotToggleTimers()2061 void KMyMoneyApp::slotToggleTimers()
2062 {
2063   extern bool timersOn; // main.cpp
2064 
2065   timersOn = pActions[Action::DebugTimers]->isChecked();
2066 }
2067 
slotStatusMsg(const QString & text)2068 QString KMyMoneyApp::slotStatusMsg(const QString &text)
2069 {
2070   ///////////////////////////////////////////////////////////////////
2071   // change status message permanently
2072   QString previousMessage = d->m_statusLabel->text();
2073   d->m_applicationIsReady = false;
2074 
2075   QString currentMessage = text;
2076   if (currentMessage.isEmpty() || currentMessage == i18nc("Application is ready to use", "Ready.")) {
2077     d->m_applicationIsReady = true;
2078     currentMessage = i18nc("Application is ready to use", "Ready.");
2079   }
2080   statusBar()->clearMessage();
2081   d->m_statusLabel->setText(currentMessage);
2082   return previousMessage;
2083 }
2084 
ready()2085 void KMyMoneyApp::ready()
2086 {
2087   slotStatusMsg(QString());
2088 }
2089 
isReady()2090 bool KMyMoneyApp::isReady()
2091 {
2092   return d->m_applicationIsReady;
2093 }
2094 
slotStatusProgressBar(int current,int total)2095 void KMyMoneyApp::slotStatusProgressBar(int current, int total)
2096 {
2097   if (total == -1 && current == -1) {     // reset
2098     if (d->m_progressTimer) {
2099       d->m_progressTimer->start(500);     // remove from screen in 500 msec
2100       d->m_progressBar->setValue(d->m_progressBar->maximum());
2101     }
2102 
2103   } else if (total != 0) {                // init
2104     d->m_progressTimer->stop();
2105     d->m_progressBar->setMaximum(total);
2106     d->m_progressBar->setValue(0);
2107     d->m_progressBar->show();
2108     d->m_lastUpdate = QTime::currentTime();
2109 
2110   } else {                                // update
2111     const auto currentTime = QTime::currentTime();
2112     // only process painting if last update is at least 200 ms ago
2113     if (abs(d->m_lastUpdate.msecsTo(currentTime)) > 200) {
2114       d->m_progressBar->setValue(current);
2115       d->m_lastUpdate = currentTime;
2116     }
2117   }
2118 }
2119 
slotStatusProgressDone()2120 void KMyMoneyApp::slotStatusProgressDone()
2121 {
2122   d->m_progressTimer->stop();
2123   d->m_progressBar->reset();
2124   d->m_progressBar->hide();
2125   d->m_progressBar->setValue(0);
2126 }
2127 
progressCallback(int current,int total,const QString & msg)2128 void KMyMoneyApp::progressCallback(int current, int total, const QString& msg)
2129 {
2130   if (!msg.isEmpty())
2131     kmymoney->slotStatusMsg(msg);
2132 
2133   kmymoney->slotStatusProgressBar(current, total);
2134 }
2135 
slotFileViewPersonal()2136 void KMyMoneyApp::slotFileViewPersonal()
2137 {
2138   if (!d->m_storageInfo.isOpened) {
2139     KMessageBox::information(this, i18n("No KMyMoneyFile open"));
2140     return;
2141   }
2142 
2143   KMSTATUS(i18n("Viewing personal data..."));
2144 
2145   MyMoneyFile* file = MyMoneyFile::instance();
2146   MyMoneyPayee user = file->user();
2147 
2148   QPointer<EditPersonalDataDlg> editPersonalDataDlg = new EditPersonalDataDlg(user.name(), user.address(),
2149       user.city(), user.state(), user.postcode(), user.telephone(),
2150       user.email(), this, i18n("Edit Personal Data"));
2151 
2152   if (editPersonalDataDlg->exec() == QDialog::Accepted && editPersonalDataDlg != 0) {
2153     user.setName(editPersonalDataDlg->userName());
2154     user.setAddress(editPersonalDataDlg->userStreet());
2155     user.setCity(editPersonalDataDlg->userTown());
2156     user.setState(editPersonalDataDlg->userCountry());
2157     user.setPostcode(editPersonalDataDlg->userPostcode());
2158     user.setTelephone(editPersonalDataDlg->userTelephone());
2159     user.setEmail(editPersonalDataDlg->userEmail());
2160     MyMoneyFileTransaction ft;
2161     try {
2162       file->setUser(user);
2163       ft.commit();
2164     } catch (const MyMoneyException &e) {
2165       KMessageBox::information(this, i18n("Unable to store user information: %1", QString::fromLatin1(e.what())));
2166     }
2167   }
2168   delete editPersonalDataDlg;
2169 }
2170 
slotLoadAccountTemplates()2171 void KMyMoneyApp::slotLoadAccountTemplates()
2172 {
2173   KMSTATUS(i18n("Importing account templates."));
2174 
2175   QPointer<KLoadTemplateDlg> dlg = new KLoadTemplateDlg();
2176   if (dlg->exec() == QDialog::Accepted && dlg != 0) {
2177     MyMoneyFileTransaction ft;
2178     try {
2179       // import the account templates
2180       QList<MyMoneyTemplate> templates = dlg->templates();
2181       QList<MyMoneyTemplate>::iterator it_t;
2182       for (it_t = templates.begin(); it_t != templates.end(); ++it_t) {
2183         (*it_t).importTemplate(progressCallback);
2184       }
2185       ft.commit();
2186     } catch (const MyMoneyException &e) {
2187       KMessageBox::detailedSorry(this, i18n("Unable to import template(s)"), QString::fromLatin1(e.what()));
2188     }
2189   }
2190   delete dlg;
2191 }
2192 
slotSaveAccountTemplates()2193 void KMyMoneyApp::slotSaveAccountTemplates()
2194 {
2195   KMSTATUS(i18n("Exporting account templates."));
2196 
2197   QString savePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/templates/" + QLocale().name();
2198   QDir templatesDir(savePath);
2199   if (!templatesDir.exists())
2200       templatesDir.mkpath(savePath);
2201   QString newName = QFileDialog::getSaveFileName(this, i18n("Save as..."), savePath,
2202                     i18n("KMyMoney template files (*.kmt);;All files (*)"));
2203 
2204   //
2205   // If there is no file extension, then append a .kmt at the end of the file name.
2206   // If there is a file extension, make sure it is .kmt, delete any others.
2207   //
2208   if (!newName.isEmpty()) {
2209     // find last . delimiter
2210     int nLoc = newName.lastIndexOf('.');
2211     if (nLoc != -1) {
2212       QString strExt, strTemp;
2213       strTemp = newName.left(nLoc + 1);
2214       strExt = newName.right(newName.length() - (nLoc + 1));
2215       if ((strExt.indexOf("kmt", 0, Qt::CaseInsensitive) == -1)) {
2216         strTemp.append("kmt");
2217         //append to make complete file name
2218         newName = strTemp;
2219       }
2220     } else {
2221       newName.append(".kmt");
2222     }
2223 
2224     if (okToWriteFile(QUrl::fromLocalFile(newName))) {
2225       QPointer <KTemplateExportDlg> dlg = new KTemplateExportDlg(this);
2226       if (dlg->exec() == QDialog::Accepted && dlg) {
2227           MyMoneyTemplate templ;
2228           templ.setTitle(dlg->title());
2229           templ.setShortDescription(dlg->shortDescription());
2230           templ.setLongDescription(dlg->longDescription());
2231           templ.exportTemplate(progressCallback);
2232           templ.saveTemplate(QUrl::fromLocalFile(newName));
2233       }
2234       delete dlg;
2235     }
2236   }
2237 }
2238 
okToWriteFile(const QUrl & url)2239 bool KMyMoneyApp::okToWriteFile(const QUrl &url)
2240 {
2241   Q_UNUSED(url)
2242 
2243   // check if the file exists and warn the user
2244   bool reallySaveFile = true;
2245 
2246   if (KMyMoneyUtils::fileExists(url)) {
2247     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)
2248     reallySaveFile = false;
2249   }
2250   return reallySaveFile;
2251 }
2252 
slotSettings()2253 void KMyMoneyApp::slotSettings()
2254 {
2255   // if we already have an instance of the settings dialog, then use it
2256   if (KConfigDialog::showDialog("KMyMoney-Settings"))
2257     return;
2258 
2259   // otherwise, we have to create it
2260   auto dlg = new KSettingsKMyMoney(this, "KMyMoney-Settings", KMyMoneySettings::self());
2261   connect(dlg, &KSettingsKMyMoney::settingsChanged, this, &KMyMoneyApp::slotUpdateConfiguration);
2262   dlg->show();
2263 }
2264 
slotShowCredits()2265 void KMyMoneyApp::slotShowCredits()
2266 {
2267   KAboutData aboutData = initializeCreditsData();
2268   KAboutApplicationDialog dlg(aboutData, this);
2269   dlg.exec();
2270 }
2271 
slotUpdateConfiguration(const QString & dialogName)2272 void KMyMoneyApp::slotUpdateConfiguration(const QString &dialogName)
2273 {
2274   if(dialogName.compare(QLatin1String("Plugins")) == 0) {
2275     KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Reorganize, pPlugins, this, guiFactory());
2276     actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(d->canFileSaveAs());
2277     onlineJobAdministration::instance()->updateActions();
2278     onlineJobAdministration::instance()->setOnlinePlugins(pPlugins.extended);
2279     d->m_myMoneyView->setOnlinePlugins(pPlugins.online);
2280     d->updateActions();
2281     d->m_myMoneyView->slotRefreshViews();
2282     return;
2283   }
2284   MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay());
2285 
2286 #ifdef ENABLE_UNFINISHEDFEATURES
2287   LedgerSeparator::setFirstFiscalDate(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay());
2288 #endif
2289 
2290   d->m_myMoneyView->updateViewType();
2291 
2292   // update the sql storage module settings
2293 //  MyMoneyStorageSql::setStartDate(KMyMoneySettings::startDate().date());
2294 
2295   // update the report module settings
2296   MyMoneyReport::setLineWidth(KMyMoneySettings::lineWidth());
2297 
2298   // update the holiday region configuration
2299   setHolidayRegion(KMyMoneySettings::holidayRegion());
2300 
2301   d->m_myMoneyView->slotRefreshViews();
2302 
2303   // re-read autosave configuration
2304   d->m_autoSaveEnabled = KMyMoneySettings::autoSaveFile();
2305   d->m_autoSavePeriod = KMyMoneySettings::autoSavePeriod();
2306 
2307   // stop timer if turned off but running
2308   if (d->m_autoSaveTimer->isActive() && !d->m_autoSaveEnabled) {
2309     d->m_autoSaveTimer->stop();
2310   }
2311   // start timer if turned on and needed but not running
2312   if (!d->m_autoSaveTimer->isActive() && d->m_autoSaveEnabled && d->dirty()) {
2313     d->m_autoSaveTimer->setSingleShot(true);
2314     d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000);
2315   }
2316 
2317   d->setThemedCSS();
2318 }
2319 
slotBackupFile()2320 void KMyMoneyApp::slotBackupFile()
2321 {
2322   // Save the file first so isLocalFile() works
2323   if (d->m_myMoneyView && d->dirty())
2324 
2325   {
2326     if (KMessageBox::questionYesNo(this, i18n("The file must be saved first "
2327                                    "before it can be backed up.  Do you want to continue?")) == KMessageBox::No) {
2328       return;
2329 
2330     }
2331 
2332     slotFileSave();
2333   }
2334 
2335 
2336 
2337   if (d->m_storageInfo.url.isEmpty())
2338     return;
2339 
2340   if (!d->m_storageInfo.url.isLocalFile()) {
2341     KMessageBox::sorry(this,
2342                        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()),
2343 
2344                        i18n("Local files only"));
2345     return;
2346   }
2347 
2348   QPointer<KBackupDlg> backupDlg = new KBackupDlg(this);
2349   int returncode = backupDlg->exec();
2350 
2351   if (returncode == QDialog::Accepted && backupDlg != 0) {
2352     d->m_backupMount = backupDlg->mountCheckBoxChecked();
2353     d->m_proc.clearProgram();
2354     d->m_backupState = BACKUP_MOUNTING;
2355     d->m_mountpoint = backupDlg->mountPoint();
2356 
2357     if (d->m_backupMount) {
2358       slotBackupMount();
2359     } else {
2360       progressCallback(0, 300, "");
2361 #ifdef Q_OS_WIN
2362       d->m_ignoreBackupExitCode = true;
2363       QTimer::singleShot(0, this, SLOT(slotBackupHandleEvents()));
2364 #else
2365       // If we don't have to mount a device, we just issue
2366       // a dummy command to start the copy operation
2367       d->m_proc.setProgram("true");
2368       d->m_proc.start();
2369 #endif
2370     }
2371 
2372   }
2373 
2374   delete backupDlg;
2375 }
2376 
slotBackupMount()2377 void KMyMoneyApp::slotBackupMount()
2378 {
2379   progressCallback(0, 300, i18n("Mounting %1", d->m_mountpoint));
2380   d->m_proc.setProgram("mount");
2381   d->m_proc << d->m_mountpoint;
2382   d->m_proc.start();
2383 }
2384 
slotBackupWriteFile()2385 bool KMyMoneyApp::slotBackupWriteFile()
2386 {
2387   QFileInfo fi(d->m_storageInfo.url.fileName());
2388   QString today = QDate::currentDate().toString("-yyyy-MM-dd.") + fi.suffix();
2389   QString backupfile = d->m_mountpoint + '/' + d->m_storageInfo.url.fileName();
2390   KMyMoneyUtils::appendCorrectFileExt(backupfile, today);
2391 
2392 #ifdef Q_OS_WIN
2393   // on windows, a leading slash is a problem if a drive letter follows
2394   // eg. "/Z:/path". In case we detect such a pattern, we simply remove
2395   // the leading slash
2396   const QRegularExpression re(QStringLiteral("/(?<path>\\w+:/.+)"),
2397                               QRegularExpression::CaseInsensitiveOption|QRegularExpression::UseUnicodePropertiesOption
2398                              );
2399   const auto match = re.match(backupfile);
2400   if (match.hasMatch() && !match.captured(QStringLiteral("path")).isEmpty()) {
2401     backupfile = match.captured(QStringLiteral("path"));
2402   }
2403 #endif
2404 
2405   // check if file already exists and ask what to do
2406   QFile f(backupfile);
2407   if (f.exists()) {
2408     int answer = KMessageBox::warningContinueCancel(this, i18n("Backup file for today exists on that device. Replace?"), i18n("Backup"), KGuiItem(i18n("&Replace")));
2409     if (answer == KMessageBox::Cancel) {
2410       return false;
2411     }
2412   }
2413 
2414   progressCallback(50, 0, i18n("Writing %1", backupfile));
2415   d->m_proc.clearProgram();
2416 #ifdef Q_OS_WIN
2417   d->m_proc << "cmd.exe" << "/c" << "copy" << "/b" << "/y";
2418   d->m_proc << QDir::toNativeSeparators(d->m_storageInfo.url.toLocalFile()) << "+" << "nul" << QDir::toNativeSeparators(backupfile);
2419 #else
2420   d->m_proc << "cp" << "-f";
2421   d->m_proc << d->m_storageInfo.url.toLocalFile() << backupfile;
2422 #endif
2423   d->m_backupState = BACKUP_COPYING;
2424   qDebug() << "Backup cmd:" << d->m_proc.program();
2425   d->m_proc.start();
2426   return true;
2427 }
2428 
slotBackupUnmount()2429 void KMyMoneyApp::slotBackupUnmount()
2430 {
2431   progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint));
2432   d->m_proc.clearProgram();
2433   d->m_proc.setProgram("umount");
2434   d->m_proc << d->m_mountpoint;
2435   d->m_backupState = BACKUP_UNMOUNTING;
2436   d->m_proc.start();
2437 }
2438 
slotBackupFinish()2439 void KMyMoneyApp::slotBackupFinish()
2440 {
2441   d->m_backupState = BACKUP_IDLE;
2442   progressCallback(-1, -1, QString());
2443   ready();
2444 }
2445 
slotBackupHandleEvents()2446 void KMyMoneyApp::slotBackupHandleEvents()
2447 {
2448   switch (d->m_backupState) {
2449     case BACKUP_MOUNTING:
2450 
2451       if (d->m_ignoreBackupExitCode ||
2452          (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0)) {
2453         d->m_ignoreBackupExitCode = false;
2454         d->m_backupResult = 0;
2455         if (!slotBackupWriteFile()) {
2456           d->m_backupResult = 1;
2457           if (d->m_backupMount)
2458             slotBackupUnmount();
2459           else
2460             slotBackupFinish();
2461         }
2462       } else {
2463         KMessageBox::information(this, i18n("Error mounting device"), i18n("Backup"));
2464         d->m_backupResult = 1;
2465         if (d->m_backupMount)
2466           slotBackupUnmount();
2467         else
2468           slotBackupFinish();
2469       }
2470       break;
2471 
2472     case BACKUP_COPYING:
2473       if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) {
2474 
2475         if (d->m_backupMount) {
2476           slotBackupUnmount();
2477         } else {
2478           progressCallback(300, 0, i18nc("Backup done", "Done"));
2479           KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup"));
2480           slotBackupFinish();
2481         }
2482       } else {
2483         qDebug("copy exit code is %d", d->m_proc.exitCode());
2484         d->m_backupResult = 1;
2485         KMessageBox::information(this, i18n("Error copying file to device"), i18n("Backup"));
2486         if (d->m_backupMount)
2487           slotBackupUnmount();
2488         else
2489           slotBackupFinish();
2490       }
2491       break;
2492 
2493 
2494     case BACKUP_UNMOUNTING:
2495       if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) {
2496 
2497         progressCallback(300, 0, i18nc("Backup done", "Done"));
2498         if (d->m_backupResult == 0)
2499           KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup"));
2500       } else {
2501         KMessageBox::information(this, i18n("Error unmounting device"), i18n("Backup"));
2502       }
2503       slotBackupFinish();
2504       break;
2505 
2506     default:
2507       qWarning("Unknown state for backup operation!");
2508       progressCallback(-1, -1, QString());
2509       ready();
2510       break;
2511   }
2512 }
2513 
slotShowTipOfTheDay()2514 void KMyMoneyApp::slotShowTipOfTheDay()
2515 {
2516   KTipDialog::showTip(d->m_myMoneyView, "", true);
2517 }
2518 
slotShowPreviousView()2519 void KMyMoneyApp::slotShowPreviousView()
2520 {
2521 
2522 }
2523 
slotShowNextView()2524 void KMyMoneyApp::slotShowNextView()
2525 {
2526 
2527 }
2528 
slotViewSelected(View view)2529 void KMyMoneyApp::slotViewSelected(View view)
2530 {
2531   KMyMoneySettings::setLastViewSelected(static_cast<int>(view));
2532 }
2533 
slotGenerateSql()2534 void KMyMoneyApp::slotGenerateSql()
2535 {
2536 //  QPointer<KGenerateSqlDlg> editor = new KGenerateSqlDlg(this);
2537 //  editor->setObjectName("Generate Database SQL");
2538 //  editor->exec();
2539 //  delete editor;
2540 }
2541 
slotToolsStartKCalc()2542 void KMyMoneyApp::slotToolsStartKCalc()
2543 {
2544   QString cmd = KMyMoneySettings::externalCalculator();
2545   // if none is present, we fall back to the default
2546   if (cmd.isEmpty()) {
2547 #if defined(Q_OS_WIN32)
2548     cmd = QLatin1String("calc");
2549 #elif defined(Q_OS_MAC)
2550     cmd = QLatin1String("open -a Calculator");
2551 #else
2552     cmd = QLatin1String("kcalc");
2553 #endif
2554   }
2555   KRun::runCommand(cmd, this);
2556 }
2557 
createAccount(MyMoneyAccount & newAccount,MyMoneyAccount & parentAccount,MyMoneyAccount & brokerageAccount,MyMoneyMoney openingBal)2558 void KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal)
2559 {
2560   MyMoneyFile *file = MyMoneyFile::instance();
2561   try {
2562     const MyMoneySecurity& sec = file->security(newAccount.currencyId());
2563     // Check the opening balance
2564     if (openingBal.isPositive() && newAccount.accountGroup() == eMyMoney::Account::Type::Liability) {
2565       QString message = i18n("This account is a liability and if the "
2566                              "opening balance represents money owed, then it should be negative.  "
2567                              "Negate the amount?\n\n"
2568                              "Please click Yes to change the opening balance to %1,\n"
2569                              "Please click No to leave the amount as %2,\n"
2570                              "Please click Cancel to abort the account creation."
2571                              , MyMoneyUtils::formatMoney(-openingBal, newAccount, sec)
2572                              , MyMoneyUtils::formatMoney(openingBal, newAccount, sec));
2573 
2574       int ans = KMessageBox::questionYesNoCancel(this, message);
2575       if (ans == KMessageBox::Yes) {
2576         openingBal = -openingBal;
2577 
2578       } else if (ans == KMessageBox::Cancel)
2579         return;
2580     }
2581 
2582     file->createAccount(newAccount, parentAccount, brokerageAccount, openingBal);
2583 
2584   } catch (const MyMoneyException &e) {
2585     KMessageBox::information(this, i18n("Unable to add account: %1", QString::fromLatin1(e.what())));
2586   }
2587 }
2588 
slotInvestmentNew(MyMoneyAccount & account,const MyMoneyAccount & parent)2589 void KMyMoneyApp::slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent)
2590 {
2591   KNewInvestmentWizard::newInvestment(account, parent);
2592 }
2593 
slotCategoryNew(MyMoneyAccount & account,const MyMoneyAccount & parent)2594 void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent)
2595 {
2596   KNewAccountDlg::newCategory(account, parent);
2597 }
2598 
slotCategoryNew(MyMoneyAccount & account)2599 void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account)
2600 {
2601   KNewAccountDlg::newCategory(account, MyMoneyAccount());
2602 }
2603 
slotAccountNew(MyMoneyAccount & account)2604 void KMyMoneyApp::slotAccountNew(MyMoneyAccount& account)
2605 {
2606   NewAccountWizard::Wizard::newAccount(account);
2607 }
2608 
createSchedule(MyMoneySchedule newSchedule,MyMoneyAccount & newAccount)2609 void KMyMoneyApp::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount)
2610 {
2611   MyMoneyFile* file = MyMoneyFile::instance();
2612   // Add the schedule only if one exists
2613   //
2614   // Remember to modify the first split to reference the newly created account
2615   if (!newSchedule.name().isEmpty()) {
2616     try {
2617       // We assume at least 2 splits in the transaction
2618       MyMoneyTransaction t = newSchedule.transaction();
2619       if (t.splitCount() < 2) {
2620         throw MYMONEYEXCEPTION_CSTRING("Transaction for schedule has less than 2 splits!");
2621       }
2622 
2623       MyMoneyFileTransaction ft;
2624       try {
2625         file->addSchedule(newSchedule);
2626 
2627         // in case of a loan account, we keep a reference to this
2628         // schedule in the account
2629         if (newAccount.accountType() == eMyMoney::Account::Type::Loan
2630             || newAccount.accountType() == eMyMoney::Account::Type::AssetLoan) {
2631           newAccount.setValue("schedule", newSchedule.id());
2632           file->modifyAccount(newAccount);
2633         }
2634         ft.commit();
2635       } catch (const MyMoneyException &e) {
2636         KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", QString::fromLatin1(e.what())));
2637       }
2638     } catch (const MyMoneyException &e) {
2639       KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", QString::fromLatin1(e.what())));
2640     }
2641   }
2642 }
2643 
slotReparentAccount(const MyMoneyAccount & _src,const MyMoneyInstitution & _dst)2644 void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyInstitution& _dst)
2645 {
2646   MyMoneyAccount src(_src);
2647   src.setInstitutionId(_dst.id());
2648   MyMoneyFileTransaction ft;
2649   try {
2650     MyMoneyFile::instance()->modifyAccount(src);
2651     ft.commit();
2652   } catch (const MyMoneyException &e) {
2653     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())));
2654   }
2655 }
2656 
slotReparentAccount(const MyMoneyAccount & _src,const MyMoneyAccount & _dst)2657 void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyAccount& _dst)
2658 {
2659   MyMoneyAccount src(_src);
2660   MyMoneyAccount dst(_dst);
2661   MyMoneyFileTransaction ft;
2662   try {
2663     MyMoneyFile::instance()->reparentAccount(src, dst);
2664     ft.commit();
2665   } catch (const MyMoneyException &e) {
2666     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())));
2667   }
2668 }
2669 
slotScheduleNew(const MyMoneyTransaction & _t,eMyMoney::Schedule::Occurrence occurrence)2670 void KMyMoneyApp::slotScheduleNew(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence)
2671 {
2672   KEditScheduleDlg::newSchedule(_t, occurrence);
2673 }
2674 
slotPayeeNew(const QString & newnameBase,QString & id)2675 void KMyMoneyApp::slotPayeeNew(const QString& newnameBase, QString& id)
2676 {
2677   KMyMoneyUtils::newPayee(newnameBase, id);
2678 }
2679 
slotNewFeature()2680 void KMyMoneyApp::slotNewFeature()
2681 {
2682 }
2683 
2684 // move a stock transaction from one investment account to another
moveInvestmentTransaction(const QString &,const QString & toId,const MyMoneyTransaction & tx)2685 void KMyMoneyApp::Private::moveInvestmentTransaction(const QString& /*fromId*/,
2686     const QString& toId,
2687     const MyMoneyTransaction& tx)
2688 {
2689   MyMoneyAccount toInvAcc = MyMoneyFile::instance()->account(toId);
2690   MyMoneyTransaction t(tx);
2691   // first determine which stock we are dealing with.
2692   // fortunately, investment transactions have only one stock involved
2693   QString stockAccountId;
2694   QString stockSecurityId;
2695   MyMoneySplit s;
2696   foreach (const auto split, t.splits()) {
2697     stockAccountId = split.accountId();
2698     stockSecurityId =
2699       MyMoneyFile::instance()->account(stockAccountId).currencyId();
2700     if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) {
2701       s = split;
2702       break;
2703     }
2704   }
2705   // Now check the target investment account to see if it
2706   // contains a stock with this id
2707   QString newStockAccountId;
2708   foreach (const auto sAccount, toInvAcc.accountList()) {
2709     if (MyMoneyFile::instance()->account(sAccount).currencyId() ==
2710         stockSecurityId) {
2711       newStockAccountId = sAccount;
2712       break;
2713     }
2714   }
2715   // if it doesn't exist, we need to add it as a copy of the old one
2716   // no 'copyAccount()' function??
2717   if (newStockAccountId.isEmpty()) {
2718     MyMoneyAccount stockAccount =
2719       MyMoneyFile::instance()->account(stockAccountId);
2720     MyMoneyAccount newStock;
2721     newStock.setName(stockAccount.name());
2722     newStock.setNumber(stockAccount.number());
2723     newStock.setDescription(stockAccount.description());
2724     newStock.setInstitutionId(stockAccount.institutionId());
2725     newStock.setOpeningDate(stockAccount.openingDate());
2726     newStock.setAccountType(stockAccount.accountType());
2727     newStock.setCurrencyId(stockAccount.currencyId());
2728     newStock.setClosed(stockAccount.isClosed());
2729     MyMoneyFile::instance()->addAccount(newStock, toInvAcc);
2730     newStockAccountId = newStock.id();
2731   }
2732   // now update the split and the transaction
2733   s.setAccountId(newStockAccountId);
2734   t.modifySplit(s);
2735   MyMoneyFile::instance()->modifyTransaction(t);
2736 }
2737 
showContextMenu(const QString & containerName)2738 void KMyMoneyApp::showContextMenu(const QString& containerName)
2739 {
2740   QWidget* w = factory()->container(containerName, this);
2741   if (auto menu = dynamic_cast<QMenu*>(w))
2742     menu->exec(QCursor::pos());
2743   else
2744     qDebug("menu '%s' not found: w = %p, menu = %p", qPrintable(containerName), w, menu);
2745 }
2746 
slotPrintView()2747 void KMyMoneyApp::slotPrintView()
2748 {
2749   d->m_myMoneyView->slotPrintView();
2750 }
2751 
updateCaption()2752 void KMyMoneyApp::Private::updateCaption()
2753 {
2754   auto caption = m_storageInfo.url.isEmpty() && m_myMoneyView && m_storageInfo.isOpened  ?
2755         i18n("Untitled") :
2756         m_storageInfo.url.fileName();
2757 
2758 #ifdef KMM_DEBUG
2759   caption += QString(" (%1 x %2)").arg(q->width()).arg(q->height());
2760 #endif
2761 
2762   q->setCaption(caption, MyMoneyFile::instance()->dirty());
2763 }
2764 
updateActions()2765 void KMyMoneyApp::Private::updateActions()
2766 {
2767   const QVector<Action> actions
2768   {
2769     Action::FilePersonalData, Action::FileInformation, Action::FileImportTemplate, Action::FileExportTemplate,
2770 #ifdef KMM_DEBUG
2771     Action::FileDump,
2772 #endif
2773     Action::EditFindTransaction, Action::NewCategory, Action::ToolCurrencies, Action::ToolPrices, Action::ToolUpdatePrices,
2774     Action::ToolConsistency, Action::ToolPerformance, Action::NewAccount, Action::NewInstitution, Action::NewSchedule
2775   };
2776 
2777   for (const auto &action : actions)
2778     pActions[action]->setEnabled(m_storageInfo.isOpened);
2779   pActions[Action::FileBackup]->setEnabled(m_storageInfo.isOpened && m_storageInfo.type == eKMyMoney::StorageType::XML);
2780 
2781   auto aC = q->actionCollection();
2782   aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(canFileSaveAs());
2783   aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Close)))->setEnabled(m_storageInfo.isOpened);
2784   pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(KMyMoneyUtils::canUpdateAllAccounts());
2785 }
2786 
canFileSaveAs() const2787 bool KMyMoneyApp::Private::canFileSaveAs() const
2788 {
2789   return (m_storageInfo.isOpened &&
2790           (!pPlugins.storage.isEmpty() &&
2791            !(pPlugins.storage.count() == 1 && pPlugins.storage.first()->storageType() == eKMyMoney::StorageType::GNC)));
2792 }
2793 
slotDataChanged()2794 void KMyMoneyApp::slotDataChanged()
2795 {
2796   d->fileAction(eKMyMoney::FileAction::Changed);
2797 }
2798 
slotCurrencyDialog()2799 void KMyMoneyApp::slotCurrencyDialog()
2800 {
2801   QPointer<KCurrencyEditDlg> dlg = new KCurrencyEditDlg(this);
2802   dlg->exec();
2803   delete dlg;
2804 }
2805 
slotPriceDialog()2806 void KMyMoneyApp::slotPriceDialog()
2807 {
2808   QPointer<KMyMoneyPriceDlg> dlg = new KMyMoneyPriceDlg(this);
2809   dlg->exec();
2810   delete dlg;
2811 }
2812 
slotFileConsistencyCheck()2813 void KMyMoneyApp::slotFileConsistencyCheck()
2814 {
2815   d->consistencyCheck(true);
2816 }
2817 
consistencyCheck(bool alwaysDisplayResult)2818 void KMyMoneyApp::Private::consistencyCheck(bool alwaysDisplayResult)
2819 {
2820   KMSTATUS(i18n("Running consistency check..."));
2821 
2822   MyMoneyFileTransaction ft;
2823   try {
2824     m_consistencyCheckResult = MyMoneyFile::instance()->consistencyCheck();
2825     ft.commit();
2826   } catch (const MyMoneyException &e) {
2827     m_consistencyCheckResult.append(i18n("Consistency check failed: %1", e.what()));
2828     // always display the result if the check failed
2829     alwaysDisplayResult = true;
2830   }
2831 
2832   // in case the consistency check was OK, we get a single line as result
2833   // in all erroneous cases, we get more than one line and force the
2834   // display of them.
2835 
2836   if (alwaysDisplayResult || m_consistencyCheckResult.size() > 1) {
2837     QString msg = i18n("The consistency check has found no issues in your data. Details are presented below.");
2838     if (m_consistencyCheckResult.size() > 1)
2839       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.");
2840     // install a context menu for the list after the dialog is displayed
2841     QTimer::singleShot(500, q, SLOT(slotInstallConsistencyCheckContextMenu()));
2842     KMessageBox::informationList(0, msg, m_consistencyCheckResult, i18n("Consistency check result"));
2843   }
2844   // this data is no longer needed
2845   m_consistencyCheckResult.clear();
2846 }
2847 
copyConsistencyCheckResults()2848 void KMyMoneyApp::Private::copyConsistencyCheckResults()
2849 {
2850   QClipboard *clipboard = QApplication::clipboard();
2851   clipboard->setText(m_consistencyCheckResult.join(QLatin1String("\n")));
2852 }
2853 
saveConsistencyCheckResults()2854 void KMyMoneyApp::Private::saveConsistencyCheckResults()
2855 {
2856   QUrl fileUrl = QFileDialog::getSaveFileUrl(q);
2857 
2858   if (!fileUrl.isEmpty()) {
2859     QFile file(fileUrl.toLocalFile());
2860     if (file.open(QFile::WriteOnly | QFile::Append | QFile::Text)) {
2861       QTextStream out(&file);
2862       out << m_consistencyCheckResult.join(QLatin1String("\n"));
2863       file.close();
2864     }
2865   }
2866 }
2867 
setThemedCSS()2868 void KMyMoneyApp::Private::setThemedCSS()
2869 {
2870   const QStringList CSSnames {QStringLiteral("kmymoney.css"), QStringLiteral("welcome.css")};
2871   const QString rcDir("/html/");
2872   QStringList defaultCSSDirs;
2873 #ifndef IS_APPIMAGE
2874   defaultCSSDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, rcDir, QStandardPaths::LocateDirectory);
2875 #else
2876   // according to https://docs.appimage.org/packaging-guide/ingredients.html#open-source-applications
2877   // QStandardPaths::AppDataLocation is unreliable on AppImages, so apply workaround here in case we fail to find icons
2878   // watch out for QStringBuilder here; for yet unknown reason it causes segmentation fault on startup
2879   const auto appImageAppDataLocation = QString("%1%2%3").arg(QCoreApplication::applicationDirPath(), QString("/../share/kmymoney"), rcDir);
2880   if (QFile::exists(appImageAppDataLocation + CSSnames.first())) {
2881     defaultCSSDirs.append(appImageAppDataLocation);
2882   } else {
2883     qWarning("CSS file was not found in the following location:");
2884     qWarning() << appImageAppDataLocation;
2885   }
2886 #endif
2887 
2888   // scan the list of directories to find the ones that really
2889   // contains all files we look for
2890   QString defaultCSSDir;
2891   foreach (const auto dir, defaultCSSDirs) {
2892     defaultCSSDir = dir;
2893     foreach (const auto CSSname, CSSnames) {
2894       QFileInfo fileInfo(defaultCSSDir + CSSname);
2895       if (!fileInfo.exists()) {
2896         defaultCSSDir.clear();
2897         break;
2898       }
2899     }
2900     if (!defaultCSSDir.isEmpty()) {
2901       break;
2902     }
2903   }
2904 
2905   // make sure we have the local directory where the themed version is stored
2906   const QString themedCSSDir  = QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation).first() + rcDir;
2907   QDir().mkpath(themedCSSDir);
2908 
2909   foreach (const auto CSSname, CSSnames) {
2910     const QString defaultCSSFilename = defaultCSSDir + CSSname;
2911     QFileInfo fileInfo(defaultCSSFilename);
2912     if (fileInfo.exists()) {
2913       const QString themedCSSFilename = themedCSSDir + CSSname;
2914       QFile::remove(themedCSSFilename);
2915       if (QFile::copy(defaultCSSFilename, themedCSSFilename)) {
2916         QFile cssFile (themedCSSFilename);
2917         if (cssFile.open(QIODevice::ReadWrite)) {
2918           QTextStream cssStream(&cssFile);
2919           auto cssText = cssStream.readAll();
2920           cssText.replace(QLatin1String("./"), defaultCSSDir, Qt::CaseSensitive);
2921           cssText.replace(QLatin1String("WindowText"),    KMyMoneySettings::schemeColor(SchemeColor::WindowText).name(),        Qt::CaseSensitive);
2922           cssText.replace(QLatin1String("Window"),        KMyMoneySettings::schemeColor(SchemeColor::WindowBackground).name(),  Qt::CaseSensitive);
2923           cssText.replace(QLatin1String("HighlightText"), KMyMoneySettings::schemeColor(SchemeColor::ListHighlightText).name(), Qt::CaseSensitive);
2924           cssText.replace(QLatin1String("Highlight"),     KMyMoneySettings::schemeColor(SchemeColor::ListHighlight).name(),     Qt::CaseSensitive);
2925           cssText.replace(QLatin1String("black"),         KMyMoneySettings::schemeColor(SchemeColor::ListGrid).name(),          Qt::CaseSensitive);
2926           cssStream.seek(0);
2927           cssStream << cssText;
2928           cssFile.close();
2929         }
2930       }
2931     }
2932   }
2933 }
2934 
slotCheckSchedules()2935 void KMyMoneyApp::slotCheckSchedules()
2936 {
2937   if (KMyMoneySettings::checkSchedule() == true) {
2938 
2939     KMSTATUS(i18n("Checking for overdue scheduled transactions..."));
2940     MyMoneyFile *file = MyMoneyFile::instance();
2941     QDate checkDate = QDate::currentDate().addDays(KMyMoneySettings::checkSchedulePreview());
2942 
2943     QList<MyMoneySchedule> scheduleList =  file->scheduleList();
2944     QList<MyMoneySchedule>::Iterator it;
2945 
2946     eDialogs::ScheduleResultCode rc = eDialogs::ScheduleResultCode::Enter;
2947     for (it = scheduleList.begin(); (it != scheduleList.end()) && (rc != eDialogs::ScheduleResultCode::Cancel); ++it) {
2948       // Get the copy in the file because it might be modified by commitTransaction
2949       MyMoneySchedule schedule = file->schedule((*it).id());
2950 
2951       if (schedule.autoEnter()) {
2952         try {
2953           while (!schedule.isFinished() && (schedule.adjustedNextDueDate() <= checkDate)
2954                  && rc != eDialogs::ScheduleResultCode::Ignore
2955                  && rc != eDialogs::ScheduleResultCode::Cancel) {
2956             rc = d->m_myMoneyView->enterSchedule(schedule, true, true);
2957             schedule = file->schedule((*it).id()); // get a copy of the modified schedule
2958           }
2959         } catch (const MyMoneyException &) {
2960         }
2961       }
2962       if (rc == eDialogs::ScheduleResultCode::Ignore) {
2963         // if the current schedule was ignored then we must make sure that the user can still enter the next scheduled transaction
2964         rc = eDialogs::ScheduleResultCode::Enter;
2965       }
2966     }
2967   }
2968 }
2969 
writeLastUsedDir(const QString & directory)2970 void KMyMoneyApp::writeLastUsedDir(const QString& directory)
2971 {
2972   //get global config object for our app.
2973   KSharedConfigPtr kconfig = KSharedConfig::openConfig();
2974   if (kconfig) {
2975     KConfigGroup grp = kconfig->group("General Options");
2976 
2977     //write path entry, no error handling since its void.
2978     grp.writeEntry("LastUsedDirectory", directory);
2979   }
2980 }
2981 
writeLastUsedFile(const QString & fileName)2982 void KMyMoneyApp::writeLastUsedFile(const QString& fileName)
2983 {
2984   //get global config object for our app.
2985   KSharedConfigPtr kconfig = KSharedConfig::openConfig();
2986   if (kconfig) {
2987     KConfigGroup grp = d->m_config->group("General Options");
2988 
2989     // write path entry, no error handling since its void.
2990     // use a standard string, as fileName could contain a protocol
2991     // e.g. file:/home/thb/....
2992     grp.writeEntry("LastUsedFile", fileName);
2993   }
2994 }
2995 
readLastUsedDir() const2996 QString KMyMoneyApp::readLastUsedDir() const
2997 {
2998   QString str;
2999 
3000   //get global config object for our app.
3001   KSharedConfigPtr kconfig = KSharedConfig::openConfig();
3002   if (kconfig) {
3003     KConfigGroup grp = d->m_config->group("General Options");
3004 
3005     //read path entry.  Second parameter is the default if the setting is not found, which will be the default document path.
3006     str = grp.readEntry("LastUsedDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
3007     // if the path stored is empty, we use the default nevertheless
3008     if (str.isEmpty())
3009       str = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
3010   }
3011 
3012   return str;
3013 }
3014 
readLastUsedFile() const3015 QString KMyMoneyApp::readLastUsedFile() const
3016 {
3017   QString str;
3018 
3019   // get global config object for our app.
3020   KSharedConfigPtr kconfig = KSharedConfig::openConfig();
3021   if (kconfig) {
3022     KConfigGroup grp = d->m_config->group("General Options");
3023 
3024     // read filename entry.
3025     str = grp.readEntry("LastUsedFile", "");
3026   }
3027 
3028   return str;
3029 }
3030 
filename() const3031 QString KMyMoneyApp::filename() const
3032 {
3033   return d->m_storageInfo.url.url();
3034 }
3035 
filenameURL() const3036 QUrl KMyMoneyApp::filenameURL() const
3037 {
3038   return d->m_storageInfo.url;
3039 }
3040 
writeFilenameURL(const QUrl & url)3041 void KMyMoneyApp::writeFilenameURL(const QUrl &url)
3042 {
3043   d->m_storageInfo.url = url;
3044 }
3045 
addToRecentFiles(const QUrl & url)3046 void KMyMoneyApp::addToRecentFiles(const QUrl& url)
3047 {
3048   d->m_recentFiles->addUrl(url);
3049 }
3050 
autosaveTimer()3051 QTimer* KMyMoneyApp::autosaveTimer()
3052 {
3053   return d->m_autoSaveTimer;
3054 }
3055 
webConnect() const3056 WebConnect* KMyMoneyApp::webConnect() const
3057 {
3058   return d->m_webConnect;
3059 }
3060 
instanceList() const3061 QList<QString> KMyMoneyApp::instanceList() const
3062 {
3063   QList<QString> list;
3064 #ifdef KMM_DBUS
3065   QDBusReply<QStringList> reply = QDBusConnection::sessionBus().interface()->registeredServiceNames();
3066 
3067   if (reply.isValid()) {
3068     QStringList apps = reply.value();
3069     QStringList::ConstIterator it;
3070 
3071     // build a list of service names of all running kmymoney applications without this one
3072     for (it = apps.constBegin(); it != apps.constEnd(); ++it) {
3073       // please change this method of creating a list of 'all the other kmymoney instances that are running on the system'
3074       // 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
3075       if ((*it).indexOf("org.kde.kmymoney-") == 0) {
3076         uint thisProcPid = platformTools::processId();
3077         if ((*it).indexOf(QString("org.kde.kmymoney-%1").arg(thisProcPid)) != 0)
3078           list += (*it);
3079       }
3080     }
3081   } else {
3082     qDebug("D-Bus returned the following error while obtaining instances: %s", qPrintable(reply.error().message()));
3083   }
3084 #endif
3085   return list;
3086 }
3087 
slotEquityPriceUpdate()3088 void KMyMoneyApp::slotEquityPriceUpdate()
3089 {
3090   QPointer<KEquityPriceUpdateDlg> dlg = new KEquityPriceUpdateDlg(this);
3091   if (dlg->exec() == QDialog::Accepted && dlg != 0)
3092     dlg->storePrices();
3093   delete dlg;
3094 }
3095 
webConnectUrl(const QUrl url)3096 void KMyMoneyApp::webConnectUrl(const QUrl url)
3097 {
3098   QMetaObject::invokeMethod(this, "webConnect", Qt::QueuedConnection, Q_ARG(QString, url.toLocalFile()), Q_ARG(QByteArray, QByteArray()));
3099 }
3100 
webConnect(const QString & sourceUrl,const QByteArray & asn_id)3101 void KMyMoneyApp::webConnect(const QString& sourceUrl, const QByteArray& asn_id)
3102 {
3103   //
3104   // Web connect attempts to go through the known importers and see if the file
3105   // can be importing using that method.  If so, it will import it using that
3106   // plugin
3107   //
3108 
3109   Q_UNUSED(asn_id)
3110 
3111   d->m_importUrlsQueue.enqueue(sourceUrl);
3112 
3113   // only start processing if this is the only import so far
3114   if (d->m_importUrlsQueue.count() == 1) {
3115     MyMoneyStatementReader::clearResultMessages();
3116     auto statementCount = 0;
3117     while (!d->m_importUrlsQueue.isEmpty()) {
3118       ++statementCount;
3119       // get the value of the next item from the queue
3120       // but leave it on the queue for now
3121       QString url = d->m_importUrlsQueue.head();
3122 
3123       // Bring this window to the forefront.  This method was suggested by
3124       // Lubos Lunak <l.lunak@suse.cz> of the KDE core development team.
3125       //KStartupInfo::setNewStartupId(this, asn_id);
3126 
3127       // Make sure we have an open file
3128       if (! d->m_storageInfo.isOpened &&
3129           KMessageBox::warningContinueCancel(this, i18n("You must first select a KMyMoney file before you can import a statement.")) == KMessageBox::Continue)
3130         slotFileOpen();
3131 
3132       // only continue if the user really did open a file.
3133       if (d->m_storageInfo.isOpened) {
3134         KMSTATUS(i18n("Importing a statement via Web Connect"));
3135 
3136         // remove the statement files
3137         d->unlinkStatementXML();
3138 
3139         QMap<QString, KMyMoneyPlugin::ImporterPlugin*>::const_iterator it_plugin = pPlugins.importer.constBegin();
3140         while (it_plugin != pPlugins.importer.constEnd()) {
3141           if ((*it_plugin)->isMyFormat(url)) {
3142             if (!(*it_plugin)->import(url) && !(*it_plugin)->lastError().isEmpty()) {
3143               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"));
3144             }
3145 
3146             break;
3147           }
3148           ++it_plugin;
3149         }
3150 
3151         // If we did not find a match, try importing it as a KMM statement file,
3152         // which is really just for testing.  the statement file is not exposed
3153         // to users.
3154         if (it_plugin == pPlugins.importer.constEnd())
3155           if (MyMoneyStatement::isStatementFile(url))
3156             MyMoneyStatementReader::importStatement(url, false, progressCallback);
3157 
3158       }
3159       // remove the current processed item from the queue
3160       d->m_importUrlsQueue.dequeue();
3161     }
3162 
3163     KMyMoneyUtils::showStatementImportResult(MyMoneyStatementReader::resultMessages(), statementCount);
3164   }
3165 }
3166 
slotEnableMessages()3167 void KMyMoneyApp::slotEnableMessages()
3168 {
3169   KMessageBox::enableAllMessages();
3170   KMessageBox::information(this, i18n("All messages have been enabled."), i18n("All messages"));
3171 }
3172 
createInterfaces()3173 void KMyMoneyApp::createInterfaces()
3174 {
3175   // Sets up the plugin interface
3176   KMyMoneyPlugin::pluginInterfaces().appInterface = new KMyMoneyPlugin::KMMAppInterface(this, this);
3177   KMyMoneyPlugin::pluginInterfaces().importInterface = new KMyMoneyPlugin::KMMImportInterface(this);
3178   KMyMoneyPlugin::pluginInterfaces().statementInterface = new KMyMoneyPlugin::KMMStatementInterface(this);
3179   KMyMoneyPlugin::pluginInterfaces().viewInterface = new KMyMoneyPlugin::KMMViewInterface(d->m_myMoneyView, this);
3180 
3181   // setup the calendar interface for schedules
3182   MyMoneySchedule::setProcessingCalendar(this);
3183 }
3184 
slotAutoSave()3185 void KMyMoneyApp::slotAutoSave()
3186 {
3187   if (!d->m_inAutoSaving) {
3188     // store the focus widget so we can restore it after save
3189     QPointer<QWidget> focusWidget = qApp->focusWidget();
3190     d->m_inAutoSaving = true;
3191     KMSTATUS(i18n("Auto saving..."));
3192 
3193     //calls slotFileSave if needed, and restart the timer
3194     //it the file is not saved, reinitializes the countdown.
3195     if (d->dirty() && d->m_autoSaveEnabled) {
3196       if (!slotFileSave() && d->m_autoSavePeriod > 0) {
3197         d->m_autoSaveTimer->setSingleShot(true);
3198         d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000);
3199       }
3200     }
3201 
3202     d->m_inAutoSaving = false;
3203     if (focusWidget && focusWidget != qApp->focusWidget()) {
3204       // we have a valid focus widget so restore it
3205       focusWidget->setFocus();
3206     }
3207   }
3208 }
3209 
slotDateChanged()3210 void KMyMoneyApp::slotDateChanged()
3211 {
3212   QDateTime dt = QDateTime::currentDateTime();
3213   QDateTime nextDay(QDate(dt.date().addDays(1)), QTime(0, 0, 0));
3214 
3215   // +1 is to make sure that we're already in the next day when the
3216   // signal is sent (this way we also avoid setting the timer to 0)
3217   QTimer::singleShot((static_cast<int>(dt.secsTo(nextDay)) + 1)*1000, this, SLOT(slotDateChanged()));
3218   d->m_myMoneyView->slotRefreshViews();
3219 }
3220 
setHolidayRegion(const QString & holidayRegion)3221 void KMyMoneyApp::setHolidayRegion(const QString& holidayRegion)
3222 {
3223 #ifdef ENABLE_HOLIDAYS
3224   //since the cost of updating the cache is now not negligible
3225   //check whether the region has been modified
3226   if (!d->m_holidayRegion || d->m_holidayRegion->regionCode() != holidayRegion) {
3227     // Delete the previous holidayRegion before creating a new one.
3228     delete d->m_holidayRegion;
3229     // Create a new holidayRegion.
3230     d->m_holidayRegion = new KHolidays::HolidayRegion(holidayRegion);
3231 
3232     //clear and update the holiday cache
3233     preloadHolidays();
3234   }
3235 #else
3236   Q_UNUSED(holidayRegion);
3237 #endif
3238 }
3239 
isProcessingDate(const QDate & date) const3240 bool KMyMoneyApp::isProcessingDate(const QDate& date) const
3241 {
3242   if (!d->m_processingDays.testBit(date.dayOfWeek()))
3243     return false;
3244 #ifdef ENABLE_HOLIDAYS
3245   if (!d->m_holidayRegion || !d->m_holidayRegion->isValid())
3246     return true;
3247 
3248   //check first whether it's already in cache
3249   if (d->m_holidayMap.contains(date)) {
3250     return d->m_holidayMap.value(date);
3251   } else {
3252     bool processingDay = !d->m_holidayRegion->isHoliday(date);
3253     d->m_holidayMap.insert(date, processingDay);
3254     return processingDay;
3255   }
3256 #else
3257   return true;
3258 #endif
3259 }
3260 
preloadHolidays()3261 void KMyMoneyApp::preloadHolidays()
3262 {
3263 #ifdef ENABLE_HOLIDAYS
3264   //clear the cache before loading
3265   d->m_holidayMap.clear();
3266   // only do this if it is a valid region
3267   if (d->m_holidayRegion && d->m_holidayRegion->isValid()) {
3268     // load holidays for the forecast days plus 1 cycle, to be on the safe side
3269     auto forecastDays = KMyMoneySettings::forecastDays() + KMyMoneySettings::forecastAccountCycle();
3270     QDate endDate = QDate::currentDate().addDays(forecastDays);
3271 
3272     // look for holidays for the next 2 years as a minimum. That should give a good margin for the cache
3273     if (endDate < QDate::currentDate().addYears(2))
3274       endDate = QDate::currentDate().addYears(2);
3275 
3276     KHolidays::Holiday::List holidayList = d->m_holidayRegion->holidays(QDate::currentDate(), endDate);
3277     KHolidays::Holiday::List::const_iterator holiday_it;
3278     for (holiday_it = holidayList.constBegin(); holiday_it != holidayList.constEnd(); ++holiday_it) {
3279       for (QDate holidayDate = (*holiday_it).observedStartDate(); holidayDate <= (*holiday_it).observedEndDate(); holidayDate = holidayDate.addDays(1))
3280         d->m_holidayMap.insert(holidayDate, (*holiday_it).dayType() == KHolidays::Holiday::Workday);
3281     }
3282 
3283     // prefill cache with all values of the forecast period
3284     for (QDate date = QDate::currentDate(); date <= endDate; date = date.addDays(1)) {
3285       // if it is not a processing day, set it to false
3286       if (!d->m_processingDays.testBit(date.dayOfWeek())) {
3287         d->m_holidayMap.insert(date, false);
3288       } else if (!d->m_holidayMap.contains(date)) {
3289         // if it is not a holiday nor a weekend, it is a processing day
3290         d->m_holidayMap.insert(date, true);
3291       }
3292     }
3293   }
3294 #endif
3295 }
3296 
slotFileNew()3297 bool KMyMoneyApp::slotFileNew()
3298 {
3299   KMSTATUS(i18n("Creating new document..."));
3300 
3301   if (!slotFileClose())
3302     return false;
3303 
3304   NewUserWizard::Wizard wizard;
3305   if (wizard.exec() != QDialog::Accepted)
3306     return false;
3307 
3308   d->m_storageInfo.isOpened = true;
3309   d->m_storageInfo.type = eKMyMoney::StorageType::None;
3310   d->m_storageInfo.url = QUrl();
3311 
3312   try {
3313     auto storage = new MyMoneyStorageMgr;
3314     MyMoneyFile::instance()->attachStorage(storage);
3315 
3316     MyMoneyFileTransaction ft;
3317     auto file = MyMoneyFile::instance();
3318     // store the user info
3319     file->setUser(wizard.user());
3320 
3321     // create and setup base currency
3322     file->addCurrency(wizard.baseCurrency());
3323     file->setBaseCurrency(wizard.baseCurrency());
3324 
3325     // create a possible institution
3326     MyMoneyInstitution inst = wizard.institution();
3327     if (inst.name().length()) {
3328       file->addInstitution(inst);
3329     }
3330 
3331     // create a possible checking account
3332     auto acc = wizard.account();
3333     if (acc.name().length()) {
3334       acc.setInstitutionId(inst.id());
3335       MyMoneyAccount asset = file->asset();
3336       file->addAccount(acc, asset);
3337 
3338       // create possible opening balance transaction
3339       if (!wizard.openingBalance().isZero()) {
3340         file->createOpeningBalanceTransaction(acc, wizard.openingBalance());
3341       }
3342     }
3343 
3344     // import the account templates
3345     for (auto &tmpl : wizard.templates())
3346       tmpl.importTemplate(progressCallback);
3347 
3348     ft.commit();
3349     KMyMoneySettings::setFirstTimeRun(false);
3350 
3351     d->fileAction(eKMyMoney::FileAction::Opened);
3352     if (actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->isEnabled())
3353       slotFileSaveAs();
3354   } catch (const MyMoneyException & e) {
3355     slotFileClose();
3356     d->removeStorage();
3357     KMessageBox::detailedError(this, i18n("Couldn't create a new file."), e.what());
3358     return false;
3359   }
3360 
3361   if (wizard.startSettingsAfterFinished())
3362     slotSettings();
3363   return true;
3364 }
3365 
slotFileOpen()3366 void KMyMoneyApp::slotFileOpen()
3367 {
3368   KMSTATUS(i18n("Open a file."));
3369 
3370   const QVector<eKMyMoney::StorageType> desiredFileExtensions {eKMyMoney::StorageType::XML, eKMyMoney::StorageType::GNC};
3371   QString fileExtensions;
3372   for (const auto &extension : desiredFileExtensions) {
3373     for (const auto &plugin : pPlugins.storage) {
3374       if (plugin->storageType() == extension) {
3375         fileExtensions += plugin->fileExtension() + QLatin1String(";;");
3376         break;
3377       }
3378     }
3379   }
3380 
3381   if (fileExtensions.isEmpty()) {
3382     KMessageBox::error(this, i18n("Couldn't find any plugin for opening storage."));
3383     return;
3384   }
3385 
3386   fileExtensions.append(i18n("All files (*)"));
3387 
3388   QPointer<QFileDialog> dialog = new QFileDialog(this, QString(), readLastUsedDir(), fileExtensions);
3389   dialog->setFileMode(QFileDialog::ExistingFile);
3390   dialog->setAcceptMode(QFileDialog::AcceptOpen);
3391 
3392   if (dialog->exec() == QDialog::Accepted && dialog != nullptr)
3393     slotFileOpenRecent(dialog->selectedUrls().first());
3394   delete dialog;
3395 }
3396 
slotFileOpenRecent(const QUrl & url)3397 bool KMyMoneyApp::slotFileOpenRecent(const QUrl &url)
3398 {
3399   KMSTATUS(i18n("Loading file..."));
3400 
3401   if (!url.isValid())
3402     throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid URL %1").arg(qPrintable(url.url())));
3403 
3404   if (isFileOpenedInAnotherInstance(url)) {
3405     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"));
3406     return false;
3407   }
3408 
3409   if (url.scheme() != QLatin1String("sql") && !KMyMoneyUtils::fileExists(url)) {
3410     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"));
3411     return false;
3412   }
3413 
3414   if (d->m_storageInfo.isOpened)
3415     if (!slotFileClose())
3416       return false;
3417 
3418   // open the database
3419   d->m_storageInfo.type = eKMyMoney::StorageType::None;
3420   for (auto &plugin : pPlugins.storage) {
3421     try {
3422       if (auto pStorage = plugin->open(url)) {
3423         MyMoneyFile::instance()->attachStorage(pStorage);
3424         d->m_storageInfo.type = plugin->storageType();
3425         if (plugin->storageType() != eKMyMoney::StorageType::GNC) {
3426           d->m_storageInfo.url = plugin->openUrl();
3427           writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile));
3428           /* Don't use url variable after KRecentFilesAction::addUrl
3429          * as it might delete it.
3430          * More in API reference to this method
3431          */
3432           d->m_recentFiles->addUrl(url);
3433         }
3434         d->m_storageInfo.isOpened = true;
3435         break;
3436       }
3437     } catch (const MyMoneyException &e) {
3438       KMessageBox::detailedError(this, i18n("Cannot open file as requested."), QString::fromLatin1(e.what()));
3439       return false;
3440     }
3441   }
3442 
3443   if(d->m_storageInfo.type == eKMyMoney::StorageType::None) {
3444     KMessageBox::error(this, i18n("Could not read your data source. Please check the KMyMoney settings that the necessary plugin is enabled."));
3445     return false;
3446   }
3447 
3448   d->fileAction(eKMyMoney::FileAction::Opened);
3449   return true;
3450 }
3451 
slotFileSave()3452 bool KMyMoneyApp::slotFileSave()
3453 {
3454   KMSTATUS(i18n("Saving file..."));
3455 
3456   for (const auto& plugin : pPlugins.storage) {
3457     if (plugin->storageType() == d->m_storageInfo.type) {
3458       d->consistencyCheck(false);
3459       try {
3460         if (plugin->save(d->m_storageInfo.url)) {
3461           d->fileAction(eKMyMoney::FileAction::Saved);
3462           return true;
3463         }
3464         return false;
3465       } catch (const MyMoneyException &e) {
3466         KMessageBox::detailedError(this, i18n("Failed to save your storage."), e.what());
3467         return false;
3468       }
3469     }
3470   }
3471 
3472   KMessageBox::error(this, i18n("Couldn't find suitable plugin to save your storage."));
3473   return false;
3474 }
3475 
slotFileSaveAs()3476 bool KMyMoneyApp::slotFileSaveAs()
3477 {
3478   KMSTATUS(i18n("Saving file as...."));
3479 
3480   QVector<eKMyMoney::StorageType> availableFileTypes;
3481   for (const auto& plugin : pPlugins.storage) {
3482     switch (plugin->storageType()) {
3483       case eKMyMoney::StorageType::GNC:
3484         break;
3485       default:
3486         availableFileTypes.append(plugin->storageType());
3487         break;
3488     }
3489   }
3490 
3491   auto chosenFileType = eKMyMoney::StorageType::None;
3492   switch (availableFileTypes.count()) {
3493     case 0:
3494       KMessageBox::error(this, i18n("Couldn't find any plugin for saving storage."));
3495       return false;
3496     case 1:
3497       chosenFileType = availableFileTypes.first();
3498       break;
3499     default:
3500       {
3501         QPointer<KSaveAsQuestion> dlg = new KSaveAsQuestion(availableFileTypes, this);
3502         auto rc = dlg->exec();
3503         if (dlg) {
3504           auto fileType = dlg->fileType();
3505           delete dlg;
3506           if (rc != QDialog::Accepted)
3507             return false;
3508           chosenFileType = fileType;
3509         }
3510       }
3511   }
3512 
3513   for (const auto &plugin : pPlugins.storage) {
3514     if (chosenFileType == plugin->storageType()) {
3515       try {
3516         d->consistencyCheck(false);
3517         if (plugin->saveAs()) {
3518           d->fileAction(eKMyMoney::FileAction::Saved);
3519           d->m_storageInfo.type = plugin->storageType();
3520           return true;
3521         }
3522       } catch (const MyMoneyException &e) {
3523         KMessageBox::detailedError(this, i18n("Failed to save your storage."), e.what());
3524       }
3525     }
3526   }
3527   return false;
3528 }
3529 
slotFileClose()3530 bool KMyMoneyApp::slotFileClose()
3531 {
3532   if (!d->m_storageInfo.isOpened)
3533     return true;
3534 
3535   if (!d->askAboutSaving())
3536     return false;
3537 
3538   d->fileAction(eKMyMoney::FileAction::Closing);
3539 
3540   d->removeStorage();
3541 
3542   d->m_storageInfo = KMyMoneyApp::Private::storageInfo();
3543 
3544   d->fileAction(eKMyMoney::FileAction::Closed);
3545   return true;
3546 }
3547 
slotFileQuit()3548 void KMyMoneyApp::slotFileQuit()
3549 {
3550   // don't modify the status message here as this will prevent quit from working!!
3551   // See the beginning of queryClose() and isReady() why. Thomas Baumgart 2005-10-17
3552 
3553   bool quitApplication = true;
3554 
3555   QList<KMainWindow*> memberList = KMainWindow::memberList();
3556   if (!memberList.isEmpty()) {
3557 
3558     QList<KMainWindow*>::const_iterator w_it = memberList.constBegin();
3559     for (; w_it != memberList.constEnd(); ++w_it) {
3560       // only close the window if the closeEvent is accepted. If the user presses Cancel on the saveModified() dialog,
3561       // the window and the application stay open.
3562       if (!(*w_it)->close()) {
3563         quitApplication = false;
3564         break;
3565       }
3566     }
3567   }
3568 
3569   // We will only quit if all windows were processed and not cancelled
3570   if (quitApplication) {
3571     QCoreApplication::quit();
3572   }
3573 }
3574 
fileAction(eKMyMoney::FileAction action)3575 void KMyMoneyApp::Private::fileAction(eKMyMoney::FileAction action)
3576 {
3577   switch(action) {
3578     case eKMyMoney::FileAction::Opened:
3579       q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false);
3580       updateAccountNames();
3581       updateCurrencyNames();
3582       selectBaseCurrency();
3583 
3584       // setup the standard precision
3585       AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction()));
3586 
3587       applyFileFixes();
3588       Models::instance()->fileOpened();
3589       connectStorageToModels();
3590       // inform everyone about new data
3591       MyMoneyFile::instance()->forceDataChanged();
3592       // Enable save in case the fix changed the contents
3593       q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(dirty());
3594       updateActions();
3595       m_myMoneyView->slotFileOpened();
3596       onlineJobAdministration::instance()->updateActions();
3597       m_myMoneyView->enableViewsIfFileOpen(m_storageInfo.isOpened);
3598       m_myMoneyView->slotRefreshViews();
3599       onlineJobAdministration::instance()->updateOnlineTaskProperties();
3600       q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
3601 
3602 #ifdef ENABLE_ACTIVITIES
3603       {
3604         // make sure that we don't store the DB password in activity
3605         QUrl url(m_storageInfo.url);
3606         url.setPassword(QString());
3607         m_activityResourceInstance->setUri(url);
3608       }
3609 #endif
3610       // start the check for scheduled transactions that need to be
3611       // entered as soon as the event loop becomes active.
3612       QMetaObject::invokeMethod(q, "slotCheckSchedules",  Qt::QueuedConnection);
3613 
3614       // make sure to catch view activations
3615       connect(m_myMoneyView, &KMyMoneyView::viewActivated, q, &KMyMoneyApp::slotViewSelected);
3616       break;
3617 
3618     case eKMyMoney::FileAction::Saved:
3619       q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
3620       q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false);
3621       m_autoSaveTimer->stop();
3622       break;
3623 
3624     case eKMyMoney::FileAction::Closing:
3625       disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
3626       // make sure to not catch view activations anymore
3627       disconnect(m_myMoneyView, &KMyMoneyView::viewActivated, q, &KMyMoneyApp::slotViewSelected);
3628       m_myMoneyView->slotFileClosed();
3629       // notify the models that the file is going to be closed (we should have something like dataChanged that reaches the models first)
3630       Models::instance()->fileClosed();
3631       break;
3632 
3633     case eKMyMoney::FileAction::Closed:
3634       q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
3635       disconnectStorageFromModels();
3636       q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false);
3637       m_myMoneyView->enableViewsIfFileOpen(m_storageInfo.isOpened);
3638       updateActions();
3639       break;
3640 
3641     case eKMyMoney::FileAction::Changed:
3642       q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged);
3643       q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(true && !m_storageInfo.url.isEmpty());
3644       // As this method is called every time the MyMoneyFile instance
3645       // notifies a modification, it's the perfect place to start the timer if needed
3646       if (m_autoSaveEnabled && !m_autoSaveTimer->isActive()) {
3647         m_autoSaveTimer->setSingleShot(true);
3648         m_autoSaveTimer->start(m_autoSavePeriod * 60 * 1000);  //miliseconds
3649       }
3650       pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(KMyMoneyUtils::canUpdateAllAccounts());
3651       break;
3652 
3653     default:
3654       break;
3655   }
3656 
3657   updateCaption();
3658 }
3659 
KMStatus(const QString & text)3660 KMStatus::KMStatus(const QString &text)
3661   : m_prevText(kmymoney->slotStatusMsg(text))
3662 {
3663 }
3664 
~KMStatus()3665 KMStatus::~KMStatus()
3666 {
3667   kmymoney->slotStatusMsg(m_prevText);
3668 }
3669 
unlinkStatementXML()3670 void KMyMoneyApp::Private::unlinkStatementXML()
3671 {
3672   QDir d(KMyMoneySettings::logPath(), "kmm-statement*");
3673   for (uint i = 0; i < d.count(); ++i) {
3674     qDebug("Remove %s", qPrintable(d[i]));
3675     d.remove(KMyMoneySettings::logPath() + QString("/%1").arg(d[i]));
3676   }
3677 }
3678