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