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