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