diff --git a/kmymoney/dialogs/CMakeLists.txt b/kmymoney/dialogs/CMakeLists.txt index 241086a01..bafdb4390 100644 --- a/kmymoney/dialogs/CMakeLists.txt +++ b/kmymoney/dialogs/CMakeLists.txt @@ -1,103 +1,103 @@ add_subdirectory( settings ) ########### next target ############### set(libdialogs_a_SOURCES splitadjustdialog.cpp investactivities.cpp investtransactioneditor.cpp kaccountselectdlg.cpp kbackupdlg.cpp kbalancechartdlg.cpp kbalancewarning.cpp kcategoryreassigndlg.cpp kchooseimportexportdlg.cpp kconfirmmanualenterdlg.cpp kcurrencycalculator.cpp kcurrencyeditdlg.cpp kavailablecurrencydlg.cpp kcurrencyeditordlg.cpp keditscheduledlg.cpp kenterscheduledlg.cpp kequitypriceupdatedlg.cpp kequitypriceupdateconfdlg.cpp kfindtransactiondlg.cpp - kgpgkeyselectiondlg.cpp kloadtemplatedlg.cpp kmergetransactionsdlg.cpp kmymoneyfileinfodlg.cpp kmymoneypricedlg.cpp kmymoneysplittable.cpp knewaccountdlg.cpp hierarchyfilterproxymodel.cpp knewbankdlg.cpp knewequityentrydlg.cpp editpersonaldatadlg.cpp kpayeereassigndlg.cpp ktagreassigndlg.cpp kselecttransactionsdlg.cpp ksplittransactiondlg.cpp ktemplateexportdlg.cpp kupdatestockpricedlg.cpp transactioneditor.cpp stdtransactioneditor.cpp transactionmatcher.cpp + ksaveasquestion.cpp ) set(dialogs_HEADERS splitadjustdialog.h investtransactioneditor.h kcurrencycalculator.h transactioneditor.h stdtransactioneditor.h ) set(dialogs_UI splitadjustdialog.ui kaccountselectdlg.ui kbackupdlg.ui kcategoryreassigndlg.ui kchooseimportexportdlg.ui kconfirmmanualenterdlg.ui kcurrencycalculator.ui kcurrencyeditdlg.ui kavailablecurrencydlg.ui kcurrencyeditordlg.ui keditscheduledlg.ui kenterscheduledlg.ui kequitypriceupdatedlg.ui kequitypriceupdateconfdlg.ui kfindtransactiondlg.ui - kgpgkeyselectiondlg.ui kloadtemplatedlg.ui kmymoneyfileinfodlg.ui kmymoneypricedlg.ui knewaccountdlg.ui knewbankdlg.ui knewequityentrydlg.ui editpersonaldatadlg.ui kpayeereassigndlg.ui ktagreassigndlg.ui kselecttransactionsdlg.ui ksortoptiondlg.ui ksplitcorrectiondlg.ui ksplittransactiondlg.ui ktemplateexportdlg.ui kupdatestockpricedlg.ui + ksaveasquestion.ui ) ki18n_wrap_ui(libdialogs_a_SOURCES ${dialogs_UI} ) add_library(dialogs STATIC ${libdialogs_a_SOURCES}) target_link_libraries(dialogs PUBLIC KChart KF5::ItemViews KF5::I18n KF5::TextWidgets KF5::Completion Qt5::Widgets Alkimia::alkimia kmm_mymoney onlinetask_interfaces kmm_widgets kmm_utils_platformtools ) target_link_libraries(dialogs LINK_PUBLIC kmm_widgets kmm_mymoney onlinetask_interfaces ) ########### install files ############### install(FILES ${dialogs_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel) diff --git a/kmymoney/dialogs/kgpgkeyselectiondlg.cpp b/kmymoney/dialogs/kgpgkeyselectiondlg.cpp deleted file mode 100644 index 0614d46b1..000000000 --- a/kmymoney/dialogs/kgpgkeyselectiondlg.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2008-2018 Thomas Baumgart - * Copyright 2017-2018 Łukasz Wojniłowicz - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "kgpgkeyselectiondlg.h" - -// ---------------------------------------------------------------------------- -// QT Includes -#include -#include - -// ---------------------------------------------------------------------------- -// KDE Includes - -// ---------------------------------------------------------------------------- -// Project Includes - -#include -#include - -class KGpgKeySelectionDlgPrivate -{ - Q_DISABLE_COPY(KGpgKeySelectionDlgPrivate) - -public: - KGpgKeySelectionDlgPrivate() - : ui(new Ui::KGpgKeySelectionDlg) - , needCheckList(true) - , listOk(false) - , checkCount(0) - { - } - - ~KGpgKeySelectionDlgPrivate() - { - delete ui; - } - - Ui::KGpgKeySelectionDlg* ui; - bool needCheckList; - bool listOk; - int checkCount; -}; - - -KGpgKeySelectionDlg::KGpgKeySelectionDlg(QWidget *parent) : - QDialog(parent), - d_ptr(new KGpgKeySelectionDlgPrivate) -{ - Q_D(KGpgKeySelectionDlg); - d->ui->setupUi(this); - connect(d->ui->m_secretKey, SIGNAL(currentIndexChanged(int)), this, SLOT(slotIdChanged())); - connect(d->ui->m_listWidget, &KEditListWidget::changed, this, &KGpgKeySelectionDlg::slotIdChanged); - connect(d->ui->m_listWidget, &KEditListWidget::added, this, &KGpgKeySelectionDlg::slotKeyListChanged); - connect(d->ui->m_listWidget, &KEditListWidget::removed, this, &KGpgKeySelectionDlg::slotKeyListChanged); -} - -KGpgKeySelectionDlg::~KGpgKeySelectionDlg() -{ - Q_D(KGpgKeySelectionDlg); - delete d; -} - -void KGpgKeySelectionDlg::setSecretKeys(const QStringList& keyList, const QString& defaultKey) -{ - static constexpr char recoveryKeyId[] = "59B0F826D2B08440"; - - Q_D(KGpgKeySelectionDlg); - d->ui->m_secretKey->addItem(i18n("No encryption")); - - foreach(auto key, keyList) { - QStringList fields = key.split(':', QString::SkipEmptyParts); - if (fields[0] != recoveryKeyId) { - // replace parenthesis in name field with brackets - auto name = fields[1]; - name.replace('(', "["); - name.replace(')', "]"); - name = QString("%1 (0x%2)").arg(name).arg(fields[0]); - d->ui->m_secretKey->addItem(name); - if (name.contains(defaultKey)) { - d->ui->m_secretKey->setCurrentText(name); - } - } - } -} - -QString KGpgKeySelectionDlg::secretKey() const -{ - Q_D(const KGpgKeySelectionDlg); - const bool enabled = (d->ui->m_secretKey->currentIndex() != 0); - QString key; - if (enabled) { - key = d->ui->m_secretKey->currentText(); - } - return key; -} - -void KGpgKeySelectionDlg::setAdditionalKeys(const QStringList& list) -{ - Q_D(KGpgKeySelectionDlg); - d->ui->m_listWidget->clear(); - d->ui->m_listWidget->insertStringList(list); - slotKeyListChanged(); -} - -QStringList KGpgKeySelectionDlg::additionalKeys() const -{ - Q_D(const KGpgKeySelectionDlg); - return d->ui->m_listWidget->items(); -} - -#if 0 -void KGpgKeySelectionDlg::slotShowHelp() -{ - QString anchor = m_helpAnchor[m_criteriaTab->currentPage()]; - if (anchor.isEmpty()) - anchor = QString("details.search"); - - KHelpClient::invokeHelp(anchor); -} -#endif - -void KGpgKeySelectionDlg::slotKeyListChanged() -{ - Q_D(KGpgKeySelectionDlg); - d->needCheckList = true; - slotIdChanged(); -} - -void KGpgKeySelectionDlg::slotIdChanged() -{ - Q_D(KGpgKeySelectionDlg); - // this looks a bit awkward. Here's why: KGPGFile::keyAvailable() starts - // an external task and processes UI events while it waits for the external - // process to finish. Thus, the first time we get here, the external process - // is started and the user may press a second key which calls this routine - // again. - // - // The second invocation is counted, but the check is not started until the - // first one finishes. Once the external process finishes, we check if we - // were called in the meantime and restart the check. - if (++d->checkCount == 1) { - const bool enabled = (d->ui->m_secretKey->currentIndex() != 0); - d->ui->m_listWidget->setEnabled(enabled); - d->ui->m_keyLed->setState(enabled ? KLed::On : KLed::Off); - while (enabled) { - // first we check the current edit field if filled - bool keysOk = true; - if (!d->ui->m_listWidget->currentText().isEmpty()) { - keysOk = KGPGFile::keyAvailable(d->ui->m_listWidget->currentText()); - } - - // if it is available, then scan the current list if we need to - if (keysOk) { - if (d->needCheckList) { - QStringList keys = d->ui->m_listWidget->items(); - QStringList::const_iterator it_s; - for (it_s = keys.constBegin(); keysOk && it_s != keys.constEnd(); ++it_s) { - if (!KGPGFile::keyAvailable(*it_s)) - keysOk = false; - } - d->listOk = keysOk; - d->needCheckList = false; - - } else { - keysOk = d->listOk; - } - } - - // did we receive some more requests to check? - if (d->checkCount > 1) { - d->checkCount = 1; - continue; - } - - if (!d->ui->m_listWidget->items().isEmpty()) { - d->ui->m_keyLed->setState(static_cast(keysOk ? KLed::On : KLed::Off)); - } else { - d->ui->m_keyLed->setState(KLed::On); - } - break; - } - - --d->checkCount; - d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!enabled || (d->ui->m_keyLed->state() == KLed::On)); - } -} diff --git a/kmymoney/dialogs/kgpgkeyselectiondlg.h b/kmymoney/dialogs/kgpgkeyselectiondlg.h deleted file mode 100644 index b9a6424ce..000000000 --- a/kmymoney/dialogs/kgpgkeyselectiondlg.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2008-2018 Thomas Baumgart - * Copyright 2017-2018 Łukasz Wojniłowicz - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef KGPGKEYSELECTIONDLG_H -#define KGPGKEYSELECTIONDLG_H - -// ---------------------------------------------------------------------------- -// QT Includes - -#include - -// ---------------------------------------------------------------------------- -// KDE Includes - -// ---------------------------------------------------------------------------- -// Project Includes - -/** - * @author Thomas Baumgart - */ -class KGpgKeySelectionDlgPrivate; -class KGpgKeySelectionDlg : public QDialog -{ - Q_OBJECT - Q_DISABLE_COPY(KGpgKeySelectionDlg) - -public: - - explicit KGpgKeySelectionDlg(QWidget* parent = nullptr); - ~KGpgKeySelectionDlg(); - - /** - * preset the key selector with the keys contained in @a keyList. - * The key contained in @a defaultKey is made the current selection. - */ - void setSecretKeys(const QStringList& keyList, const QString& defaultKey); - - /** - * preset the additional key list with the given key ids in @a list - */ - void setAdditionalKeys(const QStringList& list); - - /** - * Returns the selected secret key. In case "No encryption" is selected, - * the string is empty. - */ - QString secretKey() const; - - /** - * Returns the list of keys currently listed in the KEditListWidget - */ - QStringList additionalKeys() const; - -protected Q_SLOTS: - void slotIdChanged(); - void slotKeyListChanged(); - -private: - KGpgKeySelectionDlgPrivate * const d_ptr; - Q_DECLARE_PRIVATE(KGpgKeySelectionDlg) -}; - -#endif diff --git a/kmymoney/dialogs/kgpgkeyselectiondlg.ui b/kmymoney/dialogs/kgpgkeyselectiondlg.ui deleted file mode 100644 index bae6fe640..000000000 --- a/kmymoney/dialogs/kgpgkeyselectiondlg.ui +++ /dev/null @@ -1,139 +0,0 @@ - - - KGpgKeySelectionDlg - - - - 0 - 0 - 575 - 480 - - - - Select additional keys - - - true - - - true - - - - - - You have configured KMyMoney to save your data secured with GPG. Please choose the key you want to use for encryption of your data. - - - true - - - - - - - - - - Add additional keys here - - - - - - - Enter the id of the key you want to use for data encryption. This can either be an e-mail address or the hexadecimal key id. In case of the key id, do not forget the leading 0x. - - - - - - - - - - - - Keys for all of the above user ids found - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - KEditListWidget - QWidget -
keditlistwidget.h
-
- - KLed - QWidget -
kled.h
-
-
- - buttonBox - - - - - buttonBox - accepted() - KGpgKeySelectionDlg - accept() - - - 244 - 415 - - - 157 - 274 - - - - - buttonBox - rejected() - KGpgKeySelectionDlg - reject() - - - 312 - 415 - - - 286 - 274 - - - - -
diff --git a/kmymoney/dialogs/ksaveasquestion.cpp b/kmymoney/dialogs/ksaveasquestion.cpp new file mode 100644 index 000000000..6c83c1469 --- /dev/null +++ b/kmymoney/dialogs/ksaveasquestion.cpp @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Łukasz Wojniłowicz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ksaveasquestion.h" +#include "kmymoneyenums.h" + +// ---------------------------------------------------------------------------- +// QT Includes + +// ---------------------------------------------------------------------------- +// KDE Includes + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "ui_ksaveasquestion.h" + +KSaveAsQuestion::KSaveAsQuestion(QVector fileTypes, QWidget* parent) : + QDialog(parent), + ui(new Ui::KSaveAsQuestion) +{ + ui->setupUi(this); + for (const auto& fileType : fileTypes) { + switch (fileType) { + case eKMyMoney::StorageType::XML: + ui->fileType->addItem(i18n("XML"), static_cast(fileType)); + break; + case eKMyMoney::StorageType::SQL: + ui->fileType->addItem(i18n("SQL"), static_cast(fileType)); + + break; + default: + break; + } + } + const auto ixXML = ui->fileType->findData(static_cast(eKMyMoney::StorageType::XML)); + ui->fileType->setCurrentIndex(ixXML != -1 ? ixXML : 0); +} + +KSaveAsQuestion::~KSaveAsQuestion() +{ + delete ui; +} + +eKMyMoney::StorageType KSaveAsQuestion::fileType() const +{ + return static_cast(ui->fileType->currentData().toInt()); +} diff --git a/kmymoney/dialogs/ksaveasquestion.h b/kmymoney/dialogs/ksaveasquestion.h new file mode 100644 index 000000000..2519d0c61 --- /dev/null +++ b/kmymoney/dialogs/ksaveasquestion.h @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Łukasz Wojniłowicz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KSAVEASQUESTION_H +#define KSAVEASQUESTION_H + +#include + +namespace Ui { class KSaveAsQuestion; } +namespace eKMyMoney { enum class StorageType; } + +class KSaveAsQuestion : public QDialog +{ + Q_DISABLE_COPY(KSaveAsQuestion) + +public: + explicit KSaveAsQuestion(QVector filetypes, QWidget* parent = nullptr); + ~KSaveAsQuestion(); + eKMyMoney::StorageType fileType() const; + +private: + Ui::KSaveAsQuestion * const ui; +}; + +#endif diff --git a/kmymoney/dialogs/ksaveasquestion.ui b/kmymoney/dialogs/ksaveasquestion.ui new file mode 100644 index 000000000..71cfa999c --- /dev/null +++ b/kmymoney/dialogs/ksaveasquestion.ui @@ -0,0 +1,75 @@ + + + KSaveAsQuestion + + + + 0 + 0 + 456 + 136 + + + + Save storage as... + + + Choose XML if unsure. + + + + + + <html><head/><body><p align="center">Availability of storage types depends on plugins enabled in the settings.<br/>If you don't know what to choose here, then <span style=" font-weight:600;">XML</span> is your best choice.</p></body></html> + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + buttonBox + accepted() + KSaveAsQuestion + accept() + + + 320 + 272 + + + 320 + 150 + + + + + buttonBox + rejected() + KSaveAsQuestion + reject() + + + 320 + 272 + + + 320 + 150 + + + + + diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp index 60b5ff599..97fc73fd2 100644 --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -1,4002 +1,3747 @@ /*************************************************************************** kmymoney.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2007 by Thomas Baumgart (C) 2017, 2018 by Łukasz Wojniłowicz ****************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include "kmymoney.h" // ---------------------------------------------------------------------------- // Std C++ / STL Includes #include #include #include // ---------------------------------------------------------------------------- // QT Includes #include #include // only for performance tests #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KF5Holidays_FOUND #include #include #endif #ifdef KF5Activities_FOUND #include #endif // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneysettings.h" #include "kmymoneyadaptor.h" #include "dialogs/settings/ksettingskmymoney.h" #include "dialogs/kbackupdlg.h" #include "dialogs/kenterscheduledlg.h" #include "dialogs/kconfirmmanualenterdlg.h" #include "dialogs/kmymoneypricedlg.h" #include "dialogs/kcurrencyeditdlg.h" #include "dialogs/kequitypriceupdatedlg.h" #include "dialogs/kmymoneyfileinfodlg.h" #include "dialogs/knewbankdlg.h" +#include "dialogs/ksaveasquestion.h" #include "wizards/newinvestmentwizard/knewinvestmentwizard.h" #include "dialogs/knewaccountdlg.h" #include "dialogs/editpersonaldatadlg.h" #include "dialogs/kcurrencycalculator.h" #include "dialogs/keditscheduledlg.h" #include "wizards/newloanwizard/keditloanwizard.h" #include "dialogs/kpayeereassigndlg.h" #include "dialogs/kcategoryreassigndlg.h" #include "wizards/endingbalancedlg/kendingbalancedlg.h" #include "dialogs/kbalancechartdlg.h" #include "dialogs/kloadtemplatedlg.h" -#include "dialogs/kgpgkeyselectiondlg.h" #include "dialogs/ktemplateexportdlg.h" #include "dialogs/transactionmatcher.h" #include "wizards/newuserwizard/knewuserwizard.h" #include "wizards/newaccountwizard/knewaccountwizard.h" #include "dialogs/kbalancewarning.h" #include "widgets/kmymoneyaccountselector.h" #include "widgets/kmymoneypayeecombo.h" #include "widgets/amountedit.h" #include "widgets/kmymoneyedit.h" #include "widgets/kmymoneymvccombo.h" #include "views/kmymoneyview.h" #include "models/models.h" #include "models/accountsmodel.h" #include "models/equitiesmodel.h" #include "models/securitiesmodel.h" #include "mymoney/mymoneyobject.h" #include "mymoney/mymoneyfile.h" #include "mymoney/mymoneyinstitution.h" #include "mymoney/mymoneyaccount.h" #include "mymoney/mymoneyaccountloan.h" #include "mymoney/mymoneysecurity.h" #include "mymoney/mymoneypayee.h" #include "mymoney/mymoneyprice.h" #include "mymoney/mymoneytag.h" #include "mymoney/mymoneybudget.h" #include "mymoney/mymoneyreport.h" #include "mymoney/mymoneysplit.h" #include "mymoney/mymoneyutils.h" #include "mymoney/mymoneystatement.h" #include "mymoney/mymoneyforecast.h" #include "mymoney/mymoneytransactionfilter.h" #include "converter/mymoneystatementreader.h" #include "converter/mymoneytemplate.h" #include "plugins/interfaces/kmmappinterface.h" #include "plugins/interfaces/kmmviewinterface.h" #include "plugins/interfaces/kmmstatementinterface.h" #include "plugins/interfaces/kmmimportinterface.h" #include "plugins/interfaceloader.h" #include "plugins/onlinepluginextended.h" #include "pluginloader.h" #include "kmymoneyplugin.h" #include "tasks/credittransfer.h" #include "icons/icons.h" #include "misc/webconnect.h" #include "storage/mymoneystoragemgr.h" #include "imymoneystorageformat.h" #include #include "transactioneditor.h" #include #include #include "kmymoneyutils.h" #include "kcreditswindow.h" #include "ledgerdelegate.h" #include "storageenums.h" #include "mymoneyenums.h" #include "dialogenums.h" #include "viewenums.h" #include "menuenums.h" +#include "kmymoneyenums.h" #include "misc/platformtools.h" #ifdef KMM_DEBUG #include "mymoney/storage/mymoneystoragedump.h" #include "mymoneytracer.h" #endif using namespace Icons; using namespace eMenu; -static constexpr KCompressionDevice::CompressionType const& COMPRESSION_TYPE = KCompressionDevice::GZip; //static constexpr char recoveryKeyId[] = "0xD2B08440"; static constexpr char recoveryKeyId[] = "59B0F826D2B08440"; // define the default period to warn about an expiring recoverkey to 30 days // but allows to override this setting during build time #ifndef RECOVER_KEY_EXPIRATION_WARNING #define RECOVER_KEY_EXPIRATION_WARNING 30 #endif enum backupStateE { BACKUP_IDLE = 0, BACKUP_MOUNTING, BACKUP_COPYING, BACKUP_UNMOUNTING }; class KMyMoneyApp::Private { public: Private(KMyMoneyApp *app) : q(app), - m_statementXMLindex(0), - m_balanceWarning(0), m_backupState(backupStateE::BACKUP_IDLE), m_backupResult(0), m_backupMount(0), m_ignoreBackupExitCode(false), - m_fileOpen(false), - m_fmode(QFileDevice::ReadUser | QFileDevice::WriteUser), - m_fileType(KMyMoneyApp::KmmXML), m_myMoneyView(nullptr), m_startDialog(false), m_progressBar(nullptr), m_statusLabel(nullptr), m_autoSaveEnabled(true), m_autoSaveTimer(nullptr), m_progressTimer(nullptr), m_autoSavePeriod(0), m_inAutoSaving(false), - m_saveEncrypted(nullptr), - m_additionalKeyLabel(nullptr), - m_additionalKeyButton(nullptr), m_recentFiles(nullptr), #ifdef KF5Holidays_FOUND m_holidayRegion(nullptr), #endif #ifdef KF5Activities_FOUND m_activityResourceInstance(nullptr), #endif m_applicationIsReady(true), m_webConnect(new WebConnect(app)) { // since the days of the week are from 1 to 7, // and a day of the week is used to index this bit array, // resize the array to 8 elements (element 0 is left unused) m_processingDays.resize(8); } - void closeFile(); void unlinkStatementXML(); void moveInvestmentTransaction(const QString& fromId, const QString& toId, const MyMoneyTransaction& t); QList > automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount); + struct storageInfo { + eKMyMoney::StorageType type {eKMyMoney::StorageType::None}; + bool isOpened {false}; + QUrl url; + }; + storageInfo m_storageInfo; /** * The public interface. */ KMyMoneyApp * const q; - int m_statementXMLindex; - KBalanceWarning* m_balanceWarning; - /** the configuration object of the application */ KSharedConfigPtr m_config; /** * The following variable represents the state while crafting a backup. * It can have the following values * * - IDLE: the default value if not performing a backup * - MOUNTING: when a mount command has been issued * - COPYING: when a copy command has been issued * - UNMOUNTING: when an unmount command has been issued */ backupStateE m_backupState; /** * This variable keeps the result of the backup operation. */ int m_backupResult; /** * This variable is set, when the user selected to mount/unmount * the backup volume. */ bool m_backupMount; /** * Flag for internal run control */ bool m_ignoreBackupExitCode; - bool m_fileOpen; - QFileDevice::Permissions m_fmode; - - KMyMoneyApp::fileTypeE m_fileType; - KProcess m_proc; /// A pointer to the view holding the tabs. KMyMoneyView *m_myMoneyView; - /// The URL of the file currently being edited when open. - QUrl m_fileName; - bool m_startDialog; QString m_mountpoint; QProgressBar* m_progressBar; QTime m_lastUpdate; QLabel* m_statusLabel; // allows multiple imports to be launched trough web connect and to be executed sequentially QQueue m_importUrlsQueue; // This is Auto Saving related bool m_autoSaveEnabled; QTimer* m_autoSaveTimer; QTimer* m_progressTimer; int m_autoSavePeriod; bool m_inAutoSaving; - // Pointer to the combo box used for key selection during - // File/Save as - KComboBox* m_saveEncrypted; - // id's that need to be remembered QString m_accountGoto, m_payeeGoto; - QStringList m_additionalGpgKeys; - QLabel* m_additionalKeyLabel; - QPushButton* m_additionalKeyButton; - KRecentFilesAction* m_recentFiles; #ifdef KF5Holidays_FOUND // used by the calendar interface for schedules KHolidays::HolidayRegion* m_holidayRegion; #endif #ifdef KF5Activities_FOUND KActivities::ResourceInstance * m_activityResourceInstance; #endif QBitArray m_processingDays; QMap m_holidayMap; QStringList m_consistencyCheckResult; bool m_applicationIsReady; WebConnect* m_webConnect; // methods void consistencyCheck(bool alwaysDisplayResults); static void setThemedCSS(); void copyConsistencyCheckResults(); void saveConsistencyCheckResults(); void checkAccountName(const MyMoneyAccount& _acc, const QString& name) const { auto file = MyMoneyFile::instance(); if (_acc.name() != name) { MyMoneyAccount acc(_acc); acc.setName(name); file->modifyAccount(acc); } } /** * This method updates names of currencies from file to localized names */ void updateCurrencyNames() { auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; QList storedCurrencies = MyMoneyFile::instance()->currencyList(); QList availableCurrencies = MyMoneyFile::instance()->availableCurrencyList(); QStringList currencyIDs; foreach (auto currency, availableCurrencies) currencyIDs.append(currency.id()); try { foreach (auto currency, storedCurrencies) { int i = currencyIDs.indexOf(currency.id()); if (i != -1 && availableCurrencies.at(i).name() != currency.name()) { currency.setName(availableCurrencies.at(i).name()); file->modifyCurrency(currency); } } ft.commit(); } catch (const MyMoneyException &e) { qDebug("Error %s updating currency names", e.what()); } } void updateAccountNames() { // make sure we setup the name of the base accounts in translated form try { MyMoneyFileTransaction ft; const auto file = MyMoneyFile::instance(); checkAccountName(file->asset(), i18n("Asset")); checkAccountName(file->liability(), i18n("Liability")); checkAccountName(file->income(), i18n("Income")); checkAccountName(file->expense(), i18n("Expense")); checkAccountName(file->equity(), i18n("Equity")); ft.commit(); } catch (const MyMoneyException &) { } } void ungetString(QIODevice *qfile, char *buf, int len) { buf = &buf[len-1]; while (len--) { qfile->ungetChar(*buf--); } } bool applyFileFixes() { const auto blocked = MyMoneyFile::instance()->blockSignals(true); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("General Options"); // For debugging purposes, we can turn off the automatic fix manually // by setting the entry in kmymoneyrc to true grp = config->group("General Options"); if (grp.readEntry("SkipFix", false) != true) { MyMoneyFileTransaction ft; try { // Check if we have to modify the file before we allow to work with it auto s = MyMoneyFile::instance()->storage(); while (s->fileFixVersion() < s->currentFixVersion()) { qDebug("%s", qPrintable((QString("testing fileFixVersion %1 < %2").arg(s->fileFixVersion()).arg(s->currentFixVersion())))); switch (s->fileFixVersion()) { case 0: fixFile_0(); s->setFileFixVersion(1); break; case 1: fixFile_1(); s->setFileFixVersion(2); break; case 2: fixFile_2(); s->setFileFixVersion(3); break; case 3: fixFile_3(); s->setFileFixVersion(4); break; // add new levels above. Don't forget to increase currentFixVersion() for all // the storage backends this fix applies to default: throw MYMONEYEXCEPTION(QString::fromLatin1("Unknown fix level in input file")); } } ft.commit(); } catch (const MyMoneyException &) { MyMoneyFile::instance()->blockSignals(blocked); return false; } } else { qDebug("Skipping automatic transaction fix!"); } MyMoneyFile::instance()->blockSignals(blocked); return true; } void connectStorageToModels() { q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded); q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->accountsModel(), &AccountsModel::slotObjectModified); q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved); q->connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); q->connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded); q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified); q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved); q->connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); q->connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded); q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified); q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved); q->connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); q->connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded); q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified); q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved); } void disconnectStorageFromModels() { q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->accountsModel(), &AccountsModel::slotObjectModified); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified); q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved); } - /** - * This method is used after a file or database has been - * read into storage, and performs various initialization tasks - * - * @retval true all went okay - * @retval false an exception occurred during this process - */ - bool initializeStorage() + bool askAboutSaving() { - const auto blocked = MyMoneyFile::instance()->blockSignals(true); - - updateAccountNames(); - updateCurrencyNames(); - selectBaseCurrency(); - - // setup the standard precision - AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); - KMyMoneyEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); - - if (!applyFileFixes()) - return false; - - MyMoneyFile::instance()->blockSignals(blocked); - - emit q->kmmFilePlugin(KMyMoneyApp::postOpen); - - Models::instance()->fileOpened(); - connectStorageToModels(); - - // inform everyone about new data - MyMoneyFile::instance()->forceDataChanged(); - - q->slotCheckSchedules(); - - m_myMoneyView->slotFileOpened(); - - onlineJobAdministration::instance()->updateActions(); + const auto isFileNotSaved = q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->isEnabled(); + const auto isNewFileNotSaved = m_storageInfo.isOpened && m_storageInfo.url.isEmpty(); + auto fileNeedsToBeSaved = false; + + if (isFileNotSaved && KMyMoneySettings::autoSaveOnClose()) { + fileNeedsToBeSaved = true; + } else if (isFileNotSaved || isNewFileNotSaved) { + switch (KMessageBox::warningYesNoCancel(q, i18n("The file has been changed, save it?"))) { + case KMessageBox::ButtonCode::Yes: + fileNeedsToBeSaved = true; + break; + case KMessageBox::ButtonCode::No: + fileNeedsToBeSaved = false; + break; + case KMessageBox::ButtonCode::Cancel: + default: + return false; + break; + } + } + if (fileNeedsToBeSaved) { + if (isFileNotSaved) + return q->slotFileSave(); + else if (isNewFileNotSaved) + return q->slotFileSaveAs(); + } return true; } /** * This method attaches an empty storage object to the MyMoneyFile * object. It calls removeStorage() to remove a possibly attached * storage object. */ void newStorage() { removeStorage(); auto file = MyMoneyFile::instance(); file->attachStorage(new MyMoneyStorageMgr); } /** * This method removes an attached storage from the MyMoneyFile * object. */ void removeStorage() { auto file = MyMoneyFile::instance(); auto p = file->storage(); if (p) { file->detachStorage(p); delete p; } } /** * if no base currency is defined, start the dialog and force it to be set */ void selectBaseCurrency() { auto file = MyMoneyFile::instance(); // check if we have a base currency. If not, we need to select one QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", e.what()); } if (baseId.isEmpty()) { QPointer dlg = new KCurrencyEditDlg(q); // connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotSetBaseCurrency(MyMoneySecurity))); dlg->exec(); delete dlg; } try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", e.what()); } if (!baseId.isEmpty()) { // check that all accounts have a currency QList list; file->accountList(list); QList::Iterator it; // don't forget those standard accounts list << file->asset(); list << file->liability(); list << file->income(); list << file->expense(); list << file->equity(); for (it = list.begin(); it != list.end(); ++it) { QString cid; try { if (!(*it).currencyId().isEmpty() || (*it).currencyId().length() != 0) cid = MyMoneyFile::instance()->currency((*it).currencyId()).id(); } catch (const MyMoneyException &e) { qDebug() << QLatin1String("Account") << (*it).id() << (*it).name() << e.what(); } if (cid.isEmpty()) { (*it).setCurrencyId(baseId); MyMoneyFileTransaction ft; try { file->modifyAccount(*it); ft.commit(); } catch (const MyMoneyException &e) { qDebug("Unable to setup base currency in account %s (%s): %s", qPrintable((*it).name()), qPrintable((*it).id()), e.what()); } } } } } - /** - * Calls MyMoneyFile::readAllData which reads a MyMoneyFile into appropriate - * data structures in memory. The return result is examined to make sure no - * errors occurred whilst parsing. - * - * @param url The URL to read from. - * If no protocol is specified, file:// is assumed. - * - * @return Whether the read was successful. - */ - bool openXMLFile(const QUrl &url) - { - // open the database - auto pStorage = MyMoneyFile::instance()->storage(); - if (!pStorage) - pStorage = new MyMoneyStorageMgr; - - auto rc = false; - auto pluginFound = false; - for (const auto& plugin : pPlugins.storage) { - if (plugin->formatName().compare(QLatin1String("XML")) == 0) { - rc = plugin->open(pStorage, url); - pluginFound = true; - break; - } - } - - if(!pluginFound) - KMessageBox::error(q, i18n("Couldn't find suitable plugin to read your storage.")); - - if(!rc) { - removeStorage(); - return false; - } - - if (pStorage) { - MyMoneyFile::instance()->detachStorage(); - MyMoneyFile::instance()->attachStorage(pStorage); - } - - m_fileType = KMyMoneyApp::KmmXML; - return true; - } - - bool isGNCFile(const QUrl &url) - { - if (!url.isValid()) - throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid URL %1").arg(qPrintable(url.url()))); - if (!url.isLocalFile()) - return false; - - const auto fileName = url.toLocalFile(); - const auto sFileToShort = QString::fromLatin1("File %1 is too short.").arg(fileName); - - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) - throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName)); - - QByteArray qbaFileHeader(2, '\0'); - if (file.read(qbaFileHeader.data(), 2) != 2) - throw MYMONEYEXCEPTION(sFileToShort); - - file.close(); - - QIODevice* qfile = nullptr; - QString sFileHeader(qbaFileHeader); - if (sFileHeader == QString("\037\213")) // gzipped? - qfile = new KCompressionDevice(fileName, COMPRESSION_TYPE); - else - return false; - - if (!qfile->open(QIODevice::ReadOnly)) { - delete qfile; - throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName)); - } - - // Scan the first 70 bytes to see if we find something - // we know. For now, we support our own XML format and - // GNUCash XML format. If the file is smaller, then it - // contains no valid data and we reject it anyway. - qbaFileHeader.resize(70); - if (qfile->read(qbaFileHeader.data(), 70) != 70) - throw MYMONEYEXCEPTION(sFileToShort); - - QString txt(qbaFileHeader); - - qfile->close(); - delete qfile; - - QRegExp gncexp("formatName().compare(QLatin1String("GNC")) == 0) { - pReader = plugin->reader(); - break; - } - } - if (!pReader) { - KMessageBox::error(q, i18n("Couldn't find suitable plugin to read your storage.")); - return false; - } - m_fileType = KMyMoneyApp::GncXML; - - // disconnect the current storga manager from the engine - MyMoneyFile::instance()->detachStorage(); - - // create a new empty storage object - auto storage = new MyMoneyStorageMgr; - - QIODevice* qfile = new KCompressionDevice(url.toLocalFile(), COMPRESSION_TYPE); - pReader->setProgressCallback(&KMyMoneyApp::progressCallback); - pReader->readFile(qfile, storage); - pReader->setProgressCallback(0); - delete pReader; - - // attach the storage before reading the file, since the online - // onlineJobAdministration object queries the engine during - // loading. - MyMoneyFile::instance()->attachStorage(storage); - - qfile->close(); - delete qfile; - return true; - } - - /** - * This method is called from readFile to open a database file which - * is to be processed in 'proper' database mode, i.e. in-place updates - * - * @param dbaseURL pseudo-QUrl representation of database - * - * @retval true Database opened successfully - * @retval false Could not open or read database - */ - bool openDatabase(const QUrl &url) - { - // open the database - auto pStorage = MyMoneyFile::instance()->storage(); - if (!pStorage) - pStorage = new MyMoneyStorageMgr; - - auto rc = false; - auto pluginFound = false; - for (const auto& plugin : pPlugins.storage) { - if (plugin->formatName().compare(QLatin1String("SQL")) == 0) { - rc = plugin->open(pStorage, url); - pluginFound = true; - break; - } - } - - if(!pluginFound) - KMessageBox::error(q, i18n("Couldn't find suitable plugin to read your storage.")); - - if(!rc) { - removeStorage(); - return false; - } - - if (pStorage) { - MyMoneyFile::instance()->detachStorage(); - MyMoneyFile::instance()->attachStorage(pStorage); - } - - m_fileType = KMyMoneyApp::KmmDb; - return true; - } - - /** - * Close the currently opened file and create an empty new file. - * - * @see MyMoneyFile - */ - void newFile() - { - closeFile(); - m_fileType = KMyMoneyApp::KmmXML; // assume native type until saved - m_fileOpen = true; - } - /** * Call this to see if the MyMoneyFile contains any unsaved data. * * @retval true if any data has been modified but not saved * @retval false otherwise */ bool dirty() { - if (!m_fileOpen) + if (!m_storageInfo.isOpened) return false; return MyMoneyFile::instance()->dirty(); } /* DO NOT ADD code to this function or any of it's called ones. Instead, create a new function, fixFile_n, and modify the initializeStorage() logic above to call it */ void fixFile_3() { // make sure each storage object contains a (unique) id MyMoneyFile::instance()->storageId(); } void fixFile_2() { auto file = MyMoneyFile::instance(); MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList transactionList; file->transactionList(transactionList, filter); // scan the transactions and modify transactions with two splits // which reference an account and a category to have the memo text // of the account. auto count = 0; foreach (const auto transaction, transactionList) { if (transaction.splitCount() == 2) { QString accountId; QString categoryId; QString accountMemo; QString categoryMemo; foreach (const auto split, transaction.splits()) { auto acc = file->account(split.accountId()); if (acc.isIncomeExpense()) { categoryId = split.id(); categoryMemo = split.memo(); } else { accountId = split.id(); accountMemo = split.memo(); } } if (!accountId.isEmpty() && !categoryId.isEmpty() && accountMemo != categoryMemo) { MyMoneyTransaction t(transaction); MyMoneySplit s(t.splitById(categoryId)); s.setMemo(accountMemo); t.modifySplit(s); file->modifyTransaction(t); ++count; } } } qDebug("%d transactions fixed in fixFile_2", count); } void fixFile_1() { // we need to fix reports. If the account filter list contains // investment accounts, we need to add the stock accounts to the list // as well if we don't have the expert mode enabled if (!KMyMoneySettings::expertMode()) { try { QList reports = MyMoneyFile::instance()->reportList(); QList::iterator it_r; for (it_r = reports.begin(); it_r != reports.end(); ++it_r) { QStringList list; (*it_r).accounts(list); QStringList missing; QStringList::const_iterator it_a, it_b; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { auto acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == eMyMoney::Account::Type::Investment) { foreach (const auto accountID, acc.accountList()) { if (!list.contains(accountID)) { missing.append(accountID); } } } } if (!missing.isEmpty()) { (*it_r).addAccount(missing); MyMoneyFile::instance()->modifyReport(*it_r); } } } catch (const MyMoneyException &) { } } } #if 0 if (!m_accountsView->allItemsSelected()) { // retrieve a list of selected accounts QStringList list; m_accountsView->selectedItems(list); // if we're not in expert mode, we need to make sure // that all stock accounts for the selected investment // account are also selected if (!KMyMoneySettings::expertMode()) { QStringList missing; QStringList::const_iterator it_a, it_b; for (it_a = list.begin(); it_a != list.end(); ++it_a) { auto acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == Account::Type::Investment) { foreach (const auto accountID, acc.accountList()) { if (!list.contains(accountID)) { missing.append(accountID); } } } } list += missing; } m_filter.addAccount(list); } #endif void fixFile_0() { /* (Ace) I am on a crusade against file fixups. Whenever we have to fix the * file, it is really a warning. So I'm going to print a debug warning, and * then go track them down when I see them to figure out how they got saved * out needing fixing anyway. */ auto file = MyMoneyFile::instance(); QList accountList; file->accountList(accountList); QList::Iterator it_a; QList scheduleList = file->scheduleList(); QList::Iterator it_s; MyMoneyAccount equity = file->equity(); MyMoneyAccount asset = file->asset(); bool equityListEmpty = equity.accountList().count() == 0; for (it_a = accountList.begin(); it_a != accountList.end(); ++it_a) { if ((*it_a).accountType() == eMyMoney::Account::Type::Loan || (*it_a).accountType() == eMyMoney::Account::Type::AssetLoan) { fixLoanAccount_0(*it_a); } // until early before 0.8 release, the equity account was not saved to // the file. If we have an equity account with no sub-accounts but // find and equity account that has equity() as it's parent, we reparent // this account. Need to move it to asset() first, because otherwise // MyMoneyFile::reparent would act as NOP. if (equityListEmpty && (*it_a).accountType() == eMyMoney::Account::Type::Equity) { if ((*it_a).parentAccountId() == equity.id()) { auto acc = *it_a; // tricky, force parent account to be empty so that we really // can re-parent it acc.setParentAccountId(QString()); file->reparentAccount(acc, equity); qDebug() << Q_FUNC_INFO << " fixed account " << acc.id() << " reparented to " << equity.id(); } } } for (it_s = scheduleList.begin(); it_s != scheduleList.end(); ++it_s) { fixSchedule_0(*it_s); } fixTransactions_0(); } void fixSchedule_0(MyMoneySchedule sched) { MyMoneyTransaction t = sched.transaction(); QList splitList = t.splits(); QList::ConstIterator it_s; bool updated = false; try { // Check if the splits contain valid data and set it to // be valid. for (it_s = splitList.constBegin(); it_s != splitList.constEnd(); ++it_s) { // the first split is always the account on which this transaction operates // and if the transaction commodity is not set, we take this if (it_s == splitList.constBegin() && t.commodity().isEmpty()) { qDebug() << Q_FUNC_INFO << " " << t.id() << " has no commodity"; try { auto acc = MyMoneyFile::instance()->account((*it_s).accountId()); t.setCommodity(acc.currencyId()); updated = true; } catch (const MyMoneyException &) { } } // make sure the account exists. If not, remove the split try { MyMoneyFile::instance()->account((*it_s).accountId()); } catch (const MyMoneyException &) { qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " removed, because account '" << (*it_s).accountId() << "' does not exist."; t.removeSplit(*it_s); updated = true; } if ((*it_s).reconcileFlag() != eMyMoney::Split::State::NotReconciled) { qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'"; MyMoneySplit split = *it_s; split.setReconcileDate(QDate()); split.setReconcileFlag(eMyMoney::Split::State::NotReconciled); t.modifySplit(split); updated = true; } // the schedule logic used to operate only on the value field. // This is now obsolete. if ((*it_s).shares().isZero() && !(*it_s).value().isZero()) { MyMoneySplit split = *it_s; split.setShares(split.value()); t.modifySplit(split); updated = true; } } // If there have been changes, update the schedule and // the engine data. if (updated) { sched.setTransaction(t); MyMoneyFile::instance()->modifySchedule(sched); } } catch (const MyMoneyException &e) { qWarning("Unable to update broken schedule: %s", e.what()); } } void fixLoanAccount_0(MyMoneyAccount acc) { if (acc.value("final-payment").isEmpty() || acc.value("term").isEmpty() || acc.value("periodic-payment").isEmpty() || acc.value("loan-amount").isEmpty() || acc.value("interest-calculation").isEmpty() || acc.value("schedule").isEmpty() || acc.value("fixed-interest").isEmpty()) { KMessageBox::information(q, i18n("

The account \"%1\" was previously created as loan account but some information is missing.

The new loan wizard will be started to collect all relevant information.

Please use KMyMoney version 0.8.7 or later and earlier than version 0.9 to correct the problem.

" , acc.name()), i18n("Account problem")); throw MYMONEYEXCEPTION_CSTRING("Fix LoanAccount0 not supported anymore"); } } void fixTransactions_0() { auto file = MyMoneyFile::instance(); QList scheduleList = file->scheduleList(); MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList transactionList; file->transactionList(transactionList, filter); QList::Iterator it_x; QStringList interestAccounts; KMSTATUS(i18n("Fix transactions")); q->slotStatusProgressBar(0, scheduleList.count() + transactionList.count()); int cnt = 0; // scan the schedules to find interest accounts for (it_x = scheduleList.begin(); it_x != scheduleList.end(); ++it_x) { MyMoneyTransaction t = (*it_x).transaction(); QList::ConstIterator it_s; QStringList accounts; bool hasDuplicateAccounts = false; foreach (const auto split, t.splits()) { if (accounts.contains(split.accountId())) { hasDuplicateAccounts = true; qDebug() << Q_FUNC_INFO << " " << t.id() << " has multiple splits with account " << split.accountId(); } else { accounts << split.accountId(); } if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { if (interestAccounts.contains(split.accountId()) == 0) { interestAccounts << split.accountId(); } } } if (hasDuplicateAccounts) { fixDuplicateAccounts_0(t); } ++cnt; if (!(cnt % 10)) q->slotStatusProgressBar(cnt); } // scan the transactions and modify loan transactions for (auto& transaction : transactionList) { QString defaultAction; QList splits = transaction.splits(); QStringList accounts; // check if base commodity is set. if not, set baseCurrency if (transaction.commodity().isEmpty()) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " has no base currency"; transaction.setCommodity(file->baseCurrency().id()); file->modifyTransaction(transaction); } bool isLoan = false; // Determine default action if (transaction.splitCount() == 2) { // check for transfer int accountCount = 0; MyMoneyMoney val; foreach (const auto split, splits) { auto acc = file->account(split.accountId()); if (acc.accountGroup() == eMyMoney::Account::Type::Asset || acc.accountGroup() == eMyMoney::Account::Type::Liability) { val = split.value(); accountCount++; if (acc.accountType() == eMyMoney::Account::Type::Loan || acc.accountType() == eMyMoney::Account::Type::AssetLoan) isLoan = true; } else break; } if (accountCount == 2) { if (isLoan) defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization); else defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer); } else { if (val.isNegative()) defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal); else defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit); } } isLoan = false; foreach (const auto split, splits) { auto acc = file->account(split.accountId()); MyMoneyMoney val = split.value(); if (acc.accountGroup() == eMyMoney::Account::Type::Asset || acc.accountGroup() == eMyMoney::Account::Type::Liability) { if (!val.isPositive()) { defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal); break; } else { defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit); break; } } } #if 0 // Check for correct actions in transactions referencing credit cards bool needModify = false; // The action fields are actually not used anymore in the ledger view logic // so we might as well skip this whole thing here! for (it_s = splits.begin(); needModify == false && it_s != splits.end(); ++it_s) { auto acc = file->account((*it_s).accountId()); MyMoneyMoney val = (*it_s).value(); if (acc.accountType() == Account::Type::CreditCard) { if (val < 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) needModify = true; if (val >= 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) needModify = true; } } // (Ace) Extended the #endif down to cover this conditional, because as-written // it will ALWAYS be skipped. if (needModify == true) { for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { (*it_s).setAction(defaultAction); transaction.modifySplit(*it_s); file->modifyTransaction(transaction); } splits = transaction.splits(); // update local copy qDebug("Fixed credit card assignment in %s", transaction.id().data()); } #endif // Check for correct assignment of ActionInterest in all splits // and check if there are any duplicates in this transactions for (auto& split : splits) { MyMoneyAccount splitAccount = file->account(split.accountId()); if (!accounts.contains(split.accountId())) { accounts << split.accountId(); } // if this split references an interest account, the action // must be of type ActionInterest if (interestAccounts.contains(split.accountId())) { if (split.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " contains an interest account (" << split.accountId() << ") but does not have ActionInterest"; split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)); transaction.modifySplit(split); file->modifyTransaction(transaction); qDebug("Fixed interest action in %s", qPrintable(transaction.id())); } // if it does not reference an interest account, it must not be // of type ActionInterest } else { if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " does not contain an interest account so it should not have ActionInterest"; split.setAction(defaultAction); transaction.modifySplit(split); file->modifyTransaction(transaction); qDebug("Fixed interest action in %s", qPrintable(transaction.id())); } } // check that for splits referencing an account that has // the same currency as the transactions commodity the value // and shares field are the same. if (transaction.commodity() == splitAccount.currencyId() && split.value() != split.shares()) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " " << split.id() << " uses the transaction currency, but shares != value"; split.setShares(split.value()); transaction.modifySplit(split); file->modifyTransaction(transaction); } // fix the shares and values to have the correct fraction if (!splitAccount.isInvest()) { try { int fract = splitAccount.fraction(); if (split.shares() != split.shares().convert(fract)) { qDebug("adjusting fraction in %s,%s", qPrintable(transaction.id()), qPrintable(split.id())); split.setShares(split.shares().convert(fract)); split.setValue(split.value().convert(fract)); transaction.modifySplit(split); file->modifyTransaction(transaction); } } catch (const MyMoneyException &) { qDebug("Missing security '%s', split not altered", qPrintable(splitAccount.currencyId())); } } } ++cnt; if (!(cnt % 10)) q->slotStatusProgressBar(cnt); } q->slotStatusProgressBar(-1, -1); } void fixDuplicateAccounts_0(MyMoneyTransaction& t) { qDebug("Duplicate account in transaction %s", qPrintable(t.id())); } - - + /** + * This method is used to update the caption of the application window. + * It sets the caption to "filename [modified] - KMyMoney". + * + * @param skipActions if true, the actions will not be updated. This + * is usually onyl required by some early calls when + * these widgets are not yet created (the default is false). + */ + void updateCaption(); + void updateActions(); + bool canFileSaveAs() const; + bool canUpdateAllAccounts() const; + void fileAction(eKMyMoney::FileAction action); }; KMyMoneyApp::KMyMoneyApp(QWidget* parent) : KXmlGuiWindow(parent), d(new Private(this)) { #ifdef KMM_DBUS new KmymoneyAdaptor(this); QDBusConnection::sessionBus().registerObject("/KMymoney", this); QDBusConnection::sessionBus().interface()->registerService( "org.kde.kmymoney-" + QString::number(platformTools::processId()), QDBusConnectionInterface::DontQueueService); #endif // Register the main engine types used as meta-objects qRegisterMetaType("MyMoneyMoney"); qRegisterMetaType("MyMoneySecurity"); // preset the pointer because we need it during the course of this constructor kmymoney = this; d->m_config = KSharedConfig::openConfig(); d->setThemedCSS(); MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay()); - updateCaption(true); - QFrame* frame = new QFrame; frame->setFrameStyle(QFrame::NoFrame); // values for margin (11) and spacing(6) taken from KDialog implementation QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, frame); layout->setContentsMargins(2, 2, 2, 2); layout->setSpacing(6); { #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) QString themeName = QLatin1Literal("system"); // using QIcon::setThemeName on Craft build system causes icons to disappear #else QString themeName = KMyMoneySettings::iconsTheme(); // get theme user wants #endif if (!themeName.isEmpty() && themeName != QLatin1Literal("system")) // if it isn't default theme then set it QIcon::setThemeName(themeName); Icons::setIconThemeNames(QIcon::themeName()); // get whatever theme user ends up with and hope our icon names will fit that theme } initStatusBar(); pActions = initActions(); pMenus = initMenus(); - d->newStorage(); d->m_myMoneyView = new KMyMoneyView; layout->addWidget(d->m_myMoneyView, 10); - connect(d->m_myMoneyView, &KMyMoneyView::aboutToChangeView, this, &KMyMoneyApp::slotResetSelections); connect(d->m_myMoneyView, &KMyMoneyView::viewActivated, this, &KMyMoneyApp::slotViewSelected); - connect(d->m_myMoneyView, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), - this, SLOT(slotUpdateActions())); - connect(d->m_myMoneyView, &KMyMoneyView::statusMsg, this, &KMyMoneyApp::slotStatusMsg); connect(d->m_myMoneyView, &KMyMoneyView::statusProgress, this, &KMyMoneyApp::slotStatusProgressBar); - connect(this, &KMyMoneyApp::fileLoaded, d->m_myMoneyView, &KMyMoneyView::slotRefreshViews); - // Initialize kactivities resource instance #ifdef KF5Activities_FOUND d->m_activityResourceInstance = new KActivities::ResourceInstance(window()->winId(), this); - connect(this, &KMyMoneyApp::fileLoaded, d->m_activityResourceInstance, &KActivities::ResourceInstance::setUri); #endif const auto viewActions = d->m_myMoneyView->actionsToBeConnected(); actionCollection()->addActions(viewActions.values()); for (auto it = viewActions.cbegin(); it != viewActions.cend(); ++it) pActions.insert(it.key(), it.value()); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts readOptions(); // now initialize the plugin structure createInterfaces(); KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Load, pPlugins, this, guiFactory()); onlineJobAdministration::instance()->setOnlinePlugins(pPlugins.extended); d->m_myMoneyView->setOnlinePlugins(pPlugins.online); - d->m_myMoneyView->setStoragePlugins(pPlugins.storage); setCentralWidget(frame); connect(&d->m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotBackupHandleEvents())); // force to show the home page if the file is closed connect(pActions[Action::ViewTransactionDetail], &QAction::toggled, d->m_myMoneyView, &KMyMoneyView::slotShowTransactionDetail); d->m_backupState = BACKUP_IDLE; QLocale locale; for (auto const& weekDay: locale.weekdays()) { d->m_processingDays.setBit(static_cast(weekDay)); } d->m_autoSaveTimer = new QTimer(this); d->m_progressTimer = new QTimer(this); connect(d->m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); connect(d->m_progressTimer, SIGNAL(timeout()), this, SLOT(slotStatusProgressDone())); - // make sure, we get a note when the engine changes state - connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotDataChanged())); - // connect the WebConnect server connect(d->m_webConnect, SIGNAL(gotUrl(QUrl)), this, SLOT(webConnect(QUrl))); - // make sure we have a balance warning object - d->m_balanceWarning = new KBalanceWarning(this); // setup the initial configuration slotUpdateConfiguration(QString()); // kickstart date change timer slotDateChanged(); - - connect(this, SIGNAL(fileLoaded(QUrl)), onlineJobAdministration::instance(), SLOT(updateOnlineTaskProperties())); - + d->fileAction(eKMyMoney::FileAction::Closed); } KMyMoneyApp::~KMyMoneyApp() { // don't keep track of selected view anymore as this might change by unloading plugins disconnect(d->m_myMoneyView, &KMyMoneyView::viewActivated, this, &KMyMoneyApp::slotViewSelected); // delete cached objects since they are in the way // when unloading the plugins onlineJobAdministration::instance()->clearCaches(); // we need to unload all plugins before we destroy anything else KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Unload, pPlugins, this, guiFactory()); d->removeStorage(); #ifdef KF5Holidays_FOUND delete d->m_holidayRegion; #endif #ifdef KF5Activities_FOUND delete d->m_activityResourceInstance; #endif // make sure all settings are written to disk KMyMoneySettings::self()->save(); delete d; } QUrl KMyMoneyApp::lastOpenedURL() { - QUrl url = d->m_startDialog ? QUrl() : d->m_fileName; + QUrl url = d->m_startDialog ? QUrl() : d->m_storageInfo.url; if (!url.isValid()) { url = QUrl::fromUserInput(readLastUsedFile()); } ready(); return url; } void KMyMoneyApp::slotInstallConsistencyCheckContextMenu() { // this code relies on the implementation of KMessageBox::informationList to add a context menu to that list, // please adjust it if it's necessary or rewrite the way the consistency check results are displayed if (QWidget* dialog = QApplication::activeModalWidget()) { if (QListWidget* widget = dialog->findChild()) { // give the user a hint that the data can be saved widget->setToolTip(i18n("This is the consistency check log, use the context menu to copy or save it.")); widget->setWhatsThis(widget->toolTip()); widget->setContextMenuPolicy(Qt::CustomContextMenu); connect(widget, SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotShowContextMenuForConsistencyCheck(QPoint))); } } } void KMyMoneyApp::slotShowContextMenuForConsistencyCheck(const QPoint &pos) { // allow the user to save the consistency check results if (QWidget* widget = qobject_cast< QWidget* >(sender())) { QMenu contextMenu(widget); QAction* copy = new QAction(i18n("Copy to clipboard"), widget); QAction* save = new QAction(i18n("Save to file"), widget); contextMenu.addAction(copy); contextMenu.addAction(save); QAction *result = contextMenu.exec(widget->mapToGlobal(pos)); if (result == copy) { // copy the consistency check results to the clipboard d->copyConsistencyCheckResults(); } else if (result == save) { // save the consistency check results to a file d->saveConsistencyCheckResults(); } } } QHash KMyMoneyApp::initMenus() { QHash lutMenus; const QHash menuNames { {Menu::Institution, QStringLiteral("institution_context_menu")}, {Menu::Account, QStringLiteral("account_context_menu")}, {Menu::Schedule, QStringLiteral("schedule_context_menu")}, {Menu::Category, QStringLiteral("category_context_menu")}, {Menu::Tag, QStringLiteral("tag_context_menu")}, {Menu::Payee, QStringLiteral("payee_context_menu")}, {Menu::Investment, QStringLiteral("investment_context_menu")}, {Menu::Transaction, QStringLiteral("transaction_context_menu")}, {Menu::MoveTransaction, QStringLiteral("transaction_move_menu")}, {Menu::MarkTransaction, QStringLiteral("transaction_mark_menu")}, {Menu::MarkTransactionContext, QStringLiteral("transaction_context_mark_menu")}, {Menu::OnlineJob, QStringLiteral("onlinejob_context_menu")} }; for (auto it = menuNames.cbegin(); it != menuNames.cend(); ++it) lutMenus.insert(it.key(), qobject_cast(factory()->container(it.value(), this))); return lutMenus; } QHash KMyMoneyApp::initActions() { auto aC = actionCollection(); /* Look-up table for all custom and standard actions. It's required for: 1) building QList with QActions to be added to ActionCollection 2) adding custom features to QActions like e.g. keyboard shortcut */ QHash lutActions; // ************* // Adding standard actions // ************* KStandardAction::openNew(this, &KMyMoneyApp::slotFileNew, aC); KStandardAction::open(this, &KMyMoneyApp::slotFileOpen, aC); d->m_recentFiles = KStandardAction::openRecent(this, &KMyMoneyApp::slotFileOpenRecent, aC); KStandardAction::save(this, &KMyMoneyApp::slotFileSave, aC); -// KStandardAction::saveAs(this, &KMyMoneyApp::slotFileSaveAs, aC); + KStandardAction::saveAs(this, &KMyMoneyApp::slotFileSaveAs, aC); KStandardAction::close(this, &KMyMoneyApp::slotFileClose, aC); KStandardAction::quit(this, &KMyMoneyApp::slotFileQuit, aC); lutActions.insert(Action::Print, KStandardAction::print(this, &KMyMoneyApp::slotPrintView, aC)); KStandardAction::preferences(this, &KMyMoneyApp::slotSettings, aC); // ************* // Adding all actions // ************* { // struct for creating useless (unconnected) QAction struct actionInfo { Action action; QString name; QString text; Icon icon; }; const QVector actionInfos { // ************* // The File menu // ************* {Action::FileBackup, QStringLiteral("file_backup"), i18n("Backup..."), Icon::Empty}, {Action::FileImportStatement, QStringLiteral("file_import_statement"), i18n("Statement file..."), Icon::Empty}, {Action::FileImportTemplate, QStringLiteral("file_import_template"), i18n("Account Template..."), Icon::Empty}, {Action::FileExportTemplate, QStringLiteral("file_export_template"), i18n("Account Template..."), Icon::Empty}, {Action::FilePersonalData, QStringLiteral("view_personal_data"), i18n("Personal Data..."), Icon::UserProperties}, #ifdef KMM_DEBUG {Action::FileDump, QStringLiteral("file_dump"), i18n("Dump Memory"), Icon::Empty}, #endif {Action::FileInformation, QStringLiteral("view_file_info"), i18n("File-Information..."), Icon::DocumentProperties}, // ************* // The Edit menu // ************* {Action::EditFindTransaction, QStringLiteral("edit_find_transaction"), i18n("Find transaction..."), Icon::EditFindTransaction}, // ************* // The View menu // ************* {Action::ViewTransactionDetail, QStringLiteral("view_show_transaction_detail"), i18n("Show Transaction Detail"), Icon::ViewTransactionDetail}, {Action::ViewHideReconciled, QStringLiteral("view_hide_reconciled_transactions"), i18n("Hide reconciled transactions"), Icon::HideReconciled}, {Action::ViewHideCategories, QStringLiteral("view_hide_unused_categories"), i18n("Hide unused categories"), Icon::HideCategories}, {Action::ViewShowAll, QStringLiteral("view_show_all_accounts"), i18n("Show all accounts"), Icon::Empty}, // ********************* // The institutions menu // ********************* {Action::NewInstitution, QStringLiteral("institution_new"), i18n("New institution..."), Icon::InstitutionNew}, {Action::EditInstitution, QStringLiteral("institution_edit"), i18n("Edit institution..."), Icon::InstitutionEdit}, {Action::DeleteInstitution, QStringLiteral("institution_delete"), i18n("Delete institution..."), Icon::InstitutionDelete}, // ***************** // The accounts menu // ***************** {Action::NewAccount, QStringLiteral("account_new"), i18n("New account..."), Icon::AccountNew}, {Action::OpenAccount, QStringLiteral("account_open"), i18n("Open ledger"), Icon::ViewFinancialList}, {Action::StartReconciliation, QStringLiteral("account_reconcile"), i18n("Reconcile..."), Icon::Reconcile}, {Action::FinishReconciliation, QStringLiteral("account_reconcile_finish"), i18nc("Finish reconciliation", "Finish"), Icon::AccountFinishReconciliation}, {Action::PostponeReconciliation, QStringLiteral("account_reconcile_postpone"), i18n("Postpone reconciliation"), Icon::MediaPlaybackPause}, {Action::EditAccount, QStringLiteral("account_edit"), i18n("Edit account..."), Icon::AccountEdit}, {Action::DeleteAccount, QStringLiteral("account_delete"), i18n("Delete account..."), Icon::AccountDelete}, {Action::CloseAccount, QStringLiteral("account_close"), i18n("Close account"), Icon::AccountClose}, {Action::ReopenAccount, QStringLiteral("account_reopen"), i18n("Reopen account"), Icon::AccountReopen}, {Action::ReportAccountTransactions, QStringLiteral("account_transaction_report"), i18n("Transaction report"), Icon::ViewFinancialList}, {Action::ChartAccountBalance, QStringLiteral("account_chart"), i18n("Show balance chart..."), Icon::OfficeChartLine}, {Action::MapOnlineAccount, QStringLiteral("account_online_map"), i18n("Map account..."), Icon::NewsSubscribe}, {Action::UnmapOnlineAccount, QStringLiteral("account_online_unmap"), i18n("Unmap account..."), Icon::NewsUnsubscribe}, {Action::UpdateAccount, QStringLiteral("account_online_update"), i18n("Update account..."), Icon::AccountUpdate}, {Action::UpdateAllAccounts, QStringLiteral("account_online_update_all"), i18n("Update all accounts..."), Icon::AccountUpdateAll}, {Action::AccountCreditTransfer, QStringLiteral("account_online_new_credit_transfer"), i18n("New credit transfer"), Icon::AccountCreditTransfer}, // ******************* // The categories menu // ******************* {Action::NewCategory, QStringLiteral("category_new"), i18n("New category..."), Icon::CategoryNew}, {Action::EditCategory, QStringLiteral("category_edit"), i18n("Edit category..."), Icon::CategoryEdit}, {Action::DeleteCategory, QStringLiteral("category_delete"), i18n("Delete category..."), Icon::CategoryDelete}, // ************** // The tools menu // ************** {Action::ToolCurrencies, QStringLiteral("tools_currency_editor"), i18n("Currencies..."), Icon::ViewCurrencyList}, {Action::ToolPrices, QStringLiteral("tools_price_editor"), i18n("Prices..."), Icon::Empty}, {Action::ToolUpdatePrices, QStringLiteral("tools_update_prices"), i18n("Update Stock and Currency Prices..."), Icon::ToolUpdatePrices}, {Action::ToolConsistency, QStringLiteral("tools_consistency_check"), i18n("Consistency Check"), Icon::Empty}, {Action::ToolPerformance, QStringLiteral("tools_performancetest"), i18n("Performance-Test"), Icon::Fork}, {Action::ToolCalculator, QStringLiteral("tools_kcalc"), i18n("Calculator..."), Icon::AccessoriesCalculator}, // ***************** // The settings menu // ***************** {Action::SettingsAllMessages, QStringLiteral("settings_enable_messages"), i18n("Enable all messages"), Icon::Empty}, // ************* // The help menu // ************* {Action::HelpShow, QStringLiteral("help_show_tip"), i18n("&Show tip of the day"), Icon::Tip}, // *************************** // Actions w/o main menu entry // *************************** {Action::NewTransaction, QStringLiteral("transaction_new"), i18nc("New transaction button", "New"), Icon::TransactionNew}, {Action::EditTransaction, QStringLiteral("transaction_edit"), i18nc("Edit transaction button", "Edit"), Icon::TransactionEdit}, {Action::EnterTransaction, QStringLiteral("transaction_enter"), i18nc("Enter transaction", "Enter"), Icon::DialogOK}, {Action::EditSplits, QStringLiteral("transaction_editsplits"), i18nc("Edit split button", "Edit splits"), Icon::Split}, {Action::CancelTransaction, QStringLiteral("transaction_cancel"), i18nc("Cancel transaction edit", "Cancel"), Icon::DialogCancel}, {Action::DeleteTransaction, QStringLiteral("transaction_delete"), i18nc("Delete transaction", "Delete"), Icon::EditDelete}, {Action::DuplicateTransaction, QStringLiteral("transaction_duplicate"), i18nc("Duplicate transaction", "Duplicate"), Icon::EditCopy}, {Action::MatchTransaction, QStringLiteral("transaction_match"), i18nc("Button text for match transaction", "Match"),Icon::TransactionMatch}, {Action::AcceptTransaction, QStringLiteral("transaction_accept"), i18nc("Accept 'imported' and 'matched' transaction", "Accept"), Icon::TransactionAccept}, {Action::ToggleReconciliationFlag, QStringLiteral("transaction_mark_toggle"), i18nc("Toggle reconciliation flag", "Toggle"), Icon::Empty}, {Action::MarkCleared, QStringLiteral("transaction_mark_cleared"), i18nc("Mark transaction cleared", "Cleared"), Icon::Empty}, {Action::MarkReconciled, QStringLiteral("transaction_mark_reconciled"), i18nc("Mark transaction reconciled", "Reconciled"), Icon::Empty}, {Action::MarkNotReconciled, QStringLiteral("transaction_mark_notreconciled"), i18nc("Mark transaction not reconciled", "Not reconciled"), Icon::Empty}, {Action::SelectAllTransactions, QStringLiteral("transaction_select_all"), i18nc("Select all transactions", "Select all"), Icon::Empty}, {Action::GoToAccount, QStringLiteral("transaction_goto_account"), i18n("Go to account"), Icon::GoJump}, {Action::GoToPayee, QStringLiteral("transaction_goto_payee"), i18n("Go to payee"), Icon::GoJump}, {Action::NewScheduledTransaction, QStringLiteral("transaction_create_schedule"), i18n("Create scheduled transaction..."), Icon::AppointmentNew}, {Action::AssignTransactionsNumber, QStringLiteral("transaction_assign_number"), i18n("Assign next number"), Icon::Empty}, {Action::CombineTransactions, QStringLiteral("transaction_combine"), i18nc("Combine transactions", "Combine"), Icon::Empty}, {Action::CopySplits, QStringLiteral("transaction_copy_splits"), i18n("Copy splits"), Icon::Empty}, //Investment {Action::NewInvestment, QStringLiteral("investment_new"), i18n("New investment..."), Icon::InvestmentNew}, {Action::EditInvestment, QStringLiteral("investment_edit"), i18n("Edit investment..."), Icon::InvestmentEdit}, {Action::DeleteInvestment, QStringLiteral("investment_delete"), i18n("Delete investment..."), Icon::InvestmentDelete}, {Action::UpdatePriceOnline, QStringLiteral("investment_online_price_update"), i18n("Online price update..."), Icon::InvestmentOnlinePrice}, {Action::UpdatePriceManually, QStringLiteral("investment_manual_price_update"), i18n("Manual price update..."), Icon::Empty}, //Schedule {Action::NewSchedule, QStringLiteral("schedule_new"), i18n("New scheduled transaction"), Icon::AppointmentNew}, {Action::EditSchedule, QStringLiteral("schedule_edit"), i18n("Edit scheduled transaction"), Icon::DocumentEdit}, {Action::DeleteSchedule, QStringLiteral("schedule_delete"), i18n("Delete scheduled transaction"), Icon::EditDelete}, {Action::DuplicateSchedule, QStringLiteral("schedule_duplicate"), i18n("Duplicate scheduled transaction"), Icon::EditCopy}, {Action::EnterSchedule, QStringLiteral("schedule_enter"), i18n("Enter next transaction..."), Icon::KeyEnter}, {Action::SkipSchedule, QStringLiteral("schedule_skip"), i18n("Skip next transaction..."), Icon::MediaSeekForward}, //Payees {Action::NewPayee, QStringLiteral("payee_new"), i18n("New payee"), Icon::ListAddUser}, {Action::RenamePayee, QStringLiteral("payee_rename"), i18n("Rename payee"), Icon::PayeeRename}, {Action::DeletePayee, QStringLiteral("payee_delete"), i18n("Delete payee"), Icon::ListRemoveUser}, {Action::MergePayee, QStringLiteral("payee_merge"), i18n("Merge payees"), Icon::PayeeMerge}, //Tags {Action::NewTag, QStringLiteral("tag_new"), i18n("New tag"), Icon::ListAddTag}, {Action::RenameTag, QStringLiteral("tag_rename"), i18n("Rename tag"), Icon::TagRename}, {Action::DeleteTag, QStringLiteral("tag_delete"), i18n("Delete tag"), Icon::ListRemoveTag}, //debug actions #ifdef KMM_DEBUG {Action::WizardNewUser, QStringLiteral("new_user_wizard"), i18n("Test new feature"), Icon::Empty}, {Action::DebugTraces, QStringLiteral("debug_traces"), i18n("Debug Traces"), Icon::Empty}, #endif {Action::DebugTimers, QStringLiteral("debug_timers"), i18n("Debug Timers"), Icon::Empty}, // onlineJob actions {Action::DeleteOnlineJob, QStringLiteral("onlinejob_delete"), i18n("Remove credit transfer"), Icon::EditDelete}, {Action::EditOnlineJob, QStringLiteral("onlinejob_edit"), i18n("Edit credit transfer"), Icon::DocumentEdit}, {Action::LogOnlineJob, QStringLiteral("onlinejob_log"), i18n("Show log"), Icon::Empty}, }; for (const auto& info : actionInfos) { auto a = new QAction(this); // KActionCollection::addAction by name sets object name anyways, // so, as better alternative, set it here right from the start a->setObjectName(info.name); a->setText(info.text); if (info.icon != Icon::Empty) // no need to set empty icon a->setIcon(Icons::get(info.icon)); a->setEnabled(false); lutActions.insert(info.action, a); // store QAction's pointer for later processing } } { // List with slots that get connected here. Other slots get connected in e.g. appropriate views typedef void(KMyMoneyApp::*KMyMoneyAppFunc)(); const QHash actionConnections { // ************* // The File menu // ************* // {Action::FileOpenDatabase, &KMyMoneyApp::slotOpenDatabase}, // {Action::FileSaveAsDatabase, &KMyMoneyApp::slotSaveAsDatabase}, {Action::FileBackup, &KMyMoneyApp::slotBackupFile}, {Action::FileImportTemplate, &KMyMoneyApp::slotLoadAccountTemplates}, {Action::FileExportTemplate, &KMyMoneyApp::slotSaveAccountTemplates}, {Action::FilePersonalData, &KMyMoneyApp::slotFileViewPersonal}, #ifdef KMM_DEBUG {Action::FileDump, &KMyMoneyApp::slotFileFileInfo}, #endif {Action::FileInformation, &KMyMoneyApp::slotFileInfoDialog}, // ************* // The View menu // ************* {Action::ViewTransactionDetail, &KMyMoneyApp::slotShowTransactionDetail}, {Action::ViewHideReconciled, &KMyMoneyApp::slotHideReconciledTransactions}, {Action::ViewHideCategories, &KMyMoneyApp::slotHideUnusedCategories}, {Action::ViewShowAll, &KMyMoneyApp::slotShowAllAccounts}, // ************** // The tools menu // ************** {Action::ToolCurrencies, &KMyMoneyApp::slotCurrencyDialog}, {Action::ToolPrices, &KMyMoneyApp::slotPriceDialog}, {Action::ToolUpdatePrices, &KMyMoneyApp::slotEquityPriceUpdate}, {Action::ToolConsistency, &KMyMoneyApp::slotFileConsistencyCheck}, {Action::ToolPerformance, &KMyMoneyApp::slotPerformanceTest}, // {Action::ToolSQL, &KMyMoneyApp::slotGenerateSql}, {Action::ToolCalculator, &KMyMoneyApp::slotToolsStartKCalc}, // ***************** // The settings menu // ***************** {Action::SettingsAllMessages, &KMyMoneyApp::slotEnableMessages}, // ************* // The help menu // ************* {Action::HelpShow, &KMyMoneyApp::slotShowTipOfTheDay}, // *************************** // Actions w/o main menu entry // *************************** //debug actions #ifdef KMM_DEBUG {Action::WizardNewUser, &KMyMoneyApp::slotNewFeature}, {Action::DebugTraces, &KMyMoneyApp::slotToggleTraces}, #endif {Action::DebugTimers, &KMyMoneyApp::slotToggleTimers}, }; for (auto connection = actionConnections.cbegin(); connection != actionConnections.cend(); ++connection) connect(lutActions[connection.key()], &QAction::triggered, this, connection.value()); } // ************* // Setting some of added actions checkable // ************* { // Some actions are checkable, // so set them here const QVector checkableActions { Action::ViewTransactionDetail, Action::ViewHideReconciled, Action::ViewHideCategories, #ifdef KMM_DEBUG Action::DebugTraces, Action::DebugTimers, #endif Action::ViewShowAll }; for (const auto& it : checkableActions) { lutActions[it]->setCheckable(true); lutActions[it]->setEnabled(true); } } // ************* // Setting actions that are always enabled // ************* { const QVector alwaysEnabled { Action::HelpShow, Action::SettingsAllMessages, Action::ToolPerformance, Action::ToolCalculator }; for (const auto& action : alwaysEnabled) { lutActions[action]->setEnabled(true); } } // ************* // Setting keyboard shortcuts for some of added actions // ************* { const QVector> actionShortcuts { {qMakePair(Action::EditFindTransaction, Qt::CTRL + Qt::Key_F)}, {qMakePair(Action::ViewTransactionDetail, Qt::CTRL + Qt::Key_T)}, {qMakePair(Action::ViewHideReconciled, Qt::CTRL + Qt::Key_R)}, {qMakePair(Action::ViewHideCategories, Qt::CTRL + Qt::Key_U)}, {qMakePair(Action::ViewShowAll, Qt::CTRL + Qt::SHIFT + Qt::Key_A)}, {qMakePair(Action::StartReconciliation, Qt::CTRL + Qt::SHIFT + Qt::Key_R)}, {qMakePair(Action::NewTransaction, Qt::CTRL + Qt::Key_Insert)}, {qMakePair(Action::ToggleReconciliationFlag, Qt::CTRL + Qt::Key_Space)}, {qMakePair(Action::MarkCleared, Qt::CTRL + Qt::ALT+ Qt::Key_Space)}, {qMakePair(Action::MarkReconciled, Qt::CTRL + Qt::SHIFT + Qt::Key_Space)}, {qMakePair(Action::SelectAllTransactions, Qt::CTRL + Qt::Key_A)}, #ifdef KMM_DEBUG {qMakePair(Action::WizardNewUser, Qt::CTRL + Qt::Key_G)}, #endif {qMakePair(Action::AssignTransactionsNumber, Qt::CTRL + Qt::SHIFT + Qt::Key_N)} }; for(const auto& it : actionShortcuts) aC->setDefaultShortcut(lutActions[it.first], it.second); } // ************* // Misc settings // ************* connect(onlineJobAdministration::instance(), &onlineJobAdministration::canSendCreditTransferChanged, lutActions.value(Action::AccountCreditTransfer), &QAction::setEnabled); // Setup transaction detail switch lutActions[Action::ViewTransactionDetail]->setChecked(KMyMoneySettings::showRegisterDetailed()); lutActions[Action::ViewHideReconciled]->setChecked(KMyMoneySettings::hideReconciledTransactions()); lutActions[Action::ViewHideCategories]->setChecked(KMyMoneySettings::hideUnusedCategory()); lutActions[Action::ViewShowAll]->setChecked(false); // ************* // Adding actions to ActionCollection // ************* actionCollection()->addActions(lutActions.values()); // ************************ // Currently unused actions // ************************ #if 0 new KToolBarPopupAction(i18n("View back"), "go-previous", 0, this, SLOT(slotShowPreviousView()), actionCollection(), "go_back"); new KToolBarPopupAction(i18n("View forward"), "go-next", 0, this, SLOT(slotShowNextView()), actionCollection(), "go_forward"); action("go_back")->setEnabled(false); action("go_forward")->setEnabled(false); #endif // use the absolute path to your kmymoneyui.rc file for testing purpose in createGUI(); setupGUI(); // reconnect about app entry to dialog with full credits information auto aboutApp = aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::AboutApp))); aboutApp->disconnect(); connect(aboutApp, &QAction::triggered, this, &KMyMoneyApp::slotShowCredits); QMenu *menuContainer; menuContainer = static_cast(factory()->container(QStringLiteral("import"), this)); menuContainer->setIcon(Icons::get(Icon::DocumentImport)); menuContainer = static_cast(factory()->container(QStringLiteral("export"), this)); menuContainer->setIcon(Icons::get(Icon::DocumentExport)); return lutActions; } #ifdef KMM_DEBUG void KMyMoneyApp::dumpActions() const { const QList list = actionCollection()->actions(); foreach (const auto it, list) std::cout << qPrintable(it->objectName()) << ": " << qPrintable(it->text()) << std::endl; } #endif bool KMyMoneyApp::isActionToggled(const Action _a) { return pActions[_a]->isChecked(); } void KMyMoneyApp::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR d->m_statusLabel = new QLabel; statusBar()->addWidget(d->m_statusLabel); ready(); // Initialization of progress bar taken from KDevelop ;-) d->m_progressBar = new QProgressBar; statusBar()->addWidget(d->m_progressBar); d->m_progressBar->setFixedHeight(d->m_progressBar->sizeHint().height() - 8); // hide the progress bar for now slotStatusProgressBar(-1, -1); } void KMyMoneyApp::saveOptions() { KConfigGroup grp = d->m_config->group("General Options"); grp.writeEntry("Geometry", size()); grp.writeEntry("Show Statusbar", actionCollection()->action(KStandardAction::name(KStandardAction::ShowStatusbar))->isChecked()); KConfigGroup toolbarGrp = d->m_config->group("mainToolBar"); toolBar("mainToolBar")->saveSettings(toolbarGrp); d->m_recentFiles->saveEntries(d->m_config->group("Recent Files")); } void KMyMoneyApp::readOptions() { KConfigGroup grp = d->m_config->group("General Options"); pActions[Action::ViewHideReconciled]->setChecked(KMyMoneySettings::hideReconciledTransactions()); pActions[Action::ViewHideCategories]->setChecked(KMyMoneySettings::hideUnusedCategory()); d->m_recentFiles->loadEntries(d->m_config->group("Recent Files")); // Startdialog is written in the settings dialog d->m_startDialog = grp.readEntry("StartDialog", true); } +#ifdef KMM_DEBUG void KMyMoneyApp::resizeEvent(QResizeEvent* ev) { KMainWindow::resizeEvent(ev); - updateCaption(true); -} - -int KMyMoneyApp::askSaveOnClose() -{ - int ans; - if (KMyMoneySettings::autoSaveOnClose()) { - ans = KMessageBox::Yes; - } else { - ans = KMessageBox::warningYesNoCancel(this, i18n("The file has been changed, save it?")); - } - return ans; + d->updateCaption(); } +#endif bool KMyMoneyApp::queryClose() { if (!isReady()) return false; - if (d->dirty()) { - int ans = askSaveOnClose(); + if (!slotFileClose()) + return false; - if (ans == KMessageBox::Cancel) - return false; - else if (ans == KMessageBox::Yes) { - bool saved = slotFileSave(); - saveOptions(); - return saved; - } - } -// if (d->m_myMoneyView->isDatabase()) -// slotFileClose(); // close off the database saveOptions(); return true; } ///////////////////////////////////////////////////////////////////// // SLOT IMPLEMENTATION ///////////////////////////////////////////////////////////////////// void KMyMoneyApp::slotFileInfoDialog() { QPointer dlg = new KMyMoneyFileInfoDlg(0); dlg->exec(); delete dlg; } void KMyMoneyApp::slotPerformanceTest() { // dump performance report to stderr int measurement[2]; QTime timer; MyMoneyAccount acc; qDebug("--- Starting performance tests ---"); // AccountList // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; timer.start(); for (int i = 0; i < 1000; ++i) { QList list; MyMoneyFile::instance()->accountList(list); measurement[i != 0] = timer.elapsed(); } std::cerr << "accountList()" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // Balance of asset account(s) // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->asset(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "balance(Asset)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // total balance of asset account // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->asset(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "totalBalance(Asset)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // Balance of expense account(s) // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->expense(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "balance(Expense)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // total balance of expense account // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->expense(); timer.start(); for (int i = 0; i < 1000; ++i) { MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id()); measurement[i != 0] = timer.elapsed(); } std::cerr << "totalBalance(Expense)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // transaction list // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; if (MyMoneyFile::instance()->asset().accountCount()) { MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]); filter.setDateFilter(QDate(), QDate::currentDate()); QList list; timer.start(); for (int i = 0; i < 100; ++i) { list = MyMoneyFile::instance()->transactionList(filter); measurement[i != 0] = timer.elapsed(); } std::cerr << "transactionList()" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl; } // transaction list // MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; if (MyMoneyFile::instance()->asset().accountCount()) { MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]); filter.setDateFilter(QDate(), QDate::currentDate()); QList list; timer.start(); for (int i = 0; i < 100; ++i) { MyMoneyFile::instance()->transactionList(list, filter); measurement[i != 0] = timer.elapsed(); } std::cerr << "transactionList(list)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl; } // MyMoneyFile::instance()->preloadCache(); } -void KMyMoneyApp::slotFileNew() -{ - KMSTATUS(i18n("Creating new document...")); - - slotFileClose(); - - if (!d->m_fileOpen) { - // next line required until we move all file handling out of KMyMoneyView - d->newFile(); - - d->m_fileName = QUrl(); - updateCaption(); - - NewUserWizard::Wizard *wizard = new NewUserWizard::Wizard(); - - if (wizard->exec() == QDialog::Accepted) { - MyMoneyFileTransaction ft; - MyMoneyFile* file = MyMoneyFile::instance(); - try { - // store the user info - file->setUser(wizard->user()); - - // create and setup base currency - file->addCurrency(wizard->baseCurrency()); - file->setBaseCurrency(wizard->baseCurrency()); - - // create a possible institution - MyMoneyInstitution inst = wizard->institution(); - if (inst.name().length()) { - file->addInstitution(inst); - } - - // create a possible checking account - auto acc = wizard->account(); - if (acc.name().length()) { - acc.setInstitutionId(inst.id()); - MyMoneyAccount asset = file->asset(); - file->addAccount(acc, asset); - - // create possible opening balance transaction - if (!wizard->openingBalance().isZero()) { - file->createOpeningBalanceTransaction(acc, wizard->openingBalance()); - } - } - - // import the account templates - QList templates = wizard->templates(); - QList::iterator it_t; - for (it_t = templates.begin(); it_t != templates.end(); ++it_t) { - (*it_t).importTemplate(progressCallback); - } - - d->m_fileName = wizard->url(); - ft.commit(); - KMyMoneySettings::setFirstTimeRun(false); - - // FIXME This is a bit clumsy. We re-read the freshly - // created file to be able to run through all the - // fixup logic and then save it to keep the modified - // flag off. - slotFileSave(); - if (d->openXMLFile(d->m_fileName)) { - d->m_fileOpen = true; - d->initializeStorage(); - } - slotFileSave(); - - // now keep the filename in the recent files used list - //KRecentFilesAction *p = dynamic_cast(action(KStandardAction::name(KStandardAction::OpenRecent))); - //if(p) - d->m_recentFiles->addUrl(d->m_fileName); - writeLastUsedFile(d->m_fileName.url()); - - } catch (const MyMoneyException &) { - // next line required until we move all file handling out of KMyMoneyView - d->closeFile(); - } - if (wizard->startSettingsAfterFinished()) - slotSettings(); - } else { - // next line required until we move all file handling out of KMyMoneyView - d->closeFile(); - } - delete wizard; - updateCaption(); - - emit fileLoaded(d->m_fileName); - } -} - bool KMyMoneyApp::isDatabase() { - return (d->m_fileOpen && ((d->m_fileType == KmmDb))); + return (d->m_storageInfo.isOpened && ((d->m_storageInfo.type == eKMyMoney::StorageType::SQL))); } bool KMyMoneyApp::isNativeFile() { - return (d->m_fileOpen && (d->m_fileType < MaxNativeFileType)); + return (d->m_storageInfo.isOpened && (d->m_storageInfo.type == eKMyMoney::StorageType::SQL || d->m_storageInfo.type == eKMyMoney::StorageType::XML)); } bool KMyMoneyApp::fileOpen() const { - return d->m_fileOpen; + return d->m_storageInfo.isOpened; } KMyMoneyAppCallback KMyMoneyApp::progressCallback() { return &KMyMoneyApp::progressCallback; } void KMyMoneyApp::consistencyCheck(bool alwaysDisplayResult) { d->consistencyCheck(alwaysDisplayResult); } -// General open -void KMyMoneyApp::slotFileOpen() -{ - KMSTATUS(i18n("Open a file.")); - - QString prevDir = readLastUsedDir(); - QString fileExtensions; - fileExtensions.append(i18n("KMyMoney files (*.kmy *.xml)")); - fileExtensions.append(QLatin1String(";;")); - - for (const auto& plugin : pPlugins.storage) { - const auto fileExtension = plugin->fileExtension(); - if (!fileExtension.isEmpty()) { - fileExtensions.append(fileExtension); - fileExtensions.append(QLatin1String(";;")); - } - } - fileExtensions.append(i18n("All files (*)")); - - QPointer dialog = new QFileDialog(this, QString(), prevDir, fileExtensions); - dialog->setFileMode(QFileDialog::ExistingFile); - dialog->setAcceptMode(QFileDialog::AcceptOpen); - - if (dialog->exec() == QDialog::Accepted && dialog != nullptr) { - slotFileOpenRecent(dialog->selectedUrls().first()); - } - delete dialog; -} - bool KMyMoneyApp::isImportableFile(const QUrl &url) { bool result = false; // Iterate through the plugins and see if there's a loaded plugin who can handle it QMap::const_iterator it_plugin = pPlugins.importer.constBegin(); while (it_plugin != pPlugins.importer.constEnd()) { if ((*it_plugin)->isMyFormat(url.path())) { result = true; break; } ++it_plugin; } // If we did not find a match, try importing it as a KMM statement file, // which is really just for testing. the statement file is not exposed // to users. if (it_plugin == pPlugins.importer.constEnd()) if (MyMoneyStatement::isStatementFile(url.path())) result = true; // Place code here to test for QIF and other locally-supported formats // (i.e. not a plugin). If you add them here, be sure to add it to // the webConnect function. return result; } bool KMyMoneyApp::isFileOpenedInAnotherInstance(const QUrl &url) { const auto instances = instanceList(); #ifdef KMM_DBUS // check if there are other instances which might have this file open for (const auto& instance : instances) { QDBusInterface remoteApp(instance, "/KMymoney", "org.kde.kmymoney"); QDBusReply reply = remoteApp.call("filename"); if (!reply.isValid()) qDebug("D-Bus error while calling app->filename()"); else if (reply.value() == url.url()) return true; } #else Q_UNUSED(url) #endif return false; } -void KMyMoneyApp::slotFileOpenRecent(const QUrl &url) -{ - KMSTATUS(i18n("Loading file...")); - if (isFileOpenedInAnotherInstance(url)) { - KMessageBox::sorry(this, i18n("

File %1 is already opened in another instance of KMyMoney

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Duplicate open")); - return; - } - - if (url.scheme() != QLatin1String("sql") && !KMyMoneyUtils::fileExists(url)) { - KMessageBox::sorry(this, i18n("

%1 is either an invalid filename or the file does not exist. You can open another file or create a new one.

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("File not found")); - return; - } - - if (d->m_fileOpen) - slotFileClose(); - - if (d->m_fileOpen) - return; - - try { - auto isOpened = false; - if (url.scheme() == QLatin1String("sql")) - isOpened = d->openDatabase(url); - else if (d->isGNCFile(url)) - isOpened = d->openGNCFile(url); - else - isOpened = d->openXMLFile(url); - - if (!isOpened) - return; - - d->m_fileOpen = true; - if (!d->initializeStorage()) { - d->m_fileOpen = false; - return; - } - - if (isNativeFile()) { - d->m_fileName = url; - updateCaption(); - writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile)); - /* Don't use url variable after KRecentFilesAction::addUrl - * as it might delete it. - * More in API reference to this method - */ - d->m_recentFiles->addUrl(url); - } else { - d->m_fileName = QUrl(); // imported files have no filename - } - - } catch (const MyMoneyException &e) { - KMessageBox::sorry(this, i18n("Cannot open file as requested. Error was: %1", QString::fromLatin1(e.what()))); - } - updateCaption(); - emit fileLoaded(d->m_fileName); -} - -bool KMyMoneyApp::slotFileSave() -{ - // if there's nothing changed, there's no need to save anything - if (!d->dirty()) - return true; - - bool rc = false; - - KMSTATUS(i18n("Saving file...")); - - if (d->m_fileName.isEmpty()) - return false; - - d->consistencyCheck(false); - - setEnabled(false); - QString format; - switch (d->m_fileType) { - case KMyMoneyApp::KmmXML: - case KMyMoneyApp::GncXML: - format = QStringLiteral("XML"); - break; - case KMyMoneyApp::KmmDb: - format = QStringLiteral("SQL"); - break; - default: - return false; - } - - auto pluginFound = false; - for (const auto& plugin : pPlugins.storage) { - if (plugin->formatName().compare(format) == 0) { - rc = plugin->save(d->m_fileName); - pluginFound = true; - break; - } - } - if(!pluginFound) - KMessageBox::error(this, i18n("Couldn't find suitable plugin to save your storage.")); - - setEnabled(true); - - d->m_autoSaveTimer->stop(); - - updateCaption(); - return rc; -} - -void KMyMoneyApp::slotFileCloseWindow() -{ - KMSTATUS(i18n("Closing window...")); - - if (d->dirty()) { - int answer = askSaveOnClose(); - if (answer == KMessageBox::Cancel) - return; - else if (answer == KMessageBox::Yes) - slotFileSave(); - } - close(); -} - -void KMyMoneyApp::slotFileClose() -{ -// bool okToSelect = true; - - // check if transaction editor is open and ask user what he wants to do -// slotTransactionsCancelOrEnter(okToSelect); - -// if (!okToSelect) -// return; - - // no update status here, as we might delete the status too early. - if (d->dirty()) { - int answer = askSaveOnClose(); - if (answer == KMessageBox::Cancel) - return; - else if (answer == KMessageBox::Yes) - slotFileSave(); - } - - d->closeFile(); -} - -void KMyMoneyApp::slotFileQuit() -{ - // don't modify the status message here as this will prevent quit from working!! - // See the beginning of queryClose() and isReady() why. Thomas Baumgart 2005-10-17 - - bool quitApplication = true; - - QList memberList = KMainWindow::memberList(); - if (!memberList.isEmpty()) { - - QList::const_iterator w_it = memberList.constBegin(); - for (; w_it != memberList.constEnd(); ++w_it) { - // only close the window if the closeEvent is accepted. If the user presses Cancel on the saveModified() dialog, - // the window and the application stay open. - if (!(*w_it)->close()) { - quitApplication = false; - break; - } - } - } - - // We will only quit if all windows were processed and not cancelled - if (quitApplication) { - QCoreApplication::quit(); - } -} - void KMyMoneyApp::slotShowTransactionDetail() { } void KMyMoneyApp::slotHideReconciledTransactions() { KMyMoneySettings::setHideReconciledTransactions(pActions[Action::ViewHideReconciled]->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotHideUnusedCategories() { KMyMoneySettings::setHideUnusedCategory(pActions[Action::ViewHideCategories]->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotShowAllAccounts() { KMyMoneySettings::setShowAllAccounts(pActions[Action::ViewShowAll]->isChecked()); d->m_myMoneyView->slotRefreshViews(); } #ifdef KMM_DEBUG void KMyMoneyApp::slotFileFileInfo() { - if (!d->m_fileOpen) { + if (!d->m_storageInfo.isOpened) { KMessageBox::information(this, i18n("No KMyMoneyFile open")); return; } QFile g("kmymoney.dump"); g.open(QIODevice::WriteOnly); QDataStream st(&g); MyMoneyStorageDump dumper; dumper.writeStream(st, MyMoneyFile::instance()->storage()); g.close(); } void KMyMoneyApp::slotToggleTraces() { MyMoneyTracer::onOff(pActions[Action::DebugTraces]->isChecked() ? 1 : 0); } #endif void KMyMoneyApp::slotToggleTimers() { extern bool timersOn; // main.cpp timersOn = pActions[Action::DebugTimers]->isChecked(); } QString KMyMoneyApp::slotStatusMsg(const QString &text) { /////////////////////////////////////////////////////////////////// // change status message permanently QString previousMessage = d->m_statusLabel->text(); d->m_applicationIsReady = false; QString currentMessage = text; if (currentMessage.isEmpty() || currentMessage == i18nc("Application is ready to use", "Ready.")) { d->m_applicationIsReady = true; currentMessage = i18nc("Application is ready to use", "Ready."); } statusBar()->clearMessage(); d->m_statusLabel->setText(currentMessage); return previousMessage; } void KMyMoneyApp::ready() { slotStatusMsg(QString()); } bool KMyMoneyApp::isReady() { return d->m_applicationIsReady; } void KMyMoneyApp::slotStatusProgressBar(int current, int total) { if (total == -1 && current == -1) { // reset if (d->m_progressTimer) { d->m_progressTimer->start(500); // remove from screen in 500 msec d->m_progressBar->setValue(d->m_progressBar->maximum()); } } else if (total != 0) { // init d->m_progressTimer->stop(); d->m_progressBar->setMaximum(total); d->m_progressBar->setValue(0); d->m_progressBar->show(); } else { // update QTime currentTime = QTime::currentTime(); // only process painting if last update is at least 250 ms ago if (abs(d->m_lastUpdate.msecsTo(currentTime)) > 250) { d->m_progressBar->setValue(current); d->m_lastUpdate = currentTime; } } } void KMyMoneyApp::slotStatusProgressDone() { d->m_progressTimer->stop(); d->m_progressBar->reset(); d->m_progressBar->hide(); d->m_progressBar->setValue(0); } void KMyMoneyApp::progressCallback(int current, int total, const QString& msg) { if (!msg.isEmpty()) kmymoney->slotStatusMsg(msg); kmymoney->slotStatusProgressBar(current, total); } void KMyMoneyApp::slotFileViewPersonal() { - if (!d->m_fileOpen) { + if (!d->m_storageInfo.isOpened) { KMessageBox::information(this, i18n("No KMyMoneyFile open")); return; } KMSTATUS(i18n("Viewing personal data...")); MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyPayee user = file->user(); QPointer editPersonalDataDlg = new EditPersonalDataDlg(user.name(), user.address(), user.city(), user.state(), user.postcode(), user.telephone(), user.email(), this, i18n("Edit Personal Data")); if (editPersonalDataDlg->exec() == QDialog::Accepted && editPersonalDataDlg != 0) { user.setName(editPersonalDataDlg->userName()); user.setAddress(editPersonalDataDlg->userStreet()); user.setCity(editPersonalDataDlg->userTown()); user.setState(editPersonalDataDlg->userCountry()); user.setPostcode(editPersonalDataDlg->userPostcode()); user.setTelephone(editPersonalDataDlg->userTelephone()); user.setEmail(editPersonalDataDlg->userEmail()); MyMoneyFileTransaction ft; try { file->setUser(user); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to store user information: %1", QString::fromLatin1(e.what()))); } } delete editPersonalDataDlg; } void KMyMoneyApp::slotLoadAccountTemplates() { KMSTATUS(i18n("Importing account templates.")); int rc; QPointer dlg = new KLoadTemplateDlg(); if ((rc = dlg->exec()) == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { // import the account templates QList templates = dlg->templates(); QList::iterator it_t; for (it_t = templates.begin(); it_t != templates.end(); ++it_t) { (*it_t).importTemplate(progressCallback); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to import template(s)"), QString::fromLatin1(e.what())); } } delete dlg; } void KMyMoneyApp::slotSaveAccountTemplates() { KMSTATUS(i18n("Exporting account templates.")); QString savePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/templates/" + QLocale().name(); QDir templatesDir(savePath); if (!templatesDir.exists()) templatesDir.mkpath(savePath); QString newName = QFileDialog::getSaveFileName(this, i18n("Save as..."), savePath, i18n("KMyMoney template files (*.kmt);;All files (*)")); // // If there is no file extension, then append a .kmt at the end of the file name. // If there is a file extension, make sure it is .kmt, delete any others. // if (!newName.isEmpty()) { // find last . delimiter int nLoc = newName.lastIndexOf('.'); if (nLoc != -1) { QString strExt, strTemp; strTemp = newName.left(nLoc + 1); strExt = newName.right(newName.length() - (nLoc + 1)); if ((strExt.indexOf("kmt", 0, Qt::CaseInsensitive) == -1)) { strTemp.append("kmt"); //append to make complete file name newName = strTemp; } } else { newName.append(".kmt"); } if (okToWriteFile(QUrl::fromLocalFile(newName))) { QPointer dlg = new KTemplateExportDlg(this); if (dlg->exec() == QDialog::Accepted && dlg) { MyMoneyTemplate templ; templ.setTitle(dlg->title()); templ.setShortDescription(dlg->shortDescription()); templ.setLongDescription(dlg->longDescription()); templ.exportTemplate(progressCallback); templ.saveTemplate(QUrl::fromLocalFile(newName)); } delete dlg; } } } bool KMyMoneyApp::okToWriteFile(const QUrl &url) { Q_UNUSED(url) // check if the file exists and warn the user bool reallySaveFile = true; if (KMyMoneyUtils::fileExists(url)) { if (KMessageBox::warningYesNo(this, QLatin1String("") + i18n("The file %1 already exists. Do you really want to overwrite it?", url.toDisplayString(QUrl::PreferLocalFile)) + QLatin1String(""), i18n("File already exists")) != KMessageBox::Yes) reallySaveFile = false; } return reallySaveFile; } void KMyMoneyApp::slotSettings() { // if we already have an instance of the settings dialog, then use it if (KConfigDialog::showDialog("KMyMoney-Settings")) return; // otherwise, we have to create it auto dlg = new KSettingsKMyMoney(this, "KMyMoney-Settings", KMyMoneySettings::self()); connect(dlg, &KSettingsKMyMoney::settingsChanged, this, &KMyMoneyApp::slotUpdateConfiguration); dlg->show(); } void KMyMoneyApp::slotShowCredits() { KAboutData aboutData = initializeCreditsData(); KAboutApplicationDialog dlg(aboutData, this); dlg.exec(); } void KMyMoneyApp::slotUpdateConfiguration(const QString &dialogName) { if(dialogName.compare(QLatin1String("Plugins")) == 0) { KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Reorganize, pPlugins, this, guiFactory()); + actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(d->canFileSaveAs()); onlineJobAdministration::instance()->updateActions(); onlineJobAdministration::instance()->setOnlinePlugins(pPlugins.extended); d->m_myMoneyView->setOnlinePlugins(pPlugins.online); - d->m_myMoneyView->setStoragePlugins(pPlugins.storage); + d->updateActions(); return; } MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay()); #ifdef ENABLE_UNFINISHEDFEATURES LedgerSeparator::setFirstFiscalDate(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay()); #endif d->m_myMoneyView->updateViewType(); // update the sql storage module settings // MyMoneyStorageSql::setStartDate(KMyMoneySettings::startDate().date()); // update the report module settings MyMoneyReport::setLineWidth(KMyMoneySettings::lineWidth()); // update the holiday region configuration setHolidayRegion(KMyMoneySettings::holidayRegion()); d->m_myMoneyView->slotRefreshViews(); // re-read autosave configuration d->m_autoSaveEnabled = KMyMoneySettings::autoSaveFile(); d->m_autoSavePeriod = KMyMoneySettings::autoSavePeriod(); // stop timer if turned off but running if (d->m_autoSaveTimer->isActive() && !d->m_autoSaveEnabled) { d->m_autoSaveTimer->stop(); } // start timer if turned on and needed but not running if (!d->m_autoSaveTimer->isActive() && d->m_autoSaveEnabled && d->dirty()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } d->setThemedCSS(); // check if the recovery key is still valid or expires soon if (KMyMoneySettings::writeDataEncrypted() && KMyMoneySettings::encryptRecover()) { if (KGPGFile::GPGAvailable()) { KGPGFile file; QDateTime expirationDate = file.keyExpires(QLatin1String(recoveryKeyId)); if (expirationDate.isValid() && QDateTime::currentDateTime().daysTo(expirationDate) <= RECOVER_KEY_EXPIRATION_WARNING) { bool skipMessage = false; //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup grp; QDate lastWarned; if (kconfig) { grp = d->m_config->group("General Options"); lastWarned = grp.readEntry("LastRecoverKeyExpirationWarning", QDate()); if (QDate::currentDate() == lastWarned) { skipMessage = true; } } if (!skipMessage) { if (kconfig) { grp.writeEntry("LastRecoverKeyExpirationWarning", QDate::currentDate()); } KMessageBox::information(this, i18np("You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 day. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", "You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 days. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", QDateTime::currentDateTime().daysTo(expirationDate)), i18n("Recover key expires soon")); } } } } } void KMyMoneyApp::slotBackupFile() { // Save the file first so isLocalFile() works if (d->m_myMoneyView && d->dirty()) { if (KMessageBox::questionYesNo(this, i18n("The file must be saved first " "before it can be backed up. Do you want to continue?")) == KMessageBox::No) { return; } slotFileSave(); } - if (d->m_fileName.isEmpty()) + if (d->m_storageInfo.url.isEmpty()) return; - if (!d->m_fileName.isLocalFile()) { + if (!d->m_storageInfo.url.isLocalFile()) { KMessageBox::sorry(this, - i18n("The current implementation of the backup functionality only supports local files as source files. Your current source file is '%1'.", d->m_fileName.url()), + 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()), i18n("Local files only")); return; } QPointer backupDlg = new KBackupDlg(this); int returncode = backupDlg->exec(); if (returncode == QDialog::Accepted && backupDlg != 0) { d->m_backupMount = backupDlg->mountCheckBoxChecked(); d->m_proc.clearProgram(); d->m_backupState = BACKUP_MOUNTING; d->m_mountpoint = backupDlg->mountPoint(); if (d->m_backupMount) { slotBackupMount(); } else { progressCallback(0, 300, ""); #ifdef Q_OS_WIN d->m_ignoreBackupExitCode = true; QTimer::singleShot(0, this, SLOT(slotBackupHandleEvents())); #else // If we don't have to mount a device, we just issue // a dummy command to start the copy operation d->m_proc.setProgram("true"); d->m_proc.start(); #endif } } delete backupDlg; } void KMyMoneyApp::slotBackupMount() { progressCallback(0, 300, i18n("Mounting %1", d->m_mountpoint)); d->m_proc.setProgram("mount"); d->m_proc << d->m_mountpoint; d->m_proc.start(); } bool KMyMoneyApp::slotBackupWriteFile() { - QFileInfo fi(d->m_fileName.fileName()); + QFileInfo fi(d->m_storageInfo.url.fileName()); QString today = QDate::currentDate().toString("-yyyy-MM-dd.") + fi.suffix(); - QString backupfile = d->m_mountpoint + '/' + d->m_fileName.fileName(); + QString backupfile = d->m_mountpoint + '/' + d->m_storageInfo.url.fileName(); KMyMoneyUtils::appendCorrectFileExt(backupfile, today); // check if file already exists and ask what to do QFile f(backupfile); if (f.exists()) { int answer = KMessageBox::warningContinueCancel(this, i18n("Backup file for today exists on that device. Replace?"), i18n("Backup"), KGuiItem(i18n("&Replace"))); if (answer == KMessageBox::Cancel) { return false; } } progressCallback(50, 0, i18n("Writing %1", backupfile)); d->m_proc.clearProgram(); #ifdef Q_OS_WIN d->m_proc << "cmd.exe" << "/c" << "copy" << "/b" << "/y"; - d->m_proc << (QDir::toNativeSeparators(d->m_fileName.toLocalFile()) + "+ nul") << QDir::toNativeSeparators(backupfile); + d->m_proc << (QDir::toNativeSeparators(d->m_storageInfo.url.toLocalFile()) + "+ nul") << QDir::toNativeSeparators(backupfile); #else d->m_proc << "cp" << "-f"; - d->m_proc << d->m_fileName.toLocalFile() << backupfile; + d->m_proc << d->m_storageInfo.url.toLocalFile() << backupfile; #endif d->m_backupState = BACKUP_COPYING; d->m_proc.start(); return true; } void KMyMoneyApp::slotBackupUnmount() { progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint)); d->m_proc.clearProgram(); d->m_proc.setProgram("umount"); d->m_proc << d->m_mountpoint; d->m_backupState = BACKUP_UNMOUNTING; d->m_proc.start(); } void KMyMoneyApp::slotBackupFinish() { d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); } void KMyMoneyApp::slotBackupHandleEvents() { switch (d->m_backupState) { case BACKUP_MOUNTING: if (d->m_ignoreBackupExitCode || (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0)) { d->m_ignoreBackupExitCode = false; d->m_backupResult = 0; if (!slotBackupWriteFile()) { d->m_backupResult = 1; if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } } else { KMessageBox::information(this, i18n("Error mounting device"), i18n("Backup")); d->m_backupResult = 1; if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } break; case BACKUP_COPYING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { if (d->m_backupMount) { slotBackupUnmount(); } else { progressCallback(300, 0, i18nc("Backup done", "Done")); KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); slotBackupFinish(); } } else { qDebug("copy exit code is %d", d->m_proc.exitCode()); d->m_backupResult = 1; KMessageBox::information(this, i18n("Error copying file to device"), i18n("Backup")); if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } break; case BACKUP_UNMOUNTING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { progressCallback(300, 0, i18nc("Backup done", "Done")); if (d->m_backupResult == 0) KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); } else { KMessageBox::information(this, i18n("Error unmounting device"), i18n("Backup")); } slotBackupFinish(); break; default: qWarning("Unknown state for backup operation!"); progressCallback(-1, -1, QString()); ready(); break; } } void KMyMoneyApp::slotShowTipOfTheDay() { KTipDialog::showTip(d->m_myMoneyView, "", true); } void KMyMoneyApp::slotShowPreviousView() { } void KMyMoneyApp::slotShowNextView() { } void KMyMoneyApp::slotViewSelected(View view) { KMyMoneySettings::setLastViewSelected(static_cast(view)); } void KMyMoneyApp::slotGenerateSql() { // QPointer editor = new KGenerateSqlDlg(this); // editor->setObjectName("Generate Database SQL"); // editor->exec(); // delete editor; } void KMyMoneyApp::slotToolsStartKCalc() { QString cmd = KMyMoneySettings::externalCalculator(); // if none is present, we fall back to the default if (cmd.isEmpty()) { #if defined(Q_OS_WIN32) cmd = QLatin1String("calc"); #elif defined(Q_OS_MAC) cmd = QLatin1String("open -a Calculator"); #else cmd = QLatin1String("kcalc"); #endif } KRun::runCommand(cmd, this); } void KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) { MyMoneyFile *file = MyMoneyFile::instance(); try { const MyMoneySecurity& sec = file->security(newAccount.currencyId()); // Check the opening balance if (openingBal.isPositive() && newAccount.accountGroup() == eMyMoney::Account::Type::Liability) { QString message = i18n("This account is a liability and if the " "opening balance represents money owed, then it should be negative. " "Negate the amount?\n\n" "Please click Yes to change the opening balance to %1,\n" "Please click No to leave the amount as %2,\n" "Please click Cancel to abort the account creation." , MyMoneyUtils::formatMoney(-openingBal, newAccount, sec) , MyMoneyUtils::formatMoney(openingBal, newAccount, sec)); int ans = KMessageBox::questionYesNoCancel(this, message); if (ans == KMessageBox::Yes) { openingBal = -openingBal; } else if (ans == KMessageBox::Cancel) return; } file->createAccount(newAccount, parentAccount, brokerageAccount, openingBal); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add account: %1", QString::fromLatin1(e.what()))); } } void KMyMoneyApp::slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { KNewInvestmentWizard::newInvestment(account, parent); } void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { KNewAccountDlg::newCategory(account, parent); } void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account) { KNewAccountDlg::newCategory(account, MyMoneyAccount()); } void KMyMoneyApp::slotAccountNew(MyMoneyAccount& account) { NewAccountWizard::Wizard::newAccount(account); } void KMyMoneyApp::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) { MyMoneyFile* file = MyMoneyFile::instance(); // Add the schedule only if one exists // // Remember to modify the first split to reference the newly created account if (!newSchedule.name().isEmpty()) { try { // We assume at least 2 splits in the transaction MyMoneyTransaction t = newSchedule.transaction(); if (t.splitCount() < 2) { throw MYMONEYEXCEPTION_CSTRING("Transaction for schedule has less than 2 splits!"); } MyMoneyFileTransaction ft; try { file->addSchedule(newSchedule); // in case of a loan account, we keep a reference to this // schedule in the account if (newAccount.accountType() == eMyMoney::Account::Type::Loan || newAccount.accountType() == eMyMoney::Account::Type::AssetLoan) { newAccount.setValue("schedule", newSchedule.id()); file->modifyAccount(newAccount); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", QString::fromLatin1(e.what()))); } } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", QString::fromLatin1(e.what()))); } } } QList > KMyMoneyApp::Private::automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount) { static const int NR_OF_STEPS_LIMIT = 300000; static const int PROGRESSBAR_STEPS = 1000; QList > result = transactions; KMSTATUS(i18n("Running automatic reconciliation")); int progressBarIndex = 0; q->slotStatusProgressBar(progressBarIndex, NR_OF_STEPS_LIMIT / PROGRESSBAR_STEPS); // optimize the most common case - all transactions should be cleared QListIterator > itTransactionSplitResult(result); MyMoneyMoney transactionsBalance; while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); transactionsBalance += transactionSplit.second.shares(); } if (amount == transactionsBalance) { result = transactions; return result; } q->slotStatusProgressBar(progressBarIndex++, 0); // only one transaction is uncleared itTransactionSplitResult.toFront(); int index = 0; while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); if (transactionsBalance - transactionSplit.second.shares() == amount) { result.removeAt(index); return result; } index++; } q->slotStatusProgressBar(progressBarIndex++, 0); // more than one transaction is uncleared - apply the algorithm result.clear(); const MyMoneySecurity &security = MyMoneyFile::instance()->security(account.currencyId()); double precision = 0.1 / account.fraction(security); QList sumList; sumList << MyMoneyMoney(); QMap > > sumToComponentsMap; // compute the possible matches QListIterator > itTransactionSplit(transactions); while (itTransactionSplit.hasNext()) { const QPair &transactionSplit = itTransactionSplit.next(); QListIterator itSum(sumList); QList tempList; while (itSum.hasNext()) { const MyMoneyMoney &sum = itSum.next(); QList > splitIds; splitIds << qMakePair(transactionSplit.first.id(), transactionSplit.second.id()); if (sumToComponentsMap.contains(sum)) { if (sumToComponentsMap.value(sum).contains(qMakePair(transactionSplit.first.id(), transactionSplit.second.id()))) { continue; } splitIds.append(sumToComponentsMap.value(sum)); } tempList << transactionSplit.second.shares() + sum; sumToComponentsMap[transactionSplit.second.shares() + sum] = splitIds; int size = sumToComponentsMap.size(); if (size % PROGRESSBAR_STEPS == 0) { q->slotStatusProgressBar(progressBarIndex++, 0); } if (size > NR_OF_STEPS_LIMIT) { return result; // it's taking too much resources abort the algorithm } } QList unionList; unionList.append(tempList); unionList.append(sumList); qSort(unionList); sumList.clear(); MyMoneyMoney smallestSumFromUnion = unionList.first(); sumList.append(smallestSumFromUnion); QListIterator itUnion(unionList); while (itUnion.hasNext()) { MyMoneyMoney sumFromUnion = itUnion.next(); if (smallestSumFromUnion < MyMoneyMoney(1 - precision / transactions.size())*sumFromUnion) { smallestSumFromUnion = sumFromUnion; sumList.append(sumFromUnion); } } } q->slotStatusProgressBar(NR_OF_STEPS_LIMIT / PROGRESSBAR_STEPS, 0); if (sumToComponentsMap.contains(amount)) { QListIterator > itTransactionSplit2(transactions); while (itTransactionSplit2.hasNext()) { const QPair &transactionSplit = itTransactionSplit2.next(); const QList > &splitIds = sumToComponentsMap.value(amount); if (splitIds.contains(qMakePair(transactionSplit.first.id(), transactionSplit.second.id()))) { result.append(transactionSplit); } } } #ifdef KMM_DEBUG qDebug("For the amount %s a number of %d possible sums where computed from the set of %d transactions: ", qPrintable(MyMoneyUtils::formatMoney(amount, security)), sumToComponentsMap.size(), transactions.size()); #endif q->slotStatusProgressBar(-1, -1); return result; } void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyInstitution& _dst) { MyMoneyAccount src(_src); src.setInstitutionId(_dst.id()); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(src); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("

%1 cannot be moved to institution %2. Reason: %3

", src.name(), _dst.name(), QString::fromLatin1(e.what()))); } } void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyAccount& _dst) { MyMoneyAccount src(_src); MyMoneyAccount dst(_dst); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->reparentAccount(src, dst); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("

%1 cannot be moved to %2. Reason: %3

", src.name(), dst.name(), QString::fromLatin1(e.what()))); } } void KMyMoneyApp::slotScheduleNew(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence) { KEditScheduleDlg::newSchedule(_t, occurrence); } void KMyMoneyApp::slotPayeeNew(const QString& newnameBase, QString& id) { KMyMoneyUtils::newPayee(newnameBase, id); } void KMyMoneyApp::slotNewFeature() { } // move a stock transaction from one investment account to another void KMyMoneyApp::Private::moveInvestmentTransaction(const QString& /*fromId*/, const QString& toId, const MyMoneyTransaction& tx) { MyMoneyAccount toInvAcc = MyMoneyFile::instance()->account(toId); MyMoneyTransaction t(tx); // first determine which stock we are dealing with. // fortunately, investment transactions have only one stock involved QString stockAccountId; QString stockSecurityId; MyMoneySplit s; foreach (const auto split, t.splits()) { stockAccountId = split.accountId(); stockSecurityId = MyMoneyFile::instance()->account(stockAccountId).currencyId(); if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) { s = split; break; } } // Now check the target investment account to see if it // contains a stock with this id QString newStockAccountId; foreach (const auto sAccount, toInvAcc.accountList()) { if (MyMoneyFile::instance()->account(sAccount).currencyId() == stockSecurityId) { newStockAccountId = sAccount; break; } } // if it doesn't exist, we need to add it as a copy of the old one // no 'copyAccount()' function?? if (newStockAccountId.isEmpty()) { MyMoneyAccount stockAccount = MyMoneyFile::instance()->account(stockAccountId); MyMoneyAccount newStock; newStock.setName(stockAccount.name()); newStock.setNumber(stockAccount.number()); newStock.setDescription(stockAccount.description()); newStock.setInstitutionId(stockAccount.institutionId()); newStock.setOpeningDate(stockAccount.openingDate()); newStock.setAccountType(stockAccount.accountType()); newStock.setCurrencyId(stockAccount.currencyId()); newStock.setClosed(stockAccount.isClosed()); MyMoneyFile::instance()->addAccount(newStock, toInvAcc); newStockAccountId = newStock.id(); } // now update the split and the transaction s.setAccountId(newStockAccountId); t.modifySplit(s); MyMoneyFile::instance()->modifyTransaction(t); } void KMyMoneyApp::showContextMenu(const QString& containerName) { QWidget* w = factory()->container(containerName, this); if (auto menu = dynamic_cast(w)) menu->exec(QCursor::pos()); else qDebug("menu '%s' not found: w = %p, menu = %p", qPrintable(containerName), w, menu); } void KMyMoneyApp::slotPrintView() { d->m_myMoneyView->slotPrintView(); } -void KMyMoneyApp::updateCaption(bool skipActions) +void KMyMoneyApp::Private::updateCaption() { - QString caption; - - caption = d->m_fileName.fileName(); - - if (caption.isEmpty() && d->m_myMoneyView && d->m_fileOpen) - caption = i18n("Untitled"); - - // MyMoneyFile::instance()->dirty() throws an exception, if - // there's no storage object available. In this case, we - // assume that the storage object is not changed. Actually, - // this can only happen if we are newly created early on. - bool modified; - try { - modified = MyMoneyFile::instance()->dirty(); - } catch (const MyMoneyException &) { - modified = false; - skipActions = true; - } + auto caption = m_storageInfo.url.isEmpty() && m_myMoneyView && m_storageInfo.isOpened ? + i18n("Untitled") : + m_storageInfo.url.fileName(); #ifdef KMM_DEBUG - caption += QString(" (%1 x %2)").arg(width()).arg(height()); + caption += QString(" (%1 x %2)").arg(q->width()).arg(q->height()); #endif - setCaption(caption, modified); - - if (!skipActions) { - d->m_myMoneyView->enableViewsIfFileOpen(d->m_fileOpen); - slotUpdateActions(); - } + q->setCaption(caption, MyMoneyFile::instance()->dirty()); } -void KMyMoneyApp::slotUpdateActions() +void KMyMoneyApp::Private::updateActions() { - const auto file = MyMoneyFile::instance(); - const bool fileOpen = d->m_fileOpen; - const bool modified = file->dirty(); -// const bool importRunning = (d->m_smtReader != 0); - auto aC = actionCollection(); - - // ************* - // Disabling actions based on conditions - // ************* + const QVector actions { - QString tooltip = i18n("Create a new transaction"); - const QVector> actionStates { -// {qMakePair(Action::FileOpenDatabase, true)}, -// {qMakePair(Action::FileSaveAsDatabase, fileOpen)}, - {qMakePair(Action::FilePersonalData, fileOpen)}, - {qMakePair(Action::FileBackup, (fileOpen && !isDatabase()))}, - {qMakePair(Action::FileInformation, fileOpen)}, - {qMakePair(Action::FileImportTemplate, fileOpen/* && !importRunning*/)}, - {qMakePair(Action::FileExportTemplate, fileOpen/* && !importRunning*/)}, + Action::FilePersonalData, Action::FileInformation, Action::FileImportTemplate, Action::FileExportTemplate, #ifdef KMM_DEBUG - {qMakePair(Action::FileDump, fileOpen)}, + Action::FileDump, #endif - {qMakePair(Action::EditFindTransaction, fileOpen)}, - {qMakePair(Action::ToolCurrencies, fileOpen)}, - {qMakePair(Action::ToolPrices, fileOpen)}, - {qMakePair(Action::ToolUpdatePrices, fileOpen)}, - {qMakePair(Action::ToolConsistency, fileOpen)}, - {qMakePair(Action::NewAccount, fileOpen)}, - {qMakePair(Action::NewCategory, fileOpen)}, - {qMakePair(Action::AccountCreditTransfer, onlineJobAdministration::instance()->canSendCreditTransfer())}, - {qMakePair(Action::NewInstitution, fileOpen)}, -// {qMakePair(Action::TransactionNew, (fileOpen && d->m_myMoneyView->canCreateTransactions(KMyMoneyRegister::SelectedTransactions(), tooltip)))}, - {qMakePair(Action::NewSchedule, fileOpen)}, -// {qMakePair(Action::CurrencyNew, fileOpen)}, -// {qMakePair(Action::PriceNew, fileOpen)}, - }; + Action::EditFindTransaction, Action::NewCategory, Action::ToolCurrencies, Action::ToolPrices, Action::ToolUpdatePrices, + Action::ToolConsistency, Action::ToolPerformance, Action::NewAccount, Action::NewInstitution, Action::NewSchedule + }; - for (const auto& a : actionStates) - pActions[a.first]->setEnabled(a.second); - } + for (const auto &action : actions) + pActions[action]->setEnabled(m_storageInfo.isOpened); + pActions[Action::FileBackup]->setEnabled(m_storageInfo.isOpened && m_storageInfo.type == eKMyMoney::StorageType::XML); - // ************* - // Disabling standard actions based on conditions - // ************* - aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(modified /*&& !d->m_myMoneyView->isDatabase()*/); -// aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(fileOpen); - aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Close)))->setEnabled(fileOpen); - aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Print)))->setEnabled(fileOpen && d->m_myMoneyView->canPrint()); + auto aC = q->actionCollection(); + aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(canFileSaveAs()); + aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Close)))->setEnabled(m_storageInfo.isOpened); + pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(canUpdateAllAccounts()); } -void KMyMoneyApp::slotResetSelections() +bool KMyMoneyApp::Private::canFileSaveAs() const { - d->m_myMoneyView->slotObjectSelected(MyMoneyAccount()); - d->m_myMoneyView->slotObjectSelected(MyMoneyInstitution()); - d->m_myMoneyView->slotObjectSelected(MyMoneySchedule()); - d->m_myMoneyView->slotObjectSelected(MyMoneyTag()); - d->m_myMoneyView->slotSelectByVariant(QVariantList {QVariant::fromValue(KMyMoneyRegister::SelectedTransactions())}, eView::Intent::SelectRegisterTransactions); - slotUpdateActions(); + return (m_storageInfo.isOpened && + (!pPlugins.storage.isEmpty() && + !(pPlugins.storage.count() == 1 && pPlugins.storage.first()->storageType() == eKMyMoney::StorageType::GNC))); } -void KMyMoneyApp::slotDataChanged() +bool KMyMoneyApp::Private::canUpdateAllAccounts() const { - // As this method is called every time the MyMoneyFile instance - // notifies a modification, it's the perfect place to start the timer if needed - if (d->m_autoSaveEnabled && !d->m_autoSaveTimer->isActive()) { - d->m_autoSaveTimer->setSingleShot(true); - d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); //miliseconds + const auto file = MyMoneyFile::instance(); + auto rc = false; + if (!file->storageAttached()) + return rc; + + QList accList; + file->accountList(accList); + QList::const_iterator it_a; + auto it_p = pPlugins.online.constEnd(); + for (it_a = accList.constBegin(); (it_p == pPlugins.online.constEnd()) && (it_a != accList.constEnd()); ++it_a) { + if ((*it_a).hasOnlineMapping()) { + // check if provider is available + it_p = pPlugins.online.constFind((*it_a).onlineBankingSettings().value("provider").toLower()); + if (it_p != pPlugins.online.constEnd()) { + QStringList protocols; + (*it_p)->protocols(protocols); + if (!protocols.isEmpty()) { + rc = true; + break; + } + } + } } - updateCaption(); + return rc; +} + +void KMyMoneyApp::slotDataChanged() +{ + d->fileAction(eKMyMoney::FileAction::Changed); } void KMyMoneyApp::slotCurrencyDialog() { QPointer dlg = new KCurrencyEditDlg(this); dlg->exec(); delete dlg; } void KMyMoneyApp::slotPriceDialog() { QPointer dlg = new KMyMoneyPriceDlg(this); dlg->exec(); delete dlg; } void KMyMoneyApp::slotFileConsistencyCheck() { d->consistencyCheck(true); - updateCaption(); } void KMyMoneyApp::Private::consistencyCheck(bool alwaysDisplayResult) { KMSTATUS(i18n("Running consistency check...")); MyMoneyFileTransaction ft; try { m_consistencyCheckResult = MyMoneyFile::instance()->consistencyCheck(); ft.commit(); } catch (const MyMoneyException &e) { m_consistencyCheckResult.append(i18n("Consistency check failed: %1", e.what())); // always display the result if the check failed alwaysDisplayResult = true; } // in case the consistency check was OK, we get a single line as result // in all errneous cases, we get more than one line and force the // display of them. if (alwaysDisplayResult || m_consistencyCheckResult.size() > 1) { QString msg = i18n("The consistency check has found no issues in your data. Details are presented below."); if (m_consistencyCheckResult.size() > 1) 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."); // install a context menu for the list after the dialog is displayed QTimer::singleShot(500, q, SLOT(slotInstallConsistencyCheckContextMenu())); KMessageBox::informationList(0, msg, m_consistencyCheckResult, i18n("Consistency check result")); } // this data is no longer needed m_consistencyCheckResult.clear(); } void KMyMoneyApp::Private::copyConsistencyCheckResults() { QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(m_consistencyCheckResult.join(QLatin1String("\n"))); } void KMyMoneyApp::Private::saveConsistencyCheckResults() { QUrl fileUrl = QFileDialog::getSaveFileUrl(q); if (!fileUrl.isEmpty()) { QFile file(fileUrl.toLocalFile()); if (file.open(QFile::WriteOnly | QFile::Append | QFile::Text)) { QTextStream out(&file); out << m_consistencyCheckResult.join(QLatin1String("\n")); file.close(); } } } void KMyMoneyApp::Private::setThemedCSS() { const QStringList CSSnames {QStringLiteral("kmymoney.css"), QStringLiteral("welcome.css")}; const QString rcDir("/html/"); const QStringList defaultCSSDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, rcDir, QStandardPaths::LocateDirectory); // scan the list of directories to find the ones that really // contains all files we look for QString defaultCSSDir; foreach (const auto dir, defaultCSSDirs) { defaultCSSDir = dir; foreach (const auto CSSname, CSSnames) { QFileInfo fileInfo(defaultCSSDir + CSSname); if (!fileInfo.exists()) { defaultCSSDir.clear(); break; } } if (!defaultCSSDir.isEmpty()) { break; } } // make sure we have the local directory where the themed version is stored const QString themedCSSDir = QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation).first() + rcDir; QDir().mkpath(themedCSSDir); foreach (const auto CSSname, CSSnames) { const QString defaultCSSFilename = defaultCSSDir + CSSname; QFileInfo fileInfo(defaultCSSFilename); if (fileInfo.exists()) { const QString themedCSSFilename = themedCSSDir + CSSname; QFile::remove(themedCSSFilename); if (QFile::copy(defaultCSSFilename, themedCSSFilename)) { QFile cssFile (themedCSSFilename); if (cssFile.open(QIODevice::ReadWrite)) { QTextStream cssStream(&cssFile); auto cssText = cssStream.readAll(); cssText.replace(QLatin1String("./"), defaultCSSDir, Qt::CaseSensitive); cssText.replace(QLatin1String("WindowText"), KMyMoneySettings::schemeColor(SchemeColor::WindowText).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("Window"), KMyMoneySettings::schemeColor(SchemeColor::WindowBackground).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("HighlightText"), KMyMoneySettings::schemeColor(SchemeColor::ListHighlightText).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("Highlight"), KMyMoneySettings::schemeColor(SchemeColor::ListHighlight).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("black"), KMyMoneySettings::schemeColor(SchemeColor::ListGrid).name(), Qt::CaseSensitive); cssStream.seek(0); cssStream << cssText; cssFile.close(); } } } } } void KMyMoneyApp::slotCheckSchedules() { if (KMyMoneySettings::checkSchedule() == true) { KMSTATUS(i18n("Checking for overdue scheduled transactions...")); MyMoneyFile *file = MyMoneyFile::instance(); QDate checkDate = QDate::currentDate().addDays(KMyMoneySettings::checkSchedulePreview()); QList scheduleList = file->scheduleList(); QList::Iterator it; eDialogs::ScheduleResultCode rc = eDialogs::ScheduleResultCode::Enter; for (it = scheduleList.begin(); (it != scheduleList.end()) && (rc != eDialogs::ScheduleResultCode::Cancel); ++it) { // Get the copy in the file because it might be modified by commitTransaction MyMoneySchedule schedule = file->schedule((*it).id()); if (schedule.autoEnter()) { try { while (!schedule.isFinished() && (schedule.adjustedNextDueDate() <= checkDate) && rc != eDialogs::ScheduleResultCode::Ignore && rc != eDialogs::ScheduleResultCode::Cancel) { rc = d->m_myMoneyView->enterSchedule(schedule, true, true); schedule = file->schedule((*it).id()); // get a copy of the modified schedule } } catch (const MyMoneyException &) { } } if (rc == eDialogs::ScheduleResultCode::Ignore) { // if the current schedule was ignored then we must make sure that the user can still enter the next scheduled transaction rc = eDialogs::ScheduleResultCode::Enter; } } - updateCaption(); } } void KMyMoneyApp::writeLastUsedDir(const QString& directory) { //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = kconfig->group("General Options"); //write path entry, no error handling since its void. grp.writeEntry("LastUsedDirectory", directory); } } void KMyMoneyApp::writeLastUsedFile(const QString& fileName) { //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); // write path entry, no error handling since its void. // use a standard string, as fileName could contain a protocol // e.g. file:/home/thb/.... grp.writeEntry("LastUsedFile", fileName); } } QString KMyMoneyApp::readLastUsedDir() const { QString str; //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); //read path entry. Second parameter is the default if the setting is not found, which will be the default document path. str = grp.readEntry("LastUsedDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); // if the path stored is empty, we use the default nevertheless if (str.isEmpty()) str = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); } return str; } QString KMyMoneyApp::readLastUsedFile() const { QString str; // get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); // read filename entry. str = grp.readEntry("LastUsedFile", ""); } return str; } QString KMyMoneyApp::filename() const { - return d->m_fileName.url(); + return d->m_storageInfo.url.url(); } QUrl KMyMoneyApp::filenameURL() const { - return d->m_fileName; + return d->m_storageInfo.url; } void KMyMoneyApp::writeFilenameURL(const QUrl &url) { - d->m_fileName = url; + d->m_storageInfo.url = url; } void KMyMoneyApp::addToRecentFiles(const QUrl& url) { d->m_recentFiles->addUrl(url); } QTimer* KMyMoneyApp::autosaveTimer() { return d->m_autoSaveTimer; } WebConnect* KMyMoneyApp::webConnect() const { return d->m_webConnect; } QList KMyMoneyApp::instanceList() const { QList list; #ifdef KMM_DBUS QDBusReply reply = QDBusConnection::sessionBus().interface()->registeredServiceNames(); if (reply.isValid()) { QStringList apps = reply.value(); QStringList::ConstIterator it; // build a list of service names of all running kmymoney applications without this one for (it = apps.constBegin(); it != apps.constEnd(); ++it) { // please change this method of creating a list of 'all the other kmymoney instances that are running on the system' // since assuming that D-Bus creates service names with org.kde.kmymoney-PID is an observation I don't think that it's documented somwhere if ((*it).indexOf("org.kde.kmymoney-") == 0) { uint thisProcPid = platformTools::processId(); if ((*it).indexOf(QString("org.kde.kmymoney-%1").arg(thisProcPid)) != 0) list += (*it); } } } else { qDebug("D-Bus returned the following error while obtaining instances: %s", qPrintable(reply.error().message())); } #endif return list; } void KMyMoneyApp::slotEquityPriceUpdate() { QPointer dlg = new KEquityPriceUpdateDlg(this); if (dlg->exec() == QDialog::Accepted && dlg != 0) dlg->storePrices(); delete dlg; } void KMyMoneyApp::webConnect(const QString& sourceUrl, const QByteArray& asn_id) { // // Web connect attempts to go through the known importers and see if the file // can be importing using that method. If so, it will import it using that // plugin // Q_UNUSED(asn_id) d->m_importUrlsQueue.enqueue(sourceUrl); // only start processing if this is the only import so far if (d->m_importUrlsQueue.count() == 1) { while (!d->m_importUrlsQueue.isEmpty()) { // get the value of the next item from the queue // but leave it on the queue for now QString url = d->m_importUrlsQueue.head(); // Bring this window to the forefront. This method was suggested by // Lubos Lunak of the KDE core development team. // TODO: port KF5 (WebConnect) //KStartupInfo::setNewStartupId(this, asn_id); // Make sure we have an open file - if (! d->m_fileOpen && + if (! d->m_storageInfo.isOpened && KMessageBox::warningContinueCancel(this, i18n("You must first select a KMyMoney file before you can import a statement.")) == KMessageBox::Continue) slotFileOpen(); // only continue if the user really did open a file. - if (d->m_fileOpen) { + if (d->m_storageInfo.isOpened) { KMSTATUS(i18n("Importing a statement via Web Connect")); // remove the statement files d->unlinkStatementXML(); QMap::const_iterator it_plugin = pPlugins.importer.constBegin(); while (it_plugin != pPlugins.importer.constEnd()) { if ((*it_plugin)->isMyFormat(url)) { QList statements; if (!(*it_plugin)->import(url)) { KMessageBox::error(this, i18n("Unable to import %1 using %2 plugin. The plugin returned the following error: %3", url, (*it_plugin)->formatName(), (*it_plugin)->lastError()), i18n("Importing error")); } break; } ++it_plugin; } // If we did not find a match, try importing it as a KMM statement file, // which is really just for testing. the statement file is not exposed // to users. if (it_plugin == pPlugins.importer.constEnd()) if (MyMoneyStatement::isStatementFile(url)) MyMoneyStatementReader::importStatement(url, false, progressCallback); } // remove the current processed item from the queue d->m_importUrlsQueue.dequeue(); } } } void KMyMoneyApp::slotEnableMessages() { KMessageBox::enableAllMessages(); KMessageBox::information(this, i18n("All messages have been enabled."), i18n("All messages")); } void KMyMoneyApp::createInterfaces() { // Sets up the plugin interface KMyMoneyPlugin::pluginInterfaces().appInterface = new KMyMoneyPlugin::KMMAppInterface(this, this); KMyMoneyPlugin::pluginInterfaces().importInterface = new KMyMoneyPlugin::KMMImportInterface(this); KMyMoneyPlugin::pluginInterfaces().statementInterface = new KMyMoneyPlugin::KMMStatementInterface(this); KMyMoneyPlugin::pluginInterfaces().viewInterface = new KMyMoneyPlugin::KMMViewInterface(d->m_myMoneyView, this); // setup the calendar interface for schedules MyMoneySchedule::setProcessingCalendar(this); } void KMyMoneyApp::slotAutoSave() { if (!d->m_inAutoSaving) { // store the focus widget so we can restore it after save QPointer focusWidget = qApp->focusWidget(); d->m_inAutoSaving = true; KMSTATUS(i18n("Auto saving...")); //calls slotFileSave if needed, and restart the timer //it the file is not saved, reinitializes the countdown. if (d->dirty() && d->m_autoSaveEnabled) { if (!slotFileSave() && d->m_autoSavePeriod > 0) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } } d->m_inAutoSaving = false; if (focusWidget && focusWidget != qApp->focusWidget()) { // we have a valid focus widget so restore it focusWidget->setFocus(); } } } void KMyMoneyApp::slotDateChanged() { QDateTime dt = QDateTime::currentDateTime(); QDateTime nextDay(QDate(dt.date().addDays(1)), QTime(0, 0, 0)); // +1 is to make sure that we're already in the next day when the // signal is sent (this way we also avoid setting the timer to 0) QTimer::singleShot((static_cast(dt.secsTo(nextDay)) + 1)*1000, this, SLOT(slotDateChanged())); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::setHolidayRegion(const QString& holidayRegion) { #ifdef KF5Holidays_FOUND //since the cost of updating the cache is now not negligible //check whether the region has been modified if (!d->m_holidayRegion || d->m_holidayRegion->regionCode() != holidayRegion) { // Delete the previous holidayRegion before creating a new one. delete d->m_holidayRegion; // Create a new holidayRegion. d->m_holidayRegion = new KHolidays::HolidayRegion(holidayRegion); //clear and update the holiday cache preloadHolidays(); } #else Q_UNUSED(holidayRegion); #endif } bool KMyMoneyApp::isProcessingDate(const QDate& date) const { if (!d->m_processingDays.testBit(date.dayOfWeek())) return false; #ifdef KF5Holidays_FOUND if (!d->m_holidayRegion || !d->m_holidayRegion->isValid()) return true; //check first whether it's already in cache if (d->m_holidayMap.contains(date)) { return d->m_holidayMap.value(date, true); } else { bool processingDay = !d->m_holidayRegion->isHoliday(date); d->m_holidayMap.insert(date, processingDay); return processingDay; } #else return true; #endif } void KMyMoneyApp::preloadHolidays() { #ifdef KF5Holidays_FOUND //clear the cache before loading d->m_holidayMap.clear(); //only do this if it is a valid region if (d->m_holidayRegion && d->m_holidayRegion->isValid()) { //load holidays for the forecast days plus 1 cycle, to be on the safe side auto forecastDays = KMyMoneySettings::forecastDays() + KMyMoneySettings::forecastAccountCycle(); QDate endDate = QDate::currentDate().addDays(forecastDays); //look for holidays for the next 2 years as a minimum. That should give a good margin for the cache if (endDate < QDate::currentDate().addYears(2)) endDate = QDate::currentDate().addYears(2); KHolidays::Holiday::List holidayList = d->m_holidayRegion->holidays(QDate::currentDate(), endDate); KHolidays::Holiday::List::const_iterator holiday_it; for (holiday_it = holidayList.constBegin(); holiday_it != holidayList.constEnd(); ++holiday_it) { for (QDate holidayDate = (*holiday_it).observedStartDate(); holidayDate <= (*holiday_it).observedEndDate(); holidayDate = holidayDate.addDays(1)) d->m_holidayMap.insert(holidayDate, false); } for (QDate date = QDate::currentDate(); date <= endDate; date = date.addDays(1)) { //if it is not a processing day, set it to false if (!d->m_processingDays.testBit(date.dayOfWeek())) { d->m_holidayMap.insert(date, false); } else if (!d->m_holidayMap.contains(date)) { //if it is not a holiday nor a weekend, it is a processing day d->m_holidayMap.insert(date, true); } } } #endif } -KMStatus::KMStatus(const QString &text) +bool KMyMoneyApp::slotFileNew() { - m_prevText = kmymoney->slotStatusMsg(text); + KMSTATUS(i18n("Creating new document...")); + + if (!slotFileClose()) + return false; + + NewUserWizard::Wizard wizard; + if (wizard.exec() != QDialog::Accepted) + return false; + + d->m_storageInfo.isOpened = true; + d->m_storageInfo.type = eKMyMoney::StorageType::None; + d->m_storageInfo.url = QUrl(); + + try { + auto storage = new MyMoneyStorageMgr; + MyMoneyFile::instance()->attachStorage(storage); + + MyMoneyFileTransaction ft; + auto file = MyMoneyFile::instance(); + // store the user info + file->setUser(wizard.user()); + + // create and setup base currency + file->addCurrency(wizard.baseCurrency()); + file->setBaseCurrency(wizard.baseCurrency()); + + // create a possible institution + MyMoneyInstitution inst = wizard.institution(); + if (inst.name().length()) { + file->addInstitution(inst); + } + + // create a possible checking account + auto acc = wizard.account(); + if (acc.name().length()) { + acc.setInstitutionId(inst.id()); + MyMoneyAccount asset = file->asset(); + file->addAccount(acc, asset); + + // create possible opening balance transaction + if (!wizard.openingBalance().isZero()) { + file->createOpeningBalanceTransaction(acc, wizard.openingBalance()); + } + } + + // import the account templates + for (auto &tmpl : wizard.templates()) + tmpl.importTemplate(progressCallback); + + ft.commit(); + KMyMoneySettings::setFirstTimeRun(false); + + d->fileAction(eKMyMoney::FileAction::Opened); + if (actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->isEnabled()) + slotFileSaveAs(); + } catch (const MyMoneyException & e) { + slotFileClose(); + d->removeStorage(); + KMessageBox::detailedError(this, i18n("Couldn't create a new file."), e.what()); + return false; + } + + if (wizard.startSettingsAfterFinished()) + slotSettings(); + return true; } -KMStatus::~KMStatus() +void KMyMoneyApp::slotFileOpen() { - kmymoney->slotStatusMsg(m_prevText); + KMSTATUS(i18n("Open a file.")); + + const QVector desiredFileExtensions {eKMyMoney::StorageType::XML, eKMyMoney::StorageType::GNC}; + QString fileExtensions; + for (const auto &extension : desiredFileExtensions) { + for (const auto &plugin : pPlugins.storage) { + if (plugin->storageType() == extension) { + fileExtensions += plugin->fileExtension() + QLatin1String(";;"); + break; + } + } + } + + if (fileExtensions.isEmpty()) { + KMessageBox::error(this, i18n("Couldn't find any plugin for opening storage.")); + return; + } + + fileExtensions.append(i18n("All files (*)")); + + QPointer dialog = new QFileDialog(this, QString(), readLastUsedDir(), fileExtensions); + dialog->setFileMode(QFileDialog::ExistingFile); + dialog->setAcceptMode(QFileDialog::AcceptOpen); + + if (dialog->exec() == QDialog::Accepted && dialog != nullptr) + slotFileOpenRecent(dialog->selectedUrls().first()); + delete dialog; } -void KMyMoneyApp::Private::unlinkStatementXML() +bool KMyMoneyApp::slotFileOpenRecent(const QUrl &url) { - QDir d(KMyMoneySettings::logPath(), "kmm-statement*"); - for (uint i = 0; i < d.count(); ++i) { - qDebug("Remove %s", qPrintable(d[i])); - d.remove(KMyMoneySettings::logPath() + QString("/%1").arg(d[i])); + KMSTATUS(i18n("Loading file...")); + + if (!url.isValid()) + throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid URL %1").arg(qPrintable(url.url()))); + + if (isFileOpenedInAnotherInstance(url)) { + KMessageBox::sorry(this, i18n("

File %1 is already opened in another instance of KMyMoney

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Duplicate open")); + return false; + } + + if (url.scheme() != QLatin1String("sql") && !KMyMoneyUtils::fileExists(url)) { + KMessageBox::sorry(this, i18n("

%1 is either an invalid filename or the file does not exist. You can open another file or create a new one.

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("File not found")); + return false; + } + + if (d->m_storageInfo.isOpened) + if (!slotFileClose()) + return false; + + // open the database + d->m_storageInfo.type = eKMyMoney::StorageType::None; + for (auto &plugin : pPlugins.storage) { + try { + if (auto pStorage = plugin->open(url)) { + MyMoneyFile::instance()->attachStorage(pStorage); + d->m_storageInfo.type = plugin->storageType(); + if (plugin->storageType() != eKMyMoney::StorageType::GNC) { + d->m_storageInfo.url = url; + writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile)); + /* Don't use url variable after KRecentFilesAction::addUrl + * as it might delete it. + * More in API reference to this method + */ + d->m_recentFiles->addUrl(url); + } + d->m_storageInfo.isOpened = true; + break; + } + } catch (const MyMoneyException &e) { + KMessageBox::sorry(this, i18n("Cannot open file as requested. Error was: %1", QString::fromLatin1(e.what()))); + return false; + } } - m_statementXMLindex = 0; + + if(d->m_storageInfo.type == eKMyMoney::StorageType::None) { + KMessageBox::error(this, i18n("Could not read your data source. Please check the KMyMoney settings that the necessary plugin is enabled.")); + return false; + } + + d->fileAction(eKMyMoney::FileAction::Opened); + return true; } -void KMyMoneyApp::Private::closeFile() +bool KMyMoneyApp::slotFileSave() { - m_myMoneyView->slotObjectSelected(MyMoneyAccount()); - m_myMoneyView->slotObjectSelected(MyMoneyInstitution()); - m_myMoneyView->slotObjectSelected(MyMoneySchedule()); - m_myMoneyView->slotObjectSelected(MyMoneyTag()); - m_myMoneyView->slotSelectByVariant(QVariantList {QVariant::fromValue(KMyMoneyRegister::SelectedTransactions())}, eView::Intent::SelectRegisterTransactions); + KMSTATUS(i18n("Saving file...")); + + for (const auto& plugin : pPlugins.storage) { + if (plugin->storageType() == d->m_storageInfo.type) { + d->consistencyCheck(false); + try { + if (plugin->save(d->m_storageInfo.url)) { + d->fileAction(eKMyMoney::FileAction::Saved); + return true; + } + return false; + } catch (const MyMoneyException &e) { + KMessageBox::detailedError(this, i18n("Failed to save your storage."), e.what()); + return false; + } + } + } + + KMessageBox::error(this, i18n("Couldn't find suitable plugin to save your storage.")); + return false; +} + +bool KMyMoneyApp::slotFileSaveAs() +{ + KMSTATUS(i18n("Saving file as....")); + + QVector availableFileTypes; + for (const auto& plugin : pPlugins.storage) { + switch (plugin->storageType()) { + case eKMyMoney::StorageType::GNC: + break; + default: + availableFileTypes.append(plugin->storageType()); + break; + } + } - m_myMoneyView->finishReconciliation(MyMoneyAccount()); + auto chosenFileType = eKMyMoney::StorageType::None; + switch (availableFileTypes.count()) { + case 0: + KMessageBox::error(this, i18n("Couldn't find any plugin for saving storage.")); + return false; + case 1: + chosenFileType = availableFileTypes.first(); + break; + default: + { + KSaveAsQuestion dlg(availableFileTypes, this); + if (dlg.exec() != QDialog::Accepted) + return false; + chosenFileType = dlg.fileType(); + } + } - m_myMoneyView->slotFileClosed(); + for (const auto &plugin : pPlugins.storage) { + if (chosenFileType == plugin->storageType()) { + try { + d->consistencyCheck(false); + if (plugin->saveAs()) { + d->fileAction(eKMyMoney::FileAction::Saved); + d->m_storageInfo.type = plugin->storageType(); + return true; + } + } catch (const MyMoneyException &e) { + KMessageBox::detailedError(this, i18n("Failed to save your storage."), e.what()); + } + } + } + return false; +} - disconnectStorageFromModels(); +bool KMyMoneyApp::slotFileClose() +{ + if (!d->m_storageInfo.isOpened) + return true; - // notify the models that the file is going to be closed (we should have something like dataChanged that reaches the models first) - Models::instance()->fileClosed(); + if (!d->askAboutSaving()) + return false; - emit q->kmmFilePlugin(KMyMoneyApp::preClose); - if (q->isDatabase()) - MyMoneyFile::instance()->storage()->close(); // to log off a database user - newStorage(); + d->fileAction(eKMyMoney::FileAction::Closing); - emit q->kmmFilePlugin(postClose); - m_fileOpen = false; + d->removeStorage(); - m_fileName = QUrl(); - q->updateCaption(); + d->m_storageInfo = KMyMoneyApp::Private::storageInfo(); - // just create a new balance warning object - delete m_balanceWarning; - m_balanceWarning = new KBalanceWarning(q); + d->fileAction(eKMyMoney::FileAction::Closed); + return true; +} + +void KMyMoneyApp::slotFileQuit() +{ + // don't modify the status message here as this will prevent quit from working!! + // See the beginning of queryClose() and isReady() why. Thomas Baumgart 2005-10-17 + + bool quitApplication = true; - emit q->fileLoaded(m_fileName); + QList memberList = KMainWindow::memberList(); + if (!memberList.isEmpty()) { + + QList::const_iterator w_it = memberList.constBegin(); + for (; w_it != memberList.constEnd(); ++w_it) { + // only close the window if the closeEvent is accepted. If the user presses Cancel on the saveModified() dialog, + // the window and the application stay open. + if (!(*w_it)->close()) { + quitApplication = false; + break; + } + } + } + + // We will only quit if all windows were processed and not cancelled + if (quitApplication) { + QCoreApplication::quit(); + } +} + +void KMyMoneyApp::Private::fileAction(eKMyMoney::FileAction action) +{ + switch(action) { + case eKMyMoney::FileAction::Opened: + q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false); + updateAccountNames(); + updateCurrencyNames(); + selectBaseCurrency(); + + // setup the standard precision + AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); + KMyMoneyEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); + + applyFileFixes(); + Models::instance()->fileOpened(); + connectStorageToModels(); + // inform everyone about new data + MyMoneyFile::instance()->forceDataChanged(); + updateActions(); + m_myMoneyView->slotFileOpened(); + onlineJobAdministration::instance()->updateActions(); + m_myMoneyView->enableViewsIfFileOpen(m_storageInfo.isOpened); + m_myMoneyView->slotRefreshViews(); + onlineJobAdministration::instance()->updateOnlineTaskProperties(); + q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); + +#ifdef KF5Activities_FOUND + m_activityResourceInstance->setUri(m_storageInfo.url); +#endif + break; + + case eKMyMoney::FileAction::Saved: + q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); + q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false); + m_autoSaveTimer->stop(); + break; + + case eKMyMoney::FileAction::Closing: + disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); + m_myMoneyView->slotFileClosed(); + // notify the models that the file is going to be closed (we should have something like dataChanged that reaches the models first) + Models::instance()->fileClosed(); + break; + + case eKMyMoney::FileAction::Closed: + q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); + disconnectStorageFromModels(); + q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(false); + m_myMoneyView->enableViewsIfFileOpen(m_storageInfo.isOpened); + updateActions(); + break; + + case eKMyMoney::FileAction::Changed: + q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KMyMoneyApp::slotDataChanged); + q->actionCollection()->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(true && !m_storageInfo.url.isEmpty()); + // As this method is called every time the MyMoneyFile instance + // notifies a modification, it's the perfect place to start the timer if needed + if (m_autoSaveEnabled && !m_autoSaveTimer->isActive()) { + m_autoSaveTimer->setSingleShot(true); + m_autoSaveTimer->start(m_autoSavePeriod * 60 * 1000); //miliseconds + } + pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(canUpdateAllAccounts()); + break; + + default: + break; + } + + updateCaption(); +} + +KMStatus::KMStatus(const QString &text) +{ + m_prevText = kmymoney->slotStatusMsg(text); +} + +KMStatus::~KMStatus() +{ + kmymoney->slotStatusMsg(m_prevText); +} + +void KMyMoneyApp::Private::unlinkStatementXML() +{ + QDir d(KMyMoneySettings::logPath(), "kmm-statement*"); + for (uint i = 0; i < d.count(); ++i) { + qDebug("Remove %s", qPrintable(d[i])); + d.remove(KMyMoneySettings::logPath() + QString("/%1").arg(d[i])); + } } diff --git a/kmymoney/kmymoney.h b/kmymoney/kmymoney.h index fe8c5c562..a752ca1a2 100644 --- a/kmymoney/kmymoney.h +++ b/kmymoney/kmymoney.h @@ -1,705 +1,650 @@ /*************************************************************************** kmymoney.h ------------------- copyright : (C) 2000-2001 by Michael Edwardes (C) 2017, 2018 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KMYMONEY_H #define KMYMONEY_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include #include "kmymoneyutils.h" #include "mymoneyaccount.h" #include "mymoney/onlinejob.h" #include "onlinejobtyped.h" #include "mymoneykeyvaluecontainer.h" #include "mymoneymoney.h" #include "selectedtransactions.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyenums.h" #include "viewenums.h" class QResizeEvent; class MyMoneyObject; class MyMoneyInstitution; class MyMoneyAccount; class MyMoneySecurity; class MyMoneyPayee; class MyMoneyPrice; class MyMoneyTag; class MyMoneySplit; class MyMoneyTransaction; class WebConnect; class creditTransfer; class IMyMoneyOperationsFormat; template class onlineJobTyped; typedef void (*KMyMoneyAppCallback)(int, int, const QString &); +namespace eKMyMoney { enum class FileAction; } namespace eDialogs { enum class ScheduleResultCode; } namespace eMenu { enum class Action; enum class Menu; } /*! \mainpage KMyMoney Main Page for API documentation. * * \section intro Introduction * * This is the API documentation for KMyMoney. It should be used as a reference * for KMyMoney developers and users who wish to see how KMyMoney works. This * documentation will be kept up-to-date as development progresses and should be * read for new features that have been developed in KMyMoney. */ /** * The base class for KMyMoney application windows. It sets up the main * window and reads the config file as well as providing a menubar, toolbar * and statusbar. * * @see KMyMoneyView * * @author Michael Edwardes 2000-2001 * @author Thomas Baumgart 2006-2008 * * @short Main application class. */ class KMyMoneyApp : public KXmlGuiWindow, public IMyMoneyProcessingCalendar { Q_OBJECT private Q_SLOTS: /** * Add a context menu to the list used by KMessageBox::informationList to display the consistency check results. */ void slotInstallConsistencyCheckContextMenu(); /** * Handle the context menu of the list used by KMessageBox::informationList to display the consistency check results. */ void slotShowContextMenuForConsistencyCheck(const QPoint &); protected Q_SLOTS: /** * This slot is intended to be used as part of auto saving. This is used when the * QTimer emits the timeout signal and simply checks that the file is dirty (has * received modifications to its contents), and call the appropriate method to * save the file. Furthermore, re-starts the timer (possibly not needed). * @author mvillarino 2005 * @see KMyMoneyApp::slotDataChanged() */ void slotAutoSave(); /** * This slot re-enables all message for which the "Don't show again" * option had been selected. */ void slotEnableMessages(); /** * Called to run performance test. */ void slotPerformanceTest(); /** * Called to generate the sql to create kmymoney database tables etc. */ void slotGenerateSql(); #ifdef KMM_DEBUG /** * Called when the user asks for file information. */ void slotFileFileInfo(); /** * Debugging only: turn on/off traces */ void slotToggleTraces(); #endif /** * Debugging only: turn on/off timers */ void slotToggleTimers(); /** * Called when the user asks for the personal information. */ void slotFileViewPersonal(); void slotLoadAccountTemplates(); void slotSaveAccountTemplates(); /** * Open up the application wide settings dialog. * * @see KSettingsDlg */ void slotSettings(); /** * Called to show credits window. */ void slotShowCredits(); /** * Called when the user wishes to backup the current file */ void slotBackupFile(); /** * Perform mount operation before making a backup of the current file */ void slotBackupMount(); /** * Perform the backup write operation */ bool slotBackupWriteFile(); /** * Perform unmount operation after making a backup of the current file */ void slotBackupUnmount(); /** * Finish backup of the current file */ void slotBackupFinish(); /** * Handle events on making a backup of the current file */ void slotBackupHandleEvents(); void slotShowTipOfTheDay(); void slotShowPreviousView(); void slotShowNextView(); void slotViewSelected(View view); /** * Calls the print logic for the current view */ void slotPrintView(); /** * Call this slot, if any configuration parameter has changed */ void slotUpdateConfiguration(const QString &dialogName); /** * This slot is used to start new features during the development cycle */ void slotNewFeature(); /** * This slot triggers an update of all views and restarts * a single shot timer to call itself again at beginning of * the next day. */ void slotDateChanged(); /** * This slot will be called when the engine data changed * and the application object needs to update its state. */ void slotDataChanged(); /** * This slot collects information for a new scheduled transaction * based on transaction @a t and @a occurrence and saves it in the engine. */ void slotScheduleNew(const MyMoneyTransaction& t, eMyMoney::Schedule::Occurrence occurrence = eMyMoney::Schedule::Occurrence::Monthly); void slotStatusProgressDone(); public: - enum fileActions { - preOpen, postOpen, preSave, postSave, preClose, postClose - }; - - // Keep a note of the file type - typedef enum _fileTypeE { - KmmBinary = 0, // native, binary - KmmXML, // native, XML - KmmDb, // SQL database - /* insert new native file types above this line */ - MaxNativeFileType, - /* and non-native types below */ - GncXML // Gnucash XML - } fileTypeE; - /** * This method checks if there is at least one asset or liability account * in the current storage object. If not, it starts the new account wizard. */ void createInitialAccount(); /** * This method returns the last URL used or an empty URL * depending on the option setting if the last file should * be opened during startup or the open file dialog should * be displayed. * * @return URL of last opened file or empty if the program * should start with the open file dialog */ QUrl lastOpenedURL(); /** * construtor of KMyMoneyApp, calls all init functions to create the application. */ explicit KMyMoneyApp(QWidget* parent = 0); /** * Destructor */ ~KMyMoneyApp(); static void progressCallback(int current, int total, const QString&); void writeLastUsedDir(const QString& directory); QString readLastUsedDir() const; void writeLastUsedFile(const QString& fileName); QString readLastUsedFile() const; /** * Returns whether there is an importer available that can handle this file */ bool isImportableFile(const QUrl &url); - /** - * This method is used to update the caption of the application window. - * It sets the caption to "filename [modified] - KMyMoney". - * - * @param skipActions if true, the actions will not be updated. This - * is usually onyl required by some early calls when - * these widgets are not yet created (the default is false). - */ - void updateCaption(bool skipActions = false); - /** * This method returns a list of all 'other' dcop registered kmymoney processes. * It's a subset of the return of DCOPclient()->registeredApplications(). * * @retval QStringList of process ids */ QList instanceList() const; #ifdef KMM_DEBUG /** * Dump a list of the names of all defined KActions to stdout. */ void dumpActions() const; #endif /** * Popup the context menu with the respective @p containerName. * Valid container names are defined in kmymoneyui.rc */ void showContextMenu(const QString& containerName); void createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal); QString filename() const; QUrl filenameURL() const; void writeFilenameURL(const QUrl &url); void addToRecentFiles(const QUrl &url); QTimer* autosaveTimer(); /** * Checks if the file with the @a url already exists. If so, * the user is asked if he/she wants to override the file. * If the user's answer is negative, @p false will be returned. * @p true will be returned in all other cases. */ bool okToWriteFile(const QUrl &url); /** * Return pointer to the WebConnect object */ WebConnect* webConnect() const; /** * Call this to find out if the currently open file is a sql database * * @retval true file is database * @retval false file is serial */ bool isDatabase(); /** * Call this to find out if the currently open file is native KMM * * @retval true file is native * @retval false file is foreign */ bool isNativeFile(); bool fileOpen() const; KMyMoneyAppCallback progressCallback(); void consistencyCheck(bool alwaysDisplayResult); protected: /** save general Options like all bar positions and status as well as the geometry and the recent file list to the configuration * file */ void saveOptions(); /** * Creates the interfaces necessary for the plugins to work. Therefore, * this method must be called prior to loadPlugins(). */ void createInterfaces(); /** * read general options again and initialize all variables like the recent file list */ void readOptions(); /** * Gets pointers for menus preset by KXMLGUIFactory * @return pointers for menus */ QHash initMenus(); /** * Initializes QActions (names, object names, icons, some connections, shortcuts) * @return pointers for actions */ QHash initActions(); /** initializes the dynamic menus (account selectors) */ void initDynamicMenus(); /** * sets up the statusbar for the main window by initialzing a statuslabel. */ void initStatusBar(); /** queryClose is called by KMainWindow on each closeEvent of a window. Against the * default implementation (only returns true), this calls saveModified() on the document object to ask if the document shall * be saved if Modified; on cancel the closeEvent is rejected. * The settings are saved using saveOptions() if we are about to close. * @see KMainWindow#queryClose * @see QWidget#closeEvent */ bool queryClose() final override; void slotCheckSchedules(); +#ifdef KMM_DEBUG void resizeEvent(QResizeEvent*) final override; +#endif void createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount); /** * This method preloads the holidays for the duration of the default forecast period */ void preloadHolidays(); public Q_SLOTS: void slotFileInfoDialog(); - /** */ - void slotFileNew(); - - /** open a file and load it into the document*/ - void slotFileOpen(); - bool isFileOpenedInAnotherInstance(const QUrl &url); - /** opens a file from the recent files menu */ - - void slotFileOpenRecent(const QUrl &url); - - /** - * saves the current document. If it has no name yet, the user - * will be queried for it. - * - * @retval false save operation failed - * @retval true save operation was successful - */ - bool slotFileSave(); - - /** asks for saving if the file is modified, then closes the actual file and window */ - void slotFileCloseWindow(); - - /** asks for saving if the file is modified, then closes the actual file */ - void slotFileClose(); - - /** - * closes all open windows by calling close() on each memberList item - * until the list is empty, then quits the application. - * If queryClose() returns false because the user canceled the - * saveModified() dialog, the closing breaks. - */ - void slotFileQuit(); - void slotFileConsistencyCheck(); /** * fires up the price table editor */ void slotPriceDialog(); /** * fires up the currency table editor */ void slotCurrencyDialog(); /** * dummy method needed just for initialization */ void slotShowTransactionDetail(); /** * Toggles the hide reconciled transactions setting */ void slotHideReconciledTransactions(); /** * Toggles the hide unused categories setting */ void slotHideUnusedCategories(); /** * Toggles the show all accounts setting */ void slotShowAllAccounts(); /** * changes the statusbar contents for the standard label permanently, * used to indicate current actions. Returns the previous value for * 'stacked' usage. * * @param text the text that is displayed in the statusbar */ QString slotStatusMsg(const QString &text); /** * This method changes the progress bar in the status line according * to the parameters @p current and @p total. The following special * cases exist: * * - current = -1 and total = -1 will reset the progress bar * - current = ?? and total != 0 will setup the 100% mark to @p total * - current = xx and total == 0 will set the percentage * * @param current the current value with respect to the initialised * 100% mark * @param total the total value (100%) */ void slotStatusProgressBar(int current, int total = 0); /** * Called to update stock and currency prices from the user menu */ void slotEquityPriceUpdate(); /** * This slot reparents account @p src to be a child of account @p dest * * @param src account to be reparented * @param dest new parent */ void slotReparentAccount(const MyMoneyAccount& src, const MyMoneyAccount& dest); /** * This slot reparents account @p src to be a held at institution @p dest * * @param src account to be reparented * @param dest new parent institution */ void slotReparentAccount(const MyMoneyAccount& src, const MyMoneyInstitution& dest); /** * Create a new investment in a given @p parent investment account */ void slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent); /** * Brings up the new category editor and saves the information. * The dialog will be preset with the name and parent account. * * @param account reference of category to be created. The @p name member * should be filled by the caller. The object will be filled * with additional information during the creation process * esp. the @p id member. * @param parent reference to parent account (defaults to none) */ void slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent); void slotCategoryNew(MyMoneyAccount& account); /** */ void slotPayeeNew(const QString& newnameBase, QString& id); /** * This slot fires up the KCalc application */ void slotToolsStartKCalc(); - void slotResetSelections(); - /** * Brings up the new account wizard and saves the information. */ void slotAccountNew(MyMoneyAccount&); - /** - * This method updates all KAction items to the current state. - */ - void slotUpdateActions(); - void webConnect(const QString& sourceUrl, const QByteArray &asn_id); void webConnect(const QUrl url) { webConnect(url.path(), QByteArray()); } private: /** * This method sets the holidayRegion for use by the processing calendar. */ void setHolidayRegion(const QString& holidayRegion); /** * Load the status bar with the 'ready' message. This is hold in a single * place, so that is consistent with isReady(). */ void ready(); /** * Check if the status bar contains the 'ready' message. The return * value is used e.g. to detect if a quit operation is allowed or not. * * @retval true application is idle * @retval false application is active working on a longer operation */ bool isReady(); /** * Re-implemented from IMyMoneyProcessingCalendar */ bool isProcessingDate(const QDate& date) const final override; - /** - * Depending on the setting of AutoSaveOnQuit, this method - * asks the user to save the file or not. - * - * @returns see return values of KMessageBox::warningYesNoCancel() - */ - int askSaveOnClose(); - Q_SIGNALS: /** * This signal is emitted when a new file is loaded. In the case file * is closed, this signal is also emitted with an empty url. */ void fileLoaded(const QUrl &url); /** * This signal is emitted when a transaction/list of transactions has been selected by * the GUI. If no transaction is selected or the selection is removed, * @p transactions is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void transactionsSelected(const KMyMoneyRegister::SelectedTransactions& transactions); /** * This signal is emitted if a specific transaction with @a transactionId in account * @a accountId is selected */ void transactionSelected(const QString accountId, const QString& transactionId); /** * This signal is sent out, when the user presses Ctrl+A or activates * the Select all transactions action. */ void selectAllTransactions(); /** * This signal is emitted when a new institution has been selected by * the GUI. If no institution is selected or the selection is removed, * @a institution is identical to MyMoneyInstitution(). This signal is used * by plugins to get information about changes. */ void institutionSelected(const MyMoneyInstitution& institution); /** * This signal is emitted when a new schedule has been selected by * the GUI. If no schedule is selected or the selection is removed, * @a schedule is identical to MyMoneySchedule(). This signal is used * by plugins to get information about changes. */ void scheduleSelected(const MyMoneySchedule& schedule); void startMatchTransaction(const MyMoneyTransaction& t); void cancelMatchTransaction(); void kmmFilePlugin(unsigned int); public: bool isActionToggled(const eMenu::Action _a); static const QHash s_Actions; private: /// \internal d-pointer class. class Private; /* * Actually, one should write "Private * const d" but that confuses the KIDL * compiler in this context. It complains about the const keyword. So we leave * it out here */ /// \internal d-pointer instance. Private* d; + +public Q_SLOTS: + bool slotFileNew(); + void slotFileOpen(); + bool slotFileOpenRecent(const QUrl &url); + bool slotFileSave(); + bool slotFileSaveAs(); + bool slotFileClose(); + /** + * closes all open windows by calling close() on each memberList item + * until the list is empty, then quits the application. + * If queryClose() returns false because the user canceled the + * saveModified() dialog, the closing breaks. + */ + void slotFileQuit(); }; extern KMyMoneyApp *kmymoney; class KMStatus { public: explicit KMStatus(const QString &text); ~KMStatus(); private: QString m_prevText; }; #define KMSTATUS(msg) KMStatus _thisStatus(msg) #endif // KMYMONEY_H diff --git a/kmymoney/kmymoneyenums.h b/kmymoney/kmymoneyenums.h new file mode 100644 index 000000000..44772fd1d --- /dev/null +++ b/kmymoney/kmymoneyenums.h @@ -0,0 +1,42 @@ +/* + * Copyright 2018 Łukasz Wojniłowicz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KMYMONEYENUMS_H +#define KMYMONEYENUMS_H + +#include + +namespace eKMyMoney { + enum class FileAction { + Opened, + Saved, + Closing, + Closed, + Changed + }; + + enum class StorageType { + None, + XML, + SQL, + GNC + }; + + inline uint qHash(const StorageType key, uint seed) { return ::qHash(static_cast(key), seed); } + +} +#endif diff --git a/kmymoney/main.cpp b/kmymoney/main.cpp index edb783a33..f64e28fa4 100644 --- a/kmymoney/main.cpp +++ b/kmymoney/main.cpp @@ -1,363 +1,362 @@ /*************************************************************************** main.cpp ------------------- copyright : (C) 2001 by Michael Edwardes email : mte@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #ifdef KMM_DBUS #include #include #include #endif // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoney/mymoneyfile.h" #include "kmymoney.h" #include "kstartuplogo.h" #include "kcreditswindow.h" #include "kmymoneyutils.h" #include "kmymoneysettings.h" #include "misc/webconnect.h" #include "platformtools.h" #ifdef KMM_DEBUG #include "mymoneyutils.h" #include "mymoneytracer.h" #endif bool timersOn = false; KMyMoneyApp* kmymoney; static int runKMyMoney(QApplication& a, std::unique_ptr splash, const QUrl & file, bool noFile); static void migrateConfigFiles(); int main(int argc, char *argv[]) { /** * Create application first */ QApplication app(argc, argv); KLocalizedString::setApplicationDomain("kmymoney"); migrateConfigFiles(); /** * construct and register about data */ KAboutData aboutData(QStringLiteral("kmymoney"), i18n("KMyMoney"), QStringLiteral(VERSION)); aboutData.setOrganizationDomain("kde.org"); KAboutData::setApplicationData(aboutData); QStringList fileUrls; bool isNoCatchOption = false; bool isNoFileOption = false; #ifdef KMM_DEBUG bool isDumpActionsOption = false; #endif if (argc != 0) { /** * Create command line parser and feed it with known options */ QCommandLineParser parser; aboutData.setupCommandLine(&parser); // language // const QCommandLineOption langOption(QStringLiteral("lang"), i18n("language to be used")); // parser.addOption(langOption); // no file const QCommandLineOption noFileOption(QStringLiteral("n"), i18n("do not open last used file")); parser.addOption(noFileOption); // timers const QCommandLineOption timersOption(QStringLiteral("timers"), i18n("enable performance timers")); parser.addOption(timersOption); // no catch const QCommandLineOption noCatchOption(QStringLiteral("nocatch"), i18n("do not globally catch uncaught exceptions")); parser.addOption(noCatchOption); #ifdef KMM_DEBUG // The following options are only available when compiled in debug mode // trace const QCommandLineOption traceOption(QStringLiteral("trace"), i18n("turn on program traces")); parser.addOption(traceOption); // dump actions const QCommandLineOption dumpActionsOption(QStringLiteral("dump-actions"), i18n("dump the names of all defined QAction objects to stdout and quit")); parser.addOption(dumpActionsOption); #endif // INSERT YOUR COMMANDLINE OPTIONS HERE // url to open parser.addPositionalArgument(QStringLiteral("url"), i18n("file to open")); /** * do the command line parsing */ parser.parse(QApplication::arguments()); bool ishelpSet = parser.isSet(QStringLiteral("help")); if (ishelpSet || parser.isSet(QStringLiteral("author")) || parser.isSet(QStringLiteral("license"))) { aboutData = initializeCreditsData(); if (ishelpSet) parser.showHelp(); } if (parser.isSet(QStringLiteral("version"))) parser.showVersion(); /** * handle standard options */ aboutData.processCommandLine(&parser); #ifdef KMM_DEBUG if (parser.isSet(traceOption)) MyMoneyTracer::on(); timersOn = parser.isSet(timersOption); isDumpActionsOption = parser.isSet(dumpActionsOption); #endif isNoCatchOption = parser.isSet(noCatchOption); isNoFileOption = parser.isSet(noFileOption); fileUrls = parser.positionalArguments(); } // create the singletons before we start memory checking // to avoid false error reports auto file = MyMoneyFile::instance(); Q_UNUSED(file) KMyMoneyUtils::checkConstants(); // show startup logo std::unique_ptr splash(KMyMoneySettings::showSplash() ? createStartupLogo() : nullptr); app.processEvents(); // setup the MyMoneyMoney locale settings according to the KDE settings MyMoneyMoney::setThousandSeparator(QLocale().groupSeparator()); MyMoneyMoney::setDecimalSeparator(QLocale().decimalPoint()); // TODO: port to kf5 (negative numbers in parens) //MyMoneyMoney::setNegativeMonetarySignPosition(static_cast(KLocale::global()->negativeMonetarySignPosition())); //MyMoneyMoney::setPositiveMonetarySignPosition(static_cast(KLocale::global()->positiveMonetarySignPosition())); MyMoneyMoney::setNegativePrefixCurrencySymbol(platformTools::currencySymbolPosition(true) < platformTools::AfterQuantityMoney); MyMoneyMoney::setPositivePrefixCurrencySymbol(platformTools::currencySymbolPosition(false) < platformTools::AfterQuantityMoney); // QString language = parser.value(langOption); // if (!language.isEmpty()) { //if (!KLocale::global()->setLanguage(QStringList() << language)) { // qWarning("Unable to select language '%s'. This has one of two reasons:\n\ta) the standard KDE message catalog is not installed\n\tb) the KMyMoney message catalog is not installed", qPrintable(language)); //} // } kmymoney = new KMyMoneyApp(); #ifdef KMM_DEBUG if (isDumpActionsOption) { kmymoney->dumpActions(); // Before we delete the application, we make sure that we destroy all // widgets by running the event loop for some time to catch all those // widgets that are requested to be destroyed using the deleteLater() method. //QApplication::eventLoop()->processEvents(QEventLoop::ExcludeUserInput, 10); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 10); delete kmymoney; exit(0); } #endif QString fname; // in case a filename is provided we need to check if it is a local // file. In case the name does not start with "file://" or "./" or "/" // we need to prepend "./" to fake a relative filename. Otherwiese, QUrl prepends // "http://" and uses the full path which will not work. // // The handling might be different on other OSes if (!fileUrls.isEmpty()) { fname = fileUrls.front(); QFileInfo fi(fname); if (fi.isFile() && !fname.startsWith(QLatin1String("file://")) && !fname.startsWith(QLatin1String("./")) && !fname.startsWith(QLatin1String("/"))) { fname.prepend(QLatin1String("./")); } } const QUrl url = QUrl::fromUserInput(fname, QLatin1String("."), QUrl::AssumeLocalFile); int rc = 0; if (isNoCatchOption) { qDebug("Running w/o global try/catch block"); rc = runKMyMoney(app, std::move(splash), url, isNoFileOption); } else { try { rc = runKMyMoney(app, std::move(splash), url, isNoFileOption); } catch (const MyMoneyException &e) { KMessageBox::detailedError(0, i18n("Uncaught error. Please report the details to the developers"), QString::fromLatin1(e.what())); throw; } } return rc; } int runKMyMoney(QApplication& a, std::unique_ptr splash, const QUrl & file, bool noFile) { bool instantQuit = false; /** * enable high dpi icons */ a.setAttribute(Qt::AA_UseHighDpiPixmaps); if (kmymoney->webConnect()->isClient()) { // If the user launches a second copy of the app and includes a file to // open, they are probably attempting a "WebConnect" session. In this case, // we'll check to make sure it's an importable file that's passed in, and if so, we'll // notify the primary instance of the file and kill ourselves. if (file.isValid()) { if (kmymoney->isImportableFile(file)) { instantQuit = true; kmymoney->webConnect()->loadFile(file); } } } kmymoney->centralWidget()->setEnabled(false); // force complete paint of widgets qApp->processEvents(); if (!instantQuit) { QString importfile; QUrl url; // make sure, we take the file provided on the command // line before we go and open the last one used if (file.isValid()) { // Check to see if this is an importable file, as opposed to a loadable // file. If it is importable, what we really want to do is load the // last used file anyway and then immediately import this file. This // implements a "web connect" session where there is not already an // instance of the program running. if (kmymoney->isImportableFile(file)) { importfile = file.path(); url = QUrl::fromUserInput(kmymoney->readLastUsedFile()); } else { url = file; } } else { url = QUrl::fromUserInput(kmymoney->readLastUsedFile()); } KTipDialog::showTip(kmymoney, QString(), false); if (url.isValid() && !noFile) { kmymoney->slotFileOpenRecent(url); } else if (KMyMoneySettings::firstTimeRun()) { kmymoney->slotFileNew(); } KMyMoneySettings::setFirstTimeRun(false); if (!importfile.isEmpty()) kmymoney->webConnect(importfile, QByteArray()); } else { // the instantQuit flag is set, so we force the app to quit right away kmymoney->slotFileQuit(); } - kmymoney->updateCaption(); kmymoney->centralWidget()->setEnabled(true); kmymoney->show(); splash.reset(); const int rc = a.exec(); //krazy:exclude=crashy return rc; } static void migrateConfigFiles() { const QString sMainConfigName(QStringLiteral("kmymoneyrc")); const QString sMainConfigSubdirectory(QStringLiteral("kmymoney/")); // all KMM config files should be in ~/.config/kmymoney/ const QString sMainConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + sMainConfigSubdirectory; if (!QFile::exists(sMainConfigPath + sMainConfigName)) { // if main config file doesn't exist, then it's first run // it could be migration from KDE4 to KF5 so prepare list of configuration files to migrate QStringList sConfigNames { sMainConfigName, QStringLiteral("csvimporterrc"), QStringLiteral("printcheckpluginrc"), QStringLiteral("icalendarexportpluginrc"), QStringLiteral("kbankingrc"), }; // Copy KDE 4 config files to the KF5 location Kdelibs4ConfigMigrator migrator(QStringLiteral("kmymoney")); migrator.setConfigFiles(sConfigNames); migrator.setUiFiles(QStringList{QStringLiteral("kmymoneyui.rc")}); migrator.migrate(); QFileInfo fileInfo(sMainConfigPath + sMainConfigName); QDir().mkpath(fileInfo.absolutePath()); const QString sOldMainConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/'); // some files have changed their names during switch to KF5, so prepare map for name replacements QMap configNamesChange { {QStringLiteral("printcheckpluginrc"), QStringLiteral("checkprintingrc")}, {QStringLiteral("icalendarexportpluginrc"), QStringLiteral("icalendarexporterrc")} }; for (const auto& sConfigName : sConfigNames) { const auto sOldConfigFilename = sOldMainConfigPath + sConfigName; const auto sNewConfigFilename = sMainConfigPath + configNamesChange.value(sConfigName, sConfigName); if (QFile::exists(sOldConfigFilename)) { if (QFile::copy(sOldConfigFilename, sNewConfigFilename)) QFile::remove(sOldConfigFilename); } } } KConfig::setMainConfigName(sMainConfigSubdirectory + sMainConfigName); // otherwise it would be ~/.config/kmymoneyrc and not ~/.config/kmymoney/kmymoneyrc } diff --git a/kmymoney/mymoney/onlinejobadministration.cpp b/kmymoney/mymoney/onlinejobadministration.cpp index 0a9b2c017..cf277ea88 100644 --- a/kmymoney/mymoney/onlinejobadministration.cpp +++ b/kmymoney/mymoney/onlinejobadministration.cpp @@ -1,441 +1,447 @@ /* * Copyright 2013-2018 Christian Dávid * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "onlinejobadministration.h" // ---------------------------------------------------------------------------- // Std Includes #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoney/mymoneyfile.h" #include "mymoney/mymoneyaccount.h" #include "mymoney/mymoneykeyvaluecontainer.h" #include "plugins/onlinepluginextended.h" #include "onlinetasks/unavailabletask/tasks/unavailabletask.h" #include "onlinetasks/interfaces/tasks/credittransfer.h" #include "tasks/onlinetask.h" onlineJobAdministration::onlineJobAdministration(QObject *parent) : QObject(parent) , m_onlinePlugins(nullptr) , m_inRegistration(false) { } onlineJobAdministration::~onlineJobAdministration() { clearCaches(); } onlineJobAdministration* onlineJobAdministration::instance() { static onlineJobAdministration m_instance; return &m_instance; } void onlineJobAdministration::clearCaches() { qDeleteAll(m_onlineTasks); m_onlineTasks.clear(); qDeleteAll(m_onlineTaskConverter); m_onlineTaskConverter.clear(); } KMyMoneyPlugin::OnlinePluginExtended* onlineJobAdministration::getOnlinePlugin(const QString& accountId) const { MyMoneyAccount acc = MyMoneyFile::instance()->account(accountId); QMap::const_iterator it_p; it_p = m_onlinePlugins->constFind(acc.onlineBankingSettings().value("provider").toLower()); if (it_p != m_onlinePlugins->constEnd()) { // plugin found, use it return *it_p; } return 0; } void onlineJobAdministration::setOnlinePlugins(QMap& plugins) { m_onlinePlugins = &plugins; updateActions(); } void onlineJobAdministration::updateActions() { emit canSendAnyTaskChanged(canSendAnyTask()); emit canSendCreditTransferChanged(canSendCreditTransfer()); } QStringList onlineJobAdministration::availableOnlineTasks() { auto plugins = KPluginLoader::findPlugins("kmymoney", [](const KPluginMetaData& data) { return !(data.rawData()["KMyMoney"].toObject()["OnlineTask"].isNull()); }); QStringList list; for(const KPluginMetaData& plugin: plugins) { QJsonValue array = plugin.rawData()["KMyMoney"].toObject()["OnlineTask"].toObject()["Iids"]; if (array.isArray()) list.append(array.toVariant().toStringList()); } return list; } /** * @internal The real work is done here. */ bool onlineJobAdministration::isJobSupported(const QString& accountId, const QString& name) const { if (!m_onlinePlugins) return false; foreach (KMyMoneyPlugin::OnlinePluginExtended* plugin, *m_onlinePlugins) { if (plugin->availableJobs(accountId).contains(name)) return true; } return false; } bool onlineJobAdministration::isJobSupported(const QString& accountId, const QStringList& names) const { foreach (QString name, names) { if (isJobSupported(accountId, name)) return true; } return false; } bool onlineJobAdministration::isAnyJobSupported(const QString& accountId) const { if (accountId.isEmpty()) return false; if (!m_onlinePlugins) return false; foreach (KMyMoneyPlugin::OnlinePluginExtended* plugin, *m_onlinePlugins) { if (!(plugin->availableJobs(accountId).isEmpty())) return true; } return false; } onlineJob onlineJobAdministration::createOnlineJob(const QString& name, const QString& id) const { return (onlineJob(createOnlineTask(name), id)); } onlineTask* onlineJobAdministration::createOnlineTask(const QString& name) const { const onlineTask* task = rootOnlineTask(name); if (task) return task->clone(); return nullptr; } onlineTask* onlineJobAdministration::createOnlineTaskByXml(const QString& iid, const QDomElement& element) const { onlineTask* task = rootOnlineTask(iid); if (task) { return task->createFromXml(element); } qWarning("In the file is a onlineTask for which I could not find the plugin ('%s')", qPrintable(iid)); return new unavailableTask(element); } /** * @interanl Using KPluginFactory to create the plugins seemed to be good idea. The drawback is that it does not support to create non QObjects directly. * This made this function way longer than needed and adds many checks. * * @fixme Delete created tasks */ onlineTask* onlineJobAdministration::rootOnlineTask(const QString& name) const { auto plugins = KPluginLoader::findPlugins("kmymoney", [&name](const KPluginMetaData& data) { QJsonValue array = data.rawData()["KMyMoney"].toObject()["OnlineTask"].toObject()["Iids"]; if (array.isArray()) return (array.toVariant().toStringList().contains(name)); return false; }); if (plugins.isEmpty()) return nullptr; if (plugins.length() != 1) qWarning() << "Multiple plugins which offer the online task \"" << name << "\" were found. Loading a random one."; // Load plugin std::unique_ptr loader = std::unique_ptr(new QPluginLoader{plugins.first().fileName()}); QObject* plugin = loader->instance(); if (!plugin) { qWarning() << "Could not load plugin for online task " << name << ", file name " << plugins.first().fileName() << "."; return nullptr; } // Cast to KPluginFactory KPluginFactory* pluginFactory = qobject_cast< KPluginFactory* >(plugin); if (!pluginFactory) { qWarning() << "Could not create plugin factory for online task " << name << ", file name " << plugins.first().fileName() << "."; return nullptr; } // Create onlineTaskFactory const QString pluginKeyword = plugins.first().rawData()["KMyMoney"].toObject()["OnlineTask"].toObject()["PluginKeyword"].toString(); // Can create only objects which inherit from QObject directly QObject* taskFactoryObject = pluginFactory->create(pluginKeyword, onlineJobAdministration::instance()); KMyMoneyPlugin::onlineTaskFactory* taskFactory = qobject_cast< KMyMoneyPlugin::onlineTaskFactory* >(taskFactoryObject); if (!taskFactory) { qWarning() << "Could not create online task factory for online task " << name << ", file name " << plugins.first().fileName() << "."; return nullptr; } // Finally create task onlineTask* task = taskFactory->createOnlineTask(name); if (task) // Add to our cache as this is still used in several places onlineJobAdministration::instance()->registerOnlineTask(taskFactory->createOnlineTask(name)); return task; } onlineTaskConverter::convertType onlineJobAdministration::canConvert(const QString& originalTaskIid, const QString& convertTaskIid) const { return canConvert(originalTaskIid, QStringList(convertTaskIid)); } onlineTaskConverter::convertType onlineJobAdministration::canConvert(const QString& originalTaskIid, const QStringList& convertTaskIids) const { Q_ASSERT(false); //! @todo Make alive onlineTaskConverter::convertType bestConvertType = onlineTaskConverter::convertImpossible; #if 0 foreach (QString destinationName, destinationNames) { onlineTask::convertType type = canConvert(original, destinationName); if (type == onlineTask::convertionLossy) bestConvertType = onlineTask::convertionLossy; else if (type == onlineTask::convertionLoseless) return onlineTask::convertionLoseless; } #else Q_UNUSED(originalTaskIid); Q_UNUSED(convertTaskIids); #endif return bestConvertType; } /** * @todo if more than one converter offers the convert, use best */ onlineJob onlineJobAdministration::convert(const onlineJob& original, const QString& convertTaskIid, onlineTaskConverter::convertType& convertType, QString& userInformation, const QString& onlineJobId) const { onlineJob newJob; QList converterList = m_onlineTaskConverter.values(convertTaskIid); foreach (onlineTaskConverter* converter, converterList) { if (converter->convertibleTasks().contains(original.taskIid())) { onlineTask* task = converter->convert(*original.task(), convertType, userInformation); Q_ASSERT_X(convertType != onlineTaskConverter::convertImpossible || task != 0, qPrintable("converter for " + converter->convertedTask()), "Converter returned convertType 'impossible' but return was not null_ptr."); if (task != 0) { newJob = onlineJob(task, onlineJobId); break; } } } return newJob; } onlineJob onlineJobAdministration::convertBest(const onlineJob& original, const QStringList& convertTaskIids, onlineTaskConverter::convertType& convertType, QString& userInformation) const { return convertBest(original, convertTaskIids, convertType, userInformation, original.id()); } onlineJob onlineJobAdministration::convertBest(const onlineJob& original, const QStringList& convertTaskIids, onlineTaskConverter::convertType& bestConvertType, QString& bestUserInformation, const QString& onlineJobId) const { onlineJob bestConvert; bestConvertType = onlineTaskConverter::convertImpossible; bestUserInformation = QString(); foreach (QString taskIid, convertTaskIids) { // Try convert onlineTaskConverter::convertType convertType = onlineTaskConverter::convertImpossible; QString userInformation; onlineJob convertJob = convert(original, taskIid, convertType, userInformation, onlineJobId); // Check if it was successful if (bestConvertType < convertType) { bestConvert = convertJob; bestUserInformation = userInformation; bestConvertType = convertType; if (convertType == onlineTaskConverter::convertionLoseless) break; } } return bestConvert; } void onlineJobAdministration::registerAllOnlineTasks() { // avoid recursive entrance if (m_inRegistration) return; m_inRegistration = true; QStringList availableTasks = availableOnlineTasks(); foreach (const auto& name, availableTasks) { onlineTask* const task = rootOnlineTask(name); Q_UNUSED(task); } m_inRegistration = false; } void onlineJobAdministration::registerOnlineTask(onlineTask *const task) { if (Q_UNLIKELY(task == 0)) return; const bool sendAnyTask = canSendAnyTask(); const bool sendCreditTransfer = canSendCreditTransfer(); m_onlineTasks.insert(task->taskName(), task); if (sendAnyTask != canSendAnyTask()) emit canSendAnyTaskChanged(!sendAnyTask); if (sendCreditTransfer != canSendCreditTransfer()) emit canSendCreditTransferChanged(!sendCreditTransfer); } void onlineJobAdministration::registerOnlineTaskConverter(onlineTaskConverter* const converter) { if (Q_UNLIKELY(converter == 0)) return; m_onlineTaskConverter.insertMulti(converter->convertedTask(), converter); qDebug() << "onlineTaskConverter available" << converter->convertedTask() << converter->convertibleTasks(); } onlineJobAdministration::onlineJobEditOffers onlineJobAdministration::onlineJobEdits() { auto plugins = KPluginLoader::findPlugins("kmymoney", [](const KPluginMetaData& data) { return !(data.rawData()["KMyMoney"].toObject()["OnlineTask"].toObject()["Editors"].isNull()); }); onlineJobAdministration::onlineJobEditOffers list; list.reserve(plugins.size()); for(const KPluginMetaData& data: plugins) { QJsonArray editorsArray = data.rawData()["KMyMoney"].toObject()["OnlineTask"].toObject()["Editors"].toArray(); for(QJsonValue entry: editorsArray) { if (!entry.toObject()["OnlineTaskIds"].isNull()) { list.append(onlineJobAdministration::onlineJobEditOffer{ data.fileName(), entry.toObject()["PluginKeyword"].toString(), KPluginMetaData::readTranslatedString(entry.toObject(), "Name") }); } } } return list; } IonlineTaskSettings::ptr onlineJobAdministration::taskSettings(const QString& taskName, const QString& accountId) const { KMyMoneyPlugin::OnlinePluginExtended* plugin = getOnlinePlugin(accountId); if (plugin != 0) return (plugin->settings(accountId, taskName)); return IonlineTaskSettings::ptr(); } bool onlineJobAdministration::canSendAnyTask() { if (!m_onlinePlugins) return false; if (m_onlineTasks.isEmpty()) { registerAllOnlineTasks(); } + if (!MyMoneyFile::instance()->storageAttached()) + return false; + // Check if any plugin supports a loaded online task foreach (KMyMoneyPlugin::OnlinePluginExtended* plugin, *m_onlinePlugins) { QList accounts; MyMoneyFile::instance()->accountList(accounts, QStringList(), true); foreach (MyMoneyAccount account, accounts) { foreach (QString onlineTaskIid, plugin->availableJobs(account.id())) { if (m_onlineTasks.contains(onlineTaskIid)) { return true; } } } } return false; } bool onlineJobAdministration::canSendCreditTransfer() { if (!m_onlinePlugins) return false; if (m_onlineTasks.isEmpty()) { registerAllOnlineTasks(); } + if (!MyMoneyFile::instance()->storageAttached()) + return false; + foreach (onlineTask* task, m_onlineTasks) { // Check if a online task has the correct type if (dynamic_cast(task) != 0) { foreach (KMyMoneyPlugin::OnlinePluginExtended* plugin, *m_onlinePlugins) { QList accounts; MyMoneyFile::instance()->accountList(accounts, QStringList(), true); foreach (MyMoneyAccount account, accounts) { if (plugin->availableJobs(account.id()).contains(task->taskName())) return true; } } } } return false; } //! @fixme plugin query bool onlineJobAdministration::canEditOnlineJob(const onlineJob& job) { return (!job.taskIid().isEmpty() && !KServiceTypeTrader::self()->query(QLatin1String("KMyMoney/OnlineTaskUi"), QString("'%1' ~in [X-KMyMoney-onlineTaskIds]").arg(job.taskIid())).isEmpty()); } void onlineJobAdministration::updateOnlineTaskProperties() { emit canSendAnyTaskChanged(canSendAnyTask()); emit canSendCreditTransferChanged(canSendCreditTransfer()); } diff --git a/kmymoney/plugins/appinterface.h b/kmymoney/plugins/appinterface.h index adae97e8f..57a1a3326 100644 --- a/kmymoney/plugins/appinterface.h +++ b/kmymoney/plugins/appinterface.h @@ -1,77 +1,74 @@ /*************************************************************************** appinterface.h ------------------- copyright : (C) 2018 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef APPINTERFACE_H #define APPINTERFACE_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include class QTimer; class IMyMoneyOperationsFormat; typedef void (*KMyMoneyAppCallback)(int, int, const QString &); namespace KMyMoneyPlugin { class KMM_PLUGIN_EXPORT AppInterface : public QObject { Q_OBJECT public: explicit AppInterface(QObject* parent, const char* name = 0); virtual ~AppInterface(); /** * Makes sure that a MyMoneyFile is open and has been created successfully. * * @return Whether the file is open and initialised */ virtual bool fileOpen() = 0; virtual bool isDatabase() = 0; virtual bool isNativeFile() = 0; virtual QUrl filenameURL() const = 0; virtual void writeFilenameURL(const QUrl &url) = 0; virtual QUrl lastOpenedURL() = 0; virtual void writeLastUsedFile(const QString& fileName) = 0; virtual void slotFileOpenRecent(const QUrl &url) = 0; virtual void addToRecentFiles(const QUrl& url) = 0; - virtual void updateCaption(bool skipActions = false) = 0; - virtual QTimer* autosaveTimer() = 0; virtual KMyMoneyAppCallback progressCallback() = 0; virtual void writeLastUsedDir(const QString &directory) = 0; virtual QString readLastUsedDir() const = 0; virtual void consistencyCheck(bool alwaysDisplayResult) = 0; - Q_SIGNALS: void kmmFilePlugin(unsigned int); }; } #endif diff --git a/kmymoney/plugins/csv/export/csvexporter.cpp b/kmymoney/plugins/csv/export/csvexporter.cpp index 7400f7735..abaff2fba 100644 --- a/kmymoney/plugins/csv/export/csvexporter.cpp +++ b/kmymoney/plugins/csv/export/csvexporter.cpp @@ -1,105 +1,110 @@ /* * Copyright 2013-2014 Allan Anderson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "csvexporter.h" -#include "csvexportdlg.h" -#include "csvwriter.h" + // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes +#include "csvexportdlg.h" +#include "csvwriter.h" +#include "viewinterface.h" + CSVExporter::CSVExporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "csvexporter"/*must be the same as X-KDE-PluginInfo-Name*/) { Q_UNUSED(args); setComponentName("csvexporter", i18n("CSV exporter")); setXMLFile("csvexporter.rc"); createActions(); // For information, announce that we have been loaded. qDebug("Plugins: csvexporter loaded"); } CSVExporter::~CSVExporter() { qDebug("Plugins: csvexporter unloaded"); } void CSVExporter::createActions() { - m_action = actionCollection()->addAction("file_export_csv"); + const auto &kpartgui = QStringLiteral("file_export_csv"); + m_action = actionCollection()->addAction(kpartgui); m_action->setText(i18n("&CSV...")); connect(m_action, &QAction::triggered, this, &CSVExporter::slotCsvExport); + connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } void CSVExporter::slotCsvExport() { m_dlg = new CsvExportDlg(); if (m_dlg->exec()) { if (okToWriteFile(QUrl::fromUserInput(m_dlg->filename()))) { m_dlg->setWindowTitle(i18nc("CSV Exporter dialog title", "CSV Exporter")); CsvWriter* writer = new CsvWriter; writer->m_plugin = this; connect(writer, &CsvWriter::signalProgress, m_dlg, &CsvExportDlg::slotStatusProgressBar); writer->write(m_dlg->filename(), m_dlg->accountId(), m_dlg->accountSelected(), m_dlg->categorySelected(), m_dlg->startDate(), m_dlg->endDate(), m_dlg->separator()); } } } bool CSVExporter::okToWriteFile(const QUrl &url) { // check if the file exists and warn the user bool reallySaveFile = true; bool fileExists = false; if (url.isValid()) { short int detailLevel = 0; // Lowest level: file/dir/symlink/none KIO::StatJob* statjob = KIO::stat(url, KIO::StatJob::SourceSide, detailLevel); bool noerror = statjob->exec(); if (noerror) { // We want a file fileExists = !statjob->statResult().isDir(); } } if (fileExists) { if (KMessageBox::warningYesNo(0, i18n("The file %1 already exists. Do you really want to overwrite it?", url.toDisplayString(QUrl::PreferLocalFile)), i18n("File already exists")) != KMessageBox::Yes) reallySaveFile = false; } return reallySaveFile; } K_PLUGIN_FACTORY_WITH_JSON(CSVExporterFactory, "csvexporter.json", registerPlugin();) #include "csvexporter.moc" diff --git a/kmymoney/plugins/csv/import/csvimporter.cpp b/kmymoney/plugins/csv/import/csvimporter.cpp index ebdc940dd..0f4d87ddf 100644 --- a/kmymoney/plugins/csv/import/csvimporter.cpp +++ b/kmymoney/plugins/csv/import/csvimporter.cpp @@ -1,123 +1,126 @@ /* * Copyright 2010-2014 Allan Anderson * Copyright 2016-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "csvimporter.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "core/csvimportercore.h" #include "csvwizard.h" #include "statementinterface.h" +#include "viewinterface.h" CSVImporter::CSVImporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "csvimporter"/*must be the same as X-KDE-PluginInfo-Name*/) { Q_UNUSED(args); setComponentName("csvimporter", i18n("CSV importer")); setXMLFile("csvimporter.rc"); createActions(); // For information, announce that we have been loaded. qDebug("Plugins: csvimporter loaded"); } CSVImporter::~CSVImporter() { qDebug("Plugins: csvimporter unloaded"); } void CSVImporter::createActions() { - m_action = actionCollection()->addAction("file_import_csv"); + const auto &kpartgui = QStringLiteral("file_import_csv"); + m_action = actionCollection()->addAction(kpartgui); m_action->setText(i18n("CSV...")); connect(m_action, &QAction::triggered, this, &CSVImporter::startWizardRun); + connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } void CSVImporter::startWizardRun() { m_action->setEnabled(false); m_importer = new CSVImporterCore; m_wizard = new CSVWizard(this, m_importer); m_silent = false; connect(m_wizard, &CSVWizard::statementReady, this, &CSVImporter::slotGetStatement); m_action->setEnabled(false);// don't allow further plugins to start while this is open } bool CSVImporter::slotGetStatement(MyMoneyStatement& s) { const auto ret = !statementInterface()->import(s, m_silent).isEmpty(); delete m_importer; return ret; } QString CSVImporter::formatName() const { return QLatin1String("CSV"); } QString CSVImporter::formatFilenameFilter() const { return "*.csv"; } bool CSVImporter::isMyFormat(const QString& filename) const { // filename is considered a CSV file if it can be opened // and the filename ends in ".csv". bool result = false; QFile f(filename); if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { result = f.fileName().endsWith(QLatin1String(".csv")); f.close(); } return result; } bool CSVImporter::import(const QString& filename) { bool rc = true; m_importer = new CSVImporterCore; m_wizard = new CSVWizard(this, m_importer); m_wizard->presetFilename(filename); m_silent = false; connect(m_wizard, &CSVWizard::statementReady, this, &CSVImporter::slotGetStatement); return rc; } QString CSVImporter::lastError() const { return QString(); } K_PLUGIN_FACTORY_WITH_JSON(CSVImporterFactory, "csvimporter.json", registerPlugin();) #include "csvimporter.moc" diff --git a/kmymoney/plugins/gnc/import/CMakeLists.txt b/kmymoney/plugins/gnc/import/CMakeLists.txt index 785793f47..250e3d4bf 100644 --- a/kmymoney/plugins/gnc/import/CMakeLists.txt +++ b/kmymoney/plugins/gnc/import/CMakeLists.txt @@ -1,37 +1,38 @@ # patch the version with the version defined in the build system configure_file(${CMAKE_CURRENT_SOURCE_DIR}/gncimporter.json.in ${CMAKE_CURRENT_BINARY_DIR}/gncimporter.json @ONLY) ########### next target ############### set(gncimporter_PART_SRCS gncimporter.cpp kgncimportoptionsdlg.cpp kgncpricesourcedlg.cpp ../../../widgets/kmymoneymoneyvalidator.cpp mymoneygncreader.cpp ) set(gncimporter_PART_UI kgncimportoptionsdlg.ui kgncpricesourcedlg.ui ) ki18n_wrap_ui(gncimporter_PART_SRCS ${gncimporter_PART_UI}) kcoreaddons_add_plugin(gncimporter SOURCES ${gncimporter_PART_SRCS} JSON "${CMAKE_CURRENT_BINARY_DIR}/gncimporter.json" INSTALL_NAMESPACE "kmymoney") #kcoreaddons_add_plugin sets LIBRARY_OUTPUT_DIRECTORY to ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${INSTALL_NAMESPACE} set_target_properties(gncimporter PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") target_link_libraries(gncimporter PUBLIC kmm_plugin PRIVATE KF5::Completion + KF5::Archive Alkimia::alkimia ) diff --git a/kmymoney/plugins/gnc/import/gncimporter.cpp b/kmymoney/plugins/gnc/import/gncimporter.cpp index 3fe1a7b90..127b04430 100644 --- a/kmymoney/plugins/gnc/import/gncimporter.cpp +++ b/kmymoney/plugins/gnc/import/gncimporter.cpp @@ -1,89 +1,148 @@ /*************************************************************************** gncimporter.cpp ------------------- copyright : (C) 2017 by Łukasz Wojniłowicz email : lukasz.wojnilowicz@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "gncimporter.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include +#include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneygncreader.h" #include "viewinterface.h" #include "appinterface.h" #include "mymoneyfile.h" #include "mymoneyexception.h" #include "mymoneystoragemgr.h" +#include "kmymoneyenums.h" class MyMoneyStatement; +static constexpr KCompressionDevice::CompressionType const& COMPRESSION_TYPE = KCompressionDevice::GZip; + GNCImporter::GNCImporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "gncimporter"/*must be the same as X-KDE-PluginInfo-Name*/) { Q_UNUSED(args) setComponentName("gncimporter", i18n("GnuCash importer")); // For information, announce that we have been loaded. qDebug("Plugins: gncimporter loaded"); } GNCImporter::~GNCImporter() { qDebug("Plugins: gncimporter unloaded"); } -bool GNCImporter::open(MyMoneyStorageMgr *storage, const QUrl &url) -{ - Q_UNUSED(url) - Q_UNUSED(storage) - return false; +MyMoneyStorageMgr *GNCImporter::open(const QUrl &url) +{ + if (url.scheme() == QLatin1String("sql")) + return nullptr; + + if (!url.isLocalFile()) + return nullptr; + + const auto fileName = url.toLocalFile(); + const auto sFileToShort = QString::fromLatin1("File %1 is too short.").arg(fileName); + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) + throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName)); + + QByteArray qbaFileHeader(2, '\0'); + if (file.read(qbaFileHeader.data(), 2) != 2) + throw MYMONEYEXCEPTION(sFileToShort); + + file.close(); + + QIODevice* qfile = nullptr; + QString sFileHeader(qbaFileHeader); + if (sFileHeader == QString("\037\213")) // gzipped? + qfile = new KCompressionDevice(fileName, COMPRESSION_TYPE); + else + return nullptr; + + if (!qfile->open(QIODevice::ReadOnly)) { + delete qfile; + throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName)); + } + + // Scan the first 70 bytes to see if we find something + // we know. For now, we support our own XML format and + // GNUCash XML format. If the file is smaller, then it + // contains no valid data and we reject it anyway. + qbaFileHeader.resize(70); + if (qfile->read(qbaFileHeader.data(), 70) != 70) + throw MYMONEYEXCEPTION(sFileToShort); + + QString txt(qbaFileHeader); + + QRegExp gncexp("seek(0); + + auto storage = new MyMoneyStorageMgr; + pReader.setProgressCallback(appInterface()->progressCallback()); + pReader.readFile(qfile, storage); + pReader.setProgressCallback(0); + + qfile->close(); + delete qfile; + return storage; } bool GNCImporter::save(const QUrl &url) { Q_UNUSED(url) return false; } -IMyMoneyOperationsFormat* GNCImporter::reader() +bool GNCImporter::saveAs() { - return new MyMoneyGncReader; + return false; } -QString GNCImporter::formatName() const +eKMyMoney::StorageType GNCImporter::storageType() const { - return QStringLiteral("GNC"); + return eKMyMoney::StorageType::GNC; } QString GNCImporter::fileExtension() const { return i18n("GnuCash files (*.gnucash *.xac *.gnc)"); } K_PLUGIN_FACTORY_WITH_JSON(GNCImporterFactory, "gncimporter.json", registerPlugin();) #include "gncimporter.moc" diff --git a/kmymoney/plugins/gnc/import/gncimporter.h b/kmymoney/plugins/gnc/import/gncimporter.h index 53d382cd9..bc32f97ff 100644 --- a/kmymoney/plugins/gnc/import/gncimporter.h +++ b/kmymoney/plugins/gnc/import/gncimporter.h @@ -1,48 +1,49 @@ /*************************************************************************** gncimporter.h ------------------- copyright : (C) 2017 by Łukasz Wojniłowicz email : lukasz.wojnilowicz@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef GNCIMPORTER_H #define GNCIMPORTER_H // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // QT Includes // Project Includes #include "kmymoneyplugin.h" class MyMoneyGncReader; class GNCImporter : public KMyMoneyPlugin::Plugin, public KMyMoneyPlugin::StoragePlugin { Q_OBJECT Q_INTERFACES(KMyMoneyPlugin::StoragePlugin) public: explicit GNCImporter(QObject *parent, const QVariantList &args); ~GNCImporter() override; - bool open(MyMoneyStorageMgr *storage, const QUrl &url) override; + MyMoneyStorageMgr *open(const QUrl &url) override; bool save(const QUrl &url) override; - IMyMoneyOperationsFormat* reader() override; - QString formatName() const override; + bool saveAs() override; + + eKMyMoney::StorageType storageType() const override; QString fileExtension() const override; }; #endif diff --git a/kmymoney/plugins/gnc/import/mymoneygncreader.h b/kmymoney/plugins/gnc/import/mymoneygncreader.h index e16a4ba54..4b1c556fd 100644 --- a/kmymoney/plugins/gnc/import/mymoneygncreader.h +++ b/kmymoney/plugins/gnc/import/mymoneygncreader.h @@ -1,1092 +1,1092 @@ /*************************************************************************** mymoneygncreader - description ------------------- begin : Wed Mar 3 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2018 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ /* The main class of this module, MyMoneyGncReader, contains only a readFile() function, which controls the import of data from an XML file created by the current GnuCash version (1.8.8). The XML is processed in class XmlReader, which is an implementation of the Qt SAX2 reader class. Data in the input file is processed as a set of objects which fortunately, though perhaps not surprisingly, have almost a one-for-one correspondence with KMyMoney objects. These objects are bounded by start and end XML elements, and may contain both nested objects (described as sub objects in the code), and data items, also delimited by start and end elements. For example: * start of sub object within file Account Name * data string with start and end elements ... * end of sub objects A GnuCash file may consist of more than one 'book', or set of data. It is not clear how we could currently implement this, so only the first book in a file is processed. This should satisfy most user situations. GnuCash is somewhat inconsistent in its division of the major sections of the file. For example, multiple price history entries are delimited by elements, while each account starts with its own top-level element. In general, the 'container' elements are ignored. XmlReader This is an implementation of the Qt QXmlDefaultHandler class, which provides three main function calls in addition to start and end of document. The startElement() and endElement() calls are self-explanatory, the characters() function provides data strings. Thus in the above example, the sequence of calls would be startElement() for gnc:account startElement() for act:name characters() for 'Account Name' endElement() for act:name ... endElement() for gnc:account Objects Since the processing requirements of XML for most elements are very similar, the common code is implemented in a GncObject class, from which the others are derived, with virtual function calls to cater for any differences. The 'grandfather' object, GncFile representing the file (or more correctly, 'book') as a whole, is created in the startDocument() function call. The constructor function of each object is responsible for providing two lists for the XmlReader to scan, a list of element names which represent sub objects (called sub elements in the code), and a similar list of names representing data elements. In addition, an array of variables (m_v) is provided and initialized, to contain the actual data strings. Implementation Since objects may be nested, a stack is used, with the top element pointing to the 'current object'. The startDocument() call creates the first, GncFile, object at the top of the stack. As each startElement() call occurs, the two#include "mymoneygncreader.h" element lists created by the current object are scanned. If this element represents the start of a sub object, the current object's subEl() function is called to create an instance of the appropriate type. This is then pushed to the top of the stack, and the new object's initiate() function is called. This is used to process any XML attributes attached to the element; GnuCash makes little use of these. If this represents the start of a data element, a pointer (m_dataPointer) is set to point to an entry in the array (m_v) in which a subsequent characters() call can store the actual data. When an endElement() call occurs, a check is made to see if it matches the element name which started the current object. If so, the object's terminate() function is called. If the object represents a similar KMM object, this will normally result in a call to a conversion routine in the main (MyMoneyGncReader) class to convert the data to native format and place it in storage. The stack is then popped, and the parent (now current) object notified by a call to its endSubEl() function. Again depending on the type of object, this will either delete the instance, or save it in its own storage for later processing. For example, a GncSplit object makes little sense outside the context of its transaction, so will be saved by the transaction. A GncTransaction object on the other hand will be converted, along with its attendant splits, and then deleted by its parent. Since at any one time an object will only be processing either a subobject or a data element, a single object variable, m_state, is used to determine the actual type. In effect, it acts as the current index into either the subElement or dataElement list. As an object variable, it will be saved on the stack across subobject processing. Exceptions and Problems Fatal exceptions are processed via the standard MyMoneyException method. Due to differences in implementation between GnuCash and KMM, it is not always possible to provide an absolutely correct conversion. When such a problem situation is recognized, a message, along with any relevant variable data, is passed to the main class, and used to produce a report when processing terminates. Anonymizer When debugging problems, it is often useful to have a trace of what is happening within the module. However, in view of the sensitive nature of personal finance data, most users will be reluctant to provide this. Accordingly, an anonymize (hide()) function is provided to handle data strings. These may either be passed through asis (non-personal data), blanked out (non-critical but possibly personal data), replaced with a generated version (required, but possibly personal), or randomized (monetary amounts). The action for each data item is determined in the object's constructor function along with the creation of the data element list. This module will later be used as the basis of a file anonymizer, which will enable users to safely provide us with a copy of their GnuCash files, and will allow us to test the structure, if not the data content, of the file. */ #ifndef MYMONEYGNCREADER_H #define MYMONEYGNCREADER_H // system includes #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #ifndef _GNCFILEANON #include "storage/imymoneystorageformat.h" #endif // _GNCFILEANON // not sure what these are for, but leave them in #define VERSION_0_60_XML 0x10000010 // Version 0.5 file version info #define VERSION_0_61_XML 0x10000011 // use 8 bytes for MyMoneyMoney objects #define GNUCASH_ID_KEY "GNUCASH_ID" class MyMoneyAccount; class MyMoneySecurity; class MyMoneyTransaction; class MyMoneySplit; typedef QMap map_accountIds; typedef map_accountIds::iterator map_accountIds_iter; typedef map_accountIds::const_iterator map_accountIds_citer; typedef QMap map_elementVersions; class MyMoneyGncReader; class QIODevice; class QDate; class QTextCodec; class MyMoneyStorageMgr; class QXmlAttributes; class QXmlInputSource; class QXmlSimpleReader; /** GncObject is the base class for the various objects in the gnucash file Beyond the first level XML objects, elements will be of one of three types: 1. Sub object elements, which require creation of another object to process 2. Data object elements, which are only followed by data to be stored in a variable (m_v array) 3. Ignored objects, data not needed and not included herein */ class GncKvp; class GncObject { public: GncObject(); virtual ~GncObject() {} // make sure to have impl of all virtual rtns to avoid vtable errors? protected: friend class XmlReader; friend class MyMoneyGncReader; // check for sub object element; if it is, create the object GncObject *isSubElement(const QString &elName, const QXmlAttributes& elAttrs); // check for data element; if so, set data pointer bool isDataElement(const QString &elName, const QXmlAttributes& elAttrs); // process start element for 'this'; normally for attribute checking; other initialization done in constructor virtual void initiate(const QString&, const QXmlAttributes&) { return ; } // a sub object has completed; process the data it gathered virtual void endSubEl(GncObject *) { m_dataPtr = 0; return ; } // store data for data element void storeData(const QString& pData) { // NB - data MAY come in chunks, and may need to be anonymized if (m_dataPtr != 0) m_dataPtr->append(hide(pData, m_anonClass)); } // following is provided only for a future file anonymizer QString getData() const { return ((m_dataPtr != 0) ? *m_dataPtr : ""); } void resetDataPtr() { m_dataPtr = 0; } // process end element for 'this'; usually to convert to KMM format virtual void terminate() { return ; } void setVersion(const QString& v) { m_version = v; return; } QString version() const { return (m_version); } // some gnucash elements have version attribute; check it void checkVersion(const QString&, const QXmlAttributes&, const map_elementVersions&); // get name of element processed by 'this' QString getElName() const { return (m_elementName); } // pass 'main' pointer to object void setPm(MyMoneyGncReader *pM) { pMain = pM; } const QString getKvpValue(const QString& key, const QString& type = QString()) const; // debug only void debugDump(); // called by isSubElement to create appropriate sub object virtual GncObject *startSubEl() { return (0); } // called by isDataElement to set variable pointer virtual void dataEl(const QXmlAttributes&) { m_dataPtr = &(m_v[m_state]); m_anonClass = m_anonClassList[m_state]; } // return gnucash data string variable pointer virtual QString var(int i) const; // anonymize data virtual QString hide(QString, unsigned int); unsigned int kvpCount() const { return (m_kvpList.count()); } //! MyMoneyGncReader *pMain; // pointer to 'main' class // used at start of each transaction so same money hide factor is applied to all splits void adjustHideFactor(); QString m_elementName; // save 'this' element's name QString m_version; // and it's gnucash version const QString *m_subElementList; // list of sub object element names for 'this' unsigned int m_subElementListCount; // count of above const QString *m_dataElementList; // ditto for data elements unsigned int m_dataElementListCount; QString *m_dataPtr; // pointer to m_v variable for current data item mutable QList m_v; // storage for variable pointers unsigned int m_state; // effectively, the index to subElementList or dataElementList, whichever is currently in use const unsigned int *m_anonClassList; enum anonActions {ASIS, SUPPRESS, NXTACC, NXTEQU, NXTPAY, NXTSCHD, MAYBEQ, MONEY1, MONEY2}; // anonymize actions - see hide() unsigned int m_anonClass; // class of current data item for anonymizer static double m_moneyHideFactor; // a per-transaction factor QList m_kvpList; //! }; // ***************************************************************************** // This is the 'grandfather' object representing the gnucash file as a whole class GncFile : public GncObject { public: GncFile(); ~GncFile(); private: enum iSubEls {BOOK, COUNT, CMDTY, PRICE, ACCT, TX, TEMPLATES, SCHEDULES, END_FILE_SELS }; GncObject *startSubEl() final override; void endSubEl(GncObject *) final override; bool m_processingTemplates; // gnc uses same transaction element for ordinary and template tx's; this will distinguish bool m_bookFound; // to detect multi-book files }; // The following are 'utility' objects, which occur within several other object types // ************* GncKvp******************************************** // Key/value pairs, which are introduced by the 'slot' element // Consist of slot:key (the 'name' of the kvp), and slot:value (the data value) // the slot value also contains a slot type (string, integer, etc) implemented as an XML attribute // kvp's may be nested class GncKvp : public GncObject { public: GncKvp(); ~GncKvp(); //protected: friend class MyMoneyGncReader; QString key() const { return (var(KEY)); } QString value() const { return (var(VALUE)); } QString type() const { return (m_kvpType); } const GncKvp getKvp(unsigned int i) const { return (m_kvpList[i]); } private: // subsidiary objects/elements enum KvpSubEls {KVP, END_Kvp_SELS }; GncObject *startSubEl() final override; void endSubEl(GncObject *) final override; // data elements enum KvpDataEls {KEY, VALUE, END_Kvp_DELS }; void dataEl(const QXmlAttributes&) final override; QString m_kvpType; // type is an XML attribute }; // ************* GncLot******************************************** // KMM doesn't have support for lots as yet class GncLot : public GncObject { public: GncLot(); ~GncLot(); protected: friend class MyMoneyGncReader; private: }; // **************************************************************************** // commodity specification. consists of // cmdty:space - either ISO4217 if this cmdty is a currency, or, usually, the name of a stock exchange // cmdty:id - ISO4217 currency symbol, or 'ticker symbol' class GncCmdtySpec : public GncObject { public: GncCmdtySpec(); ~GncCmdtySpec(); protected: friend class MyMoneyGncReader; friend class GncTransaction; bool isCurrency() const { return (m_v[CMDTYSPC] == QString("ISO4217")); }; QString id() const { return (m_v[CMDTYID]); }; QString space() const { return (m_v[CMDTYSPC]); }; private: // data elements enum CmdtySpecDataEls {CMDTYSPC, CMDTYID, END_CmdtySpec_DELS}; QString hide(QString, unsigned int) final override; }; // ********************************************************************* // date; maybe one of two types, ts:date which is date/time, gdate which is date only // we do not preserve time data (at present) class GncDate : public GncObject { public: GncDate(); ~GncDate(); protected: friend class MyMoneyGncReader; friend class GncPrice; friend class GncTransaction; friend class GncSplit; friend class GncSchedule; friend class GncRecurrence; const QDate date() const { return (QDate::fromString(m_v[TSDATE].section(' ', 0, 0), Qt::ISODate)); }; private: // data elements enum DateDataEls {TSDATE, GDATE, END_Date_DELS}; void dataEl(const QXmlAttributes&) final override { m_dataPtr = &(m_v[TSDATE]); m_anonClass = GncObject::ASIS; } // treat both date types the same }; /** Following are the main objects within the gnucash file, which correspond largely one-for-one with similar objects in the kmymoney structure, apart from schedules which gnc splits between template (transaction data) and schedule (date data) */ //******************************************************************** class GncCountData : public GncObject { public: GncCountData(); ~GncCountData(); private: void initiate(const QString&, const QXmlAttributes&) final override; void terminate() final override; QString m_countType; // type of element being counted }; //******************************************************************** class GncCommodity : public GncObject { public: GncCommodity(); ~GncCommodity(); protected: friend class MyMoneyGncReader; // access data values bool isCurrency() const { return (var(SPACE) == QString("ISO4217")); } QString space() const { return (var(SPACE)); } QString id() const { return (var(ID)); } QString name() const { return (var(NAME)); } QString fraction() const { return (var(FRACTION)); } private: void terminate() final override; // data elements enum {SPACE, ID, NAME, FRACTION, END_Commodity_DELS}; }; // ************* GncPrice******************************************** class GncPrice : public GncObject { public: GncPrice(); ~GncPrice(); protected: friend class MyMoneyGncReader; // access data values const GncCmdtySpec *commodity() const { return (m_vpCommodity); } const GncCmdtySpec *currency() const { return (m_vpCurrency); } QString value() const { return (var(VALUE)); } QDate priceDate() const { return (m_vpPriceDate->date()); } private: void terminate() final override; // sub object elements enum PriceSubEls {CMDTY, CURR, PRICEDATE, END_Price_SELS }; GncObject *startSubEl() final override; void endSubEl(GncObject *) final override; // data elements enum PriceDataEls {VALUE, END_Price_DELS }; GncCmdtySpec *m_vpCommodity, *m_vpCurrency; GncDate *m_vpPriceDate; }; // ************* GncAccount******************************************** class GncAccount : public GncObject { public: GncAccount(); ~GncAccount(); protected: friend class MyMoneyGncReader; // access data values GncCmdtySpec *commodity() const { return (m_vpCommodity); } QString id() const { return (var(ID)); } QString name() const { return (var(NAME)); } QString desc() const { return (var(DESC)); } QString type() const { return (var(TYPE)); } QString parent() const { return (var(PARENT)); } private: // subsidiary objects/elements enum AccountSubEls {CMDTY, KVP, LOTS, END_Account_SELS }; GncObject *startSubEl() final override; void endSubEl(GncObject *) final override; void terminate() final override; // data elements enum AccountDataEls {ID, NAME, DESC, TYPE, PARENT, END_Account_DELS }; GncCmdtySpec *m_vpCommodity; }; // ************* GncSplit******************************************** class GncSplit : public GncObject { public: GncSplit(); ~GncSplit(); protected: friend class MyMoneyGncReader; // access data values QString id() const { return (var(ID)); } QString memo() const { return (var(MEMO)); } QString recon() const { return (var(RECON)); } QString value() const { return (var(VALUE)); } QString qty() const { return (var(QTY)); } QString acct() const { return (var(ACCT)); } const QDate reconDate() const { QDate x = QDate(); return (m_vpDateReconciled == NULL ? x : m_vpDateReconciled->date()); } private: // subsidiary objects/elements enum TransactionSubEls {RECDATE, END_Split_SELS }; GncObject *startSubEl() final override; void endSubEl(GncObject *) final override; // data elements enum SplitDataEls {ID, MEMO, RECON, VALUE, QTY, ACCT, END_Split_DELS }; GncDate *m_vpDateReconciled; }; // ************* GncTransaction******************************************** class GncTransaction : public GncObject { public: GncTransaction(bool processingTemplates); ~GncTransaction(); protected: friend class MyMoneyGncReader; // access data values QString id() const { return (var(ID)); } QString no() const { return (var(NO)); } QString desc() const { return (var(DESC)); } QString currency() const { return (m_vpCurrency == NULL ? QString() : m_vpCurrency->id()); } QDate dateEntered() const { return (m_vpDateEntered->date()); } QDate datePosted() const { return (m_vpDatePosted->date()); } bool isTemplate() const { return (m_template); } unsigned int splitCount() const { return (m_splitList.count()); } const GncObject *getSplit(unsigned int i) const { return (m_splitList.at(i)); } private: // subsidiary objects/elements enum TransactionSubEls {CURRCY, POSTED, ENTERED, SPLIT, KVP, END_Transaction_SELS }; GncObject *startSubEl() final override; void endSubEl(GncObject *) final override; void terminate() final override; const GncKvp getKvp(unsigned int i) const { return (m_kvpList.at(i)); } // data elements enum TransactionDataEls {ID, NO, DESC, END_Transaction_DELS }; GncCmdtySpec *m_vpCurrency; GncDate *m_vpDateEntered, *m_vpDatePosted; mutable QList m_splitList; bool m_template; // true if this is a template for scheduled transaction }; // ************* GncTemplateSplit******************************************** class GncTemplateSplit : public GncObject { public: GncTemplateSplit(); ~GncTemplateSplit(); protected: friend class MyMoneyGncReader; // access data values QString id() const { return (var(ID)); } QString memo() const { return (var(MEMO)); } QString recon() const { return (var(RECON)); } QString value() const { return (var(VALUE)); } QString qty() const { return (var(QTY)); } QString acct() const { return (var(ACCT)); } private: const GncKvp getKvp(unsigned int i) const { return (m_kvpList[i]); }; // subsidiary objects/elements enum TemplateSplitSubEls {KVP, END_TemplateSplit_SELS }; GncObject *startSubEl() final override; void endSubEl(GncObject *) final override; // data elements enum TemplateSplitDataEls {ID, MEMO, RECON, VALUE, QTY, ACCT, END_TemplateSplit_DELS }; }; // ************* GncSchedule******************************************** class GncFreqSpec; class GncRecurrence; class GncSchedDef; class GncSchedule : public GncObject { public: GncSchedule(); ~GncSchedule(); protected: friend class MyMoneyGncReader; // access data values QString name() const { return (var(NAME)); } QString enabled() const { return var(ENABLED); } QString autoCreate() const { return (var(AUTOC)); } QString autoCrNotify() const { return (var(AUTOCN)); } QString autoCrDays() const { return (var(AUTOCD)); } QString advCrDays() const { return (var(ADVCD)); } QString advCrRemindDays() const { return (var(ADVRD)); } QString instanceCount() const { return (var(INSTC)); } QString numOccurs() const { return (var(NUMOCC)); } QString remOccurs() const { return (var(REMOCC)); } QString templId() const { return (var(TEMPLID)); } QDate startDate() const { QDate x = QDate(); return (m_vpStartDate == NULL ? x : m_vpStartDate->date()); } QDate lastDate() const { QDate x = QDate(); return (m_vpLastDate == NULL ? x : m_vpLastDate->date()); } QDate endDate() const { QDate x = QDate(); return (m_vpEndDate == NULL ? x : m_vpEndDate->date()); } const GncFreqSpec *getFreqSpec() const { return (m_vpFreqSpec); } const GncSchedDef *getSchedDef() const { return (m_vpSchedDef); } private: // subsidiary objects/elements enum ScheduleSubEls {STARTDATE, LASTDATE, ENDDATE, FREQ, RECURRENCE, DEFINST, END_Schedule_SELS }; GncObject *startSubEl() final override; void endSubEl(GncObject *) final override; void terminate() final override; // data elements enum ScheduleDataEls {NAME, ENABLED, AUTOC, AUTOCN, AUTOCD, ADVCD, ADVRD, INSTC, NUMOCC, REMOCC, TEMPLID, END_Schedule_DELS }; GncDate *m_vpStartDate, *m_vpLastDate, *m_vpEndDate; GncFreqSpec *m_vpFreqSpec; mutable QList m_vpRecurrence; // gnc handles multiple occurrences GncSchedDef *m_vpSchedDef; }; // ************* GncFreqSpec******************************************** class GncFreqSpec : public GncObject { public: GncFreqSpec(); ~GncFreqSpec(); protected: friend class MyMoneyGncReader; // access data values (only interval type used at present) QString intervalType() const { return (var(INTVT)); } private: // subsidiary objects/elements enum FreqSpecSubEls {COMPO, END_FreqSpec_SELS }; GncObject *startSubEl() final override; void endSubEl(GncObject *) final override; // data elements enum FreqSpecDataEls {INTVT, MONTHLY, DAILY, WEEKLY, INTVI, INTVO, INTVD, END_FreqSpec_DELS}; void terminate() final override; mutable QList m_fsList; }; // ************* GncRecurrence******************************************** // this object replaces GncFreqSpec from Gnucash 2.2 onwards class GncRecurrence : public GncObject { public: GncRecurrence(); ~GncRecurrence(); protected: friend class MyMoneyGncReader; // access data values QDate startDate() const { QDate x = QDate(); return (m_vpStartDate == NULL ? x : m_vpStartDate->date()); } QString mult() const { return (var(MULT)); } QString periodType() const { return (var(PERIODTYPE)); } QString getFrequency() const; private: // subsidiary objects/elements enum RecurrenceSubEls {STARTDATE, END_Recurrence_SELS }; GncObject *startSubEl() final override; void endSubEl(GncObject *) final override; // data elements enum RecurrenceDataEls {MULT, PERIODTYPE, END_Recurrence_DELS}; void terminate() final override; GncDate *m_vpStartDate; }; // ************* GncSchedDef******************************************** // This is a sub-object of GncSchedule, (sx:deferredInstance) function currently unknown class GncSchedDef : public GncObject { public: GncSchedDef(); ~GncSchedDef(); protected: friend class MyMoneyGncReader; private: // subsidiary objects/elements }; // **************************************************************************************** /** XML Reader The XML reader is an implementation of the Qt SAX2 XML parser. It determines the type of object represented by the XMl, and calls the appropriate object functions */ // ***************************************************************************************** class XmlReader : public QXmlDefaultHandler { protected: friend class MyMoneyGncReader; XmlReader(MyMoneyGncReader *pM); // keep pointer to 'main' void processFile(QIODevice*); // main entry point of reader // define xml content handler functions bool startDocument() final override; bool startElement(const QString&, const QString&, const QString&, const QXmlAttributes&) final override; bool endElement(const QString&, const QString&, const QString&) final override; bool characters(const QString &) final override; bool endDocument() final override; private: QXmlInputSource *m_source; QXmlSimpleReader *m_reader; QStack m_os; // stack of sub objects GncObject *m_co; // current object, for ease of coding (=== m_os.top) MyMoneyGncReader *pMain; // the 'main' pointer, to pass on to objects bool m_headerFound; // check for gnc-v2 header #ifdef _GNCFILEANON int lastType; // 0 = start element, 1 = data, 2 = end element int indentCount; #endif // _GNCFILEANON }; /** MyMoneyGncReader - Main class for this module Controls overall operation of the importer */ #ifndef _GNCFILEANON class MyMoneyGncReader : public IMyMoneyOperationsFormat { #else class MyMoneyGncReader { #endif // _GNCFILEANON public: MyMoneyGncReader(); - virtual ~MyMoneyGncReader(); + ~MyMoneyGncReader() override; /** * Import a GnuCash XML file * * @param pDevice : pointer to GnuCash file * @param storage : pointer to MyMoneySerialize storage * * @return void * */ #ifndef _GNCFILEANON void readFile(QIODevice* pDevice, MyMoneyStorageMgr *storage) final override; // main entry point, IODevice is gnucash file void writeFile(QIODevice*, MyMoneyStorageMgr*) final override { return ; } // dummy entry needed by kmymoneywiew. we will not be writing + void setProgressCallback(void(*callback)(int, int, const QString&)) final override; #else void readFile(QString, QString); #endif // _GNCFILEANON QTextCodec *m_decoder; protected: friend class GncObject; // pity we can't just say GncObject. And compiler doesn't like multiple friends on one line... friend class GncFile; // there must be a better way... friend class GncDate; friend class GncCmdtySpec; friend class GncKvp; friend class GncLot; friend class GncCountData; friend class GncCommodity; friend class GncPrice; friend class GncAccount; friend class GncTransaction; friend class GncSplit; friend class GncTemplateTransaction; friend class GncTemplateSplit; friend class GncSchedule; friend class GncFreqSpec; friend class GncRecurrence; friend class XmlReader; #ifndef _GNCFILEANON /** functions to convert gnc objects to our equivalent */ void convertCommodity(const GncCommodity *); void convertPrice(const GncPrice *); void convertAccount(const GncAccount *); void convertTransaction(const GncTransaction *); void convertSplit(const GncSplit *); void saveTemplateTransaction(GncTransaction *t) { m_templateList.append(t); }; void convertSchedule(const GncSchedule *); void convertFreqSpec(const GncFreqSpec *); void convertRecurrence(const GncRecurrence *); #else /** functions to convert gnc objects to our equivalent */ void convertCommodity(const GncCommodity *) { return; }; void convertPrice(const GncPrice *) { return; }; void convertAccount(const GncAccount *) { return; }; void convertTransaction(const GncTransaction *) { return; }; void convertSplit(const GncSplit *) { return; }; void saveTemplateTransaction(GncTransaction *t) { return; }; void convertSchedule(const GncSchedule *) { return; }; void convertFreqSpec(const GncFreqSpec *) { return; }; #endif // _GNCFILEANON /** to post messages for final report */ void postMessage(const QString&, const unsigned int, const char *); void postMessage(const QString&, const unsigned int, const char *, const char *); void postMessage(const QString&, const unsigned int, const char *, const char *, const char *); void postMessage(const QString&, const unsigned int, const QStringList&); - void setProgressCallback(void(*callback)(int, int, const QString&)) final override; void signalProgress(int current, int total, const QString& = ""); /** user options */ /** Scheduled Transactions Due to differences in implementation, it is not always possible to import scheduled transactions correctly. Though best efforts are made, it may be that some imported transactions cause problems within kmymoney. An attempt is made within the importer to identify potential problem transactions, and setting this option will cause them to be dropped from the file. A report of which were dropped, and why, will be produced. m_dropSuspectSchedules - drop suspect scheduled transactions */ bool m_dropSuspectSchedules; /** Investments In kmymoney, all accounts representing investments (stocks, shares, bonds, etc.) must have an associated investment account (e.g. a broker account). The stock account holds the share balance, the investment account a money balance. Gnucash does not do this, so we cannot automate this function. If you have investments, you must select one of the following options. 0 - create a separate investment account for each stock with the same name as the stock 1 - create a single investment account to hold all stocks - you will be asked for a name 2 - create multiple investment accounts - you will be asked for a name for each stock N.B. :- option 2 doesn't really work quite as desired at present */ unsigned int m_investmentOption; /** Online quotes The user has the option to use the Finance::Quote system, as used by GnuCash, to retrieve online share price quotes */ bool m_useFinanceQuote; /** Tx Notes handling Under some usage conditions, non-split GnuCash transactions may contain residual, usually incorrect, memo data which is not normally visible to the user. When imported into KMyMoney however, due to display differences, this data can become visible. Often, these transactions will have a Notes field describing the real purpose of the transaction. If this option is selected, these notes, if present, will be used to override the extraneous memo data." */ bool m_useTxNotes; // set gnucash counts (not always accurate!) void setGncCommodityCount(int i) { m_gncCommodityCount = i; } void setGncAccountCount(int i) { m_gncAccountCount = i; } void setGncTransactionCount(int i) { m_gncTransactionCount = i; } void setGncScheduleCount(int i) { m_gncScheduleCount = i; } void setSmallBusinessFound(bool b) { m_smallBusinessFound = b; } void setBudgetsFound(bool b) { m_budgetsFound = b; } void setLotsFound(bool b) { m_lotsFound = b; } /* Debug Options If you don't know what these are, best leave them alone. gncdebug - produce general debug messages xmldebug - produce a trace of the gnucash file XML bAnonymize - hide personal data (account names, payees, etc., randomize money amounts) */ bool gncdebug; // general debug messages bool xmldebug; // xml trace bool bAnonymize; // anonymize input static double m_fileHideFactor; // an overall anonymization factor to be applied to all items bool developerDebug; private: void setOptions(); // to set user options from dialog void setFileHideFactor(); // the following handles the gnucash indicator for a bad value (-1/0) which causes us probs QString convBadValue(QString gncValue) const { return (gncValue == "-1/0" ? "0/1" : gncValue); } #ifndef _GNCFILEANON MyMoneyTransaction convertTemplateTransaction(const QString&, const GncTransaction *); void convertTemplateSplit(const QString&, const GncTemplateSplit *); #endif // _GNCFILEANON // wind up when all done void terminate(); QString buildReportSection(const QString&); bool writeReportToFile(const QList&); // main storage #ifndef _GNCFILEANON MyMoneyStorageMgr *m_storage; #else QTextStream oStream; #endif // _GNCFILEANON XmlReader *m_xr; /** to hold the callback pointer for the progress bar */ void (*m_progressCallback)(int, int, const QString&); // a map of which versions of the various elements (objects) we can import map_elementVersions m_versionList; // counters holding count data from the Gnc 'count-data' section int m_gncCommodityCount; int m_gncAccountCount; int m_gncTransactionCount; int m_gncScheduleCount; // flags indicating detection of features not (yet?) supported bool m_smallBusinessFound; bool m_budgetsFound; bool m_lotsFound; /** counters for reporting */ int m_commodityCount; int m_priceCount; int m_accountCount; int m_transactionCount; int m_templateCount; int m_scheduleCount; #ifndef _GNCFILEANON // counters for error reporting int m_ccCount, m_orCount, m_scCount; // currency counter QMap m_currencyCount; /** * Map gnucash vs. Kmm ids for accounts, equities, schedules, price sources */ QMap m_mapIds; QString m_rootId; // save the root id for terminate() QMap m_mapEquities; QMap m_mapSchedules; QMap m_mapSources; /** * A list of stock accounts (gnc ids) which will be held till the end so we can implement the user's investment option */ QList m_stockList; /** * Temporary storage areas for transaction processing */ QString m_txCommodity; // save commodity for current transaction QString m_txPayeeId; // gnc has payee at tx level, we need it at split level QDate m_txDatePosted; // ditto for post date QString m_txChequeNo; // ditto for cheque number /** In kmm, the order of splits is critical to some operations. These * areas will hold the splits until we've read them all */ QList m_splitList, m_liabilitySplitList, m_otherSplitList; bool m_potentialTransfer; // to determine whether this might be a transfer /** Schedules are processed through 3 different functions, any of which may set this flag */ bool m_suspectSchedule; /** * A holding area for template txs while we're waiting for the schedules */ QList m_templateList; /** Hold a list of suspect schedule ids for later processing? */ QList m_suspectList; /** * To hold message data till final report */ QMap m_messageList; /** * Internal utility functions */ QString createPayee(const QString&); // create a payee and return it's id QString createOrphanAccount(const QString&); // create unknown account and return the id QDate incrDate(QDate lastDate, unsigned char interval, unsigned int intervalCount); // for date calculations MyMoneyAccount checkConsistency(MyMoneyAccount& parent, MyMoneyAccount& child); // gnucash is sometimes TOO flexible void checkInvestmentOption(QString stockId); // implement user investment option void getPriceSource(MyMoneySecurity stock, QString gncSource); /** * This method loads all known currencies and saves them to the storage */ void loadAllCurrencies(); #endif // _GNCFILEANON }; #endif // MYMONEYGNCREADER_H diff --git a/kmymoney/plugins/icalendar/export/icalendarexporter.cpp b/kmymoney/plugins/icalendar/export/icalendarexporter.cpp index 8d1f74875..681ad06d4 100644 --- a/kmymoney/plugins/icalendar/export/icalendarexporter.cpp +++ b/kmymoney/plugins/icalendar/export/icalendarexporter.cpp @@ -1,142 +1,145 @@ /*************************************************************************** * Copyright 2009 Cristian Onet onet.cristian@gmail.com * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see * ***************************************************************************/ #include "icalendarexporter.h" #include #include #include // KDE includes #include #include #include #include #include // KMyMoney includes #include "mymoneyfile.h" #include "pluginloader.h" #include "schedulestoicalendar.h" #include "pluginsettings.h" +#include "viewinterface.h" struct iCalendarExporter::Private { QAction* m_action; QString m_profileName; QString m_iCalendarFileEntryName; KMMSchedulesToiCalendar m_exporter; }; iCalendarExporter::iCalendarExporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "icalendarexporter"/*must be the same as X-KDE-PluginInfo-Name*/), d(std::unique_ptr(new Private)) { Q_UNUSED(args); d->m_profileName = "iCalendarPlugin"; d->m_iCalendarFileEntryName = "iCalendarFile"; // Tell the host application to load my GUI component setComponentName("icalendarexporter", i18n("iCalendar exporter")); setXMLFile("icalendarexporter.rc"); // For ease announce that we have been loaded. qDebug("Plugins: icalendarexporter loaded"); // Create the actions of this plugin QString actionName = i18n("Schedules to iCalendar"); QString icalFilePath; // Note the below code only exists to move existing settings to the new plugin specific config KConfigGroup config = KSharedConfig::openConfig()->group(d->m_profileName); icalFilePath = config.readEntry(d->m_iCalendarFileEntryName, icalFilePath); // read the settings PluginSettings::self()->load(); if (!icalFilePath.isEmpty()) { // move the old setting to the new config PluginSettings::setIcalendarFile(icalFilePath); PluginSettings::self()->save(); KSharedConfig::openConfig()->deleteGroup(d->m_profileName); } else { // read it from the new config icalFilePath = PluginSettings::icalendarFile(); } if (!icalFilePath.isEmpty()) actionName = i18n("Schedules to iCalendar [%1]", icalFilePath); - d->m_action = actionCollection()->addAction("file_export_icalendar"); + const auto &kpartgui = QStringLiteral("file_export_icalendar"); + d->m_action = actionCollection()->addAction(kpartgui); d->m_action->setText(actionName); connect(d->m_action, &QAction::triggered, this, &iCalendarExporter::slotFirstExport); + connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } iCalendarExporter::~iCalendarExporter() { qDebug("Plugins: icalendarexporter unloaded"); } void iCalendarExporter::slotFirstExport() { QPointer fileDialog = new QFileDialog(d->m_action->parentWidget(), QString(), QString(), QString("%1|%2\n").arg("*.ics").arg(i18nc("ICS (Filefilter)", "iCalendar files"))); fileDialog->setAcceptMode(QFileDialog::AcceptSave); fileDialog->setWindowTitle(i18n("Export as")); if (fileDialog->exec() == QDialog::Accepted) { QUrl newURL = fileDialog->selectedUrls().front(); if (newURL.isLocalFile()) { PluginSettings::setIcalendarFile(newURL.toLocalFile()); PluginSettings::self()->save(); slotExport(); } } delete fileDialog; } void iCalendarExporter::slotExport() { QString icalFilePath = PluginSettings::icalendarFile(); if (!icalFilePath.isEmpty()) d->m_exporter.exportToFile(icalFilePath); } void iCalendarExporter::plug() { connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &iCalendarExporter::slotExport); } void iCalendarExporter::unplug() { disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &iCalendarExporter::slotExport); } void iCalendarExporter::configurationChanged() { PluginSettings::self()->load(); // export the schedules because the configuration has changed QString icalFilePath = PluginSettings::icalendarFile(); if (!icalFilePath.isEmpty()) d->m_exporter.exportToFile(icalFilePath); } K_PLUGIN_FACTORY_WITH_JSON(iCalendarExporterFactory, "icalendarexporter.json", registerPlugin();) #include "icalendarexporter.moc" diff --git a/kmymoney/plugins/icalendar/export/schedulestoicalendar.cpp b/kmymoney/plugins/icalendar/export/schedulestoicalendar.cpp index 955393651..c0853f92e 100644 --- a/kmymoney/plugins/icalendar/export/schedulestoicalendar.cpp +++ b/kmymoney/plugins/icalendar/export/schedulestoicalendar.cpp @@ -1,383 +1,386 @@ /*************************************************************************** * Copyright 2009 Cristian Onet onet.cristian@gmail.com * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see * ***************************************************************************/ #include "schedulestoicalendar.h" #include #include #include // KDE includes #include #include #include // libical includes #include // KMyMoney includes #include "mymoneymoney.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneyutils.h" #include "mymoneyschedule.h" #include "mymoneyaccount.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneypayee.h" #include "mymoneyenums.h" // plugin includes #include "pluginsettings.h" using namespace eMyMoney; int timeUnitsInSeconds(int optionValue) { // see how the items are added in the combobox of the settings editor widget static const int minute = 0; static const int hour = 1; static const int day = 2; switch (optionValue) { case minute: return 60; case hour: return 60*60; case day: return 24*60*60; default: return 1; } } int beforeAfterToInt(int optionValue) { // see how the items are added in the combobox of the settings editor widget static const int before = 0; static const int after = 1; switch (optionValue) { case before: return -1; case after: return 1; default: return -1; } } struct icaltimetype qdateToIcalTimeType(const QDate& date) { struct icaltimetype icalDate = icaltime_null_date(); icalDate.year = date.year(); icalDate.month = date.month(); icalDate.day = date.day(); icalDate.is_date = 1; return icalDate; } struct icaltimetype qdateTimeToIcalTimeType(const QDateTime& dateTime) { struct icaltimetype icalDateTime = icaltime_null_date(); icalDateTime.year = dateTime.date().year(); icalDateTime.month = dateTime.date().month(); icalDateTime.day = dateTime.date().day(); icalDateTime.hour = dateTime.time().hour(); icalDateTime.minute = dateTime.time().minute(); icalDateTime.second = dateTime.time().second(); icalDateTime.is_date = 0; return icalDateTime; } struct icalrecurrencetype scheduleToRecurenceRule(const MyMoneySchedule& schedule) { struct icalrecurrencetype recurrence; icalrecurrencetype_clear(&recurrence); if (schedule.willEnd()) recurrence.until = qdateToIcalTimeType(schedule.endDate()); recurrence.week_start = icalrecurrencetype_day_day_of_week(QLocale().firstDayOfWeek()); int frequencyFactor = 1; // used to translate kmymoney frequency to icalendar frequency switch (schedule.occurrence()) { case Schedule::Occurrence::Daily: recurrence.freq = ICAL_DAILY_RECURRENCE; break; case Schedule::Occurrence::Weekly: recurrence.freq = ICAL_WEEKLY_RECURRENCE; break; case Schedule::Occurrence::Fortnightly: recurrence.freq = ICAL_WEEKLY_RECURRENCE; frequencyFactor = 2; break; case Schedule::Occurrence::EveryOtherWeek: recurrence.freq = ICAL_WEEKLY_RECURRENCE; frequencyFactor = 2; break; case Schedule::Occurrence::EveryHalfMonth: recurrence.freq = ICAL_WEEKLY_RECURRENCE; frequencyFactor = 2; break; case Schedule::Occurrence::EveryThreeWeeks: recurrence.freq = ICAL_WEEKLY_RECURRENCE; frequencyFactor = 3; break; case Schedule::Occurrence::EveryThirtyDays: recurrence.freq = ICAL_DAILY_RECURRENCE; frequencyFactor = 30; break; case Schedule::Occurrence::Monthly: recurrence.freq = ICAL_MONTHLY_RECURRENCE; break; case Schedule::Occurrence::EveryFourWeeks: recurrence.freq = ICAL_WEEKLY_RECURRENCE; frequencyFactor = 4; break; case Schedule::Occurrence::EveryEightWeeks: recurrence.freq = ICAL_WEEKLY_RECURRENCE; frequencyFactor = 8; break; case Schedule::Occurrence::EveryOtherMonth: recurrence.freq = ICAL_MONTHLY_RECURRENCE; frequencyFactor = 2; break; case Schedule::Occurrence::EveryThreeMonths: recurrence.freq = ICAL_MONTHLY_RECURRENCE; frequencyFactor = 3; break; case Schedule::Occurrence::TwiceYearly: recurrence.freq = ICAL_MONTHLY_RECURRENCE; frequencyFactor = 6; break; case Schedule::Occurrence::EveryOtherYear: recurrence.freq = ICAL_YEARLY_RECURRENCE; frequencyFactor = 2; break; case Schedule::Occurrence::Quarterly: recurrence.freq = ICAL_MONTHLY_RECURRENCE; frequencyFactor = 3; break; case Schedule::Occurrence::EveryFourMonths: recurrence.freq = ICAL_MONTHLY_RECURRENCE; frequencyFactor = 4; break; case Schedule::Occurrence::Yearly: recurrence.freq = ICAL_YEARLY_RECURRENCE; break; case Schedule::Occurrence::Once: case Schedule::Occurrence::Any: default: qWarning() << "Once, any or unknown recurrence returned recurrence is invalid" << endl; recurrence.freq = ICAL_NO_RECURRENCE; break; } recurrence.interval = frequencyFactor*schedule.occurrenceMultiplier(); return recurrence; } QString scheduleToDescription(const MyMoneySchedule& schedule) { auto file = MyMoneyFile::instance(); const MyMoneyAccount& account = schedule.account(); const MyMoneyTransaction& transaction = schedule.transaction(); QString payeeName; MyMoneyMoney amount; QString category; bool isTransfer = false; bool isIncome = false; foreach (const auto split, transaction.splits()) { if (split.accountId() != account.id()) { if (!category.isEmpty()) category += ", "; // this is a split transaction const MyMoneyAccount& splitAccount = file->account(split.accountId()); category = splitAccount.name(); isTransfer = splitAccount.accountGroup() == Account::Type::Asset || splitAccount.accountGroup() == Account::Type::Liability; isIncome = splitAccount.accountGroup() == Account::Type::Income; } else { payeeName = file->payee(split.payeeId()).name(); // make the amount positive since the message makes it clear if this is an income or expense amount = split.shares().abs(); } } QString description = isTransfer ? i18n("Transfer from %1 to %2, Payee %3, amount %4", account.name(), category, payeeName, MyMoneyUtils::formatMoney(amount, file->currency(account.currencyId()))) : ( isIncome ? i18n("From %1 into %2, Category %3, sum of %4", payeeName, account.name(), category, MyMoneyUtils::formatMoney(amount, file->currency(account.currencyId()))) : i18n("From account %1, Pay to %2, Category %3, sum of %4", account.name(), payeeName, category, MyMoneyUtils::formatMoney(amount, file->currency(account.currencyId()))) ); if (!transaction.memo().isEmpty()) description = i18nc("The first string is the schedules details", "%1, memo %2", description, transaction.memo()); return description; } struct KMMSchedulesToiCalendar::Private { QString m_icalendarAsString; }; KMMSchedulesToiCalendar::KMMSchedulesToiCalendar() : d(new Private) { } KMMSchedulesToiCalendar::~KMMSchedulesToiCalendar() { delete d; } void KMMSchedulesToiCalendar::exportToFile(const QString& filePath, bool settingsChaged) { + if (!MyMoneyFile::instance()->storageAttached()) + return; + QFile icsFile(filePath); icsFile.open(QIODevice::ReadOnly); QTextStream stream(&icsFile); d->m_icalendarAsString = stream.readAll(); icsFile.close(); // create the calendar bool newCalendar = false; icalcomponent* vCalendar = 0; if (d->m_icalendarAsString.isEmpty()) { newCalendar = true; vCalendar = icalcomponent_new_vcalendar(); } else { vCalendar = icalcomponent_new_from_string(d->m_icalendarAsString.toUtf8()); if (vCalendar == 0) { qDebug() << "Error parsing the following string into an icalendar:" << endl; qDebug() << d->m_icalendarAsString << endl; qDebug() << "so we will overwrite this with a new calendar" << endl; newCalendar = true; vCalendar = icalcomponent_new_vcalendar(); } } if (vCalendar == 0) { // one way or the other we must have a calendar by now qDebug() << "Unable to create vcalendar component" << endl; return; } if (newCalendar) { // set proid and version icalcomponent_add_property(vCalendar, icalproperty_new_prodid("icalendarexport"));; icalcomponent_add_property(vCalendar, icalproperty_new_version("2.0")); } // export schedules as TODOs auto file = MyMoneyFile::instance(); QList schedules = file->scheduleList(); for (QList::const_iterator itSchedule = schedules.constBegin(); itSchedule != schedules.constEnd(); ++itSchedule) { const MyMoneySchedule& myMoneySchedule = *itSchedule; if (myMoneySchedule.isFinished()) continue; // skip this schedule if it is already finished icalcomponent* schedule = 0; bool newTodo = false; if (!newCalendar) { // try to find the schedule to update it if we do not use a new calendar icalcomponent* itVTODO = icalcomponent_get_first_component(vCalendar, ICAL_VTODO_COMPONENT); for (; itVTODO != 0; itVTODO = icalcomponent_get_next_component(vCalendar, ICAL_VTODO_COMPONENT)) { if (icalcomponent_get_uid(itVTODO) == myMoneySchedule.id()) { // we found our todo stop searching schedule = itVTODO; break; } } if (schedule == 0) { schedule = icalcomponent_new_vtodo(); newTodo = true; } } else { schedule = icalcomponent_new_vtodo(); newTodo = true; } // description icalcomponent_set_description(schedule, scheduleToDescription(myMoneySchedule).toUtf8()); // summary icalcomponent_set_summary(schedule, myMoneySchedule.name().toUtf8()); // uid icalcomponent_set_uid(schedule, myMoneySchedule.id().toUtf8()); // dtstart icalcomponent_set_dtstart(schedule, qdateToIcalTimeType(myMoneySchedule.startDate())); // due icalcomponent_set_due(schedule, qdateToIcalTimeType(myMoneySchedule.nextDueDate())); if (newTodo) { // created icalcomponent_add_property(schedule, icalproperty_new_created(qdateTimeToIcalTimeType(QDateTime::currentDateTime()))); } else { // last modified icalproperty* pLastMod = icalcomponent_get_first_property(schedule, ICAL_LASTMODIFIED_PROPERTY); if (pLastMod != 0) { // set the current property icalproperty_set_lastmodified(pLastMod, qdateTimeToIcalTimeType(QDateTime::currentDateTime())); } else { // create a new property icalcomponent_add_property(schedule, icalproperty_new_lastmodified(qdateTimeToIcalTimeType(QDateTime::currentDateTime()))); } } // recurrence icalproperty* pRRule = icalcomponent_get_first_property(schedule, ICAL_RRULE_PROPERTY); if (pRRule != 0) { icalcomponent_remove_property(schedule, pRRule); } if (myMoneySchedule.occurrence() != Schedule::Occurrence::Once && myMoneySchedule.occurrence() != Schedule::Occurrence::Any) icalcomponent_add_property(schedule, icalproperty_new_rrule(scheduleToRecurenceRule(myMoneySchedule))); icalcomponent* oldAlarm = icalcomponent_get_first_component(schedule, ICAL_VALARM_COMPONENT); if (oldAlarm && settingsChaged) icalcomponent_remove_component(schedule, oldAlarm); if (PluginSettings::createAlarm() && (!oldAlarm || settingsChaged)) { // alarm: beginning wiht one day before the todo is due every one hour icalcomponent* alarm = icalcomponent_new_valarm(); // alarm: action icalcomponent_add_property(alarm, icalproperty_new_action(ICAL_ACTION_DISPLAY)); // alarm: description icalcomponent_set_description(alarm, scheduleToDescription(myMoneySchedule).toUtf8()); // alarm: trigger int triggerInterval = beforeAfterToInt(PluginSettings::beforeAfter()) * PluginSettings::timeUnits() * timeUnitsInSeconds(PluginSettings::timeUnitInSeconds()); icalcomponent_add_property(alarm, icalproperty_new_trigger(icaltriggertype_from_int(triggerInterval))); // alarm: duration int intervalBetweenReminders = PluginSettings::intervalBetweenRemindersTimeUnits() * timeUnitsInSeconds(PluginSettings::intervalBetweenRemindersTimeUnitInSeconds()); icalcomponent_set_duration(alarm, icaldurationtype_from_int(intervalBetweenReminders)); if (PluginSettings::repeatingReminders()) { // alarm: repeat icalcomponent_add_property(alarm, icalproperty_new_repeat(PluginSettings::numberOfReminders())); } // add the alarm to the schedule icalcomponent_add_component(schedule, alarm); } // add the schedule to the caledar if (newTodo) icalcomponent_add_component(vCalendar, schedule); } icsFile.open(QIODevice::WriteOnly); d->m_icalendarAsString = QString::fromUtf8(icalcomponent_as_ical_string(vCalendar)); // reclaim some memory :) icalcomponent_free(vCalendar); // write the calendar to the file stream << d->m_icalendarAsString << endl; icsFile.close(); } diff --git a/kmymoney/plugins/interfaces/kmmappinterface.cpp b/kmymoney/plugins/interfaces/kmmappinterface.cpp index 69bc277ba..4091fd81b 100644 --- a/kmymoney/plugins/interfaces/kmmappinterface.cpp +++ b/kmymoney/plugins/interfaces/kmmappinterface.cpp @@ -1,112 +1,102 @@ /*************************************************************************** kmmappinterface.cpp ------------------- begin : Mon Apr 14 2008 copyright : (C) 2008 Thomas Baumgart email : ipwizard@users.sourceforge.net (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kmmappinterface.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "kmymoney.h" KMyMoneyPlugin::KMMAppInterface::KMMAppInterface(KMyMoneyApp* app, QObject* parent, const char* name) : AppInterface(parent, name), m_app(app) { connect(m_app, &KMyMoneyApp::kmmFilePlugin, this, &AppInterface::kmmFilePlugin); } bool KMyMoneyPlugin::KMMAppInterface::fileOpen() { return m_app->fileOpen(); } bool KMyMoneyPlugin::KMMAppInterface::isDatabase() { return m_app->isDatabase(); } bool KMyMoneyPlugin::KMMAppInterface::isNativeFile() { return m_app->isNativeFile(); } QUrl KMyMoneyPlugin::KMMAppInterface::filenameURL() const { return m_app->filenameURL(); } void KMyMoneyPlugin::KMMAppInterface::writeFilenameURL(const QUrl &url) { m_app->writeFilenameURL(url); } QUrl KMyMoneyPlugin::KMMAppInterface::lastOpenedURL() { return m_app->lastOpenedURL(); } void KMyMoneyPlugin::KMMAppInterface::writeLastUsedFile(const QString& fileName) { m_app->writeLastUsedFile(fileName); } void KMyMoneyPlugin::KMMAppInterface::slotFileOpenRecent(const QUrl &url) { m_app->slotFileOpenRecent(url); } void KMyMoneyPlugin::KMMAppInterface::addToRecentFiles(const QUrl& url) { m_app->addToRecentFiles(url); } -void KMyMoneyPlugin::KMMAppInterface::updateCaption(bool skipActions) -{ - m_app->updateCaption(skipActions); -} - -QTimer* KMyMoneyPlugin::KMMAppInterface::autosaveTimer() -{ - return m_app->autosaveTimer(); -} - KMyMoneyAppCallback KMyMoneyPlugin::KMMAppInterface::progressCallback() { return m_app->progressCallback(); } void KMyMoneyPlugin::KMMAppInterface::writeLastUsedDir(const QString &directory) { m_app->writeLastUsedDir(directory); } QString KMyMoneyPlugin::KMMAppInterface::readLastUsedDir() const { return m_app->readLastUsedDir(); } void KMyMoneyPlugin::KMMAppInterface::consistencyCheck(bool alwaysDisplayResult) { m_app->consistencyCheck(alwaysDisplayResult); } diff --git a/kmymoney/plugins/interfaces/kmmappinterface.h b/kmymoney/plugins/interfaces/kmmappinterface.h index 1af3cded9..dd4bf1eb0 100644 --- a/kmymoney/plugins/interfaces/kmmappinterface.h +++ b/kmymoney/plugins/interfaces/kmmappinterface.h @@ -1,79 +1,77 @@ /*************************************************************************** kmmappinterface.h ------------------- begin : Mon Apr 14 2008 copyright : (C) 2008 Thomas Baumgart email : ipwizard@users.sourceforge.net (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KMMAPPINTERFACE_H #define KMMAPPINTERFACE_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "appinterface.h" class KMyMoneyApp; namespace KMyMoneyPlugin { /** * This class represents the implementation of the * AppInterface. */ class KMMAppInterface : public AppInterface { Q_OBJECT public: explicit KMMAppInterface(KMyMoneyApp* app, QObject* parent, const char* name = 0); ~KMMAppInterface() override = default; /** * Makes sure that a MyMoneyFile is open and has been created successfully. * * @return Whether the file is open and initialised */ bool fileOpen() override; bool isDatabase() override; bool isNativeFile() override; QUrl filenameURL() const override; void writeFilenameURL(const QUrl &url) override; QUrl lastOpenedURL() override; void writeLastUsedFile(const QString& fileName) override; void slotFileOpenRecent(const QUrl &url) override; void addToRecentFiles(const QUrl& url) override; - void updateCaption(bool skipActions = false) override; - QTimer* autosaveTimer() override; KMyMoneyAppCallback progressCallback() override; void writeLastUsedDir(const QString &directory) override; QString readLastUsedDir() const override; void consistencyCheck(bool alwaysDisplayResult) override; private: KMyMoneyApp* m_app; }; } #endif diff --git a/kmymoney/plugins/kmymoneyplugin.cpp b/kmymoney/plugins/kmymoneyplugin.cpp index 9aaa17236..5a8140e09 100644 --- a/kmymoney/plugins/kmymoneyplugin.cpp +++ b/kmymoney/plugins/kmymoneyplugin.cpp @@ -1,119 +1,114 @@ /*************************************************************************** kmymoneyplugin.cpp ------------------- begin : Wed Jan 5 2005 copyright : (C) 2005 Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kmymoneyplugin.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "interfaceloader.h" KMyMoneyPlugin::Container pPlugins; KMyMoneyPlugin::Plugin::Plugin(QObject* parent, const char* name) : QObject(), KXMLGUIClient() { Q_UNUSED(parent) setObjectName(name); } KMyMoneyPlugin::Plugin::~Plugin() { } void KMyMoneyPlugin::Plugin::plug() { } void KMyMoneyPlugin::Plugin::unplug() { } void KMyMoneyPlugin::Plugin::configurationChanged() { } KToggleAction* KMyMoneyPlugin::Plugin::toggleAction(const QString& actionName) const { static KToggleAction dummyAction(QString("Dummy"), 0); KToggleAction* p = dynamic_cast(actionCollection()->action(QString(actionName.toLatin1()))); if (!p) { qWarning("Action '%s' is not of type KToggleAction", qPrintable(actionName)); p = &dummyAction; } qWarning("Action with name '%s' not found!", qPrintable(actionName)); return p; } KMyMoneyPlugin::OnlinePlugin::OnlinePlugin() { } KMyMoneyPlugin::OnlinePlugin::~OnlinePlugin() { } KMyMoneyPlugin::AppInterface* KMyMoneyPlugin::Plugin::appInterface() const { Q_CHECK_PTR(KMyMoneyPlugin::pluginInterfaces().appInterface); return KMyMoneyPlugin::pluginInterfaces().appInterface; } KMyMoneyPlugin::ViewInterface* KMyMoneyPlugin::Plugin::viewInterface() const { Q_CHECK_PTR(KMyMoneyPlugin::pluginInterfaces().viewInterface); return KMyMoneyPlugin::pluginInterfaces().viewInterface; } KMyMoneyPlugin::StatementInterface* KMyMoneyPlugin::Plugin::statementInterface() const { Q_CHECK_PTR(KMyMoneyPlugin::pluginInterfaces().statementInterface); return KMyMoneyPlugin::pluginInterfaces().statementInterface; } KMyMoneyPlugin::ImportInterface* KMyMoneyPlugin::Plugin::importInterface() const { Q_CHECK_PTR(KMyMoneyPlugin::pluginInterfaces().importInterface); return KMyMoneyPlugin::pluginInterfaces().importInterface; } KMyMoneyPlugin::ImporterPlugin::ImporterPlugin() { } KMyMoneyPlugin::ImporterPlugin::~ImporterPlugin() { } - -IMyMoneyOperationsFormat* KMyMoneyPlugin::StoragePlugin::reader() -{ - return nullptr; -} diff --git a/kmymoney/plugins/kmymoneyplugin.h b/kmymoney/plugins/kmymoneyplugin.h index b8a71722e..b77d807aa 100644 --- a/kmymoney/plugins/kmymoneyplugin.h +++ b/kmymoney/plugins/kmymoneyplugin.h @@ -1,372 +1,375 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2005 Thomas Baumgart * Copyright (C) 2015 Christian Dávid * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KMYMONEYPLUGIN_H #define KMYMONEYPLUGIN_H #include // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include class KToggleAction; // ---------------------------------------------------------------------------- // Project Includes #include "mymoneykeyvaluecontainer.h" class MyMoneyStorageMgr; class MyMoneyAccount; class KMyMoneySettings; class IMyMoneyOperationsFormat; namespace KMyMoneyPlugin { class AppInterface; } namespace KMyMoneyPlugin { class ImportInterface; } namespace KMyMoneyPlugin { class StatementInterface; } namespace KMyMoneyPlugin { class ViewInterface; } +namespace eKMyMoney { enum class StorageType; } + /** * @defgroup KMyMoneyPlugin * * KMyMoney knows several types of plugins. The most common and generic one is KMyMoneyPlugin::Plugin. * * Another group of plugins are just loaded on demand and offer special functions with a tight integration into KMyMoney. Whenever possible you should use this kind of plugins. * At the moment this are the onlineTask and payeeIdentifierData. * * @{ */ namespace KMyMoneyPlugin { /** * This class describes the interface between KMyMoney and it's plugins. * * The plugins are based on Qt 5's plugin system. So you must compile json information into the plugin. * KMyMoney looks into the folder "${PLUGIN_INSTALL_DIR}/kmymoney/" and loads all plugins found there (if the user did not deactivate the plugin). * * The json header of the plugin must comply with the requirements of KCoreAddon's KPluginMetaData class. * To load the plugin at start up the service type "KMyMoney/Plugin" must be set. * * @warning The plugin system for KMyMoney 5 is still in development. Especially the loading of the on-demand plugins (mainly undocumented :( ) will change. * * A basic json header is shown below. * @code{.json} { "KPlugin": { "Authors": [ { "Name": "Author's Names, Second Author", "Email": "E-Mail 1, E-Mail 2" } ], "Description": "Short description for plugin list (translateable)", "EnabledByDefault": true, "Icon": "icon to be shown in plugin list", "Id": "a unique identifier", "License": "see KPluginMetaData for list of predefined licenses (translateable)", "Name": "Name of the plugin (translateable)", "ServiceTypes": [ "KMyMoney/Plugin" ], "Version": "@PROJECT_VERSION@@PROJECT_VERSION_SUFFIX@", } } * @endcode * * This example assumes you are using * @code{.cmake} configure_file(${CMAKE_CURRENT_SOURCE_DIR}/... ${CMAKE_CURRENT_BINARY_DIR}/... @ONLY) @endcode * to replace the version variables using cmake. * * @see http://doc.qt.io/qt-5/plugins-howto.html * @see https://api.kde.org/frameworks/kcoreaddons/html/classKPluginMetaData.html * */ class KMM_PLUGIN_EXPORT Plugin : public QObject, public KXMLGUIClient { Q_OBJECT public: explicit Plugin(QObject* parent = nullptr, const char* name = ""); virtual ~Plugin(); public Q_SLOTS: /** * @brief Called during plug in process */ virtual void plug(); /** * @brief Called before unloading */ virtual void unplug(); /** * @brief Called if the configuration of the plugin was changed * @todo Implement */ virtual void configurationChanged() ; protected: /** See KMyMoneyApp::toggleAction() for a description */ KToggleAction* toggleAction(const QString& name) const; // define interface classes here. The interface classes provide a mechanism // for the plugin to interact with KMyMoney // they are defined in the following form for an interface // named Xxx: // // XxxInterface* xxxInterface(); AppInterface* appInterface() const; ViewInterface* viewInterface() const; StatementInterface* statementInterface() const; ImportInterface* importInterface() const; }; /** * This class describes the interface between the KMyMoney * application and it's ONLINE-BANKING plugins. All online banking plugins * must provide this interface. * * A good tutorial on how to design and develop a plugin * structure for a KDE application (e.g. KMyMoney) can be found at * http://developer.kde.org/documentation/tutorials/developing-a-plugin-structure/index.html * */ class KMM_PLUGIN_EXPORT OnlinePlugin { public: OnlinePlugin(); virtual ~OnlinePlugin(); virtual void protocols(QStringList& protocolList) const = 0; /** * This method returns a pointer to a widget representing an additional * tab that will be added to the KNewAccountDlg. The string referenced * with @a tabName will be filled with the text that should be placed * on the tab. It should return 0 if no additional tab is needed. * * Information about the account can be taken out of @a account. * * Once the pointer to the widget is returned to KMyMoney, it takes care * of destruction of all included widgets when the dialog is closed. The plugin * can access the widgets created after the call to storeConfigParameters() * happened. */ virtual QWidget* accountConfigTab(const MyMoneyAccount& account, QString& tabName) = 0; /** * This method is called by the framework whenever it is time to store * the configuration data maintained by the plugin. The plugin should use * the widgets created in accountConfigTab() to extract the current values. * * @param current The @a current container contains the current settings */ virtual MyMoneyKeyValueContainer onlineBankingSettings(const MyMoneyKeyValueContainer& current) = 0; /** * This method is called by the framework when the user wants to map * a KMyMoney account onto an online account. The KMyMoney account is identified * by @a acc and the online provider should store its data in @a onlineBankingSettings * upon success. * * @retval true if account is mapped * @retval false if account is not mapped */ virtual bool mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& onlineBankingSettings) = 0; /** * This method is called by the framework when the user wants to update * a KMyMoney account with data from an online account. The KMyMoney account is identified * by @a acc. The online provider should read its data from acc.onlineBankingSettings(). * @a true is returned upon success. The plugin might consider to stack the requests * in case @a moreAccounts is @p true. @a moreAccounts defaults to @p false. * * @retval true if account is updated * @retval false if account is not updated */ virtual bool updateAccount(const MyMoneyAccount& acc, bool moreAccounts = false) = 0; }; /** * This class describes the interface between the KMyMoney * application and it's IMPORTER plugins. All importer plugins * must provide this interface. * * A good tutorial on how to design and develop a plugin * structure for a KDE application (e.g. KMyMoney) can be found at * http://developer.kde.org/documentation/tutorials/developing-a-plugin-structure/index.html * */ class KMM_PLUGIN_EXPORT ImporterPlugin { public: ImporterPlugin(); virtual ~ImporterPlugin(); /** * This method returns the english-language name of the format * this plugin imports, e.g. "OFX" * * @return QString Name of the format */ virtual QString formatName() const = 0; /** * This method returns the filename filter suitable for passing to * KFileDialog::setFilter(), e.g. "*.ofx *.qfx" which describes how * files of this format are likely to be named in the file system * * @return QString Filename filter string */ virtual QString formatFilenameFilter() const = 0; /** * This method returns whether this plugin is able to import * a particular file. * * @param filename Fully-qualified pathname to a file * * @return bool Whether the indicated file is importable by this plugin */ virtual bool isMyFormat(const QString& filename) const = 0; /** * Import a file * * @param filename File to import * * @return bool Whether the import was successful. */ virtual bool import(const QString& filename) = 0; /** * Returns the error result of the last import * * @return QString English-language name of the error encountered in the * last import, or QString() if it was successful. * */ virtual QString lastError() const = 0; }; /** * This class describes the interface between the KMyMoney * application and it's STORAGE plugins. All storage plugins * must provide this interface. * */ class KMM_PLUGIN_EXPORT StoragePlugin { public: StoragePlugin() = default; virtual ~StoragePlugin() = default; /** * @brief Loads file into storage * @param storage Storage manager for the file * @param url URL of the file * @return true if successfully opened */ - virtual bool open(MyMoneyStorageMgr *storage, const QUrl &url) = 0; + virtual MyMoneyStorageMgr *open(const QUrl &url) = 0; /** * @brief Saves storage into file * @param url URL of the file * @return true if successfully saved */ virtual bool save(const QUrl &url) = 0; /** - * @brief Returns storage reader - * @return storage reader + * @brief Saves storage into file + * @param url URL of the file + * @return true if successfully saved */ - virtual IMyMoneyOperationsFormat* reader(); + virtual bool saveAs() = 0; /** * @brief Storage identifier * @return Storage identifier */ - virtual QString formatName() const = 0; + virtual eKMyMoney::StorageType storageType() const = 0; virtual QString fileExtension() const = 0; }; /** * This class describes the interface between the KMyMoney * application and its data plugins. All data plugins * must provide this interface. * */ class KMM_PLUGIN_EXPORT DataPlugin { public: DataPlugin() = default; virtual ~DataPlugin() = default; /** * @brief Gets data from data service * @param arg Item name to retrieve data for * @param type Data type to retrieve for item * @return a data like int or QString */ virtual QVariant requestData(const QString &arg, uint type) = 0; }; class OnlinePluginExtended; /** * @brief The Container struct to hold all plugin interfaces */ struct Container { QMap standard; // this should contain all loaded plugins because every plugin should inherit Plugin class QMap online; // casted standard plugin, if such interface is available QMap extended; // casted standard plugin, if such interface is available QMap importer; // casted standard plugin, if such interface is available QMap storage; // casted standard plugin, if such interface is available QMap data; // casted standard plugin, if such interface is available }; } // end of namespace /** * @brief Structure of plugins objects by their interfaces */ KMM_PLUGIN_EXPORT extern KMyMoneyPlugin::Container pPlugins; Q_DECLARE_INTERFACE(KMyMoneyPlugin::OnlinePlugin, "org.kmymoney.plugin.onlineplugin") Q_DECLARE_INTERFACE(KMyMoneyPlugin::ImporterPlugin, "org.kmymoney.plugin.importerplugin") Q_DECLARE_INTERFACE(KMyMoneyPlugin::StoragePlugin, "org.kmymoney.plugin.storageplugin") Q_DECLARE_INTERFACE(KMyMoneyPlugin::DataPlugin, "org.kmymoney.plugin.dataplugin") /** @} */ #endif diff --git a/kmymoney/plugins/ofx/import/ofximporter.cpp b/kmymoney/plugins/ofx/import/ofximporter.cpp index 1270de618..0142acce2 100644 --- a/kmymoney/plugins/ofx/import/ofximporter.cpp +++ b/kmymoney/plugins/ofx/import/ofximporter.cpp @@ -1,881 +1,884 @@ /*************************************************************************** ofximporter.cpp ------------------- begin : Sat Jan 01 2005 copyright : (C) 2005 by Ace Jones email : Ace Jones ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "ofximporter.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include "konlinebankingstatus.h" #include "konlinebankingsetupwizard.h" #include "kofxdirectconnectdlg.h" #include "mymoneyaccount.h" #include "mymoneyexception.h" #include "mymoneystatement.h" #include "statementinterface.h" #include "importinterface.h" +#include "viewinterface.h" #include "ui_importoption.h" //#define DEBUG_LIBOFX using KWallet::Wallet; class OFXImporter::Private { public: Private() : m_valid(false), m_preferName(PreferId), m_walletIsOpen(false), m_statusDlg(0), m_wallet(0), m_updateStartDate(QDate(1900,1,1)) {} bool m_valid; enum NamePreference { PreferId = 0, PreferName, PreferMemo } m_preferName; bool m_walletIsOpen; QList m_statementlist; QList m_securitylist; QString m_fatalerror; QStringList m_infos; QStringList m_warnings; QStringList m_errors; KOnlineBankingStatus* m_statusDlg; Wallet *m_wallet; QDate m_updateStartDate; QStringList m_importSummary; }; OFXImporter::OFXImporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "ofximporter"), /* * the string in the line above must be the same as * X-KDE-PluginInfo-Name and the provider name assigned in * OfxImporterPlugin::onlineBankingSettings() */ KMyMoneyPlugin::ImporterPlugin(), d(new Private) { Q_UNUSED(args) setComponentName(QStringLiteral("ofximporter"), i18n("OFX Importer")); setXMLFile(QStringLiteral("ofximporter.rc")); createActions(); // For ease announce that we have been loaded. qDebug("Plugins: ofximporter loaded"); } OFXImporter::~OFXImporter() { delete d; qDebug("Plugins: ofximporter unloaded"); } void OFXImporter::createActions() { - QAction *action = actionCollection()->addAction("file_import_ofx"); - action->setText(i18n("OFX...")); - connect(action, &QAction::triggered, this, static_cast(&OFXImporter::slotImportFile)); + const auto &kpartgui = QStringLiteral("file_import_ofx"); + auto a = actionCollection()->addAction(kpartgui); + a->setText(i18n("OFX...")); + connect(a, &QAction::triggered, this, static_cast(&OFXImporter::slotImportFile)); + connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } void OFXImporter::slotImportFile() { QWidget * widget = new QWidget; Ui_ImportOption* option = new Ui_ImportOption; option->setupUi(widget); QUrl url = importInterface()->selectFile(i18n("OFX import file selection"), QString(), QStringLiteral("*.ofx *.qfx *.ofc|OFX files (*.ofx *.qfx *.ofc);;*|All files (*)"), QFileDialog::ExistingFile, widget); d->m_preferName = static_cast(option->m_preferName->currentIndex()); if (url.isValid()) { if (isMyFormat(url.path())) { d->m_importSummary.clear(); slotImportFile(url.path()); if (!d->m_importSummary.isEmpty()) { KMessageBox::informationList(nullptr, i18n("The statement has been processed with the following results:"), d->m_importSummary, i18n("Statement stats")); d->m_importSummary.clear(); } } else { KMessageBox::error(0, i18n("Unable to import %1 using the OFX importer plugin. This file is not the correct format.", url.toDisplayString()), i18n("Incorrect format")); } } delete option; delete widget; } QString OFXImporter::formatName() const { return QStringLiteral("OFX"); } QString OFXImporter::formatFilenameFilter() const { return QStringLiteral("*.ofx *.qfx *.ofc"); } bool OFXImporter::isMyFormat(const QString& filename) const { // filename is considered an Ofx file if it contains // the tag "" or "" in the first 20 lines. // which contain some data bool result = false; QFile f(filename); if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream ts(&f); int lineCount = 20; while (!ts.atEnd() && !result && lineCount != 0) { // get a line of data and remove all unnecessary whitepace chars QString line = ts.readLine().simplified(); if (line.contains(QStringLiteral(""), Qt::CaseInsensitive) || line.contains(QStringLiteral(""), Qt::CaseInsensitive)) result = true; // count only lines that contain some non white space chars if (!line.isEmpty()) lineCount--; } f.close(); } return result; } bool OFXImporter::import(const QString& filename) { d->m_fatalerror = i18n("Unable to parse file"); d->m_valid = false; d->m_errors.clear(); d->m_warnings.clear(); d->m_infos.clear(); d->m_statementlist.clear(); d->m_securitylist.clear(); QByteArray filename_deep = QFile::encodeName(filename); ofx_STATUS_msg = true; ofx_INFO_msg = true; ofx_WARNING_msg = true; ofx_ERROR_msg = true; #ifdef DEBUG_LIBOFX ofx_PARSER_msg = true; ofx_DEBUG_msg = true; ofx_DEBUG1_msg = true; ofx_DEBUG2_msg = true; ofx_DEBUG3_msg = true; ofx_DEBUG4_msg = true; ofx_DEBUG5_msg = true; #endif LibofxContextPtr ctx = libofx_get_new_context(); Q_CHECK_PTR(ctx); qDebug("setup callback routines"); ofx_set_transaction_cb(ctx, ofxTransactionCallback, this); ofx_set_statement_cb(ctx, ofxStatementCallback, this); ofx_set_account_cb(ctx, ofxAccountCallback, this); ofx_set_security_cb(ctx, ofxSecurityCallback, this); ofx_set_status_cb(ctx, ofxStatusCallback, this); qDebug("process data"); libofx_proc_file(ctx, filename_deep, AUTODETECT); libofx_free_context(ctx); if (d->m_valid) { d->m_fatalerror.clear(); d->m_valid = storeStatements(d->m_statementlist); } return d->m_valid; } QString OFXImporter::lastError() const { if (d->m_errors.count() == 0) return d->m_fatalerror; return d->m_errors.join(QStringLiteral("

")); } /* __________________________________________________________________________ * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA * * Static callbacks for LibOFX * * YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY */ int OFXImporter::ofxTransactionCallback(struct OfxTransactionData data, void * pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); MyMoneyStatement& s = pofx->back(); MyMoneyStatement::Transaction t; if (data.date_posted_valid) { QDateTime dt; dt.setTime_t(data.date_posted); t.m_datePosted = dt.date(); } else if (data.date_initiated_valid) { QDateTime dt; dt.setTime_t(data.date_initiated); t.m_datePosted = dt.date(); } if (t.m_datePosted.isValid()) { // verify the transaction date is one we want if (t.m_datePosted < pofx->d->m_updateStartDate) { //kDebug(0) << "discarding transaction dated" << qPrintable(t.m_datePosted.toString(Qt::ISODate)); return 0; } } if (data.amount_valid) { t.m_amount = MyMoneyMoney(data.amount, 1000); } if (data.check_number_valid) { t.m_strNumber = QString::fromUtf8(data.check_number); } if (data.fi_id_valid) { t.m_strBankID = QStringLiteral("ID ") + QString::fromUtf8(data.fi_id); } else if (data.reference_number_valid) { t.m_strBankID = QStringLiteral("REF ") + QString::fromUtf8(data.reference_number); } // Decide whether to use NAME, PAYEEID or MEMO to construct the payee bool validity[3] = {false, false, false}; QStringList values; switch (pofx->d->m_preferName) { case OFXImporter::Private::PreferId: // PAYEEID default: validity[0] = data.payee_id_valid; validity[1] = data.name_valid; validity[2] = data.memo_valid; values += QString::fromUtf8(data.payee_id); values += QString::fromUtf8(data.name); values += QString::fromUtf8(data.memo); break; case OFXImporter::Private::PreferName: // NAME validity[0] = data.name_valid; validity[1] = data.payee_id_valid; validity[2] = data.memo_valid; values += QString::fromUtf8(data.name); values += QString::fromUtf8(data.payee_id); values += QString::fromUtf8(data.memo); break; case OFXImporter::Private::PreferMemo: // MEMO validity[0] = data.memo_valid; validity[1] = data.payee_id_valid; validity[2] = data.name_valid; values += QString::fromUtf8(data.memo); values += QString::fromUtf8(data.payee_id); values += QString::fromUtf8(data.name); break; } // for investment transactions we don't use the meme as payee if (data.invtransactiontype_valid) { values.clear(); validity[0] = data.payee_id_valid; validity[1] = data.name_valid; validity[2] = false; values += QString::fromUtf8(data.payee_id); values += QString::fromUtf8(data.name); } for (int idx = 0; idx < 3; ++idx) { if (validity[idx]) { t.m_strPayee = values[idx]; break; } } // extract memo field if we haven't used it as payee if ((data.memo_valid) && (pofx->d->m_preferName != OFXImporter::Private::PreferMemo)) { t.m_strMemo = QString::fromUtf8(data.memo); } // If the payee or memo fields are blank, set them to // the other one which is NOT blank. (acejones) if (t.m_strPayee.isEmpty()) { // But we only create a payee for non-investment transactions (ipwizard) if (! t.m_strMemo.isEmpty() && data.invtransactiontype_valid == false) t.m_strPayee = t.m_strMemo; } else { if (t.m_strMemo.isEmpty()) t.m_strMemo = t.m_strPayee; } if (data.security_data_valid) { struct OfxSecurityData* secdata = data.security_data_ptr; if (secdata->ticker_valid) { t.m_strSymbol = QString::fromUtf8(secdata->ticker); } if (secdata->secname_valid) { t.m_strSecurity = QString::fromUtf8(secdata->secname); } } t.m_shares = MyMoneyMoney(); if (data.units_valid) { t.m_shares = MyMoneyMoney(data.units, 100000).reduce(); } t.m_price = MyMoneyMoney(); if (data.unitprice_valid) { t.m_price = MyMoneyMoney(data.unitprice, 100000).reduce(); } t.m_fees = MyMoneyMoney(); if (data.fees_valid) { t.m_fees += MyMoneyMoney(data.fees, 1000).reduce(); } if (data.commission_valid) { t.m_fees += MyMoneyMoney(data.commission, 1000).reduce(); } bool unhandledtype = false; QString type; if (data.invtransactiontype_valid) { switch (data.invtransactiontype) { case OFX_BUYDEBT: case OFX_BUYMF: case OFX_BUYOPT: case OFX_BUYOTHER: case OFX_BUYSTOCK: t.m_eAction = eMyMoney::Transaction::Action::Buy; break; case OFX_REINVEST: t.m_eAction = eMyMoney::Transaction::Action::ReinvestDividend; break; case OFX_SELLDEBT: case OFX_SELLMF: case OFX_SELLOPT: case OFX_SELLOTHER: case OFX_SELLSTOCK: t.m_eAction = eMyMoney::Transaction::Action::Sell; break; case OFX_INCOME: t.m_eAction = eMyMoney::Transaction::Action::CashDividend; // NOTE: With CashDividend, the amount of the dividend should // be in data.amount. Since I've never seen an OFX file with // cash dividends, this is an assumption on my part. (acejones) break; // // These types are all not handled. We will generate a warning for them. // case OFX_CLOSUREOPT: unhandledtype = true; type = QStringLiteral("CLOSUREOPT (Close a position for an option)"); break; case OFX_INVEXPENSE: unhandledtype = true; type = QStringLiteral("INVEXPENSE (Misc investment expense that is associated with a specific security)"); break; case OFX_JRNLFUND: unhandledtype = true; type = QStringLiteral("JRNLFUND (Journaling cash holdings between subaccounts within the same investment account)"); break; case OFX_MARGININTEREST: unhandledtype = true; type = QStringLiteral("MARGININTEREST (Margin interest expense)"); break; case OFX_RETOFCAP: unhandledtype = true; type = QStringLiteral("RETOFCAP (Return of capital)"); break; case OFX_SPLIT: unhandledtype = true; type = QStringLiteral("SPLIT (Stock or mutial fund split)"); break; case OFX_TRANSFER: unhandledtype = true; type = QStringLiteral("TRANSFER (Transfer holdings in and out of the investment account)"); break; default: unhandledtype = true; type = QString("UNKNOWN %1").arg(data.invtransactiontype); break; } } else t.m_eAction = eMyMoney::Transaction::Action::None; // In the case of investment transactions, the 'total' is supposed to the total amount // of the transaction. units * unitprice +/- commission. Easy, right? Sadly, it seems // some ofx creators do not follow this in all circumstances. Therefore, we have to double- // check the total here and adjust it if it's wrong. #if 0 // Even more sadly, this logic is BROKEN. It consistently results in bogus total // values, because of rounding errors in the price. A more through solution would // be to test if the comission alone is causing a discrepency, and adjust in that case. if (data.invtransactiontype_valid && data.unitprice_valid) { double proper_total = t.m_dShares * data.unitprice + t.m_moneyFees; if (proper_total != t.m_moneyAmount) { pofx->addWarning(QString("Transaction %1 has an incorrect total of %2. Using calculated total of %3 instead.").arg(t.m_strBankID).arg(t.m_moneyAmount).arg(proper_total)); t.m_moneyAmount = proper_total; } } #endif if (unhandledtype) pofx->addWarning(QString("Transaction %1 has an unsupported type (%2).").arg(t.m_strBankID, type)); else s.m_listTransactions += t; // kDebug(2) << Q_FUNC_INFO << "return 0 "; return 0; } int OFXImporter::ofxStatementCallback(struct OfxStatementData data, void* pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); MyMoneyStatement& s = pofx->back(); pofx->setValid(); if (data.currency_valid) { s.m_strCurrency = QString::fromUtf8(data.currency); } if (data.account_id_valid) { s.m_strAccountNumber = QString::fromUtf8(data.account_id); } if (data.date_start_valid) { QDateTime dt; dt.setTime_t(data.date_start); s.m_dateBegin = dt.date(); } if (data.date_end_valid) { QDateTime dt; dt.setTime_t(data.date_end); s.m_dateEnd = dt.date(); } if (data.ledger_balance_valid && data.ledger_balance_date_valid) { s.m_closingBalance = MyMoneyMoney(data.ledger_balance); QDateTime dt; dt.setTime_t(data.ledger_balance_date); s.m_dateEnd = dt.date(); } // kDebug(2) << Q_FUNC_INFO << " return 0"; return 0; } int OFXImporter::ofxAccountCallback(struct OfxAccountData data, void * pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); pofx->addnew(); MyMoneyStatement& s = pofx->back(); // Having any account at all makes an ofx statement valid pofx->d->m_valid = true; if (data.account_id_valid) { s.m_strAccountName = QString::fromUtf8(data.account_name); s.m_strAccountNumber = QString::fromUtf8(data.account_id); } if (data.bank_id_valid) { s.m_strRoutingNumber = QString::fromUtf8(data.bank_id); } if (data.broker_id_valid) { s.m_strRoutingNumber = QString::fromUtf8(data.broker_id); } if (data.currency_valid) { s.m_strCurrency = QString::fromUtf8(data.currency); } if (data.account_type_valid) { switch (data.account_type) { case OfxAccountData::OFX_CHECKING : s.m_eType = eMyMoney::Statement::Type::Checkings; break; case OfxAccountData::OFX_SAVINGS : s.m_eType = eMyMoney::Statement::Type::Savings; break; case OfxAccountData::OFX_MONEYMRKT : s.m_eType = eMyMoney::Statement::Type::Investment; break; case OfxAccountData::OFX_CREDITLINE : s.m_eType = eMyMoney::Statement::Type::CreditCard; break; case OfxAccountData::OFX_CMA : s.m_eType = eMyMoney::Statement::Type::CreditCard; break; case OfxAccountData::OFX_CREDITCARD : s.m_eType = eMyMoney::Statement::Type::CreditCard; break; case OfxAccountData::OFX_INVESTMENT : s.m_eType = eMyMoney::Statement::Type::Investment; break; } } // ask KMyMoney for an account id s.m_accountId = pofx->account(QStringLiteral("kmmofx-acc-ref"), QString("%1-%2").arg(s.m_strRoutingNumber, s.m_strAccountNumber)).id(); // copy over the securities s.m_listSecurities = pofx->d->m_securitylist; // kDebug(2) << Q_FUNC_INFO << " return 0"; return 0; } int OFXImporter::ofxSecurityCallback(struct OfxSecurityData data, void* pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); MyMoneyStatement::Security sec; if (data.unique_id_valid) { sec.m_strId = QString::fromUtf8(data.unique_id); } if (data.secname_valid) { sec.m_strName = QString::fromUtf8(data.secname); } if (data.ticker_valid) { sec.m_strSymbol = QString::fromUtf8(data.ticker); } pofx->d->m_securitylist += sec; return 0; } int OFXImporter::ofxStatusCallback(struct OfxStatusData data, void * pv) { // kDebug(2) << Q_FUNC_INFO; OFXImporter* pofx = reinterpret_cast(pv); QString message; // if we got this far, we know we were able to parse the file. // so if it fails after here it can only because there were no actual // accounts in the file! pofx->d->m_fatalerror = i18n("No accounts found."); if (data.ofx_element_name_valid) message.prepend(QString("%1: ").arg(QString::fromUtf8(data.ofx_element_name))); if (data.code_valid) message += QString("%1 (Code %2): %3").arg(QString::fromUtf8(data.name)).arg(data.code).arg(QString::fromUtf8(data.description)); if (data.server_message_valid) message += QString(" (%1)").arg(QString::fromUtf8(data.server_message)); if (data.severity_valid) { switch (data.severity) { case OfxStatusData::INFO: pofx->addInfo(message); break; case OfxStatusData::ERROR: pofx->addError(message); break; case OfxStatusData::WARN: pofx->addWarning(message); break; default: pofx->addWarning(message); pofx->addWarning(QStringLiteral("Previous message was an unknown type. 'WARNING' was assumed.")); break; } } // kDebug(2) << Q_FUNC_INFO << " return 0 "; return 0; } QStringList OFXImporter::importStatement(const MyMoneyStatement &s) { qDebug("OfxImporterPlugin::importStatement start"); return statementInterface()->import(s, true); } MyMoneyAccount OFXImporter::account(const QString& key, const QString& value) const { return statementInterface()->account(key, value); } void OFXImporter::protocols(QStringList& protocolList) const { protocolList.clear(); protocolList << QStringLiteral("OFX"); } QWidget* OFXImporter::accountConfigTab(const MyMoneyAccount& acc, QString& name) { name = i18n("Online settings"); d->m_statusDlg = new KOnlineBankingStatus(acc, 0); return d->m_statusDlg; } MyMoneyKeyValueContainer OFXImporter::onlineBankingSettings(const MyMoneyKeyValueContainer& current) { MyMoneyKeyValueContainer kvp(current); // keep the provider name in sync with the one found in kmm_ofximport.desktop kvp[QStringLiteral("provider")] = objectName().toLower(); if (d->m_statusDlg) { kvp.deletePair(QStringLiteral("appId")); kvp.deletePair(QStringLiteral("kmmofx-headerVersion")); kvp.deletePair(QStringLiteral("password")); d->m_wallet = openSynchronousWallet(); if (d->m_wallet && (d->m_wallet->hasFolder(KWallet::Wallet::PasswordFolder()) || d->m_wallet->createFolder(KWallet::Wallet::PasswordFolder())) && d->m_wallet->setFolder(KWallet::Wallet::PasswordFolder())) { QString key = OFX_PASSWORD_KEY(kvp.value(QStringLiteral("url")), kvp.value(QStringLiteral("uniqueId"))); if (d->m_statusDlg->m_storePassword->isChecked()) { d->m_wallet->writePassword(key, d->m_statusDlg->m_password->text()); } else { if (d->m_wallet->hasEntry(key)) { d->m_wallet->removeEntry(key); } } } else { if (d->m_statusDlg->m_storePassword->isChecked()) { kvp.setValue(QStringLiteral("password"), d->m_statusDlg->m_password->text()); } } if (!d->m_statusDlg->appId().isEmpty()) kvp.setValue(QStringLiteral("appId"), d->m_statusDlg->appId()); kvp.setValue(QStringLiteral("kmmofx-headerVersion"), d->m_statusDlg->headerVersion()); kvp.setValue(QStringLiteral("kmmofx-numRequestDays"), QString::number(d->m_statusDlg->m_numdaysSpin->value())); kvp.setValue(QStringLiteral("kmmofx-todayMinus"), QString::number(d->m_statusDlg->m_todayRB->isChecked())); kvp.setValue(QStringLiteral("kmmofx-lastUpdate"), QString::number(d->m_statusDlg->m_lastUpdateRB->isChecked())); kvp.setValue(QStringLiteral("kmmofx-pickDate"), QString::number(d->m_statusDlg->m_pickDateRB->isChecked())); kvp.setValue(QStringLiteral("kmmofx-specificDate"), d->m_statusDlg->m_specificDate->date().toString()); kvp.setValue(QStringLiteral("kmmofx-preferName"), QString::number(d->m_statusDlg->m_preferredPayee->currentIndex())); if (!d->m_statusDlg->m_clientUidEdit->text().isEmpty()) kvp.setValue(QStringLiteral("clientUid"), d->m_statusDlg->m_clientUidEdit->text()); else kvp.deletePair(QStringLiteral("clientUid")); // get rid of pre 4.6 values kvp.deletePair(QStringLiteral("kmmofx-preferPayeeid")); } return kvp; } bool OFXImporter::mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& settings) { Q_UNUSED(acc); bool rc = false; QPointer wiz = new KOnlineBankingSetupWizard(0); if (wiz->isInit()) { if (wiz->exec() == QDialog::Accepted) { rc = wiz->chosenSettings(settings); } } delete wiz; return rc; } bool OFXImporter::updateAccount(const MyMoneyAccount& acc, bool moreAccounts) { Q_UNUSED(moreAccounts); qDebug("OfxImporterPlugin::updateAccount"); try { if (!acc.id().isEmpty()) { // Save the value of preferName to be used by ofxTransactionCallback d->m_preferName = static_cast(acc.onlineBankingSettings().value(QStringLiteral("kmmofx-preferName")).toInt()); QPointer dlg = new KOfxDirectConnectDlg(acc); d->m_importSummary.clear(); connect(dlg.data(), &KOfxDirectConnectDlg::statementReady, this, static_cast(&OFXImporter::slotImportFile)); // get the date of the earliest transaction that we are interested in // from the settings for this account MyMoneyKeyValueContainer settings = acc.onlineBankingSettings(); if (!settings.value(QStringLiteral("provider")).isEmpty()) { if ((settings.value(QStringLiteral("kmmofx-todayMinus")).toInt() != 0) && !settings.value(QStringLiteral("kmmofx-numRequestDays")).isEmpty()) { //kDebug(0) << "start date = today minus"; d->m_updateStartDate = QDate::currentDate().addDays(-settings.value(QStringLiteral("kmmofx-numRequestDays")).toInt()); } else if ((settings.value(QStringLiteral("kmmofx-lastUpdate")).toInt() != 0) && !acc.value(QStringLiteral("lastImportedTransactionDate")).isEmpty()) { //kDebug(0) << "start date = last update"; d->m_updateStartDate = QDate::fromString(acc.value(QStringLiteral("lastImportedTransactionDate")), Qt::ISODate); } else if ((settings.value(QStringLiteral("kmmofx-pickDate")).toInt() != 0) && !settings.value(QStringLiteral("kmmofx-specificDate")).isEmpty()) { //kDebug(0) << "start date = pick date"; d->m_updateStartDate = QDate::fromString(settings.value(QStringLiteral("kmmofx-specificDate"))); } else { //kDebug(0) << "start date = today - 2 months"; d->m_updateStartDate = QDate::currentDate().addMonths(-2); } } //kDebug(0) << "ofx plugin: account" << acc.name() << "earliest transaction date to process =" << qPrintable(d->m_updateStartDate.toString(Qt::ISODate)); if (dlg->init()) dlg->exec(); delete dlg; if (!d->m_importSummary.isEmpty()) { KMessageBox::informationList(nullptr, i18n("The statement has been processed with the following results:"), d->m_importSummary, i18n("Statement stats")); d->m_importSummary.clear(); } // reset the earliest-interesting-transaction date to the non-specific account setting d->m_updateStartDate = QDate(1900,1,1); } } catch (const MyMoneyException &e) { KMessageBox::information(0 , i18n("Error connecting to bank: %1", QString::fromLatin1(e.what()))); } return false; } void OFXImporter::slotImportFile(const QString& url) { qDebug("OfxImporterPlugin::slotImportFile"); if (!import(url)) { KMessageBox::error(0, QString("%1").arg(i18n("

Unable to import '%1' using the OFX importer plugin. The plugin returned the following error:

%2

", url, lastError())), i18n("Importing error")); } } bool OFXImporter::storeStatements(const QList &statements) { if (statements.isEmpty()) return true; auto ok = true; auto abort = false; // FIXME Deal with warnings/errors coming back from plugins /*if ( ofx.errors().count() ) { if ( KMessageBox::warningContinueCancelList(this,i18n("The following errors were returned from your bank"),ofx.errors(),i18n("OFX Errors")) == KMessageBox::Cancel ) abort = true; } if ( ofx.warnings().count() ) { if ( KMessageBox::warningContinueCancelList(this,i18n("The following warnings were returned from your bank"),ofx.warnings(),i18n("OFX Warnings"),KStandardGuiItem::cont(),"ofxwarnings") == KMessageBox::Cancel ) abort = true; }*/ qDebug("OfxImporterPlugin::storeStatements() with %d statements called", statements.count()); for (const auto& statement : statements) { if (abort) break; const auto importSummary = importStatement(statement); if (importSummary.isEmpty()) ok = false; d->m_importSummary.append(importSummary); } if (!ok) KMessageBox::error(nullptr, i18n("Importing process terminated unexpectedly."), i18n("Failed to import all statements.")); return ok; } void OFXImporter::addnew() { d->m_statementlist.push_back(MyMoneyStatement()); } MyMoneyStatement& OFXImporter::back() { return d->m_statementlist.back(); } bool OFXImporter::isValid() const { return d->m_valid; } void OFXImporter::setValid() { d->m_valid = true; } void OFXImporter::addInfo(const QString& _msg) { d->m_infos += _msg; } void OFXImporter::addWarning(const QString& _msg) { d->m_warnings += _msg; } void OFXImporter::addError(const QString& _msg) { d->m_errors += _msg; } const QStringList& OFXImporter::infos() const // krazy:exclude=spelling { return d->m_infos; } const QStringList& OFXImporter::warnings() const { return d->m_warnings; } const QStringList& OFXImporter::errors() const { return d->m_errors; } K_PLUGIN_FACTORY_WITH_JSON(OFXImporterFactory, "ofximporter.json", registerPlugin();) #include "ofximporter.moc" diff --git a/kmymoney/plugins/qif/export/qifexporter.cpp b/kmymoney/plugins/qif/export/qifexporter.cpp index 984c92fe2..ca14ab9a6 100644 --- a/kmymoney/plugins/qif/export/qifexporter.cpp +++ b/kmymoney/plugins/qif/export/qifexporter.cpp @@ -1,82 +1,85 @@ /*************************************************************************** qifexporter.cpp ------------------- copyright : (C) 2017 by Łukasz Wojniłowicz email : lukasz.wojnilowicz@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "qifexporter.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kexportdlg.h" #include "mymoneyqifwriter.h" +#include "viewinterface.h" QIFExporter::QIFExporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "qifexporter"/*must be the same as X-KDE-PluginInfo-Name*/) { Q_UNUSED(args); setComponentName("qifexporter", i18n("QIF exporter")); setXMLFile("qifexporter.rc"); createActions(); // For information, announce that we have been loaded. qDebug("Plugins: qifexporter loaded"); } QIFExporter::~QIFExporter() { qDebug("Plugins: qifexporter unloaded"); } void QIFExporter::createActions() { - m_action = actionCollection()->addAction("file_export_qif"); + const auto &kpartgui = QStringLiteral("file_export_qif"); + m_action = actionCollection()->addAction(kpartgui); m_action->setText(i18n("QIF...")); connect(m_action, &QAction::triggered, this, &QIFExporter::slotQifExport); + connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } void QIFExporter::slotQifExport() { m_action->setEnabled(false); QPointer dlg = new KExportDlg(nullptr); if (dlg->exec() == QDialog::Accepted && dlg != nullptr) { // if (okToWriteFile(QUrl::fromLocalFile(dlg->filename()))) { MyMoneyQifWriter writer; connect(&writer, SIGNAL(signalProgress(int,int)), this, SLOT(slotStatusProgressBar(int,int))); writer.write(dlg->filename(), dlg->profile(), dlg->accountId(), dlg->accountSelected(), dlg->categorySelected(), dlg->startDate(), dlg->endDate()); // } } delete dlg; m_action->setEnabled(true); } K_PLUGIN_FACTORY_WITH_JSON(QIFExporterFactory, "qifexporter.json", registerPlugin();) #include "qifexporter.moc" diff --git a/kmymoney/plugins/qif/import/qifimporter.cpp b/kmymoney/plugins/qif/import/qifimporter.cpp index 966a32a68..11d742929 100644 --- a/kmymoney/plugins/qif/import/qifimporter.cpp +++ b/kmymoney/plugins/qif/import/qifimporter.cpp @@ -1,105 +1,108 @@ /*************************************************************************** qifimporter.cpp ------------------- copyright : (C) 2017 by Łukasz Wojniłowicz email : lukasz.wojnilowicz@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "qifimporter.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kimportdlg.h" #include "mymoneyqifreader.h" #include "statementinterface.h" +#include "viewinterface.h" class MyMoneyStatement; QIFImporter::QIFImporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "qifimporter"/*must be the same as X-KDE-PluginInfo-Name*/) { Q_UNUSED(args); setComponentName("qifimporter", i18n("QIF importer")); setXMLFile("qifimporter.rc"); createActions(); // For information, announce that we have been loaded. qDebug("Plugins: qifimporter loaded"); } QIFImporter::~QIFImporter() { qDebug("Plugins: qifimporter unloaded"); } void QIFImporter::createActions() { - m_action = actionCollection()->addAction("file_import_qif"); + const auto &kpartgui = QStringLiteral("file_import_qif"); + m_action = actionCollection()->addAction(kpartgui); m_action->setText(i18n("QIF...")); connect(m_action, &QAction::triggered, this, &QIFImporter::slotQifImport); + connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } void QIFImporter::slotQifImport() { m_action->setEnabled(false); QPointer dlg = new KImportDlg(nullptr); if (dlg->exec() == QDialog::Accepted && dlg != nullptr) { m_qifReader = new MyMoneyQifReader; connect(m_qifReader, &MyMoneyQifReader::statementsReady, this, &QIFImporter::slotGetStatements); m_qifReader->setURL(dlg->file()); m_qifReader->setProfile(dlg->profile()); m_qifReader->setCategoryMapping(dlg->m_typeComboBox->currentIndex() == 0); if (!m_qifReader->startImport()) delete m_qifReader; } delete dlg; m_action->setEnabled(true); } bool QIFImporter::slotGetStatements(const QList &statements) { auto ret = true; QStringList importSummary; for (const auto& statement : statements) { const auto singleImportSummary = statementInterface()->import(statement); if (singleImportSummary.isEmpty()) ret = false; importSummary.append(singleImportSummary); } delete m_qifReader; if (!importSummary.isEmpty()) KMessageBox::informationList(nullptr, i18n("The statement has been processed with the following results:"), importSummary, i18n("Statement stats")); return ret; } K_PLUGIN_FACTORY_WITH_JSON(QIFImporterFactory, "qifimporter.json", registerPlugin();) #include "qifimporter.moc" diff --git a/kmymoney/plugins/sql/sqlstorage.cpp b/kmymoney/plugins/sql/sqlstorage.cpp index bd99fc624..d1216f077 100644 --- a/kmymoney/plugins/sql/sqlstorage.cpp +++ b/kmymoney/plugins/sql/sqlstorage.cpp @@ -1,314 +1,311 @@ /*************************************************************************** sqlstorage.cpp ------------------- copyright : (C) 2018 by Łukasz Wojniłowicz email : lukasz.wojnilowicz@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "sqlstorage.h" #include #include // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "appinterface.h" #include "viewinterface.h" #include "kselectdatabasedlg.h" #include "kgeneratesqldlg.h" #include "mymoneyfile.h" #include "mymoneystoragesql.h" #include "mymoneyexception.h" -//#include "mymoneystoragemgr.h" +#include "mymoneystoragemgr.h" #include "icons.h" #include "kmymoneysettings.h" +#include "kmymoneyenums.h" using namespace Icons; SQLStorage::SQLStorage(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "sqlstorage"/*must be the same as X-KDE-PluginInfo-Name*/) { Q_UNUSED(args) setComponentName("sqlstorage", i18n("SQL storage")); setXMLFile("sqlstorage.rc"); createActions(); // For information, announce that we have been loaded. qDebug("Plugins: sqlstorage loaded"); } SQLStorage::~SQLStorage() { qDebug("Plugins: sqlstorage unloaded"); } -bool SQLStorage::open(MyMoneyStorageMgr *storage, const QUrl &url) +MyMoneyStorageMgr *SQLStorage::open(const QUrl &url) { + if (url.scheme() != QLatin1String("sql")) + return nullptr; + + auto storage = new MyMoneyStorageMgr; auto reader = std::make_unique(storage, url); QUrl dbURL(url); bool retry = true; while (retry) { switch (reader->open(dbURL, QIODevice::ReadWrite)) { case 0: // opened okay retry = false; break; case 1: // permanent error KMessageBox::detailedError(nullptr, i18n("Cannot open database %1\n", dbURL.toDisplayString()), reader->lastError()); - return false; + delete storage; + return nullptr; case -1: // retryable error if (KMessageBox::warningYesNo(nullptr, reader->lastError(), PACKAGE) == KMessageBox::No) { - return false; + delete storage; + return nullptr; } else { QUrlQuery query(dbURL); const QString optionKey = QLatin1String("options"); QString options = query.queryItemValue(optionKey); if(!options.isEmpty()) { options += QLatin1Char(','); } options += QLatin1String("override"); query.removeQueryItem(QLatin1String("mode")); query.removeQueryItem(optionKey); query.addQueryItem(optionKey, options); dbURL.setQuery(query); } } } // single user mode; read some of the data into memory // FIXME - readFile no longer relevant? // tried removing it but then got no indication that loading was complete // also, didn't show home page // reader->setProgressCallback(&KMyMoneyView::progressCallback); if (!reader->readFile()) { KMessageBox::detailedError(nullptr, i18n("An unrecoverable error occurred while reading the database"), reader->lastError().toLatin1(), i18n("Database malfunction")); - return false; + delete storage; + return nullptr; } // reader->setProgressCallback(0); - return true; + return storage; } bool SQLStorage::save(const QUrl &url) { - return saveDatabase(url); + auto rc = false; + if (!appInterface()->fileOpen()) { + KMessageBox::error(nullptr, i18n("Tried to access a file when it has not been opened")); + return (rc); + } + auto writer = new MyMoneyStorageSql(MyMoneyFile::instance()->storage(), url); + writer->open(url, QIODevice::WriteOnly); +// writer->setProgressCallback(&KMyMoneyView::progressCallback); + if (!writer->writeFile()) { + KMessageBox::detailedError(nullptr, + i18n("An unrecoverable error occurred while writing to the database.\n" + "It may well be corrupt."), + writer->lastError().toLatin1(), + i18n("Database malfunction")); + rc = false; + } else { + rc = true; + } + writer->setProgressCallback(0); + delete writer; + return rc; +} + +bool SQLStorage::saveAs() +{ + auto rc = false; + QUrl oldUrl; + // in event of it being a database, ensure that all data is read into storage for saveas + if (appInterface()->isDatabase()) + oldUrl = appInterface()->filenameURL().isEmpty() ? appInterface()->lastOpenedURL() : appInterface()->filenameURL(); + + QPointer dialog = new KSelectDatabaseDlg(QIODevice::WriteOnly); + QUrl url = oldUrl; + if (!dialog->checkDrivers()) { + delete dialog; + return rc; + } + + while (oldUrl == url && dialog->exec() == QDialog::Accepted && dialog != 0) { + url = dialog->selectedURL(); + // If the protocol is SQL for the old and new, and the hostname and database names match + // Let the user know that the current database cannot be saved on top of itself. + if (url.scheme() == "sql" && oldUrl.scheme() == "sql" + && oldUrl.host() == url.host() + && QUrlQuery(oldUrl).queryItemValue("driver") == QUrlQuery(url).queryItemValue("driver") + && oldUrl.path().right(oldUrl.path().length() - 1) == url.path().right(url.path().length() - 1)) { + KMessageBox::sorry(nullptr, i18n("Cannot save to current database.")); + } else { + try { + rc = saveAsDatabase(url); + } catch (const MyMoneyException &e) { + KMessageBox::sorry(nullptr, i18n("Cannot save to current database: %1", QString::fromLatin1(e.what()))); + } + } + } + delete dialog; + + if (rc) { + //KRecentFilesAction *p = dynamic_cast(action("file_open_recent")); + //if(p) + appInterface()->addToRecentFiles(url); + appInterface()->writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile)); + appInterface()->writeFilenameURL(url); + } + return rc; } -QString SQLStorage::formatName() const +eKMyMoney::StorageType SQLStorage::storageType() const { - return QStringLiteral("SQL"); + return eKMyMoney::StorageType::SQL; } QString SQLStorage::fileExtension() const { - return QString(); + return i18n("Database files (*.db *.sql)"); } void SQLStorage::createActions() { m_openDBaction = actionCollection()->addAction("open_database"); m_openDBaction->setText(i18n("Open database...")); m_openDBaction->setIcon(Icons::get(Icon::SVNUpdate)); connect(m_openDBaction, &QAction::triggered, this, &SQLStorage::slotOpenDatabase); - m_saveAsDBaction = actionCollection()->addAction("saveas_database"); - m_saveAsDBaction->setText(i18n("Save as database...")); - m_saveAsDBaction->setIcon(Icons::get(Icon::FileArchiver)); - connect(m_saveAsDBaction, &QAction::triggered, this, &SQLStorage::slotSaveAsDatabase); - m_generateDB = actionCollection()->addAction("tools_generate_sql"); m_generateDB->setText(i18n("Generate Database SQL")); connect(m_generateDB, &QAction::triggered, this, &SQLStorage::slotGenerateSql); } void SQLStorage::slotOpenDatabase() { QPointer dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite); if (!dialog->checkDrivers()) { delete dialog; return; } if (dialog->exec() == QDialog::Accepted && dialog != 0) { auto url = dialog->selectedURL(); QUrl newurl = url; if ((newurl.scheme() == QLatin1String("sql"))) { const QString key = QLatin1String("driver"); // take care and convert some old url to their new counterpart QUrlQuery query(newurl); if (query.queryItemValue(key) == QLatin1String("QMYSQL3")) { // fix any old urls query.removeQueryItem(key); query.addQueryItem(key, QLatin1String("QMYSQL")); } if (query.queryItemValue(key) == QLatin1String("QSQLITE3")) { query.removeQueryItem(key); query.addQueryItem(key, QLatin1String("QSQLITE")); } newurl.setQuery(query); // check if a password is needed. it may be if the URL came from the last/recent file list dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite, newurl); if (!dialog->checkDrivers()) { delete dialog; return; } // if we need to supply a password, then show the dialog // otherwise it isn't needed if ((query.queryItemValue("secure").toLower() == QLatin1String("yes")) && newurl.password().isEmpty()) { if (dialog->exec() == QDialog::Accepted && dialog != nullptr) { newurl = dialog->selectedURL(); } else { delete dialog; return; } } delete dialog; } appInterface()->slotFileOpenRecent(newurl); } delete dialog; } -void SQLStorage::slotSaveAsDatabase() -{ - bool rc = false; - QUrl oldUrl; - // in event of it being a database, ensure that all data is read into storage for saveas - if (appInterface()->isDatabase()) - oldUrl = appInterface()->filenameURL().isEmpty() ? appInterface()->lastOpenedURL() : appInterface()->filenameURL(); - - QPointer dialog = new KSelectDatabaseDlg(QIODevice::WriteOnly); - QUrl url = oldUrl; - if (!dialog->checkDrivers()) { - delete dialog; - return; - } - - while (oldUrl == url && dialog->exec() == QDialog::Accepted && dialog != 0) { - url = dialog->selectedURL(); - // If the protocol is SQL for the old and new, and the hostname and database names match - // Let the user know that the current database cannot be saved on top of itself. - if (url.scheme() == "sql" && oldUrl.scheme() == "sql" - && oldUrl.host() == url.host() - && QUrlQuery(oldUrl).queryItemValue("driver") == QUrlQuery(url).queryItemValue("driver") - && oldUrl.path().right(oldUrl.path().length() - 1) == url.path().right(url.path().length() - 1)) { - KMessageBox::sorry(nullptr, i18n("Cannot save to current database.")); - } else { - try { - rc = saveAsDatabase(url); - } catch (const MyMoneyException &e) { - KMessageBox::sorry(nullptr, i18n("Cannot save to current database: %1", QString::fromLatin1(e.what()))); - } - } - } - delete dialog; - - if (rc) { - //KRecentFilesAction *p = dynamic_cast(action("file_open_recent")); - //if(p) - appInterface()->addToRecentFiles(url); - appInterface()->writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile)); - } - appInterface()->autosaveTimer()->stop(); - appInterface()->updateCaption(); - return; -} - void SQLStorage::slotGenerateSql() { QPointer editor = new KGenerateSqlDlg(nullptr); editor->setObjectName("Generate Database SQL"); editor->exec(); delete editor; } bool SQLStorage::saveAsDatabase(const QUrl &url) { auto writer = new MyMoneyStorageSql(MyMoneyFile::instance()->storage(), url); auto canWrite = false; switch (writer->open(url, QIODevice::WriteOnly)) { case 0: canWrite = true; break; case -1: // dbase already has data, see if he wants to clear it out if (KMessageBox::warningContinueCancel(nullptr, i18n("Database contains data which must be removed before using Save As.\n" "Do you wish to continue?"), "Database not empty") == KMessageBox::Continue) { if (writer->open(url, QIODevice::WriteOnly, true) == 0) canWrite = true; } else { delete writer; return false; } break; } if (canWrite) { delete writer; - saveDatabase(url); + save(url); return true; } else { KMessageBox::detailedError(nullptr, i18n("Cannot open or create database %1.\n" "Retry Save As Database and click Help" " for further info.", url.toDisplayString()), writer->lastError()); delete writer; return false; } } -bool SQLStorage::saveDatabase(const QUrl &url) -{ - auto rc = false; - if (!appInterface()->fileOpen()) { - KMessageBox::error(nullptr, i18n("Tried to access a file when it has not been opened")); - return (rc); - } - auto writer = new MyMoneyStorageSql(MyMoneyFile::instance()->storage(), url); - writer->open(url, QIODevice::WriteOnly); -// writer->setProgressCallback(&KMyMoneyView::progressCallback); - if (!writer->writeFile()) { - KMessageBox::detailedError(nullptr, - i18n("An unrecoverable error occurred while writing to the database.\n" - "It may well be corrupt."), - writer->lastError().toLatin1(), - i18n("Database malfunction")); - rc = false; - } else { - rc = true; - } - writer->setProgressCallback(0); - delete writer; - return rc; -} - K_PLUGIN_FACTORY_WITH_JSON(SQLStorageFactory, "sqlstorage.json", registerPlugin();) #include "sqlstorage.moc" diff --git a/kmymoney/plugins/sql/sqlstorage.h b/kmymoney/plugins/sql/sqlstorage.h index bfc656191..e4f411cdb 100644 --- a/kmymoney/plugins/sql/sqlstorage.h +++ b/kmymoney/plugins/sql/sqlstorage.h @@ -1,71 +1,70 @@ /*************************************************************************** sqlstorage.h ------------------- copyright : (C) 2018 by Łukasz Wojniłowicz email : lukasz.wojnilowicz@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef SQLSTORAGE_H #define SQLSTORAGE_H // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // QT Includes // Project Includes #include "kmymoneyplugin.h" class MyMoneyStorageMgr; class SQLStorage : public KMyMoneyPlugin::Plugin, public KMyMoneyPlugin::StoragePlugin { Q_OBJECT Q_INTERFACES(KMyMoneyPlugin::StoragePlugin) public: explicit SQLStorage(QObject *parent, const QVariantList &args); ~SQLStorage() override; QAction *m_openDBaction; QAction *m_saveAsDBaction; QAction *m_generateDB; - bool open(MyMoneyStorageMgr *storage, const QUrl &url) override; + MyMoneyStorageMgr *open(const QUrl &url) override; bool save(const QUrl &url) override; - QString formatName() const override; + bool saveAs() override; + eKMyMoney::StorageType storageType() const override; QString fileExtension() const override; protected: void createActions(); private: /** * Saves the data into permanent storage on a new or empty SQL database. * * @param url The pseudo URL of the database * * @retval false save operation failed * @retval true save operation was successful */ bool saveAsDatabase(const QUrl &url); - bool saveDatabase(const QUrl &url); private Q_SLOTS: void slotOpenDatabase(); - void slotSaveAsDatabase(); void slotGenerateSql(); }; #endif diff --git a/kmymoney/plugins/xml/CMakeLists.txt b/kmymoney/plugins/xml/CMakeLists.txt index 201612676..3af06d301 100644 --- a/kmymoney/plugins/xml/CMakeLists.txt +++ b/kmymoney/plugins/xml/CMakeLists.txt @@ -1,44 +1,41 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/xmlstorage.json.in ${CMAKE_CURRENT_BINARY_DIR}/xmlstorage.json @ONLY) set(xmlstorage_SOURCES xmlstorage.cpp mymoneystoragexml.cpp mymoneystoragenames.cpp mymoneystorageanon.cpp kgpgkeyselectiondlg.cpp ) ki18n_wrap_ui(xmlstorage_SOURCES kgpgkeyselectiondlg.ui) kcoreaddons_add_plugin(xmlstorage SOURCES ${xmlstorage_SOURCES} JSON "${CMAKE_CURRENT_BINARY_DIR}/xmlstorage.json" INSTALL_NAMESPACE "kmymoney") #kcoreaddons_add_plugin sets LIBRARY_OUTPUT_DIRECTORY to ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${INSTALL_NAMESPACE} set_target_properties(xmlstorage PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") target_link_libraries(xmlstorage PUBLIC kmm_plugin PRIVATE Qt5::Xml KF5::Archive KF5::I18n KF5::CoreAddons kgpgfile kmymoney_common ) -install(FILES xmlstorage.rc - DESTINATION "${KXMLGUI_INSTALL_DIR}/xmlstorage") - # install(FILES kmymoney-xmlstorageplugin.desktop # DESTINATION ${SERVICETYPES_INSTALL_DIR} # ) # if(BUILD_TESTING) # add_subdirectory(tests) # endif() diff --git a/kmymoney/plugins/xml/mymoneystoragexml.h b/kmymoney/plugins/xml/mymoneystoragexml.h index 83c3b98ce..e0dcd30ea 100644 --- a/kmymoney/plugins/xml/mymoneystoragexml.h +++ b/kmymoney/plugins/xml/mymoneystoragexml.h @@ -1,197 +1,197 @@ /* * Copyright 2002-2004 Kevin Tambascio * Copyright 2002-2016 Thomas Baumgart * Copyright 2004-2005 Ace Jones * Copyright 2006 Darren Gould * Copyright 2017-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef MYMONEYSTORAGEXML_H #define MYMONEYSTORAGEXML_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "imymoneystorageformat.h" /** *@author Kevin Tambascio (ktambascio@users.sourceforge.net) */ #define VERSION_0_60_XML 0x10000010 // Version 0.5 file version info #define VERSION_0_61_XML 0x10000011 // use 8 bytes for MyMoneyMoney objects class QString; class QIODevice; class QDomElement; class QDomDocument; class QDate; class MyMoneyStorageMgr; class MyMoneyInstitution; class MyMoneyAccount; class MyMoneySecurity; class MyMoneySchedule; class MyMoneyPayee; class MyMoneyTag; class MyMoneyBudget; class MyMoneyReport; class MyMoneyPrice; class MyMoneyTransaction; class MyMoneyCostCenter; class onlineJob; template class QList; typedef QPair MyMoneySecurityPair; typedef QMap MyMoneyPriceEntries; typedef QMap MyMoneyPriceList; class MyMoneyStorageXML : public IMyMoneyOperationsFormat { friend class MyMoneyXmlContentHandler; public: MyMoneyStorageXML(); virtual ~MyMoneyStorageXML(); enum fileVersionDirectionType { Reading = 0, /**< version of file to be read */ Writing = 1 /**< version to be used when writing a file */ }; -protected: - void setProgressCallback(void(*callback)(int, int, const QString&)) override; + void readFile(QIODevice* s, MyMoneyStorageMgr* storage) override; + void writeFile(QIODevice* s, MyMoneyStorageMgr* storage) override; + void setProgressCallback(void(*callback)(int, int, const QString&)) override; + + protected: void signalProgress(int current, int total, const QString& = ""); /** * This method returns the version of the underlying file. It * is used by the MyMoney objects contained in a MyMoneyStorageBin object (e.g. * MyMoneyAccount, MyMoneyInstitution, MyMoneyTransaction, etc.) to * determine the layout used when reading/writing a persistant file. * A parameter is used to determine the direction. * * @param dir information about the direction (reading/writing). The * default is reading. * * @return version QString of file's version * * @see m_fileVersionRead, m_fileVersionWrite */ static unsigned int fileVersion(fileVersionDirectionType dir = Reading); QList readElements(QString groupTag, QString itemTag = QString()); bool readFileInformation(const QDomElement& fileInfo); virtual void writeFileInformation(QDomElement& fileInfo); virtual void writeUserInformation(QDomElement& userInfo); virtual void writeInstitution(QDomElement& institutions, const MyMoneyInstitution& i); virtual void writeInstitutions(QDomElement& institutions); virtual void writePrices(QDomElement& prices); virtual void writePricePair(QDomElement& price, const MyMoneyPriceEntries& p); virtual void writePrice(QDomElement& prices, const MyMoneyPrice& p); virtual void writePayees(QDomElement& payees); virtual void writePayee(QDomElement& payees, const MyMoneyPayee& p); virtual void writeTags(QDomElement& tags); virtual void writeTag(QDomElement& tags, const MyMoneyTag& ta); virtual void writeAccounts(QDomElement& accounts); virtual void writeAccount(QDomElement& accounts, const MyMoneyAccount& p); virtual void writeTransactions(QDomElement& transactions); virtual void writeTransaction(QDomElement& transactions, const MyMoneyTransaction& tx); virtual void writeSchedules(QDomElement& scheduled); virtual void writeSchedule(QDomElement& scheduledTx, const MyMoneySchedule& tx); virtual void writeReports(QDomElement& e); virtual void writeReport(QDomElement& report, const MyMoneyReport& r); virtual void writeBudgets(QDomElement& e); virtual void writeBudget(QDomElement& budget, const MyMoneyBudget& b); virtual void writeOnlineJobs(QDomElement& onlineJobs); virtual void writeOnlineJob(QDomElement& onlineJobs, const onlineJob& job); virtual void writeSecurities(QDomElement& securities); virtual void writeSecurity(QDomElement& securityElement, const MyMoneySecurity& security); virtual void writeCostCenters(QDomElement& parent); virtual void writeCostCenter(QDomElement& costCenters, const MyMoneyCostCenter& costCenter); virtual void writeCurrencies(QDomElement& currencies); virtual QDomElement writeKeyValuePairs(const QMap pairs); - virtual void readFile(QIODevice* s, MyMoneyStorageMgr* storage) override; - virtual void writeFile(QIODevice* s, MyMoneyStorageMgr* storage) override; - bool readUserInformation(const QDomElement& userElement); void readPricePair(const QDomElement& pricePair); const MyMoneyPrice readPrice(const QString& from, const QString& to, const QDomElement& price); QDomElement findChildElement(const QString& name, const QDomElement& root); private: void (*m_progressCallback)(int, int, const QString&); enum elNameE { enAddress, enCreationDate, enLastModifiedDate, enVersion, enFixVersion, enPair }; static const QString getElName(const elNameE _el); protected: MyMoneyStorageMgr *m_storage; QDomDocument *m_doc; private: /// \internal d-pointer class. class Private; /// \internal d-pointer instance. Private* const d; /** * This member is used to store the file version information * obtained while reading a file. */ static unsigned int fileVersionRead; /** * This member is used to store the file version information * to be used when writing a file. */ static unsigned int fileVersionWrite; /** * This member keeps the id of the base currency. We need this * temporarily to convert the price history from the old to the * new format. This should go at some time beyond 0.8 (ipwizard) */ QString m_baseCurrencyId; }; #endif diff --git a/kmymoney/plugins/xml/xmlstorage.cpp b/kmymoney/plugins/xml/xmlstorage.cpp index 0f068de5b..d50b46e5b 100644 --- a/kmymoney/plugins/xml/xmlstorage.cpp +++ b/kmymoney/plugins/xml/xmlstorage.cpp @@ -1,517 +1,508 @@ /* * Copyright 2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "xmlstorage.h" #include #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "appinterface.h" #include "viewinterface.h" #include "mymoneyfile.h" +#include "mymoneystoragemgr.h" #include "mymoneyexception.h" #include "mymoneystoragebin.h" #include "mymoneystoragexml.h" #include "mymoneystorageanon.h" #include "icons.h" #include "kmymoneysettings.h" #include "kmymoneyutils.h" #include "kgpgfile.h" #include "kgpgkeyselectiondlg.h" +#include "kmymoneyenums.h" using namespace Icons; static constexpr KCompressionDevice::CompressionType const& COMPRESSION_TYPE = KCompressionDevice::GZip; // static constexpr char recoveryKeyId[] = "0xD2B08440"; static constexpr char recoveryKeyId[] = "59B0F826D2B08440"; XMLStorage::XMLStorage(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "xmlstorage"/*must be the same as X-KDE-PluginInfo-Name*/) { Q_UNUSED(args) setComponentName("xmlstorage", i18n("XML storage")); - setXMLFile("xmlstorage.rc"); - createActions(); // For information, announce that we have been loaded. qDebug("Plugins: xmlstorage loaded"); } XMLStorage::~XMLStorage() { qDebug("Plugins: xmlstorage unloaded"); } -bool XMLStorage::open(MyMoneyStorageMgr *storage, const QUrl &url) +MyMoneyStorageMgr *XMLStorage::open(const QUrl &url) { - if (!url.isValid()) - throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid URL %1").arg(qPrintable(url.url()))); + if (url.scheme() == QLatin1String("sql")) + return nullptr; QString fileName; auto downloadedFile = false; if (url.isLocalFile()) { fileName = url.toLocalFile(); } else { fileName = KMyMoneyUtils::downloadFile(url); downloadedFile = true; } if (!KMyMoneyUtils::fileExists(QUrl::fromLocalFile(fileName))) throw MYMONEYEXCEPTION(QString::fromLatin1("Error opening the file.\n" "Requested file: '%1'.\n" "Downloaded file: '%2'").arg(qPrintable(url.url()), fileName)); QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName)); QByteArray qbaFileHeader(2, '\0'); const auto sFileToShort = QString::fromLatin1("File %1 is too short.").arg(fileName); if (file.read(qbaFileHeader.data(), 2) != 2) throw MYMONEYEXCEPTION(sFileToShort); file.close(); // There's a problem with the KFilterDev and KGPGFile classes: // One supports the at(n) member but not ungetch() together with // read() and the other does not provide an at(n) method but // supports read() that considers the ungetch() buffer. QFile // supports everything so this is not a problem. We solve the problem // for now by keeping track of which method can be used. auto haveAt = true; auto isEncrypted = false; QIODevice* qfile = nullptr; QString sFileHeader(qbaFileHeader); if (sFileHeader == QString("\037\213")) { // gzipped? qfile = new KCompressionDevice(fileName, COMPRESSION_TYPE); } else if (sFileHeader == QString("--") || // PGP ASCII armored? sFileHeader == QString("\205\001") || // PGP binary? sFileHeader == QString("\205\002")) { // PGP binary? if (KGPGFile::GPGAvailable()) { qfile = new KGPGFile(fileName); haveAt = false; isEncrypted = true; } else { throw MYMONEYEXCEPTION(QString::fromLatin1("GPG is not available for decryption of file %1").arg(fileName)); } } else { // we can't use file directly, as we delete qfile later on qfile = new QFile(file.fileName()); } if (!qfile->open(QIODevice::ReadOnly)) { delete qfile; throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName)); } qbaFileHeader.resize(8); if (qfile->read(qbaFileHeader.data(), 8) != 8) throw MYMONEYEXCEPTION(sFileToShort); if (haveAt) qfile->seek(0); else ungetString(qfile, qbaFileHeader.data(), 8); // Ok, we got the first block of 8 bytes. Read in the two // unsigned long int's by preserving endianess. This is // achieved by reading them through a QDataStream object qint32 magic0, magic1; QDataStream s(&qbaFileHeader, QIODevice::ReadOnly); s >> magic0; s >> magic1; // If both magic numbers match (we actually read in the // text 'KMyMoney' then we assume a binary file and // construct a reader for it. Otherwise, we construct // an XML reader object. // // The expression magic0 < 30 is only used to create // a binary reader if we assume an old binary file. This // should be removed at some point. An alternative is to // check the beginning of the file against an pattern // of the XML file (e.g. '?File %1 contains the old binary format used by KMyMoney. Please use an older version of KMyMoney (0.8.x) that still supports this format to convert it to the new XML based format.").arg(fileName)); } // Scan the first 70 bytes to see if we find something // we know. For now, we support our own XML format and // GNUCash XML format. If the file is smaller, then it // contains no valid data and we reject it anyway. qbaFileHeader.resize(70); if (qfile->read(qbaFileHeader.data(), 70) != 70) throw MYMONEYEXCEPTION(sFileToShort); if (haveAt) qfile->seek(0); else ungetString(qfile, qbaFileHeader.data(), 70); - IMyMoneyOperationsFormat* pReader = nullptr; QRegExp kmyexp(""); QByteArray txt(qbaFileHeader, 70); - if (kmyexp.indexIn(txt) != -1) { - pReader = new MyMoneyStorageXML; - } else { - throw MYMONEYEXCEPTION(QString::fromLatin1("%1").arg(i18n("File %1 contains an unknown file format.", fileName))); - } - - // disconnect the current storga manager from the engine - MyMoneyFile::instance()->detachStorage(); + if (kmyexp.indexIn(txt) == -1) + return nullptr; // attach the storage before reading the file, since the online // onlineJobAdministration object queries the engine during // loading. - MyMoneyFile::instance()->attachStorage(storage); - pReader->setProgressCallback(appInterface()->progressCallback()); - pReader->readFile(qfile, storage); - pReader->setProgressCallback(0); - delete pReader; + auto storage = new MyMoneyStorageMgr; + MyMoneyStorageXML pReader; + pReader.setProgressCallback(appInterface()->progressCallback()); + pReader.readFile(qfile, storage); + pReader.setProgressCallback(0); qfile->close(); delete qfile; // if a temporary file was downloaded, then it will be removed // with the next call. Otherwise, it stays untouched on the local // filesystem. if (downloadedFile) QFile::remove(fileName); - // encapsulate transactions to the engine to be able to commit/rollback - MyMoneyFileTransaction ft; // make sure we setup the encryption key correctly - if (isEncrypted && MyMoneyFile::instance()->value("kmm-encryption-key").isEmpty()) - MyMoneyFile::instance()->setValue("kmm-encryption-key", KMyMoneySettings::gpgRecipientList().join(",")); - ft.commit(); - return true; + if (isEncrypted) { + MyMoneyFile::instance()->attachStorage(storage); + if (MyMoneyFile::instance()->value("kmm-encryption-key").isEmpty()) { + // encapsulate transactions to the engine to be able to commit/rollback + MyMoneyFileTransaction ft; + MyMoneyFile::instance()->setValue("kmm-encryption-key", KMyMoneySettings::gpgRecipientList().join(",")); + ft.commit(); + } + MyMoneyFile::instance()->detachStorage(); + } + + return storage; } bool XMLStorage::save(const QUrl &url) { QString filename = url.path(); if (!appInterface()->fileOpen()) { KMessageBox::error(nullptr, i18n("Tried to access a file when it has not been opened")); return false; } std::unique_ptr storageWriter; // If this file ends in ".ANON.XML" then this should be written using the // anonymous writer. bool plaintext = filename.right(4).toLower() == ".xml"; if (filename.right(9).toLower() == ".anon.xml") storageWriter = std::make_unique(); else storageWriter = std::make_unique(); QString keyList; if (!appInterface()->filenameURL().isEmpty()) keyList = MyMoneyFile::instance()->value("kmm-encryption-key"); - else + if (keyList.isEmpty()) keyList = m_encryptionKeys; // actually, url should be the parameter to this function // but for now, this would involve too many changes auto rc = true; try { if (! url.isValid()) { throw MYMONEYEXCEPTION(QString::fromLatin1("Malformed URL '%1'").arg(url.url())); } if (url.isLocalFile()) { filename = url.toLocalFile(); try { const unsigned int nbak = KMyMoneySettings::autoBackupCopies(); if (nbak) { KBackup::numberedBackupFile(filename, QString(), QStringLiteral("~"), nbak); } saveToLocalFile(filename, storageWriter.get(), plaintext, keyList); - } catch (const MyMoneyException &) { - throw MYMONEYEXCEPTION(QString::fromLatin1("Unable to write changes to '%1'").arg(filename)); + } catch (const MyMoneyException &e) { + qWarning("Unable to write changes to: %s\nReason: %s", qPrintable(filename), e.what()); + throw; } } else { QTemporaryFile tmpfile; tmpfile.open(); // to obtain the name tmpfile.close(); saveToLocalFile(tmpfile.fileName(), storageWriter.get(), plaintext, keyList); Q_CONSTEXPR int permission = -1; QFile file(tmpfile.fileName()); file.open(QIODevice::ReadOnly); KIO::StoredTransferJob *putjob = KIO::storedPut(file.readAll(), url, permission, KIO::JobFlag::Overwrite); if (!putjob->exec()) { throw MYMONEYEXCEPTION(QString::fromLatin1("Unable to upload to '%1'.
%2").arg(url.toDisplayString(), putjob->errorString())); } file.close(); } } catch (const MyMoneyException &e) { KMessageBox::error(nullptr, QString::fromLatin1(e.what())); MyMoneyFile::instance()->setDirty(); rc = false; } return rc; } -QString XMLStorage::formatName() const +bool XMLStorage::saveAs() { - return QStringLiteral("XML"); + auto rc = false; + QStringList m_additionalGpgKeys; + m_encryptionKeys.clear(); + + QString selectedKeyName; + if (KGPGFile::GPGAvailable() && KMyMoneySettings::writeDataEncrypted()) { + // fill the secret key list and combo box + QStringList keyList; + KGPGFile::secretKeyList(keyList); + + QPointer dlg = new KGpgKeySelectionDlg(nullptr); + dlg->setSecretKeys(keyList, KMyMoneySettings::gpgRecipient()); + dlg->setAdditionalKeys(KMyMoneySettings::gpgRecipientList()); + rc = dlg->exec(); + if ((rc == QDialog::Accepted) && (dlg != 0)) { + m_additionalGpgKeys = dlg->additionalKeys(); + selectedKeyName = dlg->secretKey(); + } + delete dlg; + if (rc != QDialog::Accepted) { + return rc; + } + } + + QString prevDir; // don't prompt file name if not a native file + if (appInterface()->isNativeFile()) + prevDir = appInterface()->readLastUsedDir(); + + QPointer dlg = + new QFileDialog(nullptr, i18n("Save As"), prevDir, + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.kmy")).arg(i18nc("KMyMoney (Filefilter)", "KMyMoney files")) + + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.xml")).arg(i18nc("XML (Filefilter)", "XML files")) + + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.anon.xml")).arg(i18nc("Anonymous (Filefilter)", "Anonymous files")) + + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*")).arg(i18nc("All files (Filefilter)", "All files"))); + dlg->setAcceptMode(QFileDialog::AcceptSave); + + if (dlg->exec() == QDialog::Accepted && dlg != 0) { + QUrl newURL = dlg->selectedUrls().first(); + if (!newURL.fileName().isEmpty()) { + appInterface()->consistencyCheck(false); + QString newName = newURL.toDisplayString(QUrl::PreferLocalFile); + + // append extension if not present + if (!newName.endsWith(QLatin1String(".kmy"), Qt::CaseInsensitive) && + !newName.endsWith(QLatin1String(".xml"), Qt::CaseInsensitive)) + newName.append(QLatin1String(".kmy")); + newURL = QUrl::fromUserInput(newName); + + // If this is the anonymous file export, just save it, don't actually take the + // name, or remember it! Don't even try to encrypt it + if (newName.endsWith(QLatin1String(".anon.xml"), Qt::CaseInsensitive)) + rc = save(newURL); + else { + appInterface()->writeFilenameURL(newURL); + QRegExp keyExp(".* \\((.*)\\)"); + if (keyExp.indexIn(selectedKeyName) != -1) { + m_encryptionKeys = keyExp.cap(1); + if (!m_additionalGpgKeys.isEmpty()) { + if (!m_encryptionKeys.isEmpty()) + m_encryptionKeys.append(QLatin1Char(',')); + m_encryptionKeys.append(m_additionalGpgKeys.join(QLatin1Char(','))); + } + } + rc = save(newURL); + appInterface()->addToRecentFiles(newURL); + //write the directory used for this file as the default one for next time. + appInterface()->writeLastUsedDir(newURL.toDisplayString(QUrl::RemoveFilename | QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); + appInterface()->writeLastUsedFile(newName); + } + } + } + (*appInterface()->progressCallback())(0,0, i18nc("Application is ready to use", "Ready.")); + delete dlg; + return rc; } -QString XMLStorage::fileExtension() const +eKMyMoney::StorageType XMLStorage::storageType() const { - return QString(); + return eKMyMoney::StorageType::XML; } -void XMLStorage::createActions() +QString XMLStorage::fileExtension() const { - m_saveAsXMLaction = actionCollection()->addAction("saveas_xml"); - m_saveAsXMLaction->setText(i18n("Save as XML...")); - m_saveAsXMLaction->setIcon(Icons::get(Icon::FileArchiver)); - connect(m_saveAsXMLaction, &QAction::triggered, this, &XMLStorage::slotSaveAsXML); + return i18n("KMyMoney files (*.kmy *.xml)"); } void XMLStorage::ungetString(QIODevice *qfile, char *buf, int len) { buf = &buf[len-1]; while (len--) { qfile->ungetChar(*buf--); } } void XMLStorage::saveToLocalFile(const QString& localFile, IMyMoneyOperationsFormat* pWriter, bool plaintext, const QString& keyList) { // Check GPG encryption bool encryptFile = true; bool encryptRecover = false; if (!keyList.isEmpty()) { if (!KGPGFile::GPGAvailable()) { KMessageBox::sorry(nullptr, i18n("GPG does not seem to be installed on your system. Please make sure that GPG can be found using the standard search path. This time, encryption is disabled."), i18n("GPG not found")); encryptFile = false; } else { if (KMyMoneySettings::encryptRecover()) { encryptRecover = true; if (!KGPGFile::keyAvailable(QString(recoveryKeyId))) { KMessageBox::sorry(nullptr, i18n("

You have selected to encrypt your data also with the KMyMoney recover key, but the key with id

%1

has not been found in your keyring at this time. Please make sure to import this key into your keyring. You can find it on the KMyMoney web-site. This time your data will not be encrypted with the KMyMoney recover key.

", QString(recoveryKeyId)), i18n("GPG Key not found")); encryptRecover = false; } } for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { if (!KGPGFile::keyAvailable(key)) { KMessageBox::sorry(nullptr, i18n("

You have specified to encrypt your data for the user-id

%1.

Unfortunately, a valid key for this user-id was not found in your keyring. Please make sure to import a valid key for this user-id. This time, encryption is disabled.

", key), i18n("GPG Key not found")); encryptFile = false; break; } } if (encryptFile == true) { QString msg = i18n("

You have configured to save your data in encrypted form using GPG. Make sure you understand that you might lose all your data if you encrypt it, but cannot decrypt it later on. If unsure, answer No.

"); if (KMessageBox::questionYesNo(nullptr, msg, i18n("Store GPG encrypted"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "StoreEncrypted") == KMessageBox::No) { encryptFile = false; } } } } // Create a temporary file if needed QString writeFile = localFile; QTemporaryFile tmpFile; if (QFile::exists(localFile)) { tmpFile.open(); writeFile = tmpFile.fileName(); tmpFile.close(); } /** * @brief Automatically restore settings when scope is left */ struct restorePreviousSettingsHelper { restorePreviousSettingsHelper() : m_signalsWereBlocked{MyMoneyFile::instance()->signalsBlocked()} { MyMoneyFile::instance()->blockSignals(true); } ~restorePreviousSettingsHelper() { MyMoneyFile::instance()->blockSignals(m_signalsWereBlocked); } const bool m_signalsWereBlocked; } restoreHelper; MyMoneyFileTransaction ft; MyMoneyFile::instance()->deletePair("kmm-encryption-key"); std::unique_ptr device; if (!keyList.isEmpty() && encryptFile && !plaintext) { std::unique_ptr kgpg = std::unique_ptr(new KGPGFile{writeFile}); if (kgpg) { for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { kgpg->addRecipient(key.toLatin1()); } if (encryptRecover) { kgpg->addRecipient(recoveryKeyId); } MyMoneyFile::instance()->setValue("kmm-encryption-key", keyList); device = std::unique_ptr(kgpg.release()); } } else { QFile *file = new QFile(writeFile); // The second parameter of KCompressionDevice means that KCompressionDevice will delete the QFile object device = std::unique_ptr(new KCompressionDevice{file, true, (plaintext) ? KCompressionDevice::None : COMPRESSION_TYPE}); } ft.commit(); if (!device || !device->open(QIODevice::WriteOnly)) { throw MYMONEYEXCEPTION(QString::fromLatin1("Unable to open file '%1' for writing.").arg(localFile)); } pWriter->setProgressCallback(appInterface()->progressCallback()); pWriter->writeFile(device.get(), MyMoneyFile::instance()->storage()); device->close(); // Check for errors if possible, only possible for KGPGFile QFileDevice *fileDevice = qobject_cast(device.get()); if (fileDevice && fileDevice->error() != QFileDevice::NoError) { throw MYMONEYEXCEPTION(QString::fromLatin1("Failure while writing to '%1'").arg(localFile)); } if (writeFile != localFile) { // This simple comparison is possible because the strings are equal if no temporary file was created. // If a temporary file was created, it is made in a way that the name is definitely different. So no // symlinks etc. have to be evaluated. if (!QFile::remove(localFile) || !QFile::rename(writeFile, localFile)) throw MYMONEYEXCEPTION(QString::fromLatin1("Failure while writing to '%1'").arg(localFile)); } QFile::setPermissions(localFile, QFileDevice::ReadUser | QFileDevice::WriteUser); pWriter->setProgressCallback(0); } -void XMLStorage::slotSaveAsXML() -{ - bool rc = false; - QStringList m_additionalGpgKeys; - m_encryptionKeys.clear(); - - QString selectedKeyName; - if (KGPGFile::GPGAvailable() && KMyMoneySettings::writeDataEncrypted()) { - // fill the secret key list and combo box - QStringList keyList; - KGPGFile::secretKeyList(keyList); - - QPointer dlg = new KGpgKeySelectionDlg(nullptr); - dlg->setSecretKeys(keyList, KMyMoneySettings::gpgRecipient()); - dlg->setAdditionalKeys(KMyMoneySettings::gpgRecipientList()); - rc = dlg->exec(); - if ((rc == QDialog::Accepted) && (dlg != 0)) { - m_additionalGpgKeys = dlg->additionalKeys(); - selectedKeyName = dlg->secretKey(); - } - delete dlg; - if (rc != QDialog::Accepted) { - return; - } - } - - QString prevDir; // don't prompt file name if not a native file - if (appInterface()->isNativeFile()) - prevDir = appInterface()->readLastUsedDir(); - - QPointer dlg = - new QFileDialog(nullptr, i18n("Save As"), prevDir, - QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.kmy")).arg(i18nc("KMyMoney (Filefilter)", "KMyMoney files")) + - QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.xml")).arg(i18nc("XML (Filefilter)", "XML files")) + - QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.anon.xml")).arg(i18nc("Anonymous (Filefilter)", "Anonymous files")) + - QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*")).arg(i18nc("All files (Filefilter)", "All files"))); - dlg->setAcceptMode(QFileDialog::AcceptSave); - - if (dlg->exec() == QDialog::Accepted && dlg != 0) { - QUrl newURL = dlg->selectedUrls().first(); - if (!newURL.fileName().isEmpty()) { - appInterface()->consistencyCheck(false); - QString newName = newURL.toDisplayString(QUrl::PreferLocalFile); - - // append extension if not present - if (!newName.endsWith(QLatin1String(".kmy"), Qt::CaseInsensitive) && - !newName.endsWith(QLatin1String(".xml"), Qt::CaseInsensitive)) - newName.append(QLatin1String(".kmy")); - newURL = QUrl::fromUserInput(newName); - appInterface()->addToRecentFiles(newURL); - - // If this is the anonymous file export, just save it, don't actually take the - // name, or remember it! Don't even try to encrypt it - if (newName.endsWith(QLatin1String(".anon.xml"), Qt::CaseInsensitive)) - rc = save(newURL); - else { - appInterface()->writeFilenameURL(newURL); - QRegExp keyExp(".* \\((.*)\\)"); - if (keyExp.indexIn(selectedKeyName) != -1) { - m_encryptionKeys = keyExp.cap(1); - if (!m_additionalGpgKeys.isEmpty()) { - if (!m_encryptionKeys.isEmpty()) - m_encryptionKeys.append(QLatin1Char(',')); - m_encryptionKeys.append(m_additionalGpgKeys.join(QLatin1Char(','))); - } - } - rc = save(newURL); - //write the directory used for this file as the default one for next time. - appInterface()->writeLastUsedDir(newURL.toDisplayString(QUrl::RemoveFilename | QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); - appInterface()->writeLastUsedFile(newName); - } - appInterface()->autosaveTimer()->stop(); - } - } - (*appInterface()->progressCallback())(0,0, i18nc("Application is ready to use", "Ready.")); - delete dlg; - appInterface()->updateCaption(); -} - K_PLUGIN_FACTORY_WITH_JSON(XMLStorageFactory, "xmlstorage.json", registerPlugin();) #include "xmlstorage.moc" diff --git a/kmymoney/plugins/xml/xmlstorage.h b/kmymoney/plugins/xml/xmlstorage.h index d595c31e5..334d996f2 100644 --- a/kmymoney/plugins/xml/xmlstorage.h +++ b/kmymoney/plugins/xml/xmlstorage.h @@ -1,79 +1,76 @@ /* * Copyright 2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef XMLSTORAGE_H #define XMLSTORAGE_H // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // QT Includes // Project Includes #include "kmymoneyplugin.h" class QIODevice; class MyMoneyStorageMgr; class XMLStorage : public KMyMoneyPlugin::Plugin, public KMyMoneyPlugin::StoragePlugin { Q_OBJECT Q_INTERFACES(KMyMoneyPlugin::StoragePlugin) public: explicit XMLStorage(QObject *parent, const QVariantList &args); ~XMLStorage() override; QAction *m_saveAsXMLaction; - bool open(MyMoneyStorageMgr *storage, const QUrl &url) override; + MyMoneyStorageMgr *open(const QUrl &url) override; bool save(const QUrl &url) override; - QString formatName() const override; + bool saveAs() override; + eKMyMoney::StorageType storageType() const override; QString fileExtension() const override; private: void createActions(); void ungetString(QIODevice *qfile, char *buf, int len); /** * This method is used by saveFile() to store the data * either directly in the destination file if it is on * the local file system or in a temporary file when * the final destination is reached over a network * protocol (e.g. FTP) * * @param localFile the name of the local file * @param writer pointer to the formatter * @param plaintext whether to override any compression & encryption settings * @param keyList QString containing a comma separated list of keys to be used for encryption * If @p keyList is empty, the file will be saved unencrypted * * @note This method will close the file when it is written. */ void saveToLocalFile(const QString& localFile, IMyMoneyOperationsFormat* pWriter, bool plaintext, const QString& keyList); QString m_encryptionKeys; -private Q_SLOTS: - void slotSaveAsXML(); - - }; #endif diff --git a/kmymoney/plugins/xml/xmlstorage.rc b/kmymoney/plugins/xml/xmlstorage.rc deleted file mode 100644 index 567b1ee86..000000000 --- a/kmymoney/plugins/xml/xmlstorage.rc +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/kmymoney/views/kaccountsview.cpp b/kmymoney/views/kaccountsview.cpp index c41915c1c..ecd5b6754 100644 --- a/kmymoney/views/kaccountsview.cpp +++ b/kmymoney/views/kaccountsview.cpp @@ -1,583 +1,565 @@ /*************************************************************************** kaccountsview.cpp ------------------- copyright : (C) 2007 by Thomas Baumgart (C) 2017, 2018 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kaccountsview_p.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "onlinejobadministration.h" #include "knewaccountwizard.h" #include "kbalancechartdlg.h" #include "kmymoneyutils.h" #include "kmymoneysettings.h" #include "storageenums.h" #include "menuenums.h" using namespace Icons; KAccountsView::KAccountsView(QWidget *parent) : KMyMoneyAccountsViewBase(*new KAccountsViewPrivate(this), parent) { connect(pActions[eMenu::Action::NewAccount], &QAction::triggered, this, &KAccountsView::slotNewAccount); connect(pActions[eMenu::Action::EditAccount], &QAction::triggered, this, &KAccountsView::slotEditAccount); connect(pActions[eMenu::Action::DeleteAccount], &QAction::triggered, this, &KAccountsView::slotDeleteAccount); connect(pActions[eMenu::Action::CloseAccount], &QAction::triggered, this, &KAccountsView::slotCloseAccount); connect(pActions[eMenu::Action::ReopenAccount], &QAction::triggered, this, &KAccountsView::slotReopenAccount); connect(pActions[eMenu::Action::ChartAccountBalance], &QAction::triggered, this, &KAccountsView::slotChartAccountBalance); connect(pActions[eMenu::Action::MapOnlineAccount], &QAction::triggered, this, &KAccountsView::slotAccountMapOnline); connect(pActions[eMenu::Action::UnmapOnlineAccount], &QAction::triggered, this, &KAccountsView::slotAccountUnmapOnline); connect(pActions[eMenu::Action::UpdateAccount], &QAction::triggered, this, &KAccountsView::slotAccountUpdateOnline); connect(pActions[eMenu::Action::UpdateAllAccounts], &QAction::triggered, this, &KAccountsView::slotAccountUpdateOnlineAll); } KAccountsView::~KAccountsView() { } void KAccountsView::executeCustomAction(eView::Action action) { switch(action) { case eView::Action::Refresh: refresh(); break; case eView::Action::SetDefaultFocus: { Q_D(KAccountsView); QTimer::singleShot(0, d->ui->m_accountTree, SLOT(setFocus())); } break; default: break; } } void KAccountsView::refresh() { Q_D(KAccountsView); if (!isVisible()) { d->m_needsRefresh = true; return; } d->m_needsRefresh = false; // TODO: check why the invalidate is needed here d->m_proxyModel->invalidate(); d->m_proxyModel->setHideClosedAccounts(KMyMoneySettings::hideClosedAccounts()); d->m_proxyModel->setHideEquityAccounts(!KMyMoneySettings::expertMode()); if (KMyMoneySettings::showCategoriesInAccountsView()) { d->m_proxyModel->addAccountGroup(QVector {eMyMoney::Account::Type::Income, eMyMoney::Account::Type::Expense}); } else { d->m_proxyModel->removeAccountType(eMyMoney::Account::Type::Income); d->m_proxyModel->removeAccountType(eMyMoney::Account::Type::Expense); } // reinitialize the default state of the hidden categories label d->m_haveUnusedCategories = false; d->ui->m_hiddenCategories->hide(); // hides label d->m_proxyModel->setHideUnusedIncomeExpenseAccounts(KMyMoneySettings::hideUnusedCategory()); } void KAccountsView::showEvent(QShowEvent * event) { Q_D(KAccountsView); if (!d->m_proxyModel) d->init(); emit customActionRequested(View::Accounts, eView::Action::AboutToShow); if (d->m_needsRefresh) refresh(); // don't forget base class implementation QWidget::showEvent(event); } void KAccountsView::updateActions(const MyMoneyObject& obj) { Q_D(KAccountsView); const auto file = MyMoneyFile::instance(); - if (d->m_onlinePlugins) { - QList accList; - file->accountList(accList); - QList::const_iterator it_a; - auto it_p = d->m_onlinePlugins->constEnd(); - for (it_a = accList.constBegin(); (it_p == d->m_onlinePlugins->constEnd()) && (it_a != accList.constEnd()); ++it_a) { - if ((*it_a).hasOnlineMapping()) { - // check if provider is available - it_p = d->m_onlinePlugins->constFind((*it_a).onlineBankingSettings().value("provider").toLower()); - if (it_p != d->m_onlinePlugins->constEnd()) { - QStringList protocols; - (*it_p)->protocols(protocols); - if (!protocols.isEmpty()) - pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(true); - } - } - } - } - if (typeid(obj) != typeid(MyMoneyAccount) && (obj.id().isEmpty() && d->m_currentAccount.id().isEmpty())) // do not disable actions that were already disabled) return; const auto& acc = static_cast(obj); const QVector actionsToBeDisabled { eMenu::Action::NewAccount, eMenu::Action::EditAccount, eMenu::Action::DeleteAccount, eMenu::Action::CloseAccount, eMenu::Action::ReopenAccount, eMenu::Action::ChartAccountBalance, - eMenu::Action::UnmapOnlineAccount, eMenu::Action::MapOnlineAccount, eMenu::Action::UpdateAccount + eMenu::Action::UnmapOnlineAccount, eMenu::Action::MapOnlineAccount, + eMenu::Action::UpdateAccount }; for (const auto& a : actionsToBeDisabled) pActions[a]->setEnabled(false); pActions[eMenu::Action::NewAccount]->setEnabled(true); if (acc.id().isEmpty()) { d->m_currentAccount = MyMoneyAccount(); return; } else if (file->isStandardAccount(acc.id())) { d->m_currentAccount = acc; return; } d->m_currentAccount = acc; switch (acc.accountGroup()) { case eMyMoney::Account::Type::Asset: case eMyMoney::Account::Type::Liability: case eMyMoney::Account::Type::Equity: { pActions[eMenu::Action::EditAccount]->setEnabled(true); pActions[eMenu::Action::DeleteAccount]->setEnabled(!file->isReferenced(acc)); auto b = acc.isClosed() ? true : false; pActions[eMenu::Action::ReopenAccount]->setEnabled(b); pActions[eMenu::Action::CloseAccount]->setEnabled(!b); if (!acc.isClosed()) { b = (d->canCloseAccount(acc) == KAccountsViewPrivate::AccountCanClose) ? true : false; pActions[eMenu::Action::CloseAccount]->setEnabled(b); d->hintCloseAccountAction(acc, pActions[eMenu::Action::CloseAccount]); } pActions[eMenu::Action::ChartAccountBalance]->setEnabled(true); if (d->m_currentAccount.hasOnlineMapping()) { pActions[eMenu::Action::UnmapOnlineAccount]->setEnabled(true); if (d->m_onlinePlugins) { // check if provider is available QMap::const_iterator it_p; it_p = d->m_onlinePlugins->constFind(d->m_currentAccount.onlineBankingSettings().value(QLatin1String("provider")).toLower()); if (it_p != d->m_onlinePlugins->constEnd()) { QStringList protocols; (*it_p)->protocols(protocols); if (protocols.count() > 0) { pActions[eMenu::Action::UpdateAccount]->setEnabled(true); } } } } else { pActions[eMenu::Action::MapOnlineAccount]->setEnabled(d->m_onlinePlugins && !d->m_onlinePlugins->isEmpty()); } break; } default: break; } QBitArray skip((int)eStorage::Reference::Count); if (!d->m_currentAccount.id().isEmpty()) { if (!file->isStandardAccount(d->m_currentAccount.id())) { switch (d->m_currentAccount.accountGroup()) { case eMyMoney::Account::Type::Asset: case eMyMoney::Account::Type::Liability: case eMyMoney::Account::Type::Equity: break; default: break; } } } } /** * The view is notified that an unused income expense account has been hidden. */ void KAccountsView::slotUnusedIncomeExpenseAccountHidden() { Q_D(KAccountsView); d->m_haveUnusedCategories = true; d->ui->m_hiddenCategories->setVisible(d->m_haveUnusedCategories); } void KAccountsView::slotNetWorthChanged(const MyMoneyMoney &netWorth) { Q_D(KAccountsView); d->netBalProChanged(netWorth, d->ui->m_totalProfitsLabel, View::Accounts); } void KAccountsView::slotShowAccountMenu(const MyMoneyAccount& acc) { Q_UNUSED(acc); pMenus[eMenu::Menu::Account]->exec(QCursor::pos()); } void KAccountsView::slotSelectByObject(const MyMoneyObject& obj, eView::Intent intent) { switch(intent) { case eView::Intent::UpdateActions: updateActions(obj); break; case eView::Intent::OpenContextMenu: slotShowAccountMenu(static_cast(obj)); break; default: break; } } void KAccountsView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent) { Q_D(KAccountsView); switch (intent) { case eView::Intent::UpdateNetWorth: if (variant.count() == 1 && d->m_proxyModel) slotNetWorthChanged(variant.first().value()); break; case eView::Intent::SetOnlinePlugins: if (variant.count() == 1) d->m_onlinePlugins = static_cast*>(variant.first().value()); break; default: break; } } void KAccountsView::slotNewAccount() { MyMoneyAccount account; account.setOpeningDate(KMyMoneySettings::firstFiscalDate()); NewAccountWizard::Wizard::newAccount(account); } void KAccountsView::slotEditAccount() { Q_D(KAccountsView); switch (d->m_currentAccount.accountType()) { case eMyMoney::Account::Type::Loan: case eMyMoney::Account::Type::AssetLoan: d->editLoan(); break; default: d->editAccount(); break; } emit selectByObject(d->m_currentAccount, eView::Intent::None); } void KAccountsView::slotDeleteAccount() { Q_D(KAccountsView); if (d->m_currentAccount.id().isEmpty()) return; // need an account ID const auto file = MyMoneyFile::instance(); // can't delete standard accounts or account which still have transactions assigned if (file->isStandardAccount(d->m_currentAccount.id())) return; // check if the account is referenced by a transaction or schedule QBitArray skip((int)eStorage::Reference::Count); skip.fill(false); skip.setBit((int)eStorage::Reference::Account); skip.setBit((int)eStorage::Reference::Institution); skip.setBit((int)eStorage::Reference::Payee); skip.setBit((int)eStorage::Reference::Tag); skip.setBit((int)eStorage::Reference::Security); skip.setBit((int)eStorage::Reference::Currency); skip.setBit((int)eStorage::Reference::Price); if (file->isReferenced(d->m_currentAccount, skip)) return; MyMoneyFileTransaction ft; // retain the account name for a possible later usage in the error message box // since the account removal notifies the views the selected account can be changed // so we make sure by doing this that we display the correct name in the error message auto selectedAccountName = d->m_currentAccount.name(); try { file->removeAccount(d->m_currentAccount); d->m_currentAccount.clearId(); emit selectByObject(MyMoneyAccount(), eView::Intent::None); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to delete account '%1'. Cause: %2", selectedAccountName, QString::fromLatin1(e.what()))); } } void KAccountsView::slotCloseAccount() { Q_D(KAccountsView); MyMoneyFileTransaction ft; try { d->m_currentAccount.setClosed(true); MyMoneyFile::instance()->modifyAccount(d->m_currentAccount); emit selectByObject(d->m_currentAccount, eView::Intent::None); ft.commit(); if (KMyMoneySettings::hideClosedAccounts()) KMessageBox::information(this, i18n("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 the views. You can make it visible again by going to the View menu and selecting Show all accounts or by deselecting the Do not show closed accounts setting."), i18n("Information"), "CloseAccountInfo"); } catch (const MyMoneyException &) { } } void KAccountsView::slotReopenAccount() { Q_D(KAccountsView); const auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { auto& acc = d->m_currentAccount; while (acc.isClosed()) { acc.setClosed(false); file->modifyAccount(acc); acc = file->account(acc.parentAccountId()); } emit selectByObject(d->m_currentAccount, eView::Intent::None); ft.commit(); } catch (const MyMoneyException &) { } } void KAccountsView::slotChartAccountBalance() { Q_D(KAccountsView); if (!d->m_currentAccount.id().isEmpty()) { QPointer dlg = new KBalanceChartDlg(d->m_currentAccount, this); dlg->exec(); delete dlg; } } void KAccountsView::slotNewCategory() { Q_D(KAccountsView); KNewAccountDlg::newCategory(d->m_currentAccount, MyMoneyAccount()); } void KAccountsView::slotNewPayee(const QString& nameBase, QString& id) { KMyMoneyUtils::newPayee(nameBase, id); } void KAccountsView::slotAccountUnmapOnline() { Q_D(KAccountsView); // no account selected if (d->m_currentAccount.id().isEmpty()) return; // not a mapped account if (!d->m_currentAccount.hasOnlineMapping()) return; if (KMessageBox::warningYesNo(this, QString("%1").arg(i18n("Do you really want to remove the mapping of account %1 to an online account? Depending on the details of the online banking method used, this action cannot be reverted.", d->m_currentAccount.name())), i18n("Remove mapping to online account")) == KMessageBox::Yes) { MyMoneyFileTransaction ft; try { d->m_currentAccount.setOnlineBankingSettings(MyMoneyKeyValueContainer()); // delete the kvp that is used in MyMoneyStatementReader too // we should really get rid of it, but since I don't know what it // is good for, I'll keep it around. (ipwizard) d->m_currentAccount.deletePair("StatementKey"); MyMoneyFile::instance()->modifyAccount(d->m_currentAccount); ft.commit(); // The mapping could disable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to unmap account from online account: %1", QString::fromLatin1(e.what()))); } } updateActions(d->m_currentAccount); } void KAccountsView::slotAccountMapOnline() { Q_D(KAccountsView); // no account selected if (d->m_currentAccount.id().isEmpty()) return; // already an account mapped if (d->m_currentAccount.hasOnlineMapping()) return; // check if user tries to map a brokerageAccount if (d->m_currentAccount.name().contains(i18n(" (Brokerage)"))) { if (KMessageBox::warningContinueCancel(this, i18n("You try to map a brokerage account to an online account. This is usually not advisable. In general, the investment account should be mapped to the online account. Please cancel if you intended to map the investment account, continue otherwise"), i18n("Mapping brokerage account")) == KMessageBox::Cancel) { return; } } if (!d->m_onlinePlugins) return; // if we have more than one provider let the user select the current provider QString provider; QMap::const_iterator it_p; switch (d->m_onlinePlugins->count()) { case 0: break; case 1: provider = d->m_onlinePlugins->begin().key(); break; default: { QMenu popup(this); popup.setTitle(i18n("Select online banking plugin")); // Populate the pick list with all the provider for (it_p = d->m_onlinePlugins->constBegin(); it_p != d->m_onlinePlugins->constEnd(); ++it_p) { popup.addAction(it_p.key())->setData(it_p.key()); } QAction *item = popup.actions()[0]; if (item) { popup.setActiveAction(item); } // cancelled if ((item = popup.exec(QCursor::pos(), item)) == 0) { return; } provider = item->data().toString(); } break; } if (provider.isEmpty()) return; // find the provider it_p = d->m_onlinePlugins->constFind(provider.toLower()); if (it_p != d->m_onlinePlugins->constEnd()) { // plugin found, call it MyMoneyKeyValueContainer settings; if ((*it_p)->mapAccount(d->m_currentAccount, settings)) { settings["provider"] = provider.toLower(); MyMoneyAccount acc(d->m_currentAccount); acc.setOnlineBankingSettings(settings); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(acc); ft.commit(); // The mapping could enable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to map account to online account: %1", QString::fromLatin1(e.what()))); } } } updateActions(d->m_currentAccount); } void KAccountsView::slotAccountUpdateOnlineAll() { Q_D(KAccountsView); QList accList; MyMoneyFile::instance()->accountList(accList); QList::iterator it_a; QMap::const_iterator it_p; // remove all those from the list, that don't have a 'provider' or the // provider is not currently present for (it_a = accList.begin(); it_a != accList.end();) { if (!(*it_a).hasOnlineMapping() || d->m_onlinePlugins->find((*it_a).onlineBankingSettings().value("provider").toLower()) == d->m_onlinePlugins->end()) { it_a = accList.erase(it_a); } else ++it_a; } const QVector disabledActions {eMenu::Action::UpdateAccount, eMenu::Action::UpdateAllAccounts}; for (const auto& a : disabledActions) pActions[a]->setEnabled(false); // now work on the remaining list of accounts int cnt = accList.count() - 1; for (it_a = accList.begin(); it_a != accList.end(); ++it_a) { it_p = d->m_onlinePlugins->constFind((*it_a).onlineBankingSettings().value("provider").toLower()); (*it_p)->updateAccount(*it_a, cnt != 0); --cnt; } // re-enable the disabled actions updateActions(d->m_currentAccount); } void KAccountsView::slotAccountUpdateOnline() { Q_D(KAccountsView); // no account selected if (d->m_currentAccount.id().isEmpty()) return; // no online account mapped if (!d->m_currentAccount.hasOnlineMapping()) return; const QVector disabledActions {eMenu::Action::UpdateAccount, eMenu::Action::UpdateAllAccounts}; for (const auto& a : disabledActions) pActions[a]->setEnabled(false); // find the provider QMap::const_iterator it_p; it_p = d->m_onlinePlugins->constFind(d->m_currentAccount.onlineBankingSettings().value("provider").toLower()); if (it_p != d->m_onlinePlugins->constEnd()) { // plugin found, call it (*it_p)->updateAccount(d->m_currentAccount); } // re-enable the disabled actions updateActions(d->m_currentAccount); } diff --git a/kmymoney/views/kgloballedgerview.cpp b/kmymoney/views/kgloballedgerview.cpp index b203c40f0..5f5221d67 100644 --- a/kmymoney/views/kgloballedgerview.cpp +++ b/kmymoney/views/kgloballedgerview.cpp @@ -1,2071 +1,2079 @@ /*************************************************************************** kgloballedgerview.cpp - description ------------------- begin : Wed Jul 26 2006 copyright : (C) 2006 by Thomas Baumgart email : Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kgloballedgerview_p.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyaccount.h" #include "mymoneyfile.h" #include "kmymoneyaccountcombo.h" #include "kmymoneypayeecombo.h" #include "keditscheduledlg.h" #include "kendingbalancedlg.h" #include "register.h" #include "transactioneditor.h" #include "selectedtransactions.h" #include "kmymoneysettings.h" #include "registersearchline.h" #include "kfindtransactiondlg.h" #include "accountsmodel.h" #include "models.h" #include "mymoneyschedule.h" #include "mymoneysecurity.h" #include "mymoneytransaction.h" #include "mymoneytransactionfilter.h" #include "mymoneysplit.h" #include "transaction.h" #include "transactionform.h" #include "widgetenums.h" #include "mymoneyenums.h" #include "menuenums.h" using namespace eMenu; QDate KGlobalLedgerViewPrivate::m_lastPostDate; KGlobalLedgerView::KGlobalLedgerView(QWidget *parent) : KMyMoneyViewBase(*new KGlobalLedgerViewPrivate(this), parent) { typedef void(KGlobalLedgerView::*KGlobalLedgerViewFunc)(); const QHash actionConnections { {Action::NewTransaction, &KGlobalLedgerView::slotNewTransaction}, {Action::EditTransaction, &KGlobalLedgerView::slotEditTransaction}, {Action::DeleteTransaction, &KGlobalLedgerView::slotDeleteTransaction}, {Action::DuplicateTransaction, &KGlobalLedgerView::slotDuplicateTransaction}, {Action::EnterTransaction, &KGlobalLedgerView::slotEnterTransaction}, {Action::AcceptTransaction, &KGlobalLedgerView::slotAcceptTransaction}, {Action::CancelTransaction, &KGlobalLedgerView::slotCancelTransaction}, {Action::EditSplits, &KGlobalLedgerView::slotEditSplits}, {Action::CopySplits, &KGlobalLedgerView::slotCopySplits}, {Action::GoToPayee, &KGlobalLedgerView::slotGoToPayee}, {Action::GoToAccount, &KGlobalLedgerView::slotGoToAccount}, {Action::MatchTransaction, &KGlobalLedgerView::slotMatchTransactions}, {Action::CombineTransactions, &KGlobalLedgerView::slotCombineTransactions}, {Action::ToggleReconciliationFlag, &KGlobalLedgerView::slotToggleReconciliationFlag}, {Action::MarkCleared, &KGlobalLedgerView::slotMarkCleared}, {Action::MarkReconciled, &KGlobalLedgerView::slotMarkReconciled}, {Action::MarkNotReconciled, &KGlobalLedgerView::slotMarkNotReconciled}, {Action::SelectAllTransactions, &KGlobalLedgerView::slotSelectAllTransactions}, {Action::NewScheduledTransaction, &KGlobalLedgerView::slotCreateScheduledTransaction}, {Action::AssignTransactionsNumber, &KGlobalLedgerView::slotAssignNumber}, {Action::StartReconciliation, &KGlobalLedgerView::slotStartReconciliation}, {Action::FinishReconciliation, &KGlobalLedgerView::slotFinishReconciliation}, {Action::PostponeReconciliation, &KGlobalLedgerView::slotPostponeReconciliation}, {Action::OpenAccount, &KGlobalLedgerView::slotOpenAccount}, {Action::EditFindTransaction, &KGlobalLedgerView::slotFindTransaction}, }; for (auto a = actionConnections.cbegin(); a != actionConnections.cend(); ++a) connect(pActions[a.key()], &QAction::triggered, this, a.value()); Q_D(KGlobalLedgerView); d->m_balanceWarning.reset(new KBalanceWarning(this)); } KGlobalLedgerView::~KGlobalLedgerView() { } void KGlobalLedgerView::executeCustomAction(eView::Action action) { switch(action) { case eView::Action::Refresh: refresh(); break; case eView::Action::SetDefaultFocus: { Q_D(KGlobalLedgerView); QTimer::singleShot(0, d->m_registerSearchLine->searchLine(), SLOT(setFocus())); } break; + case eView::Action::DisableViewDepenedendActions: + pActions[Action::SelectAllTransactions]->setEnabled(false); + break; + default: break; } } void KGlobalLedgerView::refresh() { Q_D(KGlobalLedgerView); if (isVisible()) { if (!d->m_inEditMode) { setUpdatesEnabled(false); d->loadView(); setUpdatesEnabled(true); d->m_needsRefresh = false; // force a new account if the current one is empty d->m_newAccountLoaded = d->m_currentAccount.id().isEmpty(); } } else { d->m_needsRefresh = true; } } void KGlobalLedgerView::showEvent(QShowEvent* event) { Q_D(KGlobalLedgerView); if (d->m_needLoad) d->init(); emit customActionRequested(View::Ledgers, eView::Action::AboutToShow); if (d->m_needsRefresh) { if (!d->m_inEditMode) { setUpdatesEnabled(false); d->loadView(); setUpdatesEnabled(true); d->m_needsRefresh = false; d->m_newAccountLoaded = false; } } else { if (!d->m_lastSelectedAccountID.isEmpty()) { try { const auto acc = MyMoneyFile::instance()->account(d->m_lastSelectedAccountID); slotSelectAccount(acc.id()); } catch (const MyMoneyException &) { d->m_lastSelectedAccountID.clear(); // account is invalid } } else { slotSelectAccount(d->m_accountComboBox->getSelected()); } KMyMoneyRegister::SelectedTransactions list(d->m_register); updateLedgerActions(list); emit selectByVariant(QVariantList {QVariant::fromValue(list)}, eView::Intent::SelectRegisterTransactions); } + pActions[Action::SelectAllTransactions]->setEnabled(true); // don't forget base class implementation QWidget::showEvent(event); } void KGlobalLedgerView::updateActions(const MyMoneyObject& obj) { Q_D(KGlobalLedgerView); // if (typeid(obj) != typeid(MyMoneyAccount) && // (obj.id().isEmpty() && d->m_currentAccount.id().isEmpty())) // do not disable actions that were already disabled)) // return; const auto& acc = static_cast(obj); const QVector actionsToBeDisabled { Action::StartReconciliation, Action::FinishReconciliation, Action::PostponeReconciliation, Action::OpenAccount, Action::NewTransaction }; for (const auto& a : actionsToBeDisabled) pActions[a]->setEnabled(false); auto b = acc.isClosed() ? false : true; pMenus[Menu::MoveTransaction]->setEnabled(b); QString tooltip; pActions[Action::NewTransaction]->setEnabled(canCreateTransactions(tooltip)); pActions[Action::NewTransaction]->setToolTip(tooltip); const auto file = MyMoneyFile::instance(); if (!acc.id().isEmpty() && !file->isStandardAccount(acc.id())) { switch (acc.accountGroup()) { case eMyMoney::Account::Type::Asset: case eMyMoney::Account::Type::Liability: case eMyMoney::Account::Type::Equity: pActions[Action::OpenAccount]->setEnabled(true); if (acc.accountGroup() != eMyMoney::Account::Type::Equity) { if (d->m_reconciliationAccount.id().isEmpty()) { pActions[Action::StartReconciliation]->setEnabled(true); pActions[Action::StartReconciliation]->setToolTip(i18n("Reconcile")); } else { auto tip = i18n("Reconcile - disabled because you are currently reconciling %1", d->m_reconciliationAccount.name()); pActions[Action::StartReconciliation]->setToolTip(tip); if (!d->m_transactionEditor) { pActions[Action::FinishReconciliation]->setEnabled(acc.id() == d->m_reconciliationAccount.id()); pActions[Action::PostponeReconciliation]->setEnabled(acc.id() == d->m_reconciliationAccount.id()); } } } break; case eMyMoney::Account::Type::Income : case eMyMoney::Account::Type::Expense : pActions[Action::OpenAccount]->setEnabled(true); break; default: break; } } d->m_currentAccount = acc; // slotSelectAccount(acc); } void KGlobalLedgerView::updateLedgerActions(const KMyMoneyRegister::SelectedTransactions& list) { Q_D(KGlobalLedgerView); d->selectTransactions(list); updateLedgerActionsInternal(); } void KGlobalLedgerView::updateLedgerActionsInternal() { Q_D(KGlobalLedgerView); const QVector actionsToBeDisabled { Action::EditTransaction, Action::EditSplits, Action::EnterTransaction, Action::CancelTransaction, Action::DeleteTransaction, Action::MatchTransaction, Action::AcceptTransaction, Action::DuplicateTransaction, Action::ToggleReconciliationFlag, Action::MarkCleared, Action::GoToAccount, Action::GoToPayee, Action::AssignTransactionsNumber, Action::NewScheduledTransaction, - Action::CombineTransactions, Action::SelectAllTransactions, Action::CopySplits, + Action::CombineTransactions, Action::CopySplits, }; for (const auto& a : actionsToBeDisabled) pActions[a]->setEnabled(false); const auto file = MyMoneyFile::instance(); pActions[Action::MatchTransaction]->setText(i18nc("Button text for match transaction", "Match")); // pActions[Action::TransactionNew]->setToolTip(i18n("Create a new transaction")); pMenus[Menu::MoveTransaction]->setEnabled(false); pMenus[Menu::MarkTransaction]->setEnabled(false); pMenus[Menu::MarkTransactionContext]->setEnabled(false); - pActions[Action::SelectAllTransactions]->setEnabled(true); if (!d->m_selectedTransactions.isEmpty() && !d->m_selectedTransactions.first().isScheduled()) { // enable 'delete transaction' only if at least one of the // selected transactions does not reference a closed account bool enable = false; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = d->m_selectedTransactions.constBegin(); (enable == false) && (it_t != d->m_selectedTransactions.constEnd()); ++it_t) { enable = !(*it_t).transaction().id().isEmpty() && !file->referencesClosedAccount((*it_t).transaction()); } pActions[Action::DeleteTransaction]->setEnabled(enable); if (!d->m_transactionEditor) { QString tooltip = i18n("Duplicate the current selected transactions"); pActions[Action::DuplicateTransaction]->setEnabled(canDuplicateTransactions(d->m_selectedTransactions, tooltip) && !d->m_selectedTransactions[0].transaction().id().isEmpty()); pActions[Action::DuplicateTransaction]->setToolTip(tooltip); if (canEditTransactions(d->m_selectedTransactions, tooltip)) { pActions[Action::EditTransaction]->setEnabled(true); // editing splits is allowed only if we have one transaction selected if (d->m_selectedTransactions.count() == 1) { pActions[Action::EditSplits]->setEnabled(true); } if (d->m_currentAccount.isAssetLiability() && d->m_currentAccount.accountType() != eMyMoney::Account::Type::Investment) { pActions[Action::NewScheduledTransaction]->setEnabled(d->m_selectedTransactions.count() == 1); } } pActions[Action::EditTransaction]->setToolTip(tooltip); if (!d->m_currentAccount.isClosed()) pMenus[Menu::MoveTransaction]->setEnabled(true); pMenus[Menu::MarkTransaction]->setEnabled(true); pMenus[Menu::MarkTransactionContext]->setEnabled(true); // Allow marking the transaction if at least one is selected pActions[Action::MarkCleared]->setEnabled(true); pActions[Action::MarkReconciled]->setEnabled(true); pActions[Action::MarkNotReconciled]->setEnabled(true); pActions[Action::ToggleReconciliationFlag]->setEnabled(true); if (!d->m_accountGoto.isEmpty()) pActions[Action::GoToAccount]->setEnabled(true); if (!d->m_payeeGoto.isEmpty()) pActions[Action::GoToPayee]->setEnabled(true); // Matching is enabled as soon as one regular and one imported transaction is selected int matchedCount = 0; int importedCount = 0; KMyMoneyRegister::SelectedTransactions::const_iterator it; for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { if ((*it).transaction().isImported()) ++importedCount; if ((*it).split().isMatched()) ++matchedCount; } if (d->m_selectedTransactions.count() == 2 /* && pActions[Action::TransactionEdit]->isEnabled() */) { pActions[Action::MatchTransaction]->setEnabled(true); } if (importedCount != 0 || matchedCount != 0) pActions[Action::AcceptTransaction]->setEnabled(true); if (matchedCount != 0) { pActions[Action::MatchTransaction]->setEnabled(true); pActions[Action::MatchTransaction]->setText(i18nc("Button text for unmatch transaction", "Unmatch")); pActions[Action::MatchTransaction]->setIcon(QIcon("process-stop")); } if (d->m_selectedTransactions.count() > 1) { pActions[Action::CombineTransactions]->setEnabled(true); } if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { switch (st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: multipleSplitTransactions++; break; } } if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) { pActions[Action::CopySplits]->setEnabled(true); } } if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; foreach(const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { switch(st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: multipleSplitTransactions++; break; } } if(singleSplitTransactions > 0 && multipleSplitTransactions == 1) { pActions[Action::CopySplits]->setEnabled(true); } } } else { pActions[Action::AssignTransactionsNumber]->setEnabled(d->m_transactionEditor->canAssignNumber()); pActions[Action::NewTransaction]->setEnabled(false); pActions[Action::DeleteTransaction]->setEnabled(false); QString reason; pActions[Action::EnterTransaction]->setEnabled(d->m_transactionEditor->isComplete(reason)); //FIXME: Port to KDE4 // the next line somehow worked in KDE3 but does not have // any influence under KDE4 /// Works for me when 'reason' is set. Allan pActions[Action::EnterTransaction]->setToolTip(reason); pActions[Action::CancelTransaction]->setEnabled(true); } } } void KGlobalLedgerView::slotAboutToSelectItem(KMyMoneyRegister::RegisterItem* item, bool& okToSelect) { Q_UNUSED(item); slotCancelOrEnterTransactions(okToSelect); } void KGlobalLedgerView::slotUpdateSummaryLine(const KMyMoneyRegister::SelectedTransactions& selection) { Q_D(KGlobalLedgerView); if (selection.count() > 1) { MyMoneyMoney balance; foreach (const KMyMoneyRegister::SelectedTransaction& t, selection) { if (!t.isScheduled()) { balance += t.split().shares(); } } d->m_rightSummaryLabel->setText(QString("%1: %2").arg(QChar(0x2211), balance.formatMoney("", d->m_precision))); } else { if (d->isReconciliationAccount()) { d->m_rightSummaryLabel->setText(i18n("Difference: %1", d->m_totalBalance.formatMoney("", d->m_precision))); } else { if (d->m_currentAccount.accountType() != eMyMoney::Account::Type::Investment) { d->m_rightSummaryLabel->setText(i18n("Balance: %1", d->m_totalBalance.formatMoney("", d->m_precision))); bool showNegative = d->m_totalBalance.isNegative(); if (d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Liability && !d->m_totalBalance.isZero()) showNegative = !showNegative; if (showNegative) { QPalette palette = d->m_rightSummaryLabel->palette(); palette.setColor(d->m_rightSummaryLabel->foregroundRole(), KMyMoneySettings::schemeColor(SchemeColor::Negative)); d->m_rightSummaryLabel->setPalette(palette); } } else { d->m_rightSummaryLabel->setText(i18n("Investment value: %1%2", d->m_balanceIsApproximated ? "~" : "", d->m_totalBalance.formatMoney(MyMoneyFile::instance()->baseCurrency().tradingSymbol(), d->m_precision))); } } } } void KGlobalLedgerView::resizeEvent(QResizeEvent* ev) { Q_D(KGlobalLedgerView); if (d->m_needLoad) d->init(); d->m_register->resize((int)eWidgets::eTransaction::Column::Detail); d->m_form->resize((int)eWidgets::eTransactionForm::Column::Value1); KMyMoneyViewBase::resizeEvent(ev); } void KGlobalLedgerView::slotSetReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance) { Q_D(KGlobalLedgerView); if(d->m_needLoad) d->init(); if (d->m_reconciliationAccount.id() != acc.id()) { // make sure the account is selected if (!acc.id().isEmpty()) slotSelectAccount(acc.id()); d->m_reconciliationAccount = acc; d->m_reconciliationDate = reconciliationDate; d->m_endingBalance = endingBalance; if (acc.accountGroup() == eMyMoney::Account::Type::Liability) d->m_endingBalance = -endingBalance; d->m_newAccountLoaded = true; if (acc.id().isEmpty()) { d->m_buttonbar->removeAction(pActions[Action::PostponeReconciliation]); d->m_buttonbar->removeAction(pActions[Action::FinishReconciliation]); } else { d->m_buttonbar->addAction(pActions[Action::PostponeReconciliation]); d->m_buttonbar->addAction(pActions[Action::FinishReconciliation]); // when we start reconciliation, we need to reload the view // because no data has been changed. When postponing or finishing // reconciliation, the data change in the engine takes care of updateing // the view. refresh(); } } } void KGlobalLedgerView::slotSetReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate) { slotSetReconcileAccount(acc, reconciliationDate, MyMoneyMoney()); } void KGlobalLedgerView::slotSetReconcileAccount(const MyMoneyAccount& acc) { slotSetReconcileAccount(acc, QDate(), MyMoneyMoney()); } void KGlobalLedgerView::slotSetReconcileAccount() { slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); } void KGlobalLedgerView::slotShowTransactionMenu(const MyMoneySplit& sp) { Q_UNUSED(sp) pMenus[Menu::Transaction]->exec(QCursor::pos()); } void KGlobalLedgerView::slotContinueReconciliation() { Q_D(KGlobalLedgerView); const auto file = MyMoneyFile::instance(); MyMoneyAccount account; try { account = file->account(d->m_currentAccount.id()); // get rid of previous run. delete d->m_endingBalanceDlg; d->m_endingBalanceDlg = new KEndingBalanceDlg(account, this); if (account.isAssetLiability()) { if (d->m_endingBalanceDlg->exec() == QDialog::Accepted) { if (KMyMoneySettings::autoReconciliation()) { MyMoneyMoney startBalance = d->m_endingBalanceDlg->previousBalance(); MyMoneyMoney endBalance = d->m_endingBalanceDlg->endingBalance(); QDate endDate = d->m_endingBalanceDlg->statementDate(); QList > transactionList; MyMoneyTransactionFilter filter(account.id()); filter.addState((int)eMyMoney::TransactionFilter::State::Cleared); filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled); filter.setDateFilter(QDate(), endDate); filter.setConsiderCategory(false); filter.setReportAllSplits(true); file->transactionList(transactionList, filter); QList > result = d->automaticReconciliation(account, transactionList, endBalance - startBalance); if (!result.empty()) { QString message = i18n("KMyMoney has detected transactions matching your reconciliation data.\nWould you like KMyMoney to clear these transactions for you?"); if (KMessageBox::questionYesNo(this, message, i18n("Automatic reconciliation"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "AcceptAutomaticReconciliation") == KMessageBox::Yes) { // mark the transactions cleared KMyMoneyRegister::SelectedTransactions oldSelection = d->m_selectedTransactions; d->m_selectedTransactions.clear(); QListIterator > itTransactionSplitResult(result); while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); d->m_selectedTransactions.append(KMyMoneyRegister::SelectedTransaction(transactionSplit.first, transactionSplit.second, QString())); } // mark all transactions in d->m_selectedTransactions as 'Cleared' d->markTransaction(eMyMoney::Split::State::Cleared); d->m_selectedTransactions = oldSelection; } } } if (!file->isStandardAccount(account.id()) && account.isAssetLiability()) { if (!isVisible()) emit customActionRequested(View::Ledgers, eView::Action::SwitchView); Models::instance()->accountsModel()->slotReconcileAccount(account, d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance()); slotSetReconcileAccount(account, d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance()); // check if the user requests us to create interest // or charge transactions. auto ti = d->m_endingBalanceDlg->interestTransaction(); auto tc = d->m_endingBalanceDlg->chargeTransaction(); MyMoneyFileTransaction ft; try { if (ti != MyMoneyTransaction()) { MyMoneyFile::instance()->addTransaction(ti); } if (tc != MyMoneyTransaction()) { MyMoneyFile::instance()->addTransaction(tc); } ft.commit(); } catch (const MyMoneyException &e) { qWarning("interest transaction not stored: '%s'", e.what()); } // reload the account object as it might have changed in the meantime d->m_reconciliationAccount = file->account(account.id()); updateActions(d->m_currentAccount); updateLedgerActionsInternal(); // slotUpdateActions(); } } } } catch (const MyMoneyException &) { } } void KGlobalLedgerView::slotLedgerSelected(const QString& _accId, const QString& transaction) { auto acc = MyMoneyFile::instance()->account(_accId); QString accId(_accId); switch (acc.accountType()) { case Account::Type::Stock: // if a stock account is selected, we show the // the corresponding parent (investment) account acc = MyMoneyFile::instance()->account(acc.parentAccountId()); accId = acc.id(); // intentional fall through case Account::Type::Checkings: case Account::Type::Savings: case Account::Type::Cash: case Account::Type::CreditCard: case Account::Type::Loan: case Account::Type::Asset: case Account::Type::Liability: case Account::Type::AssetLoan: case Account::Type::Income: case Account::Type::Expense: case Account::Type::Investment: case Account::Type::Equity: if (!isVisible()) emit customActionRequested(View::Ledgers, eView::Action::SwitchView); slotSelectAccount(accId, transaction); break; case Account::Type::CertificateDep: case Account::Type::MoneyMarket: case Account::Type::Currency: qDebug("No ledger view available for account type %d", (int)acc.accountType()); break; default: qDebug("Unknown account type %d in KMyMoneyView::slotLedgerSelected", (int)acc.accountType()); break; } } void KGlobalLedgerView::slotSelectByObject(const MyMoneyObject& obj, eView::Intent intent) { switch(intent) { case eView::Intent::UpdateActions: updateActions(obj); break; case eView::Intent::FinishEnteringOverdueScheduledTransactions: slotContinueReconciliation(); break; case eView::Intent::SynchronizeAccountInLedgersView: slotSelectAccount(obj); break; default: break; } } void KGlobalLedgerView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent) { switch(intent) { case eView::Intent::ShowTransaction: if (variant.count() == 2) slotLedgerSelected(variant.at(0).toString(), variant.at(1).toString()); break; + case eView::Intent::SelectRegisterTransactions: + if (variant.count() == 1) + updateLedgerActions(variant.at(0).value()); + break; default: break; } } void KGlobalLedgerView::slotSelectAccount(const MyMoneyObject& obj) { Q_D(KGlobalLedgerView); if (typeid(obj) != typeid(MyMoneyAccount)) return/* false */; d->m_lastSelectedAccountID = obj.id(); } void KGlobalLedgerView::slotSelectAccount(const QString& id) { slotSelectAccount(id, QString()); } bool KGlobalLedgerView::slotSelectAccount(const QString& id, const QString& transactionId) { Q_D(KGlobalLedgerView); auto rc = true; if (!id.isEmpty()) { if (d->m_currentAccount.id() != id) { try { d->m_currentAccount = MyMoneyFile::instance()->account(id); // if a stock account is selected, we show the // the corresponding parent (investment) account if (d->m_currentAccount.isInvest()) { d->m_currentAccount = MyMoneyFile::instance()->account(d->m_currentAccount.parentAccountId()); } d->m_lastSelectedAccountID = d->m_currentAccount.id(); d->m_newAccountLoaded = true; refresh(); } catch (const MyMoneyException &) { qDebug("Unable to retrieve account %s", qPrintable(id)); rc = false; } } else { // we need to refresh m_account.m_accountList, a child could have been deleted d->m_currentAccount = MyMoneyFile::instance()->account(id); emit selectByObject(d->m_currentAccount, eView::Intent::None); emit selectByObject(d->m_currentAccount, eView::Intent::SynchronizeAccountInInvestmentView); } d->selectTransaction(transactionId); } return rc; } bool KGlobalLedgerView::selectEmptyTransaction() { Q_D(KGlobalLedgerView); bool rc = false; if (!d->m_inEditMode) { // in case we don't know the type of transaction to be created, // have at least one selected transaction and the id of // this transaction is not empty, we take it as template for the // transaction to be created KMyMoneyRegister::SelectedTransactions list(d->m_register); if ((d->m_action == eWidgets::eRegister::Action::None) && (!list.isEmpty()) && (!list[0].transaction().id().isEmpty())) { // the new transaction to be created will have the same type // as the one that currently has the focus KMyMoneyRegister::Transaction* t = dynamic_cast(d->m_register->focusItem()); if (t) d->m_action = t->actionType(); d->m_register->clearSelection(); } // if we still don't have an idea which type of transaction // to create, we use the default. if (d->m_action == eWidgets::eRegister::Action::None) { d->setupDefaultAction(); } d->m_register->selectItem(d->m_register->lastItem()); d->m_register->updateRegister(); rc = true; } return rc; } TransactionEditor* KGlobalLedgerView::startEdit(const KMyMoneyRegister::SelectedTransactions& list) { Q_D(KGlobalLedgerView); // we use the warnlevel to keep track, if we have to warn the // user that some or all splits have been reconciled or if the // user cannot modify the transaction if at least one split // has the status frozen. The following value are used: // // 0 - no sweat, user can modify // 1 - user should be warned that at least one split has been reconciled // already // 2 - user will be informed, that this transaction cannot be changed anymore int warnLevel = list.warnLevel(); Q_ASSERT(warnLevel < 2); // otherwise the edit action should not be enabled switch (warnLevel) { case 0: break; case 1: if (KMessageBox::warningContinueCancel(this, i18n( "At least one split of the selected transactions has been reconciled. " "Do you wish to continue to edit the transactions anyway?" ), i18n("Transaction already reconciled"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "EditReconciledTransaction") == KMessageBox::Cancel) { warnLevel = 2; } break; case 2: KMessageBox::sorry(this, i18n("At least one split of the selected transactions has been frozen. " "Editing the transactions is therefore prohibited."), i18n("Transaction already frozen")); break; case 3: KMessageBox::sorry(this, i18n("At least one split of the selected transaction references an account that has been closed. " "Editing the transactions is therefore prohibited."), i18n("Account closed")); break; } if (warnLevel > 1) return 0; TransactionEditor* editor = 0; KMyMoneyRegister::Transaction* item = dynamic_cast(d->m_register->focusItem()); if (item) { // in case the current focus item is not selected, we move the focus to the first selected transaction if (!item->isSelected()) { KMyMoneyRegister::RegisterItem* p; for (p = d->m_register->firstItem(); p; p = p->nextItem()) { KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t && t->isSelected()) { d->m_register->setFocusItem(t); item = t; break; } } } // decide, if we edit in the register or in the form TransactionEditorContainer* parent; if (d->m_formFrame->isVisible()) parent = d->m_form; else { parent = d->m_register; } editor = item->createEditor(parent, list, KGlobalLedgerViewPrivate::m_lastPostDate); // check that we use the same transaction commodity in all selected transactions // if not, we need to update this in the editor's list. The user can also bail out // of this operation which means that we have to stop editing here. if (editor) { if (!editor->fixTransactionCommodity(d->m_currentAccount)) { // if the user wants to quit, we need to destroy the editor // and bail out delete editor; editor = 0; } } if (editor) { if (parent == d->m_register) { // make sure, the height of the table is correct d->m_register->updateRegister(KMyMoneySettings::ledgerLens() | !KMyMoneySettings::transactionForm()); } d->m_inEditMode = true; connect(editor, &TransactionEditor::transactionDataSufficient, pActions[Action::EnterTransaction], &QAction::setEnabled); connect(editor, &TransactionEditor::returnPressed, pActions[Action::EnterTransaction], &QAction::trigger); connect(editor, &TransactionEditor::escapePressed, pActions[Action::CancelTransaction], &QAction::trigger); connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, editor, &TransactionEditor::slotReloadEditWidgets); connect(editor, &TransactionEditor::finishEdit, this, &KGlobalLedgerView::slotLeaveEditMode); connect(editor, &TransactionEditor::objectCreation, d->m_mousePressFilter, &MousePressFilter::setFilterDeactive); connect(editor, &TransactionEditor::assignNumber, this, &KGlobalLedgerView::slotAssignNumber); connect(editor, &TransactionEditor::lastPostDateUsed, this, &KGlobalLedgerView::slotKeepPostDate); // create the widgets, place them in the parent and load them with data // setup tab order d->m_tabOrderWidgets.clear(); editor->setup(d->m_tabOrderWidgets, d->m_currentAccount, d->m_action); Q_ASSERT(!d->m_tabOrderWidgets.isEmpty()); // install event filter in all taborder widgets QWidgetList::const_iterator it_w = d->m_tabOrderWidgets.constBegin(); for (; it_w != d->m_tabOrderWidgets.constEnd(); ++it_w) { (*it_w)->installEventFilter(this); } // Install a filter that checks if a mouse press happened outside // of one of our own widgets. qApp->installEventFilter(d->m_mousePressFilter); // Check if the editor has some preference on where to set the focus // If not, set the focus to the first widget in the tab order QWidget* focusWidget = editor->firstWidget(); if (!focusWidget) focusWidget = d->m_tabOrderWidgets.first(); // for some reason, this only works reliably if delayed a bit QTimer::singleShot(10, focusWidget, SLOT(setFocus())); // preset to 'I have no idea which type to create' for the next round. d->m_action = eWidgets::eRegister::Action::None; } } return editor; } void KGlobalLedgerView::slotTransactionsContextMenuRequested() { Q_D(KGlobalLedgerView); auto transactions = d->m_selectedTransactions; updateLedgerActionsInternal(); // emit transactionsSelected(d->m_selectedTransactions); // that should select MyMoneySchedule in KScheduledView if (!transactions.isEmpty() && transactions.first().isScheduled()) emit selectByObject(MyMoneyFile::instance()->schedule(transactions.first().scheduleId()), eView::Intent::OpenContextMenu); else slotShowTransactionMenu(MyMoneySplit()); } void KGlobalLedgerView::slotLeaveEditMode(const KMyMoneyRegister::SelectedTransactions& list) { Q_D(KGlobalLedgerView); d->m_inEditMode = false; qApp->removeEventFilter(d->m_mousePressFilter); // a possible focusOut event may have removed the focus, so we // install it back again. d->m_register->focusItem()->setFocus(true); // if we come back from editing a new item, we make sure that // we always select the very last known transaction entry no // matter if the transaction has been created or not. if (list.count() && list[0].transaction().id().isEmpty()) { // block signals to prevent some infinite loops that might occur here. d->m_register->blockSignals(true); d->m_register->clearSelection(); KMyMoneyRegister::RegisterItem* p = d->m_register->lastItem(); if (p && p->prevItem()) p = p->prevItem(); d->m_register->selectItem(p); d->m_register->updateRegister(true); d->m_register->blockSignals(false); // we need to update the form manually as sending signals was blocked KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t) d->m_form->slotSetTransaction(t); } else { if (!KMyMoneySettings::transactionForm()) { // update the row height of the transactions because it might differ between viewing/editing mode when not using the transaction form d->m_register->blockSignals(true); d->m_register->updateRegister(true); d->m_register->blockSignals(false); } } d->m_needsRefresh = true; // TODO: Why transaction in view doesn't update without this? if (d->m_needsRefresh) refresh(); d->m_register->setFocus(); } bool KGlobalLedgerView::focusNextPrevChild(bool next) { Q_D(KGlobalLedgerView); bool rc = false; // qDebug("KGlobalLedgerView::focusNextPrevChild(editmode=%s)", m_inEditMode ? "true" : "false"); if (d->m_inEditMode) { QWidget *w = 0; w = qApp->focusWidget(); // qDebug("w = %p", w); int currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); while (w && currentWidgetIndex == -1) { // qDebug("'%s' not in list, use parent", qPrintable(w->objectName())); w = w->parentWidget(); currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); } if (currentWidgetIndex != -1) { // if(w) qDebug("tab order is at '%s'", qPrintable(w->objectName())); currentWidgetIndex += next ? 1 : -1; if (currentWidgetIndex < 0) currentWidgetIndex = d->m_tabOrderWidgets.size() - 1; else if (currentWidgetIndex >= d->m_tabOrderWidgets.size()) currentWidgetIndex = 0; w = d->m_tabOrderWidgets[currentWidgetIndex]; // qDebug("currentWidgetIndex = %d, w = %p", currentWidgetIndex, w); if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) { // qDebug("Selecting '%s' (%p) as focus", qPrintable(w->objectName()), w); w->setFocus(); rc = true; } } } else rc = KMyMoneyViewBase::focusNextPrevChild(next); return rc; } bool KGlobalLedgerView::eventFilter(QObject* o, QEvent* e) { Q_D(KGlobalLedgerView); bool rc = false; // Need to capture mouse position here as QEvent::ToolTip is too slow d->m_tooltipPosn = QCursor::pos(); if (e->type() == QEvent::KeyPress) { if (d->m_inEditMode) { // qDebug("object = %s, key = %d", o->className(), k->key()); if (o == d->m_register) { // we hide all key press events from the register // while editing a transaction rc = true; } } } if (!rc) rc = KMyMoneyViewBase::eventFilter(o, e); return rc; } void KGlobalLedgerView::slotSortOptions() { Q_D(KGlobalLedgerView); QPointer dlg = new KSortOptionDlg(this); QString key; QString sortOrder, def; if (d->isReconciliationAccount()) { key = "kmm-sort-reconcile"; def = KMyMoneySettings::sortReconcileView(); } else { key = "kmm-sort-std"; def = KMyMoneySettings::sortNormalView(); } // check if we have an account override of the sort order if (!d->m_currentAccount.value(key).isEmpty()) sortOrder = d->m_currentAccount.value(key); QString oldOrder = sortOrder; dlg->setSortOption(sortOrder, def); if (dlg->exec() == QDialog::Accepted) { if (dlg != 0) { sortOrder = dlg->sortOption(); if (sortOrder != oldOrder) { if (sortOrder.isEmpty()) { d->m_currentAccount.deletePair(key); } else { d->m_currentAccount.setValue(key, sortOrder); } MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(d->m_currentAccount); ft.commit(); } catch (const MyMoneyException &e) { qDebug("Unable to update sort order for account '%s': %s", qPrintable(d->m_currentAccount.name()), e.what()); } } } } delete dlg; } void KGlobalLedgerView::slotToggleTransactionMark(KMyMoneyRegister::Transaction* /* t */) { Q_D(KGlobalLedgerView); if (!d->m_inEditMode) { slotToggleReconciliationFlag(); } } void KGlobalLedgerView::slotKeepPostDate(const QDate& date) { KGlobalLedgerViewPrivate::m_lastPostDate = date; } QString KGlobalLedgerView::accountId() const { Q_D(const KGlobalLedgerView); return d->m_currentAccount.id(); } bool KGlobalLedgerView::canCreateTransactions(QString& tooltip) const { Q_D(const KGlobalLedgerView); bool rc = true; // we can only create transactions in the ledger view so // we check that this is the active page if(!isVisible()) { tooltip = i18n("Creating transactions can only be performed in the ledger view"); rc = false; } if (d->m_currentAccount.id().isEmpty()) { tooltip = i18n("Cannot create transactions when no account is selected."); rc = false; } if (d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Income || d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Expense) { tooltip = i18n("Cannot create transactions in the context of a category."); d->showTooltip(tooltip); rc = false; } if (d->m_currentAccount.isClosed()) { tooltip = i18n("Cannot create transactions in a closed account."); d->showTooltip(tooltip); rc = false; } return rc; } bool KGlobalLedgerView::canModifyTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { Q_D(const KGlobalLedgerView); return d->canProcessTransactions(list, tooltip) && list.canModify(); } bool KGlobalLedgerView::canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { Q_D(const KGlobalLedgerView); return d->canProcessTransactions(list, tooltip) && list.canDuplicate(); } bool KGlobalLedgerView::canEditTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { Q_D(const KGlobalLedgerView); // check if we can edit the list of transactions. We can edit, if // // a) no mix of standard and investment transactions exist // b) if a split transaction is selected, this is the only selection // c) none of the splits is frozen // d) the transaction having the current focus is selected // check for d) if (!d->canProcessTransactions(list, tooltip)) return false; // check for c) if (list.warnLevel() == 2) { tooltip = i18n("Cannot edit transactions with frozen splits."); d->showTooltip(tooltip); return false; } bool rc = true; int investmentTransactions = 0; int normalTransactions = 0; if (d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Income || d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Expense) { tooltip = i18n("Cannot edit transactions in the context of a category."); d->showTooltip(tooltip); rc = false; } if (d->m_currentAccount.isClosed()) { tooltip = i18n("Cannot create or edit any transactions in Account %1 as it is closed", d->m_currentAccount.name()); d->showTooltip(tooltip); rc = false; } KMyMoneyRegister::SelectedTransactions::const_iterator it_t; QString action; for (it_t = list.begin(); rc && it_t != list.end(); ++it_t) { if ((*it_t).transaction().id().isEmpty()) { tooltip.clear(); rc = false; continue; } if (KMyMoneyUtils::transactionType((*it_t).transaction()) == KMyMoneyUtils::InvestmentTransaction) { if (action.isEmpty()) { action = (*it_t).split().action(); continue; } if (action == (*it_t).split().action()) { continue; } else { tooltip = (i18n("Cannot edit mixed investment action/type transactions together.")); d->showTooltip(tooltip); rc = false; break; } } if (KMyMoneyUtils::transactionType((*it_t).transaction()) == KMyMoneyUtils::InvestmentTransaction) ++investmentTransactions; else ++normalTransactions; // check for a) if (investmentTransactions != 0 && normalTransactions != 0) { tooltip = i18n("Cannot edit investment transactions and non-investment transactions together."); d->showTooltip(tooltip); rc = false; break; } // check for b) but only for normalTransactions if ((*it_t).transaction().splitCount() > 2 && normalTransactions != 0) { if (list.count() > 1) { tooltip = i18n("Cannot edit multiple split transactions at once."); d->showTooltip(tooltip); rc = false; break; } } } // check for multiple transactions being selected in an investment account // we do not allow editing in this case: https://bugs.kde.org/show_bug.cgi?id=240816 // later on, we might allow to edit investment transactions of the same type /// Can now disable the following check. /* if (rc == true && investmentTransactions > 1) { tooltip = i18n("Cannot edit multiple investment transactions at once"); rc = false; }*/ // now check that we have the correct account type for investment transactions if (rc == true && investmentTransactions != 0) { if (d->m_currentAccount.accountType() != eMyMoney::Account::Type::Investment) { tooltip = i18n("Cannot edit investment transactions in the context of this account."); rc = false; } } return rc; } void KGlobalLedgerView::slotMoveToAccount(const QString& id) { Q_D(KGlobalLedgerView); // close the menu, if it is still open if (pMenus[Menu::Transaction]->isVisible()) pMenus[Menu::Transaction]->close(); if (!d->m_selectedTransactions.isEmpty()) { MyMoneyFileTransaction ft; try { foreach (const auto selection, d->m_selectedTransactions) { if (d->m_currentAccount.accountType() == eMyMoney::Account::Type::Investment) { d->moveInvestmentTransaction(d->m_currentAccount.id(), id, selection.transaction()); } else { auto changed = false; auto t = selection.transaction(); foreach (const auto split, selection.transaction().splits()) { if (split.accountId() == d->m_currentAccount.id()) { MyMoneySplit s = split; s.setAccountId(id); t.modifySplit(s); changed = true; } } if (changed) { MyMoneyFile::instance()->modifyTransaction(t); } } } ft.commit(); } catch (const MyMoneyException &) { } } } void KGlobalLedgerView::slotUpdateMoveToAccountMenu() { Q_D(KGlobalLedgerView); d->createTransactionMoveMenu(); // in case we were not able to create the selector, we // better get out of here. Anything else would cause // a crash later on (accountSet.load) if (!d->m_moveToAccountSelector) return; if (!d->m_currentAccount.id().isEmpty()) { AccountSet accountSet; if (d->m_currentAccount.accountType() == eMyMoney::Account::Type::Investment) { accountSet.addAccountType(eMyMoney::Account::Type::Investment); } else if (d->m_currentAccount.isAssetLiability()) { accountSet.addAccountType(eMyMoney::Account::Type::Checkings); accountSet.addAccountType(eMyMoney::Account::Type::Savings); accountSet.addAccountType(eMyMoney::Account::Type::Cash); accountSet.addAccountType(eMyMoney::Account::Type::AssetLoan); accountSet.addAccountType(eMyMoney::Account::Type::CertificateDep); accountSet.addAccountType(eMyMoney::Account::Type::MoneyMarket); accountSet.addAccountType(eMyMoney::Account::Type::Asset); accountSet.addAccountType(eMyMoney::Account::Type::Currency); accountSet.addAccountType(eMyMoney::Account::Type::CreditCard); accountSet.addAccountType(eMyMoney::Account::Type::Loan); accountSet.addAccountType(eMyMoney::Account::Type::Liability); } else if (d->m_currentAccount.isIncomeExpense()) { accountSet.addAccountType(eMyMoney::Account::Type::Income); accountSet.addAccountType(eMyMoney::Account::Type::Expense); } accountSet.load(d->m_moveToAccountSelector); // remove those accounts that we currently reference foreach (const auto selection, d->m_selectedTransactions) { foreach (const auto split, selection.transaction().splits()) { d->m_moveToAccountSelector->removeItem(split.accountId()); } } // remove those accounts from the list that are denominated // in a different currency auto list = d->m_moveToAccountSelector->accountList(); QList::const_iterator it_a; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { auto acc = MyMoneyFile::instance()->account(*it_a); if (acc.currencyId() != d->m_currentAccount.currencyId()) d->m_moveToAccountSelector->removeItem((*it_a)); } } } void KGlobalLedgerView::slotObjectDestroyed(QObject* o) { Q_D(KGlobalLedgerView); if (o == d->m_moveToAccountSelector) { d->m_moveToAccountSelector = nullptr; } } void KGlobalLedgerView::slotCancelOrEnterTransactions(bool& okToSelect) { Q_D(KGlobalLedgerView); static bool oneTime = false; if (!oneTime) { oneTime = true; auto dontShowAgain = "CancelOrEditTransaction"; // qDebug("KMyMoneyApp::slotCancelOrEndEdit"); if (d->m_transactionEditor) { if (KMyMoneySettings::focusChangeIsEnter() && pActions[Action::EnterTransaction]->isEnabled()) { slotEnterTransaction(); if (d->m_transactionEditor) { // if at this stage the editor is still there that means that entering the transaction was cancelled // for example by pressing cancel on the exchange rate editor so we must stay in edit mode okToSelect = false; } } else { // okToSelect is preset to true if a cancel of the dialog is useful and false if it is not int rc; KGuiItem noGuiItem = KStandardGuiItem::save(); KGuiItem yesGuiItem = KStandardGuiItem::discard(); KGuiItem cancelGuiItem = KStandardGuiItem::cont(); // if the transaction can't be entered make sure that it can't be entered by pressing no either if (!pActions[Action::EnterTransaction]->isEnabled()) { noGuiItem.setEnabled(false); noGuiItem.setToolTip(pActions[Action::EnterTransaction]->toolTip()); } if (okToSelect == true) { rc = KMessageBox::warningYesNoCancel(this, i18n("

Please select what you want to do: discard the changes, save the changes or continue to edit the transaction.

You can also set an option to save the transaction automatically when e.g. selecting another transaction.

"), i18n("End transaction edit"), yesGuiItem, noGuiItem, cancelGuiItem, dontShowAgain); } else { rc = KMessageBox::warningYesNo(this, i18n("

Please select what you want to do: discard the changes, save the changes or continue to edit the transaction.

You can also set an option to save the transaction automatically when e.g. selecting another transaction.

"), i18n("End transaction edit"), yesGuiItem, noGuiItem, dontShowAgain); } switch (rc) { case KMessageBox::Yes: slotCancelTransaction(); break; case KMessageBox::No: slotEnterTransaction(); // make sure that we'll see this message the next time no matter // if the user has chosen the 'Don't show again' checkbox KMessageBox::enableMessage(dontShowAgain); if (d->m_transactionEditor) { // if at this stage the editor is still there that means that entering the transaction was cancelled // for example by pressing cancel on the exchange rate editor so we must stay in edit mode okToSelect = false; } break; case KMessageBox::Cancel: // make sure that we'll see this message the next time no matter // if the user has chosen the 'Don't show again' checkbox KMessageBox::enableMessage(dontShowAgain); okToSelect = false; break; } } } oneTime = false; } } void KGlobalLedgerView::slotNewSchedule(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence) { KEditScheduleDlg::newSchedule(_t, occurrence); } void KGlobalLedgerView::slotNewTransactionForm(eWidgets::eRegister::Action id) { Q_D(KGlobalLedgerView); if (!d->m_inEditMode) { d->m_action = id; // since we jump here via code, we have to make sure to react only // if the action is enabled if (pActions[Action::NewTransaction]->isEnabled()) { if (d->createNewTransaction()) { d->m_transactionEditor = d->startEdit(d->m_selectedTransactions); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(this/*d->m_myMoneyView*/, !KMyMoneySettings::stringMatchFromStart()); KMyMoneyPayeeCombo* payeeEdit = dynamic_cast(d->m_transactionEditor->haveWidget("payee")); if (payeeEdit && !d->m_lastPayeeEnteredId.isEmpty()) { // in case we entered a new transaction before and used a payee, // we reuse it here. Save the text to the edit widget, select it // so that hitting any character will start entering another payee. payeeEdit->setSelectedItem(d->m_lastPayeeEnteredId); payeeEdit->lineEdit()->selectAll(); } if (d->m_transactionEditor) { connect(d->m_transactionEditor.data(), &TransactionEditor::statusProgress, this, &KGlobalLedgerView::slotStatusProgress); connect(d->m_transactionEditor.data(), &TransactionEditor::statusMsg, this, &KGlobalLedgerView::slotStatusMsg); connect(d->m_transactionEditor.data(), &TransactionEditor::scheduleTransaction, this, &KGlobalLedgerView::slotNewSchedule); } updateLedgerActionsInternal(); // emit transactionsSelected(d->m_selectedTransactions); } } } } } void KGlobalLedgerView::slotNewTransaction() { slotNewTransactionForm(eWidgets::eRegister::Action::None); } void KGlobalLedgerView::slotEditTransaction() { Q_D(KGlobalLedgerView); // qDebug("KMyMoneyApp::slotTransactionsEdit()"); // since we jump here via code, we have to make sure to react only // if the action is enabled if (pActions[Action::EditTransaction]->isEnabled()) { // as soon as we edit a transaction, we don't remember the last payee entered d->m_lastPayeeEnteredId.clear(); d->m_transactionEditor = d->startEdit(d->m_selectedTransactions); KMyMoneyMVCCombo::setSubstringSearchForChildren(this/*d->m_myMoneyView*/, !KMyMoneySettings::stringMatchFromStart()); updateLedgerActionsInternal(); } } void KGlobalLedgerView::slotDeleteTransaction() { Q_D(KGlobalLedgerView); // since we may jump here via code, we have to make sure to react only // if the action is enabled if (!pActions[Action::DeleteTransaction]->isEnabled()) return; if (d->m_selectedTransactions.isEmpty()) return; if (d->m_selectedTransactions.warnLevel() == 1) { if (KMessageBox::warningContinueCancel(this, i18n("At least one split of the selected transactions has been reconciled. " "Do you wish to delete the transactions anyway?"), i18n("Transaction already reconciled")) == KMessageBox::Cancel) return; } auto msg = i18np("Do you really want to delete the selected transaction?", "Do you really want to delete all %1 selected transactions?", d->m_selectedTransactions.count()); if (KMessageBox::questionYesNo(this, msg, i18n("Delete transaction")) == KMessageBox::Yes) { //KMSTATUS(i18n("Deleting transactions")); d->doDeleteTransactions(); } } void KGlobalLedgerView::slotDuplicateTransaction() { Q_D(KGlobalLedgerView); // since we may jump here via code, we have to make sure to react only // if the action is enabled if (pActions[Action::DuplicateTransaction]->isEnabled()) { KMyMoneyRegister::SelectedTransactions selectionList = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::iterator it_t; int i = 0; int cnt = d->m_selectedTransactions.count(); // KMSTATUS(i18n("Duplicating transactions")); emit selectByVariant(QVariantList {QVariant(0), QVariant(cnt)}, eView::Intent::ReportProgress); MyMoneyFileTransaction ft; MyMoneyTransaction lt; try { foreach (const auto selection, selectionList) { auto t = selection.transaction(); // wipe out any reconciliation information for (auto& split : t.splits()) { split.setReconcileFlag(eMyMoney::Split::State::NotReconciled); split.setReconcileDate(QDate()); split.setBankID(QString()); } // clear invalid data t.setEntryDate(QDate()); t.clearId(); // and set the post date to today t.setPostDate(QDate::currentDate()); MyMoneyFile::instance()->addTransaction(t); lt = t; emit selectByVariant(QVariantList {QVariant(i++), QVariant(0)}, eView::Intent::ReportProgress); } ft.commit(); // select the new transaction in the ledger if (!d->m_currentAccount.id().isEmpty()) slotLedgerSelected(d->m_currentAccount.id(), lt.id()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to duplicate transaction(s)"), QString::fromLatin1(e.what())); } // switch off the progress bar emit selectByVariant(QVariantList {QVariant(-1), QVariant(-1)}, eView::Intent::ReportProgress); } } void KGlobalLedgerView::slotEnterTransaction() { Q_D(KGlobalLedgerView); // since we jump here via code, we have to make sure to react only // if the action is enabled if (pActions[Action::EnterTransaction]->isEnabled()) { // disable the action while we process it to make sure it's processed only once since // d->m_transactionEditor->enterTransactions(newId) will run QCoreApplication::processEvents // we could end up here twice which will cause a crash slotUpdateActions() will enable the action again pActions[Action::EnterTransaction]->setEnabled(false); if (d->m_transactionEditor) { QString accountId = d->m_currentAccount.id(); QString newId; connect(d->m_transactionEditor.data(), &TransactionEditor::balanceWarning, d->m_balanceWarning.data(), &KBalanceWarning::slotShowMessage); if (d->m_transactionEditor->enterTransactions(newId)) { KMyMoneyPayeeCombo* payeeEdit = dynamic_cast(d->m_transactionEditor->haveWidget("payee")); if (payeeEdit && !newId.isEmpty()) { d->m_lastPayeeEnteredId = payeeEdit->selectedItem(); } d->deleteTransactionEditor(); } if (!newId.isEmpty()) { slotLedgerSelected(accountId, newId); } } updateLedgerActionsInternal(); } } void KGlobalLedgerView::slotAcceptTransaction() { Q_D(KGlobalLedgerView); KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; int cnt = list.count(); int i = 0; emit selectByVariant(QVariantList {QVariant(0), QVariant(cnt)}, eView::Intent::ReportProgress); MyMoneyFileTransaction ft; try { for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { // reload transaction in case it got changed during the course of this loop MyMoneyTransaction t = MyMoneyFile::instance()->transaction((*it_t).transaction().id()); if (t.isImported()) { t.setImported(false); if (!d->m_currentAccount.id().isEmpty()) { foreach (const auto split, t.splits()) { if (split.accountId() == d->m_currentAccount.id()) { if (split.reconcileFlag() == eMyMoney::Split::State::NotReconciled) { MyMoneySplit s = split; s.setReconcileFlag(eMyMoney::Split::State::Cleared); t.modifySplit(s); } } } } MyMoneyFile::instance()->modifyTransaction(t); } if ((*it_t).split().isMatched()) { // reload split in case it got changed during the course of this loop MyMoneySplit s = t.splitById((*it_t).split().id()); TransactionMatcher matcher(d->m_currentAccount); matcher.accept(t, s); } emit selectByVariant(QVariantList {QVariant(i++), QVariant(0)}, eView::Intent::ReportProgress); } emit selectByVariant(QVariantList {QVariant(-1), QVariant(-1)}, eView::Intent::ReportProgress); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to accept transaction"), QString::fromLatin1(e.what())); } } void KGlobalLedgerView::slotCancelTransaction() { Q_D(KGlobalLedgerView); // since we jump here via code, we have to make sure to react only // if the action is enabled if (pActions[Action::CancelTransaction]->isEnabled()) { // make sure, we block the enter function pActions[Action::EnterTransaction]->setEnabled(false); // qDebug("KMyMoneyApp::slotTransactionsCancel"); d->deleteTransactionEditor(); updateLedgerActions(d->m_selectedTransactions); emit selectByVariant(QVariantList {QVariant::fromValue(d->m_selectedTransactions)}, eView::Intent::SelectRegisterTransactions); } } void KGlobalLedgerView::slotEditSplits() { Q_D(KGlobalLedgerView); // since we jump here via code, we have to make sure to react only // if the action is enabled if (pActions[Action::EditSplits]->isEnabled()) { // as soon as we edit a transaction, we don't remember the last payee entered d->m_lastPayeeEnteredId.clear(); d->m_transactionEditor = d->startEdit(d->m_selectedTransactions); updateLedgerActions(d->m_selectedTransactions); emit selectByVariant(QVariantList {QVariant::fromValue(d->m_selectedTransactions)}, eView::Intent::SelectRegisterTransactions); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(this/*d->m_myMoneyView*/, !KMyMoneySettings::stringMatchFromStart()); if (d->m_transactionEditor->slotEditSplits() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { QString id; connect(d->m_transactionEditor.data(), &TransactionEditor::balanceWarning, d->m_balanceWarning.data(), &KBalanceWarning::slotShowMessage); d->m_transactionEditor->enterTransactions(id); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify transaction"), QString::fromLatin1(e.what())); } } } d->deleteTransactionEditor(); updateLedgerActions(d->m_selectedTransactions); emit selectByVariant(QVariantList {QVariant::fromValue(d->m_selectedTransactions)}, eView::Intent::SelectRegisterTransactions); } } void KGlobalLedgerView::slotCopySplits() { Q_D(KGlobalLedgerView); const auto file = MyMoneyFile::instance(); if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; KMyMoneyRegister::SelectedTransaction selectedSourceTransaction; foreach (const auto& st, d->m_selectedTransactions) { switch (st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: selectedSourceTransaction = st; multipleSplitTransactions++; break; } } if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) { MyMoneyFileTransaction ft; try { const auto& sourceTransaction = selectedSourceTransaction.transaction(); const auto& sourceSplit = selectedSourceTransaction.split(); foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { auto t = st.transaction(); // don't process the source transaction if (sourceTransaction.id() == t.id()) { continue; } const auto& baseSplit = st.split(); if (t.splitCount() == 1) { foreach (const auto& split, sourceTransaction.splits()) { // Don't copy the source split, as we already have that // as part of the destination transaction if (split.id() == sourceSplit.id()) { continue; } MyMoneySplit sp(split); // clear the ID and reconciliation state sp.clearId(); sp.setReconcileFlag(eMyMoney::Split::State::NotReconciled); sp.setReconcileDate(QDate()); // in case it is a simple transaction consisting of two splits, // we can adjust the share and value part of the second split we // just created. We need to keep a possible price in mind in case // of different currencies if (sourceTransaction.splitCount() == 2) { sp.setValue(-baseSplit.value()); sp.setShares(-(baseSplit.shares() * baseSplit.price())); } t.addSplit(sp); } file->modifyTransaction(t); } } ft.commit(); } catch (const MyMoneyException &) { qDebug() << "transactionCopySplits() failed"; } } } } void KGlobalLedgerView::slotGoToPayee() { Q_D(KGlobalLedgerView); if (!d->m_payeeGoto.isEmpty()) { try { QString transactionId; if (d->m_selectedTransactions.count() == 1) { transactionId = d->m_selectedTransactions[0].transaction().id(); } // make sure to pass copies, as d->myMoneyView->slotPayeeSelected() overrides // d->m_payeeGoto and d->m_currentAccount while calling slotUpdateActions() QString payeeId = d->m_payeeGoto; QString accountId = d->m_currentAccount.id(); emit selectByVariant(QVariantList {QVariant(payeeId), QVariant(accountId), QVariant(transactionId)}, eView::Intent::ShowPayee); // emit openPayeeRequested(payeeId, accountId, transactionId); } catch (const MyMoneyException &) { } } } void KGlobalLedgerView::slotGoToAccount() { Q_D(KGlobalLedgerView); if (!d->m_accountGoto.isEmpty()) { try { QString transactionId; if (d->m_selectedTransactions.count() == 1) { transactionId = d->m_selectedTransactions[0].transaction().id(); } // make sure to pass a copy, as d->myMoneyView->slotLedgerSelected() overrides // d->m_accountGoto while calling slotUpdateActions() slotLedgerSelected(d->m_accountGoto, transactionId); } catch (const MyMoneyException &) { } } } void KGlobalLedgerView::slotMatchTransactions() { Q_D(KGlobalLedgerView); // if the menu action is retrieved it can contain an '&' character for the accelerator causing the comparison to fail if not removed QString transactionActionText = pActions[Action::MatchTransaction]->text(); transactionActionText.remove('&'); if (transactionActionText == i18nc("Button text for match transaction", "Match")) d->transactionMatch(); else d->transactionUnmatch(); } void KGlobalLedgerView::slotCombineTransactions() { qDebug("slotTransactionCombine() not implemented yet"); } void KGlobalLedgerView::slotToggleReconciliationFlag() { Q_D(KGlobalLedgerView); d->markTransaction(eMyMoney::Split::State::Unknown); } void KGlobalLedgerView::slotMarkCleared() { Q_D(KGlobalLedgerView); d->markTransaction(eMyMoney::Split::State::Cleared); } void KGlobalLedgerView::slotMarkReconciled() { Q_D(KGlobalLedgerView); d->markTransaction(eMyMoney::Split::State::Reconciled); } void KGlobalLedgerView::slotMarkNotReconciled() { Q_D(KGlobalLedgerView); d->markTransaction(eMyMoney::Split::State::NotReconciled); } void KGlobalLedgerView::slotSelectAllTransactions() { Q_D(KGlobalLedgerView); if(d->m_needLoad) d->init(); d->m_register->clearSelection(); KMyMoneyRegister::RegisterItem* p = d->m_register->firstItem(); while (p) { KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t) { if (t->isVisible() && t->isSelectable() && !t->isScheduled() && !t->id().isEmpty()) { t->setSelected(true); } } p = p->nextItem(); } // this is here only to re-paint the items without selecting anything because the data (including the selection) is not really held in the model right now d->m_register->selectAll(); // inform everyone else about the selected items KMyMoneyRegister::SelectedTransactions list(d->m_register); updateLedgerActions(list); emit selectByVariant(QVariantList {QVariant::fromValue(list)}, eView::Intent::SelectRegisterTransactions); } void KGlobalLedgerView::slotCreateScheduledTransaction() { Q_D(KGlobalLedgerView); if (d->m_selectedTransactions.count() == 1) { // make sure to have the current selected split as first split in the schedule MyMoneyTransaction t = d->m_selectedTransactions[0].transaction(); MyMoneySplit s = d->m_selectedTransactions[0].split(); QString splitId = s.id(); s.clearId(); s.setReconcileFlag(eMyMoney::Split::State::NotReconciled); s.setReconcileDate(QDate()); t.removeSplits(); t.addSplit(s); foreach (const auto split, d->m_selectedTransactions[0].transaction().splits()) { if (split.id() != splitId) { MyMoneySplit s0 = split; s0.clearId(); s0.setReconcileFlag(eMyMoney::Split::State::NotReconciled); s0.setReconcileDate(QDate()); t.addSplit(s0); } } KEditScheduleDlg::newSchedule(t, eMyMoney::Schedule::Occurrence::Monthly); } } void KGlobalLedgerView::slotAssignNumber() { Q_D(KGlobalLedgerView); if (d->m_transactionEditor) d->m_transactionEditor->assignNextNumber(); } void KGlobalLedgerView::slotStartReconciliation() { Q_D(KGlobalLedgerView); // we cannot reconcile standard accounts if (!MyMoneyFile::instance()->isStandardAccount(d->m_currentAccount.id())) emit selectByObject(d->m_currentAccount, eView::Intent::StartEnteringOverdueScheduledTransactions); // asynchronous call to KScheduledView::slotEnterOverdueSchedules is made here // after that all activity should be continued in KGlobalLedgerView::slotContinueReconciliation() } void KGlobalLedgerView::slotFinishReconciliation() { Q_D(KGlobalLedgerView); const auto file = MyMoneyFile::instance(); if (!d->m_reconciliationAccount.id().isEmpty()) { // retrieve list of all transactions that are not reconciled or cleared QList > transactionList; MyMoneyTransactionFilter filter(d->m_reconciliationAccount.id()); filter.addState((int)eMyMoney::TransactionFilter::State::Cleared); filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled); filter.setDateFilter(QDate(), d->m_endingBalanceDlg->statementDate()); filter.setConsiderCategory(false); filter.setReportAllSplits(true); file->transactionList(transactionList, filter); auto balance = MyMoneyFile::instance()->balance(d->m_reconciliationAccount.id(), d->m_endingBalanceDlg->statementDate()); MyMoneyMoney actBalance, clearedBalance; actBalance = clearedBalance = balance; // walk the list of transactions to figure out the balance(s) for (auto it = transactionList.constBegin(); it != transactionList.constEnd(); ++it) { if ((*it).second.reconcileFlag() == eMyMoney::Split::State::NotReconciled) { clearedBalance -= (*it).second.shares(); } } if (d->m_endingBalanceDlg->endingBalance() != clearedBalance) { auto message = i18n("You are about to finish the reconciliation of this account with a difference between your bank statement and the transactions marked as cleared.\n" "Are you sure you want to finish the reconciliation?"); if (KMessageBox::questionYesNo(this, message, i18n("Confirm end of reconciliation"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::No) return; } MyMoneyFileTransaction ft; // refresh object d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); // Turn off reconciliation mode // Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); // slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); // d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount); // only update the last statement balance here, if we haven't a newer one due // to download of online statements. if (d->m_reconciliationAccount.value("lastImportedTransactionDate").isEmpty() || QDate::fromString(d->m_reconciliationAccount.value("lastImportedTransactionDate"), Qt::ISODate) < d->m_endingBalanceDlg->statementDate()) { d->m_reconciliationAccount.setValue("lastStatementBalance", d->m_endingBalanceDlg->endingBalance().toString()); // in case we override the last statement balance here, we have to make sure // that we don't show the online balance anymore, as it might be different d->m_reconciliationAccount.deletePair("lastImportedTransactionDate"); } d->m_reconciliationAccount.setLastReconciliationDate(d->m_endingBalanceDlg->statementDate()); // keep a record of this reconciliation d->m_reconciliationAccount.addReconciliation(d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance()); d->m_reconciliationAccount.deletePair("lastReconciledBalance"); d->m_reconciliationAccount.deletePair("statementBalance"); d->m_reconciliationAccount.deletePair("statementDate"); try { // update the account data file->modifyAccount(d->m_reconciliationAccount); /* // collect the list of cleared splits for this account filter.clear(); filter.addAccount(d->m_reconciliationAccount.id()); filter.addState(eMyMoney::TransactionFilter::Cleared); filter.setConsiderCategory(false); filter.setReportAllSplits(true); file->transactionList(transactionList, filter); */ // walk the list of transactions/splits and mark the cleared ones as reconciled for (auto it = transactionList.begin(); it != transactionList.end(); ++it) { MyMoneySplit sp = (*it).second; // skip the ones that are not marked cleared if (sp.reconcileFlag() != eMyMoney::Split::State::Cleared) continue; // always retrieve a fresh copy of the transaction because we // might have changed it already with another split MyMoneyTransaction t = file->transaction((*it).first.id()); sp.setReconcileFlag(eMyMoney::Split::State::Reconciled); sp.setReconcileDate(d->m_endingBalanceDlg->statementDate()); t.modifySplit(sp); // update the engine ... file->modifyTransaction(t); // ... and the list (*it) = qMakePair(t, sp); } ft.commit(); // reload account data from engine as the data might have changed in the meantime d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); /** * This signal is emitted when an account has been successfully reconciled * and all transactions are updated in the engine. It can be used by plugins * to create reconciliation reports. * * @param account the account data * @param date the reconciliation date as provided through the dialog * @param startingBalance the starting balance as provided through the dialog * @param endingBalance the ending balance as provided through the dialog * @param transactionList reference to QList of QPair containing all * transaction/split pairs processed by the reconciliation. */ emit selectByVariant(QVariantList { QVariant::fromValue(d->m_reconciliationAccount), QVariant::fromValue(d->m_endingBalanceDlg->statementDate()), QVariant::fromValue(d->m_endingBalanceDlg->previousBalance()), QVariant::fromValue(d->m_endingBalanceDlg->endingBalance()), QVariant::fromValue(transactionList) }, eView::Intent::AccountReconciled); } catch (const MyMoneyException &) { qDebug("Unexpected exception when setting cleared to reconcile"); } // Turn off reconciliation mode Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); } // Turn off reconciliation mode d->m_reconciliationAccount = MyMoneyAccount(); updateActions(d->m_currentAccount); updateLedgerActionsInternal(); d->loadView(); // slotUpdateActions(); } void KGlobalLedgerView::slotPostponeReconciliation() { Q_D(KGlobalLedgerView); MyMoneyFileTransaction ft; const auto file = MyMoneyFile::instance(); if (!d->m_reconciliationAccount.id().isEmpty()) { // refresh object d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); // Turn off reconciliation mode // Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); // slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); // d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount); d->m_reconciliationAccount.setValue("lastReconciledBalance", d->m_endingBalanceDlg->previousBalance().toString()); d->m_reconciliationAccount.setValue("statementBalance", d->m_endingBalanceDlg->endingBalance().toString()); d->m_reconciliationAccount.setValue("statementDate", d->m_endingBalanceDlg->statementDate().toString(Qt::ISODate)); try { file->modifyAccount(d->m_reconciliationAccount); ft.commit(); d->m_reconciliationAccount = MyMoneyAccount(); updateActions(d->m_currentAccount); updateLedgerActionsInternal(); // slotUpdateActions(); } catch (const MyMoneyException &) { qDebug("Unexpected exception when setting last reconcile info into account"); ft.rollback(); d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); } // Turn off reconciliation mode Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); d->loadView(); } } void KGlobalLedgerView::slotOpenAccount() { Q_D(KGlobalLedgerView); if (!MyMoneyFile::instance()->isStandardAccount(d->m_currentAccount.id())) slotLedgerSelected(d->m_currentAccount.id(), QString()); } void KGlobalLedgerView::slotFindTransaction() { Q_D(KGlobalLedgerView); if (!d->m_searchDlg) { d->m_searchDlg = new KFindTransactionDlg(this); connect(d->m_searchDlg, &QObject::destroyed, this, &KGlobalLedgerView::slotCloseSearchDialog); connect(d->m_searchDlg, &KFindTransactionDlg::transactionSelected, this, &KGlobalLedgerView::slotLedgerSelected); } d->m_searchDlg->show(); d->m_searchDlg->raise(); d->m_searchDlg->activateWindow(); } void KGlobalLedgerView::slotCloseSearchDialog() { Q_D(KGlobalLedgerView); if (d->m_searchDlg) d->m_searchDlg->deleteLater(); d->m_searchDlg = nullptr; } void KGlobalLedgerView::slotStatusMsg(const QString& txt) { emit selectByVariant(QVariantList {QVariant(txt)}, eView::Intent::ReportProgressMessage); } void KGlobalLedgerView::slotStatusProgress(int cnt, int base) { emit selectByVariant(QVariantList {QVariant(cnt), QVariant(base)}, eView::Intent::ReportProgress); } void KGlobalLedgerView::slotTransactionsSelected(const KMyMoneyRegister::SelectedTransactions& list) { updateLedgerActions(list); emit selectByVariant(QVariantList {QVariant::fromValue(list)}, eView::Intent::SelectRegisterTransactions); } diff --git a/kmymoney/views/khomeview.cpp b/kmymoney/views/khomeview.cpp index 810f88da2..c53131c9a 100644 --- a/kmymoney/views/khomeview.cpp +++ b/kmymoney/views/khomeview.cpp @@ -1,196 +1,201 @@ /*************************************************************************** khomeview.cpp - description ------------------- begin : Tue Jan 22 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "khomeview_p.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes KHomeView::KHomeView(QWidget *parent) : KMyMoneyViewBase(*new KHomeViewPrivate(this), parent) { } KHomeView::~KHomeView() { } void KHomeView::wheelEvent(QWheelEvent* event) { Q_D(KHomeView); // Zoom text on Ctrl + Scroll if (event->modifiers() & Qt::CTRL) { qreal factor = d->m_view->zoomFactor(); if (event->delta() > 0) factor += 0.1; else if (event->delta() < 0) factor -= 0.1; d->m_view->setZoomFactor(factor); event->accept(); return; } } void KHomeView::executeCustomAction(eView::Action action) { + Q_D(KHomeView); switch(action) { case eView::Action::Refresh: refresh(); break; case eView::Action::Print: slotPrintView(); break; + case eView::Action::CleanupBeforeFileClose: + d->m_view->setHtml(KWelcomePage::welcomePage(), QUrl("file://")); + break; + default: break; } } void KHomeView::refresh() { Q_D(KHomeView); if (isVisible()) { d->loadView(); d->m_needsRefresh = false; } else { d->m_needsRefresh = true; } } void KHomeView::showEvent(QShowEvent* event) { Q_D(KHomeView); if (d->m_needLoad) d->init(); emit customActionRequested(View::Home, eView::Action::AboutToShow); if (d->m_needsRefresh) refresh(); QWidget::showEvent(event); } void KHomeView::slotPrintView() { Q_D(KHomeView); if (d->m_view) { d->m_currentPrinter = new QPrinter(); QPointer dialog = new QPrintDialog(d->m_currentPrinter, this); dialog->setWindowTitle(QString()); if (dialog->exec() != QDialog::Accepted) { delete d->m_currentPrinter; d->m_currentPrinter = nullptr; return; } #ifdef ENABLE_WEBENGINE d->m_view->page()->print(d->m_currentPrinter, [=] (bool) {delete d->m_currentPrinter; d->m_currentPrinter = nullptr;}); #else d->m_view->print(d->m_currentPrinter); #endif } } void KHomeView::slotOpenUrl(const QUrl &url) { Q_D(KHomeView); QString protocol = url.scheme(); QString view = url.fileName(); if (view.isEmpty()) return; QUrlQuery query(url); QString id = query.queryItemValue("id"); QString mode = query.queryItemValue("mode"); const auto file = MyMoneyFile::instance(); if (protocol == QLatin1String("http")) { QDesktopServices::openUrl(url); } else if (protocol == QLatin1String("mailto")) { QDesktopServices::openUrl(url); } else { KXmlGuiWindow* mw = KMyMoneyUtils::mainWindow(); Q_CHECK_PTR(mw); if (view == VIEW_LEDGER) { emit selectByVariant(QVariantList {QVariant(id), QVariant(QString())}, eView::Intent::ShowTransaction); } else if (view == VIEW_SCHEDULE) { if (mode == QLatin1String("enter")) { emit selectByObject(file->schedule(id), eView::Intent::None); QTimer::singleShot(0, pActions[eMenu::Action::EnterSchedule], SLOT(trigger())); } else if (mode == QLatin1String("edit")) { emit selectByObject(file->schedule(id), eView::Intent::None); QTimer::singleShot(0, pActions[eMenu::Action::EditSchedule], SLOT(trigger())); } else if (mode == QLatin1String("skip")) { emit selectByObject(file->schedule(id), eView::Intent::None); QTimer::singleShot(0, pActions[eMenu::Action::SkipSchedule], SLOT(trigger())); } else if (mode == QLatin1String("full")) { d->m_showAllSchedules = true; d->loadView(); } else if (mode == QLatin1String("reduced")) { d->m_showAllSchedules = false; d->loadView(); } } else if (view == VIEW_REPORTS) { emit selectByObject(file->report(id), eView::Intent::OpenObject); // emit openObjectRequested(file->report(id)); } else if (view == VIEW_WELCOME) { if (mode == QLatin1String("whatsnew")) d->m_view->setHtml(KWelcomePage::whatsNewPage(), QUrl("file://")); else d->m_view->setHtml(KWelcomePage::welcomePage(), QUrl("file://")); } else if (view == QLatin1String("action")) { QTimer::singleShot(0, mw->actionCollection()->action(id), SLOT(trigger())); } else if (view == VIEW_HOME) { QList list; MyMoneyFile::instance()->accountList(list); if (list.count() == 0) { KMessageBox::information(this, i18n("Before KMyMoney can give you detailed information about your financial status, you need to create at least one account. Until then, KMyMoney shows the welcome page instead.")); } d->loadView(); } else { qDebug("Unknown view '%s' in slotOpenURL()", qPrintable(view)); } } } // Make sure, that these definitions are only used within this file // this does not seem to be necessary, but when building RPMs the // build option 'final' is used and all CPP files are concatenated. // So it could well be, that in another CPP file these definitions // are also used. #undef VIEW_LEDGER #undef VIEW_SCHEDULE #undef VIEW_WELCOME #undef VIEW_HOME #undef VIEW_REPORTS diff --git a/kmymoney/views/khomeview_p.h b/kmymoney/views/khomeview_p.h index ec1d8db88..40134b649 100644 --- a/kmymoney/views/khomeview_p.h +++ b/kmymoney/views/khomeview_p.h @@ -1,1983 +1,1984 @@ /*************************************************************************** khomeview_p.h - description ------------------- begin : Tue Jan 22 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KHOMEVIEW_P_H #define KHOMEVIEW_P_H #include "khomeview.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_WEBENGINE #include #else #include #endif // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyviewbase_p.h" #include "mymoneyutils.h" #include "kmymoneyutils.h" #include "kwelcomepage.h" #include "kmymoneysettings.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneyprice.h" #include "mymoneyforecast.h" #include "kreportchartview.h" #include "pivottable.h" #include "pivotgrid.h" #include "reportaccount.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "icons.h" #include "kmymoneywebpage.h" #include "mymoneyschedule.h" #include "mymoneysecurity.h" #include "mymoneyexception.h" #include "mymoneyenums.h" #include "menuenums.h" #define VIEW_LEDGER "ledger" #define VIEW_SCHEDULE "schedule" #define VIEW_WELCOME "welcome" #define VIEW_HOME "home" #define VIEW_REPORTS "reports" using namespace Icons; using namespace eMyMoney; /** * @brief Converts a QPixmap to an data URI scheme * * According to RFC 2397 * * @param pixmap Source to convert * @return full data URI */ QString QPixmapToDataUri(const QPixmap& pixmap) { QImage image(pixmap.toImage()); QByteArray byteArray; QBuffer buffer(&byteArray); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); // writes the image in PNG format inside the buffer return QLatin1String("data:image/png;base64,") + QString(byteArray.toBase64()); } bool accountNameLess(const MyMoneyAccount &acc1, const MyMoneyAccount &acc2) { return acc1.name().localeAwareCompare(acc2.name()) < 0; } using namespace reports; class KHomeViewPrivate : public KMyMoneyViewBasePrivate { Q_DECLARE_PUBLIC(KHomeView) public: explicit KHomeViewPrivate(KHomeView *qq) : KMyMoneyViewBasePrivate(), q_ptr(qq), m_view(nullptr), m_showAllSchedules(false), m_needLoad(true), m_netWorthGraphLastValidSize(400, 300), m_currentPrinter(nullptr) { } ~KHomeViewPrivate() { // if user wants to remember the font size, store it here if (KMyMoneySettings::rememberZoomFactor() && m_view) { KMyMoneySettings::setZoomFactor(m_view->zoomFactor()); KMyMoneySettings::self()->save(); } } /** * Definition of bitmap used as argument for showAccounts(). */ enum paymentTypeE { Preferred = 1, ///< show preferred accounts Payment = 2 ///< show payment accounts }; void init() { Q_Q(KHomeView); m_needLoad = false; auto vbox = new QVBoxLayout(q); q->setLayout(vbox); vbox->setSpacing(6); vbox->setMargin(0); #ifdef ENABLE_WEBENGINE m_view = new QWebEngineView(q); #else m_view = new KWebView(q); #endif m_view->setPage(new MyQWebEnginePage(m_view)); vbox->addWidget(m_view); m_view->setHtml(KWelcomePage::welcomePage(), QUrl("file://")); #ifdef ENABLE_WEBENGINE q->connect(m_view->page(), &QWebEnginePage::urlChanged, q, &KHomeView::slotOpenUrl); #else m_view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); q->connect(m_view->page(), &KWebPage::linkClicked, q, &KHomeView::slotOpenUrl); #endif q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KHomeView::refresh); } /** * Print an account and its balance and limit */ void showAccountEntry(const MyMoneyAccount& acc, const MyMoneyMoney& value, const MyMoneyMoney& valueToMinBal, const bool showMinBal) { MyMoneyFile* file = MyMoneyFile::instance(); QString tmp; MyMoneySecurity currency = file->currency(acc.currencyId()); QString amount; QString amountToMinBal; //format amounts amount = MyMoneyUtils::formatMoney(value, acc, currency); amount.replace(QChar(' '), " "); if (showMinBal) { amountToMinBal = MyMoneyUtils::formatMoney(valueToMinBal, acc, currency); amountToMinBal.replace(QChar(' '), " "); } QString cellStatus, cellCounts, pathOK, pathTODO, pathNotOK; if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { //show account's online-status pathOK = QPixmapToDataUri(Icons::get(Icon::DialogOKApply).pixmap(QSize(16,16))); pathTODO = QPixmapToDataUri(Icons::get(Icon::MailReceive).pixmap(QSize(16,16))); pathNotOK = QPixmapToDataUri(Icons::get(Icon::DialogCancel).pixmap(QSize(16,16))); if (acc.value("lastImportedTransactionDate").isEmpty() || acc.value("lastStatementBalance").isEmpty()) cellStatus = '-'; else if (file->hasMatchingOnlineBalance(acc)) { if (file->hasNewerTransaction(acc.id(), QDate::fromString(acc.value("lastImportedTransactionDate"), Qt::ISODate))) cellStatus = QString("").arg(pathTODO); else cellStatus = QString("").arg(pathOK); } else cellStatus = QString("").arg(pathNotOK); tmp = QString("%1").arg(cellStatus); } tmp += QString("") + link(VIEW_LEDGER, QString("?id=%1").arg(acc.id())) + acc.name() + linkend() + ""; int countNotMarked = 0, countCleared = 0, countNotReconciled = 0; QString countStr; if (KMyMoneySettings::showCountOfUnmarkedTransactions() || KMyMoneySettings::showCountOfNotReconciledTransactions()) countNotMarked = file->countTransactionsWithSpecificReconciliationState(acc.id(), TransactionFilter::State::NotReconciled); if (KMyMoneySettings::showCountOfClearedTransactions() || KMyMoneySettings::showCountOfNotReconciledTransactions()) countCleared = file->countTransactionsWithSpecificReconciliationState(acc.id(), TransactionFilter::State::Cleared); if (KMyMoneySettings::showCountOfNotReconciledTransactions()) countNotReconciled = countNotMarked + countCleared; if (KMyMoneySettings::showCountOfUnmarkedTransactions()) { if (countNotMarked) countStr = QString("%1").arg(countNotMarked); else countStr = '-'; tmp += QString("%1").arg(countStr); } if (KMyMoneySettings::showCountOfClearedTransactions()) { if (countCleared) countStr = QString("%1").arg(countCleared); else countStr = '-'; tmp += QString("%1").arg(countStr); } if (KMyMoneySettings::showCountOfNotReconciledTransactions()) { if (countNotReconciled) countStr = QString("%1").arg(countNotReconciled); else countStr = '-'; tmp += QString("%1").arg(countStr); } if (KMyMoneySettings::showDateOfLastReconciliation()) { const auto lastReconciliationDate = acc.lastReconciliationDate().toString(Qt::SystemLocaleShortDate).replace(QChar(' '), " "); tmp += QString("%1").arg(lastReconciliationDate); } //show account balance tmp += QString("%1").arg(showColoredAmount(amount, value.isNegative())); //show minimum balance column if requested if (showMinBal) { //if it is an investment, show minimum balance empty if (acc.accountType() == Account::Type::Investment) { tmp += QString(" "); } else { //show minimum balance entry tmp += QString("%1").arg(showColoredAmount(amountToMinBal, valueToMinBal.isNegative())); } } // qDebug("accountEntry = '%s'", tmp.toLatin1()); m_html += tmp; } void showAccountEntry(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneySecurity currency = file->currency(acc.currencyId()); MyMoneyMoney value; bool showLimit = KMyMoneySettings::showLimitInfo(); if (acc.accountType() == Account::Type::Investment) { //investment accounts show the balances of all its subaccounts value = investmentBalance(acc); //investment accounts have no minimum balance showAccountEntry(acc, value, MyMoneyMoney(), showLimit); } else { //get balance for normal accounts value = file->balance(acc.id(), QDate::currentDate()); if (acc.currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount(acc.id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; baseValue = baseValue.convert(file->baseCurrency().smallestAccountFraction()); m_total += baseValue; } else { m_total += value; } //if credit card or checkings account, show maximum credit if (acc.accountType() == Account::Type::CreditCard || acc.accountType() == Account::Type::Checkings) { QString maximumCredit = acc.value("maxCreditAbsolute"); if (maximumCredit.isEmpty()) { maximumCredit = acc.value("minBalanceAbsolute"); } MyMoneyMoney maxCredit = MyMoneyMoney(maximumCredit); showAccountEntry(acc, value, value - maxCredit, showLimit); } else { //otherwise use minimum balance QString minimumBalance = acc.value("minBalanceAbsolute"); MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); showAccountEntry(acc, value, value - minBalance, showLimit); } } } /** * @param acc the investment account * @return the balance in the currency of the investment account */ MyMoneyMoney investmentBalance(const MyMoneyAccount& acc) { auto file = MyMoneyFile::instance(); auto value = file->balance(acc.id(), QDate::currentDate()); foreach (const auto accountID, acc.accountList()) { auto stock = file->account(accountID); if (!stock.isClosed()) { try { MyMoneyMoney val; MyMoneyMoney balance = file->balance(stock.id(), QDate::currentDate()); MyMoneySecurity security = file->security(stock.currencyId()); const MyMoneyPrice &price = file->price(stock.currencyId(), security.tradingCurrency()); val = (balance * price.rate(security.tradingCurrency())).convertPrecision(security.pricePrecision()); // adjust value of security to the currency of the account MyMoneySecurity accountCurrency = file->currency(acc.currencyId()); val = val * file->price(security.tradingCurrency(), accountCurrency.id()).rate(accountCurrency.id()); val = val.convert(acc.fraction()); value += val; } catch (const MyMoneyException &e) { qWarning("%s", qPrintable(QString("cannot convert stock balance of %1 to base currency: %2").arg(stock.name(), e.what()))); } } } return value; } /** * Print text in the color set for negative numbers, if @p amount is negative * abd @p isNegative is true */ QString showColoredAmount(const QString& amount, bool isNegative) { if (isNegative) { //if negative, get the settings for negative numbers return QString("%2").arg(KMyMoneySettings::schemeColor(SchemeColor::Negative).name(), amount); } //if positive, return the same string return amount; } /** * Run the forecast */ void doForecast() { //clear m_accountList because forecast is about to changed m_accountList.clear(); //reinitialize the object m_forecast = KMyMoneyUtils::forecast(); //If forecastDays lower than accountsCycle, adjust to the first cycle if (m_forecast.accountsCycle() > m_forecast.forecastDays()) m_forecast.setForecastDays(m_forecast.accountsCycle()); //Get all accounts of the right type to calculate forecast m_forecast.doForecast(); } /** * Calculate the forecast balance after a payment has been made */ MyMoneyMoney forecastPaymentBalance(const MyMoneyAccount& acc, const MyMoneyMoney& payment, QDate& paymentDate) { //if paymentDate before or equal to currentDate set it to current date plus 1 //so we get to accumulate forecast balance correctly if (paymentDate <= QDate::currentDate()) paymentDate = QDate::currentDate().addDays(1); //check if the account is already there if (m_accountList.find(acc.id()) == m_accountList.end() || m_accountList[acc.id()].find(paymentDate) == m_accountList[acc.id()].end()) { if (paymentDate == QDate::currentDate()) { m_accountList[acc.id()][paymentDate] = m_forecast.forecastBalance(acc, paymentDate); } else { m_accountList[acc.id()][paymentDate] = m_forecast.forecastBalance(acc, paymentDate.addDays(-1)); } } m_accountList[acc.id()][paymentDate] = m_accountList[acc.id()][paymentDate] + payment; return m_accountList[acc.id()][paymentDate]; } void loadView() { m_view->setZoomFactor(KMyMoneySettings::zoomFactor()); QList list; - MyMoneyFile::instance()->accountList(list); - if (list.count() == 0) { + if (MyMoneyFile::instance()->storage()) + MyMoneyFile::instance()->accountList(list); + if (list.isEmpty()) { m_view->setHtml(KWelcomePage::welcomePage(), QUrl("file://")); } else { //clear the forecast flag so it will be reloaded m_forecast.setForecastDone(false); const QString filename = QStandardPaths::locate(QStandardPaths::AppConfigLocation, "html/kmymoney.css"); QString header = QString("\n\n").arg(QUrl::fromLocalFile(filename).url()); header += KMyMoneyUtils::variableCSS(); header += "\n"; QString footer = "\n"; m_html.clear(); m_html += header; m_html += QString("
%1
").arg(i18n("Your Financial Summary")); QStringList settings = KMyMoneySettings::listOfItems(); QStringList::ConstIterator it; for (it = settings.constBegin(); it != settings.constEnd(); ++it) { int option = (*it).toInt(); if (option > 0) { switch (option) { case 1: // payments showPayments(); break; case 2: // preferred accounts showAccounts(Preferred, i18n("Preferred Accounts")); break; case 3: // payment accounts // Check if preferred accounts are shown separately if (settings.contains("2")) { showAccounts(static_cast(Payment | Preferred), i18n("Payment Accounts")); } else { showAccounts(Payment, i18n("Payment Accounts")); } break; case 4: // favorite reports showFavoriteReports(); break; case 5: // forecast showForecast(); break; case 6: // net worth graph over all accounts showNetWorthGraph(); break; case 8: // assets and liabilities showAssetsLiabilities(); break; case 9: // budget showBudget(); break; case 10: // cash flow summary showCashFlowSummary(); break; } m_html += "
 
\n"; } } m_html += "
"; m_html += link(VIEW_WELCOME, QString()) + i18n("Show KMyMoney welcome page") + linkend(); m_html += "
"; m_html += "
"; m_html += footer; m_view->setHtml(m_html, QUrl("file://")); } } void showNetWorthGraph() { Q_Q(KHomeView); m_html += QString("
%1
\n
 
\n").arg(i18n("Net Worth Forecast")); MyMoneyReport reportCfg = MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::eMonths, TransactionFilter::Date::UserDefined, // overridden by the setDateFilter() call below MyMoneyReport::eDetailTotal, i18n("Net Worth Forecast"), i18n("Generated Report")); reportCfg.setChartByDefault(true); reportCfg.setChartCHGridLines(false); reportCfg.setChartSVGridLines(false); reportCfg.setChartDataLabels(false); reportCfg.setChartType(MyMoneyReport::eChartLine); reportCfg.setIncludingSchedules(false); reportCfg.addAccountGroup(Account::Type::Asset); reportCfg.addAccountGroup(Account::Type::Liability); reportCfg.setColumnsAreDays(true); reportCfg.setConvertCurrency(true); reportCfg.setIncludingForecast(true); reportCfg.setDateFilter(QDate::currentDate(), QDate::currentDate().addDays(+ 90)); reports::PivotTable table(reportCfg); reports::KReportChartView* chartWidget = new reports::KReportChartView(0); table.drawChart(*chartWidget); // Adjust the size QSize netWorthGraphSize = q->size(); netWorthGraphSize -= QSize(80, 30); // consider the computed size valid only if it's smaller on both axes that the applications size // if (netWorthGraphSize.width() < kmymoney->width() || netWorthGraphSize.height() < kmymoney->height()) { m_netWorthGraphLastValidSize = netWorthGraphSize; // } chartWidget->resize(m_netWorthGraphLastValidSize); //save the chart to an image QString chart = QPixmapToDataUri(chartWidget->coordinatePlane()->parent()->grab()); m_html += QString(""); m_html += QString(""); m_html += QString("").arg(chart); m_html += QString(""); m_html += QString("
\"Networth\"
"); //delete the widget since we no longer need it delete chartWidget; } void showPayments() { MyMoneyFile* file = MyMoneyFile::instance(); QList overdues; QList schedule; int i = 0; //if forecast has not been executed yet, do it. if (!m_forecast.isForecastDone()) doForecast(); schedule = file->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate::currentDate(), QDate::currentDate().addMonths(1), false); overdues = file->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), true); if (schedule.empty() && overdues.empty()) return; // HACK // Remove the finished schedules QList::Iterator d_it; //regular schedules d_it = schedule.begin(); while (d_it != schedule.end()) { if ((*d_it).isFinished()) { d_it = schedule.erase(d_it); continue; } ++d_it; } //overdue schedules d_it = overdues.begin(); while (d_it != overdues.end()) { if ((*d_it).isFinished()) { d_it = overdues.erase(d_it); continue; } ++d_it; } m_html += "
"; m_html += QString("
%1
\n").arg(i18n("Payments")); if (!overdues.isEmpty()) { m_html += "
 
\n"; qSort(overdues); QList::Iterator it; QList::Iterator it_f; m_html += ""; m_html += QString("\n").arg(showColoredAmount(i18n("Overdue payments"), true)); m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; for (it = overdues.begin(); it != overdues.end(); ++it) { // determine number of overdue payments int cnt = (*it).transactionsRemainingUntil(QDate::currentDate().addDays(-1)); m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showPaymentEntry(*it, cnt); m_html += ""; } m_html += "
%1
"; m_html += i18n("Date"); m_html += ""; m_html += i18n("Schedule"); m_html += ""; m_html += i18n("Account"); m_html += ""; m_html += i18n("Amount"); m_html += ""; m_html += i18n("Balance after"); m_html += "
"; } if (!schedule.isEmpty()) { qSort(schedule); // Extract todays payments if any QList todays; QList::Iterator t_it; for (t_it = schedule.begin(); t_it != schedule.end();) { if ((*t_it).adjustedNextDueDate() == QDate::currentDate()) { todays.append(*t_it); (*t_it).setNextDueDate((*t_it).nextPayment(QDate::currentDate())); // if adjustedNextDueDate is still currentDate then remove it from // scheduled payments if ((*t_it).adjustedNextDueDate() == QDate::currentDate()) { t_it = schedule.erase(t_it); continue; } } ++t_it; } if (todays.count() > 0) { m_html += "
 
\n"; m_html += ""; m_html += QString("\n").arg(i18n("Today's due payments")); m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; for (t_it = todays.begin(); t_it != todays.end(); ++t_it) { m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showPaymentEntry(*t_it); m_html += ""; } m_html += "
%1
"; m_html += i18n("Date"); m_html += ""; m_html += i18n("Schedule"); m_html += ""; m_html += i18n("Account"); m_html += ""; m_html += i18n("Amount"); m_html += ""; m_html += i18n("Balance after"); m_html += "
"; } if (!schedule.isEmpty()) { m_html += "
 
\n"; QList::Iterator it; m_html += ""; m_html += QString("\n").arg(i18n("Future payments")); m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; // show all or the first 6 entries int cnt; cnt = (m_showAllSchedules) ? -1 : 6; bool needMoreLess = m_showAllSchedules; QDate lastDate = QDate::currentDate().addMonths(1); qSort(schedule); do { it = schedule.begin(); if (it == schedule.end()) break; // if the next due date is invalid (schedule is finished) // we remove it from the list QDate nextDate = (*it).nextDueDate(); if (!nextDate.isValid()) { schedule.erase(it); continue; } if (nextDate > lastDate) break; if (cnt == 0) { needMoreLess = true; break; } // in case we've shown the current recurrence as overdue, // we don't show it here again, but keep the schedule // as it might show up later in the list again if (!(*it).isOverdue()) { if (cnt > 0) --cnt; m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showPaymentEntry(*it); m_html += ""; // for single occurrence we have reported everything so we // better get out of here. if ((*it).occurrence() == Schedule::Occurrence::Once) { schedule.erase(it); continue; } } // if nextPayment returns an invalid date, setNextDueDate will // just skip it, resulting in a loop // we check the resulting date and erase the schedule if invalid if (!((*it).nextPayment((*it).nextDueDate())).isValid()) { schedule.erase(it); continue; } (*it).setNextDueDate((*it).nextPayment((*it).nextDueDate())); qSort(schedule); } while (1); if (needMoreLess) { m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); m_html += ""; m_html += ""; } m_html += "
%1
"; m_html += i18n("Date"); m_html += ""; m_html += i18n("Schedule"); m_html += ""; m_html += i18n("Account"); m_html += ""; m_html += i18n("Amount"); m_html += ""; m_html += i18n("Balance after"); m_html += "
"; if (m_showAllSchedules) { m_html += link(VIEW_SCHEDULE, QString("?mode=%1").arg("reduced")) + i18nc("Less...", "Show fewer schedules on the list") + linkend(); } else { m_html += link(VIEW_SCHEDULE, QString("?mode=%1").arg("full")) + i18nc("More...", "Show more schedules on the list") + linkend(); } m_html += "
"; } } m_html += "
"; } void showPaymentEntry(const MyMoneySchedule& sched, int cnt = 1) { QString tmp; MyMoneyFile* file = MyMoneyFile::instance(); try { MyMoneyAccount acc = sched.account(); if (!acc.id().isEmpty()) { MyMoneyTransaction t = sched.transaction(); // only show the entry, if it is still active if (!sched.isFinished()) { MyMoneySplit sp = t.splitByAccount(acc.id(), true); QString pathEnter = QPixmapToDataUri(Icons::get(Icon::KeyEnter).pixmap(QSize(16,16))); QString pathSkip = QPixmapToDataUri(Icons::get(Icon::MediaSkipForward).pixmap(QSize(16,16))); //show payment date tmp = QString("") + QLocale().toString(sched.adjustedNextDueDate(), QLocale::ShortFormat) + ""; if (!pathEnter.isEmpty()) tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=enter").arg(sched.id()), i18n("Enter schedule")) + QString("").arg(pathEnter) + linkend(); if (!pathSkip.isEmpty()) tmp += " " + link(VIEW_SCHEDULE, QString("?id=%1&mode=skip").arg(sched.id()), i18n("Skip schedule")) + QString("").arg(pathSkip) + linkend(); tmp += QString(" "); tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=edit").arg(sched.id()), i18n("Edit schedule")) + sched.name() + linkend(); //show quantity of payments overdue if any if (cnt > 1) tmp += i18np(" (%1 payment)", " (%1 payments)", cnt); //show account of the main split tmp += ""; tmp += QString(file->account(acc.id()).name()); //show amount of the schedule tmp += ""; const MyMoneySecurity& currency = MyMoneyFile::instance()->currency(acc.currencyId()); MyMoneyMoney payment = MyMoneyMoney(sp.value(t.commodity(), acc.currencyId()) * cnt); QString amount = MyMoneyUtils::formatMoney(payment, acc, currency); amount.replace(QChar(' '), " "); tmp += showColoredAmount(amount, payment.isNegative()); tmp += ""; //show balance after payments tmp += ""; QDate paymentDate = QDate(sched.adjustedNextDueDate()); MyMoneyMoney balanceAfter = forecastPaymentBalance(acc, payment, paymentDate); QString balance = MyMoneyUtils::formatMoney(balanceAfter, acc, currency); balance.replace(QChar(' '), " "); tmp += showColoredAmount(balance, balanceAfter.isNegative()); tmp += ""; // qDebug("paymentEntry = '%s'", tmp.toLatin1()); m_html += tmp; } } } catch (const MyMoneyException &e) { qDebug("Unable to display schedule entry: %s", e.what()); } } void showAccounts(paymentTypeE type, const QString& header) { MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); QList accounts; auto showClosedAccounts = KMyMoneySettings::showAllAccounts(); // get list of all accounts file->accountList(accounts); for (QList::Iterator it = accounts.begin(); it != accounts.end();) { bool removeAccount = false; if (!(*it).isClosed() || showClosedAccounts) { switch ((*it).accountType()) { case Account::Type::Expense: case Account::Type::Income: // never show a category account // Note: This might be different in a future version when // the homepage also shows category based information removeAccount = true; break; // Asset and Liability accounts are only shown if they // have the preferred flag set case Account::Type::Asset: case Account::Type::Liability: case Account::Type::Investment: // if preferred accounts are requested, then keep in list if ((*it).value("PreferredAccount") != "Yes" || (type & Preferred) == 0) { removeAccount = true; } break; // Check payment accounts. If payment and preferred is selected, // then always show them. If only payment is selected, then // show only if preferred flag is not set. case Account::Type::Checkings: case Account::Type::Savings: case Account::Type::Cash: case Account::Type::CreditCard: switch (type & (Payment | Preferred)) { case Payment: if ((*it).value("PreferredAccount") == "Yes") removeAccount = true; break; case Preferred: if ((*it).value("PreferredAccount") != "Yes") removeAccount = true; break; case Payment | Preferred: break; default: removeAccount = true; break; } break; // filter all accounts that are not used on homepage views default: removeAccount = true; break; } } else if ((*it).isClosed() || (*it).isInvest()) { // don't show if closed or a stock account removeAccount = true; } if (removeAccount) it = accounts.erase(it); else ++it; } if (!accounts.isEmpty()) { // sort the accounts by name qStableSort(accounts.begin(), accounts.end(), accountNameLess); QString tmp; int i = 0; tmp = "
" + header + "
\n
 
\n"; m_html += tmp; m_html += ""; m_html += ""; if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { QString pathStatusHeader = QPixmapToDataUri(Icons::get(Icon::Download).pixmap(QSize(16,16))); m_html += QString("").arg(pathStatusHeader); } m_html += ""; if (KMyMoneySettings::showCountOfUnmarkedTransactions()) m_html += QString(""); if (KMyMoneySettings::showCountOfClearedTransactions()) m_html += QString(""); if (KMyMoneySettings::showCountOfNotReconciledTransactions()) m_html += QString(""); if (KMyMoneySettings::showDateOfLastReconciliation()) m_html += QString("").arg(i18n("Last Reconciled")); m_html += ""; //only show limit info if user chose to do so if (KMyMoneySettings::showLimitInfo()) { m_html += ""; } m_html += ""; m_total = 0; QList::const_iterator it_m; for (it_m = accounts.constBegin(); it_m != accounts.constEnd(); ++it_m) { m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showAccountEntry(*it_m); m_html += ""; } m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); QString amount = m_total.formatMoney(file->baseCurrency().tradingSymbol(), prec); if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) m_html += ""; m_html += QString("").arg(i18n("Total")); if (KMyMoneySettings::showCountOfUnmarkedTransactions()) m_html += ""; if (KMyMoneySettings::showCountOfClearedTransactions()) m_html += ""; if (KMyMoneySettings::showCountOfNotReconciledTransactions()) m_html += ""; if (KMyMoneySettings::showDateOfLastReconciliation()) m_html += ""; m_html += QString("").arg(showColoredAmount(amount, m_total.isNegative())); m_html += "
"; m_html += i18n("Account"); m_html += "!MC!R%1"; m_html += i18n("Current Balance"); m_html += ""; m_html += i18n("To Minimum Balance / Maximum Credit"); m_html += "
%1%1
"; } } void showFavoriteReports() { QList reports = MyMoneyFile::instance()->reportList(); if (!reports.isEmpty()) { bool firstTime = 1; int row = 0; QList::const_iterator it_report = reports.constBegin(); while (it_report != reports.constEnd()) { if ((*it_report).isFavorite()) { if (firstTime) { m_html += QString("
%1
\n
 
\n").arg(i18n("Favorite Reports")); m_html += ""; m_html += ""; firstTime = false; } m_html += QString("") .arg(row++ & 0x01 ? "even" : "odd") .arg(link(VIEW_REPORTS, QString("?id=%1").arg((*it_report).id()))) .arg((*it_report).name()) .arg(linkend()) .arg((*it_report).comment()); } ++it_report; } if (!firstTime) m_html += "
"; m_html += i18n("Report"); m_html += ""; m_html += i18n("Comment"); m_html += "
%2%3%4%5
"; } } void showForecast() { MyMoneyFile* file = MyMoneyFile::instance(); QList accList; //if forecast has not been executed yet, do it. if (!m_forecast.isForecastDone()) doForecast(); accList = m_forecast.accountList(); if (accList.count() > 0) { // sort the accounts by name qStableSort(accList.begin(), accList.end(), accountNameLess); auto i = 0; auto colspan = 1; //get begin day auto beginDay = QDate::currentDate().daysTo(m_forecast.beginForecastDate()); //if begin day is today skip to next cycle if (beginDay == 0) beginDay = m_forecast.accountsCycle(); // Now output header m_html += QString("
%1
\n
 
\n").arg(i18n("%1 Day Forecast", m_forecast.forecastDays())); m_html += ""; m_html += ""; auto colWidth = 55 / (m_forecast.forecastDays() / m_forecast.accountsCycle()); for (i = 0; (i*m_forecast.accountsCycle() + beginDay) <= m_forecast.forecastDays(); ++i) { m_html += QString(""; colspan++; } m_html += ""; // Now output entries i = 0; QList::ConstIterator it_account; for (it_account = accList.constBegin(); it_account != accList.constEnd(); ++it_account) { //MyMoneyAccount acc = (*it_n); m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); m_html += QString(""; qint64 dropZero = -1; //account dropped below zero qint64 dropMinimum = -1; //account dropped below minimum balance QString minimumBalance = (*it_account).value("minimumBalance"); MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); MyMoneySecurity currency; MyMoneyMoney forecastBalance; //change account to deep currency if account is an investment if ((*it_account).isInvest()) { MyMoneySecurity underSecurity = file->security((*it_account).currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security((*it_account).currencyId()); } for (auto f = beginDay; f <= m_forecast.forecastDays(); f += m_forecast.accountsCycle()) { forecastBalance = m_forecast.forecastBalance(*it_account, QDate::currentDate().addDays(f)); QString amount; amount = MyMoneyUtils::formatMoney(forecastBalance, *it_account, currency); amount.replace(QChar(' '), " "); m_html += QString("").arg(showColoredAmount(amount, forecastBalance.isNegative())); } m_html += ""; //Check if the account is going to be below zero or below the minimal balance in the forecast period //Check if the account is going to be below minimal balance dropMinimum = m_forecast.daysToMinimumBalance(*it_account); //Check if the account is going to be below zero in the future dropZero = m_forecast.daysToZeroBalance(*it_account); // spit out possible warnings QString msg; // if a minimum balance has been specified, an appropriate warning will // only be shown, if the drop below 0 is on a different day or not present if (dropMinimum != -1 && !minBalance.isZero() && (dropMinimum < dropZero || dropZero == -1)) { switch (dropMinimum) { case 0: msg = i18n("The balance of %1 is below the minimum balance %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(minBalance, *it_account, currency)); msg = showColoredAmount(msg, true); break; default: msg = i18np("The balance of %2 will drop below the minimum balance %3 in %1 day.", "The balance of %2 will drop below the minimum balance %3 in %1 days.", dropMinimum - 1, (*it_account).name(), MyMoneyUtils::formatMoney(minBalance, *it_account, currency)); msg = showColoredAmount(msg, true); break; } if (!msg.isEmpty()) { m_html += QString("").arg(msg).arg(colspan); } } // a drop below zero is always shown msg.clear(); switch (dropZero) { case -1: break; case 0: if ((*it_account).accountGroup() == Account::Type::Asset) { msg = i18n("The balance of %1 is below %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); msg = showColoredAmount(msg, true); break; } if ((*it_account).accountGroup() == Account::Type::Liability) { msg = i18n("The balance of %1 is above %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); break; } break; default: if ((*it_account).accountGroup() == Account::Type::Asset) { msg = i18np("The balance of %2 will drop below %3 in %1 day.", "The balance of %2 will drop below %3 in %1 days.", dropZero, (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); msg = showColoredAmount(msg, true); break; } if ((*it_account).accountGroup() == Account::Type::Liability) { msg = i18np("The balance of %2 will raise above %3 in %1 day.", "The balance of %2 will raise above %3 in %1 days.", dropZero, (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); break; } } if (!msg.isEmpty()) { m_html += QString("").arg(msg).arg(colspan); } } m_html += "
"; m_html += i18n("Account"); m_html += "").arg(colWidth); m_html += i18ncp("Forecast days", "%1 day", "%1 days", i * m_forecast.accountsCycle() + beginDay); m_html += "
") + link(VIEW_LEDGER, QString("?id=%1").arg((*it_account).id())) + (*it_account).name() + linkend() + "").arg(colWidth); m_html += QString("%1
%1
%1
"; } } QString link(const QString& view, const QString& query, const QString& _title = QString()) const { QString titlePart; QString title(_title); if (!title.isEmpty()) titlePart = QString(" title=\"%1\"").arg(title.replace(QLatin1Char(' '), " ")); return QString("").arg(view, query, titlePart); } QString linkend() const { return QStringLiteral(""); } void showAssetsLiabilities() { QList accounts; QList::ConstIterator it; QList assets; QList liabilities; MyMoneyMoney netAssets; MyMoneyMoney netLiabilities; QString fontStart, fontEnd; MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); int i = 0; // get list of all accounts file->accountList(accounts); for (it = accounts.constBegin(); it != accounts.constEnd();) { if (!(*it).isClosed()) { switch ((*it).accountType()) { // group all assets into one list but make sure that investment accounts always show up case Account::Type::Investment: assets << *it; break; case Account::Type::Checkings: case Account::Type::Savings: case Account::Type::Cash: case Account::Type::Asset: case Account::Type::AssetLoan: // list account if it's the last in the hierarchy or has transactions in it if ((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { assets << *it; } break; // group the liabilities into the other case Account::Type::CreditCard: case Account::Type::Liability: case Account::Type::Loan: // list account if it's the last in the hierarchy or has transactions in it if ((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { liabilities << *it; } break; default: break; } } ++it; } //only do it if we have assets or liabilities account if (assets.count() > 0 || liabilities.count() > 0) { // sort the accounts by name qStableSort(assets.begin(), assets.end(), accountNameLess); qStableSort(liabilities.begin(), liabilities.end(), accountNameLess); QString statusHeader; if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { QString pathStatusHeader; pathStatusHeader = QPixmapToDataUri(Icons::get(Icon::ViewOutbox).pixmap(QSize(16,16))); statusHeader = QString("").arg(pathStatusHeader); } //print header m_html += "
" + i18n("Assets and Liabilities Summary") + "
\n
 
\n"; m_html += ""; //column titles m_html += ""; if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { m_html += ""; } m_html += ""; if (KMyMoneySettings::showCountOfUnmarkedTransactions()) m_html += ""; if (KMyMoneySettings::showCountOfClearedTransactions()) m_html += ""; if (KMyMoneySettings::showCountOfNotReconciledTransactions()) m_html += ""; if (KMyMoneySettings::showDateOfLastReconciliation()) m_html += ""; m_html += ""; //intermediate row to separate both columns m_html += ""; if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { m_html += ""; } m_html += ""; if (KMyMoneySettings::showCountOfUnmarkedTransactions()) m_html += ""; if (KMyMoneySettings::showCountOfClearedTransactions()) m_html += ""; if (KMyMoneySettings::showCountOfNotReconciledTransactions()) m_html += ""; if (KMyMoneySettings::showDateOfLastReconciliation()) m_html += ""; m_html += ""; QString placeHolder_Status, placeHolder_Counts; if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) placeHolder_Status = ""; if (KMyMoneySettings::showCountOfUnmarkedTransactions()) placeHolder_Counts = ""; if (KMyMoneySettings::showCountOfClearedTransactions()) placeHolder_Counts += ""; if (KMyMoneySettings::showCountOfNotReconciledTransactions()) placeHolder_Counts += ""; if (KMyMoneySettings::showDateOfLastReconciliation()) placeHolder_Counts += ""; //get asset and liability accounts QList::const_iterator asset_it = assets.constBegin(); QList::const_iterator liabilities_it = liabilities.constBegin(); for (; asset_it != assets.constEnd() || liabilities_it != liabilities.constEnd();) { m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); //write an asset account if we still have any if (asset_it != assets.constEnd()) { MyMoneyMoney value; //investment accounts consolidate the balance of its subaccounts if ((*asset_it).accountType() == Account::Type::Investment) { value = investmentBalance(*asset_it); } else { value = MyMoneyFile::instance()->balance((*asset_it).id(), QDate::currentDate()); } //calculate balance for foreign currency accounts if ((*asset_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*asset_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; baseValue = baseValue.convert(10000); netAssets += baseValue; } else { netAssets += value; } //show the account without minimum balance showAccountEntry(*asset_it, value, MyMoneyMoney(), false); ++asset_it; } else { //write a white space if we don't m_html += QString("%1%2").arg(placeHolder_Status).arg(placeHolder_Counts); } //leave the intermediate column empty m_html += ""; //write a liability account if (liabilities_it != liabilities.constEnd()) { MyMoneyMoney value; value = MyMoneyFile::instance()->balance((*liabilities_it).id(), QDate::currentDate()); //calculate balance if foreign currency if ((*liabilities_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*liabilities_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; baseValue = baseValue.convert(10000); netLiabilities += baseValue; } else { netLiabilities += value; } //show the account without minimum balance showAccountEntry(*liabilities_it, value, MyMoneyMoney(), false); ++liabilities_it; } else { //leave the space empty if we run out of liabilities m_html += QString("%1%2").arg(placeHolder_Status).arg(placeHolder_Counts); } m_html += ""; } //calculate net worth MyMoneyMoney netWorth = netAssets + netLiabilities; //format assets, liabilities and net worth QString amountAssets = netAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiabilities = netLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountNetWorth = netWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountAssets.replace(QChar(' '), " "); amountLiabilities.replace(QChar(' '), " "); amountNetWorth.replace(QChar(' '), " "); m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); //print total for assets m_html += QString("%1%3").arg(placeHolder_Status).arg(i18n("Total Assets")).arg(placeHolder_Counts).arg(showColoredAmount(amountAssets, netAssets.isNegative())); //leave the intermediate column empty m_html += ""; //print total liabilities m_html += QString("%1%3").arg(placeHolder_Status).arg(i18n("Total Liabilities")).arg(placeHolder_Counts).arg(showColoredAmount(amountLiabilities, netLiabilities.isNegative())); m_html += ""; //print net worth m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); m_html += QString("%1%2").arg(placeHolder_Status).arg(placeHolder_Counts); m_html += QString("%1%3").arg(placeHolder_Status).arg(i18n("Net Worth")).arg(placeHolder_Counts).arg(showColoredAmount(amountNetWorth, netWorth.isNegative())); m_html += ""; m_html += "
"; m_html += statusHeader; m_html += ""; m_html += i18n("Asset Accounts"); m_html += "!MC!R" + i18n("Last Reconciled") + ""; m_html += i18n("Current Balance"); m_html += ""; m_html += statusHeader; m_html += ""; m_html += i18n("Liability Accounts"); m_html += "!MC!R" + i18n("Last Reconciled") + ""; m_html += i18n("Current Balance"); m_html += "
%2%4%2%4
%2%4
"; m_html += "
"; } } void showBudget() { MyMoneyFile* file = MyMoneyFile::instance(); if (file->countBudgets()) { int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); bool isOverrun = false; int i = 0; //config report just like "Monthly Budgeted vs Actual MyMoneyReport reportCfg = MyMoneyReport( MyMoneyReport::eBudgetActual, MyMoneyReport::eMonths, TransactionFilter::Date::CurrentMonth, MyMoneyReport::eDetailAll, i18n("Monthly Budgeted vs. Actual"), i18n("Generated Report")); reportCfg.setBudget("Any", true); reports::PivotTable table(reportCfg); PivotGrid grid = table.grid(); //div header m_html += "
" + i18n("Budget") + "
\n
 
\n"; //display budget summary m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += QString(""); MyMoneyMoney totalBudgetValue = grid.m_total[eBudget].m_total; MyMoneyMoney totalActualValue = grid.m_total[eActual].m_total; MyMoneyMoney totalBudgetDiffValue = grid.m_total[eBudgetDiff].m_total; QString totalBudgetAmount = totalBudgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString totalActualAmount = totalActualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString totalBudgetDiffAmount = totalBudgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); m_html += QString("").arg(showColoredAmount(totalBudgetAmount, totalBudgetValue.isNegative())); m_html += QString("").arg(showColoredAmount(totalActualAmount, totalActualValue.isNegative())); m_html += QString("").arg(showColoredAmount(totalBudgetDiffAmount, totalBudgetDiffValue.isNegative())); m_html += ""; m_html += "
"; m_html += i18n("Current Month Summary"); m_html += "
"; m_html += i18n("Budgeted"); m_html += ""; m_html += i18n("Actual"); m_html += ""; m_html += i18n("Difference"); m_html += "
%1%1%1
"; //budget overrun m_html += "
 
\n"; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; PivotGrid::iterator it_outergroup = grid.begin(); while (it_outergroup != grid.end()) { i = 0; PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { //column number is 1 because the report includes only current month if (it_row.value()[eBudgetDiff].value(1).isNegative()) { //get report account to get the name later ReportAccount rowname = it_row.key(); //write the outergroup if it is the first row of outergroup being shown if (i == 0) { m_html += ""; m_html += QString("").arg(MyMoneyAccount::accountTypeToString(rowname.accountType())); m_html += ""; } m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); //get values from grid MyMoneyMoney actualValue = it_row.value()[eActual][1]; MyMoneyMoney budgetValue = it_row.value()[eBudget][1]; MyMoneyMoney budgetDiffValue = it_row.value()[eBudgetDiff][1]; //format amounts QString actualAmount = actualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString budgetAmount = budgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString budgetDiffAmount = budgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); //account name m_html += QString(""; //show amounts m_html += QString("").arg(showColoredAmount(budgetAmount, budgetValue.isNegative())); m_html += QString("").arg(showColoredAmount(actualAmount, actualValue.isNegative())); m_html += QString("").arg(showColoredAmount(budgetDiffAmount, budgetDiffValue.isNegative())); m_html += ""; //set the flag that there are overruns isOverrun = true; } ++it_row; } ++it_innergroup; } ++it_outergroup; } //if no negative differences are found, then inform that if (!isOverrun) { m_html += QString::fromLatin1("").arg(((i++ & 1) == 1) ? QLatin1String("even") : QLatin1String("odd")); m_html += QString::fromLatin1("").arg(i18n("No Budget Categories have been overrun")); m_html += ""; } m_html += "
"; m_html += i18n("Budget Overruns"); m_html += "
"; m_html += i18n("Account"); m_html += ""; m_html += i18n("Budgeted"); m_html += ""; m_html += i18n("Actual"); m_html += ""; m_html += i18n("Difference"); m_html += "
%1
") + link(VIEW_LEDGER, QString("?id=%1").arg(rowname.id())) + rowname.name() + linkend() + "%1%1%1
%1
"; } } void showCashFlowSummary() { MyMoneyTransactionFilter filter; MyMoneyMoney incomeValue; MyMoneyMoney expenseValue; MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); //set start and end of month dates QDate startOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), 1); QDate endOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), QDate::currentDate().daysInMonth()); //Add total income and expenses for this month //get transactions for current month filter.setDateFilter(startOfMonth, endOfMonth); filter.setReportAllSplits(false); QList transactions = file->transactionList(filter); //if no transaction then skip and print total in zero if (transactions.size() > 0) { //get all transactions for this month foreach (const auto transaction, transactions) { //get the splits for each transaction foreach (const auto split, transaction.splits()) { if (!split.shares().isZero()) { ReportAccount repSplitAcc = ReportAccount(split.accountId()); //only add if it is an income or expense if (repSplitAcc.isIncomeExpense()) { MyMoneyMoney value; //convert to base currency if necessary if (repSplitAcc.currencyId() != file->baseCurrency().id()) { MyMoneyMoney curPrice = repSplitAcc.baseCurrencyPrice(transaction.postDate()); value = (split.shares() * MyMoneyMoney::MINUS_ONE) * curPrice; value = value.convert(10000); } else { value = (split.shares() * MyMoneyMoney::MINUS_ONE); } //store depending on account type if (repSplitAcc.accountType() == Account::Type::Income) { incomeValue += value; } else { expenseValue += value; } } } } } } //format income and expenses QString amountIncome = incomeValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountExpense = expenseValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountIncome.replace(QChar(' '), " "); amountExpense.replace(QChar(' '), " "); //calculate schedules //Add all schedules for this month MyMoneyMoney scheduledIncome; MyMoneyMoney scheduledExpense; MyMoneyMoney scheduledLiquidTransfer; MyMoneyMoney scheduledOtherTransfer; //get overdues and schedules until the end of this month QList schedule = file->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), endOfMonth, false); //Remove the finished schedules QList::Iterator finished_it; for (finished_it = schedule.begin(); finished_it != schedule.end();) { if ((*finished_it).isFinished()) { finished_it = schedule.erase(finished_it); continue; } ++finished_it; } //add income and expenses QList::Iterator sched_it; for (sched_it = schedule.begin(); sched_it != schedule.end();) { QDate nextDate = (*sched_it).nextDueDate(); int cnt = 0; while (nextDate.isValid() && nextDate <= endOfMonth) { ++cnt; nextDate = (*sched_it).nextPayment(nextDate); // for single occurrence nextDate will not change, so we // better get out of here. if ((*sched_it).occurrence() == Schedule::Occurrence::Once) break; } MyMoneyAccount acc = (*sched_it).account(); if (!acc.id().isEmpty()) { MyMoneyTransaction transaction = (*sched_it).transaction(); // only show the entry, if it is still active MyMoneySplit sp = transaction.splitByAccount(acc.id(), true); // take care of the autoCalc stuff if ((*sched_it).type() == Schedule::Type::LoanPayment) { nextDate = (*sched_it).nextPayment((*sched_it).lastPayment()); //make sure we have all 'starting balances' so that the autocalc works QMap balanceMap; foreach (const auto split, transaction.splits()) { acc = file->account(split.accountId()); // collect all overdues on the first day QDate schedDate = nextDate; if (QDate::currentDate() >= nextDate) schedDate = QDate::currentDate().addDays(1); balanceMap[acc.id()] += file->balance(acc.id(), QDate::currentDate()); } KMyMoneyUtils::calculateAutoLoan(*sched_it, transaction, balanceMap); } //go through the splits and assign to liquid or other transfers const QList splits = transaction.splits(); QList::const_iterator split_it; for (split_it = splits.constBegin(); split_it != splits.constEnd(); ++split_it) { if ((*split_it).accountId() != acc.id()) { ReportAccount repSplitAcc = ReportAccount((*split_it).accountId()); //get the shares and multiply by the quantity of occurrences in the period MyMoneyMoney value = (*split_it).shares() * cnt; //convert to foreign currency if needed if (repSplitAcc.currencyId() != file->baseCurrency().id()) { MyMoneyMoney curPrice = repSplitAcc.baseCurrencyPrice(QDate::currentDate()); value = value * curPrice; value = value.convert(10000); } if ((repSplitAcc.isLiquidLiability() || repSplitAcc.isLiquidAsset()) && acc.accountGroup() != repSplitAcc.accountGroup()) { scheduledLiquidTransfer += value; } else if (repSplitAcc.isAssetLiability() && !repSplitAcc.isLiquidLiability() && !repSplitAcc.isLiquidAsset()) { scheduledOtherTransfer += value; } else if (repSplitAcc.isIncomeExpense()) { //income and expenses are stored as negative values if (repSplitAcc.accountType() == Account::Type::Income) scheduledIncome -= value; if (repSplitAcc.accountType() == Account::Type::Expense) scheduledExpense -= value; } } } } ++sched_it; } //format the currency strings QString amountScheduledIncome = scheduledIncome.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledExpense = scheduledExpense.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledLiquidTransfer = scheduledLiquidTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledOtherTransfer = scheduledOtherTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountScheduledIncome.replace(QChar(' '), " "); amountScheduledExpense.replace(QChar(' '), " "); amountScheduledLiquidTransfer.replace(QChar(' '), " "); amountScheduledOtherTransfer.replace(QChar(' '), " "); //get liquid assets and liabilities QList accounts; QList::const_iterator account_it; MyMoneyMoney liquidAssets; MyMoneyMoney liquidLiabilities; // get list of all accounts file->accountList(accounts); for (account_it = accounts.constBegin(); account_it != accounts.constEnd();) { if (!(*account_it).isClosed()) { switch ((*account_it).accountType()) { //group all assets into one list case Account::Type::Checkings: case Account::Type::Savings: case Account::Type::Cash: { MyMoneyMoney value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); //calculate balance for foreign currency accounts if ((*account_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*account_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; liquidAssets += baseValue; liquidAssets = liquidAssets.convert(10000); } else { liquidAssets += value; } break; } //group the liabilities into the other case Account::Type::CreditCard: { MyMoneyMoney value; value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); //calculate balance if foreign currency if ((*account_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*account_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; liquidLiabilities += baseValue; liquidLiabilities = liquidLiabilities.convert(10000); } else { liquidLiabilities += value; } break; } default: break; } } ++account_it; } //calculate net worth MyMoneyMoney liquidWorth = liquidAssets + liquidLiabilities; //format assets, liabilities and net worth QString amountLiquidAssets = liquidAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiquidLiabilities = liquidLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiquidWorth = liquidWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountLiquidAssets.replace(QChar(' '), " "); amountLiquidLiabilities.replace(QChar(' '), " "); amountLiquidWorth.replace(QChar(' '), " "); //show the summary m_html += "
" + i18n("Cash Flow Summary") + "
\n
 
\n"; //print header m_html += ""; //income and expense title m_html += ""; m_html += ""; //column titles m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; //add row with banding m_html += QString(""); //print current income m_html += QString("").arg(showColoredAmount(amountIncome, incomeValue.isNegative())); //print the scheduled income m_html += QString("").arg(showColoredAmount(amountScheduledIncome, scheduledIncome.isNegative())); //print current expenses m_html += QString("").arg(showColoredAmount(amountExpense, expenseValue.isNegative())); //print the scheduled expenses m_html += QString("").arg(showColoredAmount(amountScheduledExpense, scheduledExpense.isNegative())); m_html += ""; m_html += "
"; m_html += i18n("Income and Expenses of Current Month"); m_html += "
"; m_html += i18n("Income"); m_html += ""; m_html += i18n("Scheduled Income"); m_html += ""; m_html += i18n("Expenses"); m_html += ""; m_html += i18n("Scheduled Expenses"); m_html += "
%2%2%2%2
"; //print header of assets and liabilities m_html += "
 
\n"; m_html += ""; //assets and liabilities title m_html += ""; m_html += ""; //column titles m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; //add row with banding m_html += QString(""); //print current liquid assets m_html += QString("").arg(showColoredAmount(amountLiquidAssets, liquidAssets.isNegative())); //print the scheduled transfers m_html += QString("").arg(showColoredAmount(amountScheduledLiquidTransfer, scheduledLiquidTransfer.isNegative())); //print current liabilities m_html += QString("").arg(showColoredAmount(amountLiquidLiabilities, liquidLiabilities.isNegative())); //print the scheduled transfers m_html += QString("").arg(showColoredAmount(amountScheduledOtherTransfer, scheduledOtherTransfer.isNegative())); m_html += ""; m_html += "
"; m_html += i18n("Liquid Assets and Liabilities"); m_html += "
"; m_html += i18n("Liquid Assets"); m_html += ""; m_html += i18n("Transfers to Liquid Liabilities"); m_html += ""; m_html += i18n("Liquid Liabilities"); m_html += ""; m_html += i18n("Other Transfers"); m_html += "
%2%2%2%2
"; //final conclusion MyMoneyMoney profitValue = incomeValue + expenseValue + scheduledIncome + scheduledExpense; MyMoneyMoney expectedAsset = liquidAssets + scheduledIncome + scheduledExpense + scheduledLiquidTransfer + scheduledOtherTransfer; MyMoneyMoney expectedLiabilities = liquidLiabilities + scheduledLiquidTransfer; QString amountExpectedAsset = expectedAsset.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountExpectedLiabilities = expectedLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountProfit = profitValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountProfit.replace(QChar(' '), " "); amountExpectedAsset.replace(QChar(' '), " "); amountExpectedLiabilities.replace(QChar(' '), " "); //print header of cash flow status m_html += "
 
\n"; m_html += ""; //income and expense title m_html += ""; m_html += ""; //column titles m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; m_html += ""; //add row with banding m_html += QString(""); m_html += ""; //print expected assets m_html += QString("").arg(showColoredAmount(amountExpectedAsset, expectedAsset.isNegative())); //print expected liabilities m_html += QString("").arg(showColoredAmount(amountExpectedLiabilities, expectedLiabilities.isNegative())); //print expected profit m_html += QString("").arg(showColoredAmount(amountProfit, profitValue.isNegative())); m_html += ""; m_html += "
"; m_html += i18n("Cash Flow Status"); m_html += "
 "; m_html += i18n("Expected Liquid Assets"); m_html += ""; m_html += i18n("Expected Liquid Liabilities"); m_html += ""; m_html += i18n("Expected Profit/Loss"); m_html += "
 %2%2%2
"; m_html += "
"; } KHomeView *q_ptr; /** * daily balances of an account */ typedef QMap dailyBalances; #ifdef ENABLE_WEBENGINE QWebEngineView *m_view; #else KWebView *m_view; #endif QString m_html; bool m_showAllSchedules; bool m_needLoad; MyMoneyForecast m_forecast; MyMoneyMoney m_total; /** * Hold the last valid size of the net worth graph * for the times when the needed size can't be computed. */ QSize m_netWorthGraphLastValidSize; /** * daily forecast balance of accounts */ QMap m_accountList; QPrinter *m_currentPrinter; }; #endif diff --git a/kmymoney/views/kmymoneyview.cpp b/kmymoney/views/kmymoneyview.cpp index bb6c7aadd..1d87803d2 100644 --- a/kmymoney/views/kmymoneyview.cpp +++ b/kmymoney/views/kmymoneyview.cpp @@ -1,860 +1,854 @@ /*************************************************************************** kmymoneyview.cpp ------------------- copyright : (C) 2000-2001 by Michael Edwardes 2004 by Thomas Baumgart (C) 2017, 2018 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include "kmymoneyview.h" // ---------------------------------------------------------------------------- // Std Includes #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #ifdef ENABLE_UNFINISHEDFEATURES #include "simpleledgerview.h" #endif #include "kmymoneysettings.h" #include "kmymoneytitlelabel.h" #include "kcurrencyeditdlg.h" #include "mymoneyexception.h" #include "khomeview.h" #include "kaccountsview.h" #include "kcategoriesview.h" #include "kinstitutionsview.h" #include "kpayeesview.h" #include "ktagsview.h" #include "kscheduledview.h" #include "kgloballedgerview.h" #include "kinvestmentview.h" #include "models.h" #include "accountsmodel.h" #include "equitiesmodel.h" #include "securitiesmodel.h" #include "icons.h" #include "amountedit.h" +#include "onlinejobadministration.h" #include "kmymoneyaccounttreeview.h" #include "accountsviewproxymodel.h" #include "mymoneyprice.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneyaccount.h" #include "mymoneyinstitution.h" +#include "mymoneytag.h" #include "kmymoneyedit.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneyreport.h" #include "kmymoneyplugin.h" #include "mymoneyenums.h" #include "menuenums.h" using namespace Icons; using namespace eMyMoney; typedef void(KMyMoneyView::*KMyMoneyViewFunc)(); KMyMoneyView::KMyMoneyView() : KPageWidget(nullptr), - m_header(0), - m_storagePlugins(nullptr) + m_header(0) { // this is a workaround for the bug in KPageWidget that causes the header to be shown // for a short while during page switch which causes a kind of bouncing of the page's // content and if the page's content is at it's minimum size then during a page switch // the main window's size is also increased to fit the header that is shown for a sort // period - reading the code in kpagewidget.cpp we know that the header should be at (1,1) // in a grid layout so if we find it there remove it for good to avoid the described issues QGridLayout* gridLayout = qobject_cast(layout()); if (gridLayout) { QLayoutItem* headerItem = gridLayout->itemAtPosition(1, 1); // make sure that we remove only the header - we avoid surprises if the header is not at (1,1) in the layout if (headerItem && qobject_cast(headerItem->widget()) != NULL) { gridLayout->removeItem(headerItem); // after we remove the KPageWidget standard header replace it with our own title label m_header = new KMyMoneyTitleLabel(this); m_header->setObjectName("titleLabel"); m_header->setMinimumSize(QSize(100, 30)); m_header->setRightImageFile("pics/titlelabel_background.png"); m_header->setVisible(KMyMoneySettings::showTitleBar()); gridLayout->addWidget(m_header, 1, 1); } } // newStorage(); m_model = new KPageWidgetModel(this); // cannot be parentless, otherwise segfaults at exit viewBases[View::Home] = new KHomeView; viewBases[View::Institutions] = new KInstitutionsView; viewBases[View::Accounts] = new KAccountsView; viewBases[View::Schedules] = new KScheduledView; viewBases[View::Categories] = new KCategoriesView; viewBases[View::Tags] = new KTagsView; viewBases[View::Payees] = new KPayeesView; viewBases[View::Ledgers] = new KGlobalLedgerView; viewBases[View::Investments] = new KInvestmentView; #ifdef ENABLE_UNFINISHEDFEATURES viewBases[View::NewLedgers] = new SimpleLedgerView; #endif struct viewInfo { View id; QString name; Icon icon; }; const QVector viewsInfo { {View::Home, i18n("Home"), Icon::ViewHome}, {View::Institutions, i18n("Institutions"), Icon::ViewInstitutions}, {View::Accounts, i18n("Accounts"), Icon::ViewAccounts}, {View::Schedules, i18n("Scheduled\ntransactions"), Icon::ViewSchedules}, {View::Categories, i18n("Categories"), Icon::ViewCategories}, {View::Tags, i18n("Tags"), Icon::ViewTags}, {View::Payees, i18n("Payees"), Icon::ViewPayees}, {View::Ledgers, i18n("Ledgers"), Icon::ViewLedgers}, {View::Investments, i18n("Investments"), Icon::ViewInvestment}, #ifdef ENABLE_UNFINISHEDFEATURES {View::NewLedgers, i18n("New ledger"), Icon::DocumentProperties}, #endif }; for (const viewInfo& view : viewsInfo) { /* There is a bug in static int layoutText(QTextLayout *layout, int maxWidth) from kpageview_p.cpp from kwidgetsaddons. The method doesn't break strings that are too long. Following line workarounds this by using LINE SEPARATOR character which is accepted by QTextLayout::createLine().*/ viewFrames[view.id] = m_model->addPage(viewBases[view.id], QString(view.name).replace('\n', QString::fromLocal8Bit("\xe2\x80\xa8"))); viewFrames[view.id]->setIcon(Icons::get(view.icon)); connect(viewBases[view.id], &KMyMoneyViewBase::selectByObject, this, &KMyMoneyView::slotSelectByObject); connect(viewBases[view.id], &KMyMoneyViewBase::selectByVariant, this, &KMyMoneyView::slotSelectByVariant); connect(viewBases[view.id], &KMyMoneyViewBase::customActionRequested, this, &KMyMoneyView::slotCustomActionRequested); } connect(Models::instance()->accountsModel(), &AccountsModel::netWorthChanged, this, &KMyMoneyView::slotSelectByVariant); connect(Models::instance()->institutionsModel(), &AccountsModel::netWorthChanged, this, &KMyMoneyView::slotSelectByVariant); connect(Models::instance()->institutionsModel(), &AccountsModel::profitChanged, this, &KMyMoneyView::slotSelectByVariant); //set the model setModel(m_model); setCurrentPage(viewFrames[View::Home]); connect(this, SIGNAL(currentPageChanged(QModelIndex,QModelIndex)), this, SLOT(slotCurrentPageChanged(QModelIndex,QModelIndex))); updateViewType(); } KMyMoneyView::~KMyMoneyView() { } void KMyMoneyView::slotFileOpened() { if (viewBases.contains(View::OnlineJobOutbox)) viewBases[View::OnlineJobOutbox]->executeCustomAction(eView::Action::InitializeAfterFileOpen); #ifdef ENABLE_UNFINISHEDFEATURES static_cast(viewBases[View::NewLedgers])->openFavoriteLedgers(); #endif switchToDefaultView(); + slotObjectSelected(MyMoneyAccount()); // in order to enable update all accounts on file reload } void KMyMoneyView::slotFileClosed() { + slotShowHomePage(); + if (viewBases.contains(View::Home)) + viewBases[View::Home]->executeCustomAction(eView::Action::CleanupBeforeFileClose); if (viewBases.contains(View::Reports)) viewBases[View::Reports]->executeCustomAction(eView::Action::CleanupBeforeFileClose); if (viewBases.contains(View::OnlineJobOutbox)) viewBases[View::OnlineJobOutbox]->executeCustomAction(eView::Action::CleanupBeforeFileClose); #ifdef ENABLE_UNFINISHEDFEATURES static_cast(viewBases[View::NewLedgers])->closeLedgers(); #endif - slotShowHomePage(); + + pActions[eMenu::Action::Print]->setEnabled(false); + pActions[eMenu::Action::AccountCreditTransfer]->setEnabled(false); + pActions[eMenu::Action::UpdateAllAccounts]->setEnabled(false); } void KMyMoneyView::slotShowHomePage() { showPageAndFocus(View::Home); } void KMyMoneyView::slotShowInstitutionsPage() { showPageAndFocus(View::Institutions); } void KMyMoneyView::slotShowAccountsPage() { showPageAndFocus(View::Accounts); } void KMyMoneyView::slotShowSchedulesPage() { showPageAndFocus(View::Schedules); } void KMyMoneyView::slotShowCategoriesPage() { showPageAndFocus(View::Categories); } void KMyMoneyView::slotShowTagsPage() { showPageAndFocus(View::Tags); } void KMyMoneyView::slotShowPayeesPage() { showPageAndFocus(View::Payees); } void KMyMoneyView::slotShowLedgersPage() { showPageAndFocus(View::Ledgers); } void KMyMoneyView::slotShowInvestmentsPage() { showPageAndFocus(View::Investments); } void KMyMoneyView::slotShowReportsPage() { showPageAndFocus(View::Reports); } void KMyMoneyView::slotShowBudgetPage() { showPageAndFocus(View::Budget); } void KMyMoneyView::slotShowForecastPage() { showPageAndFocus(View::Forecast); } void KMyMoneyView::slotShowOutboxPage() { showPageAndFocus(View::OnlineJobOutbox); } void KMyMoneyView::showTitleBar(bool show) { if (m_header) m_header->setVisible(show); } void KMyMoneyView::updateViewType() { // set the face type KPageView::FaceType faceType = KPageView::List; switch (KMyMoneySettings::viewType()) { case 0: faceType = KPageView::List; break; case 1: faceType = KPageView::Tree; break; case 2: faceType = KPageView::Tabbed; break; } if (faceType != KMyMoneyView::faceType()) { setFaceType(faceType); if (faceType == KPageView::Tree) { QList views = findChildren(); foreach (QTreeView * view, views) { if (view && (view->parent() == this)) { view->setRootIsDecorated(false); break; } } } } } void KMyMoneyView::slotAccountTreeViewChanged(const eAccountsModel::Column column, const bool show) { QVector proxyModels { static_cast(viewBases[View::Institutions])->getProxyModel(), static_cast(viewBases[View::Accounts])->getProxyModel(), static_cast(viewBases[View::Categories])->getProxyModel() }; if (viewBases.contains(View::Budget)) proxyModels.append(static_cast(viewBases[View::Budget])->getProxyModel()); for (auto i = proxyModels.count() - 1; i >= 0; --i) { // weed out unloaded views if (!proxyModels.at(i)) proxyModels.removeAt(i); } QString question; if (show) question = i18n("Do you want to show %1 column on every loaded view?", AccountsModel::getHeaderName(column)); else question = i18n("Do you want to hide %1 column on every loaded view?", AccountsModel::getHeaderName(column)); if (proxyModels.count() == 1 || // no need to ask what to do with other views because they aren't loaded KMessageBox::questionYesNo(this, question, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("ShowColumnOnEveryView")) == KMessageBox::Yes) { Models::instance()->accountsModel()->setColumnVisibility(column, show); Models::instance()->institutionsModel()->setColumnVisibility(column, show); foreach(AccountsViewProxyModel *proxyModel, proxyModels) { if (!proxyModel) continue; proxyModel->setColumnVisibility(column, show); proxyModel->invalidate(); } } else if(show) { // in case we need to show it, we have to make sure to set the visibility // in the base model as well. Otherwise, we don't see the column through the proxy model Models::instance()->accountsModel()->setColumnVisibility(column, show); Models::instance()->institutionsModel()->setColumnVisibility(column, show); } } void KMyMoneyView::setOnlinePlugins(QMap& plugins) { if (viewBases.contains(View::Accounts)) viewBases[View::Accounts]->slotSelectByVariant(QVariantList {QVariant::fromValue(static_cast(&plugins))}, eView::Intent::SetOnlinePlugins); if (viewBases.contains(View::OnlineJobOutbox)) viewBases[View::OnlineJobOutbox]->slotSelectByVariant(QVariantList {QVariant::fromValue(static_cast(&plugins))}, eView::Intent::SetOnlinePlugins); } -void KMyMoneyView::setStoragePlugins(QMap& plugins) -{ - m_storagePlugins = &plugins; -} - eDialogs::ScheduleResultCode KMyMoneyView::enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys) { return static_cast(viewBases[View::Schedules])->enterSchedule(schedule, autoEnter, extendedKeys); } void KMyMoneyView::addView(KMyMoneyViewBase* view, const QString& name, View idView) { auto isViewInserted = false; for (auto i = (int)idView; i < (int)View::None; ++i) { if (viewFrames.contains((View)i)) { viewFrames[idView] = m_model->insertPage(viewFrames[(View)i],view, name); isViewInserted = true; break; } } if (!isViewInserted) viewFrames[idView] = m_model->addPage(view, name); viewBases[idView] = view; connect(viewBases[idView], &KMyMoneyViewBase::selectByObject, this, &KMyMoneyView::slotSelectByObject); connect(viewBases[idView], &KMyMoneyViewBase::selectByVariant, this, &KMyMoneyView::slotSelectByVariant); connect(viewBases[idView], &KMyMoneyViewBase::customActionRequested, this, &KMyMoneyView::slotCustomActionRequested); auto icon = Icon::ViewForecast; switch (idView) { case View::Reports: icon = Icon::ViewReports; break; case View::Budget: icon = Icon::ViewBudgets; break; case View::Forecast: icon = Icon::ViewForecast; break; case View::OnlineJobOutbox: icon = Icon::ViewOutbox; break; default: break; } viewFrames[idView]->setIcon(Icons::get(icon)); } void KMyMoneyView::removeView(View idView) { if (!viewBases.contains(idView)) return; disconnect(viewBases[idView], &KMyMoneyViewBase::selectByObject, this, &KMyMoneyView::slotSelectByObject); disconnect(viewBases[idView], &KMyMoneyViewBase::selectByVariant, this, &KMyMoneyView::slotSelectByVariant); disconnect(viewBases[idView], &KMyMoneyViewBase::customActionRequested, this, &KMyMoneyView::slotCustomActionRequested); m_model->removePage(viewFrames[idView]); viewFrames.remove(idView); viewBases.remove(idView); } QHash KMyMoneyView::actionsToBeConnected() { using namespace eMenu; // add fast switching of main views through Ctrl + NUM_X struct pageInfo { Action view; KMyMoneyViewFunc callback; QString text; QKeySequence shortcut = QKeySequence(); }; const QVector pageInfos { {Action::ShowHomeView, &KMyMoneyView::slotShowHomePage, i18n("Show home page"), Qt::CTRL + Qt::Key_1}, {Action::ShowInstitutionsView, &KMyMoneyView::slotShowInstitutionsPage, i18n("Show institutions page"), Qt::CTRL + Qt::Key_2}, {Action::ShowAccountsView, &KMyMoneyView::slotShowAccountsPage, i18n("Show accounts page"), Qt::CTRL + Qt::Key_3}, {Action::ShowSchedulesView, &KMyMoneyView::slotShowSchedulesPage, i18n("Show scheduled transactions page"), Qt::CTRL + Qt::Key_4}, {Action::ShowCategoriesView, &KMyMoneyView::slotShowCategoriesPage, i18n("Show categories page"), Qt::CTRL + Qt::Key_5}, {Action::ShowTagsView, &KMyMoneyView::slotShowTagsPage, i18n("Show tags page"), }, {Action::ShowPayeesView, &KMyMoneyView::slotShowPayeesPage, i18n("Show payees page"), Qt::CTRL + Qt::Key_6}, {Action::ShowLedgersView, &KMyMoneyView::slotShowLedgersPage, i18n("Show ledgers page"), Qt::CTRL + Qt::Key_7}, {Action::ShowInvestmentsView, &KMyMoneyView::slotShowInvestmentsPage, i18n("Show investments page"), Qt::CTRL + Qt::Key_8}, {Action::ShowReportsView, &KMyMoneyView::slotShowReportsPage, i18n("Show reports page"), Qt::CTRL + Qt::Key_9}, {Action::ShowBudgetView, &KMyMoneyView::slotShowBudgetPage, i18n("Show budget page"), }, {Action::ShowForecastView, &KMyMoneyView::slotShowForecastPage, i18n("Show forecast page"), }, {Action::ShowOnlineJobOutboxView, &KMyMoneyView::slotShowOutboxPage, i18n("Show outbox page") } }; QHash lutActions; auto pageCount = 0; for (const pageInfo& info : pageInfos) { auto a = new QAction(this); // KActionCollection::addAction by name sets object name anyways, // so, as better alternative, set it here right from the start a->setObjectName(QString::fromLatin1("ShowPage%1").arg(QString::number(pageCount++))); a->setText(info.text); connect(a, &QAction::triggered, this, info.callback); lutActions.insert(info.view, a); // store QAction's pointer for later processing if (!info.shortcut.isEmpty()) a->setShortcut(info.shortcut); } return lutActions; } bool KMyMoneyView::showPageHeader() const { return false; } void KMyMoneyView::showPageAndFocus(View idView) { if (viewFrames.contains(idView)) { showPage(idView); viewBases[idView]->executeCustomAction(eView::Action::SetDefaultFocus); } } void KMyMoneyView::showPage(View idView) { if (!viewFrames.contains(idView) || currentPage() == viewFrames[idView]) return; setCurrentPage(viewFrames[idView]); - pActions[eMenu::Action::Print]->setEnabled(canPrint()); - emit aboutToChangeView(); + resetViewSelection(); } bool KMyMoneyView::canPrint() { - return ((viewFrames.contains(View::Reports) && viewFrames[View::Reports] == currentPage()) || - (viewFrames.contains(View::Home) && viewFrames[View::Home] == currentPage()) + return (MyMoneyFile::instance()->storageAttached() && + ((viewFrames.contains(View::Reports) && viewFrames[View::Reports] == currentPage()) || + (viewFrames.contains(View::Home) && viewFrames[View::Home] == currentPage())) ); } void KMyMoneyView::enableViewsIfFileOpen(bool fileOpen) { // call set enabled only if the state differs to avoid widgets 'bouncing on the screen' while doing this for (auto i = (int)View::Home; i < (int)View::None; ++i) if (viewFrames.contains(View(i))) if (viewFrames[View(i)]->isEnabled() != fileOpen) viewFrames[View(i)]->setEnabled(fileOpen); emit viewStateChanged(fileOpen); } void KMyMoneyView::switchToDefaultView() { const auto idView = KMyMoneySettings::startLastViewSelected() ? static_cast(KMyMoneySettings::lastViewSelected()) : View::Home; // if we currently see a different page, then select the right one if (viewFrames.contains(idView) && viewFrames[idView] != currentPage()) showPage(idView); } void KMyMoneyView::slotPayeeSelected(const QString& payee, const QString& account, const QString& transaction) { showPage(View::Payees); static_cast(viewBases[View::Payees])->slotSelectPayeeAndTransaction(payee, account, transaction); } void KMyMoneyView::slotTagSelected(const QString& tag, const QString& account, const QString& transaction) { showPage(View::Tags); static_cast(viewBases[View::Tags])->slotSelectTagAndTransaction(tag, account, transaction); } void KMyMoneyView::finishReconciliation(const MyMoneyAccount& /* account */) { Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); static_cast(viewBases[View::Ledgers])->slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); } -void KMyMoneyView::slotSetBaseCurrency(const MyMoneySecurity& baseCurrency) -{ - if (!baseCurrency.id().isEmpty()) { - QString baseId; - try { - baseId = MyMoneyFile::instance()->baseCurrency().id(); - } catch (const MyMoneyException &e) { - qDebug("%s", e.what()); - } - - if (baseCurrency.id() != baseId) { - MyMoneyFileTransaction ft; - try { - MyMoneyFile::instance()->setBaseCurrency(baseCurrency); - ft.commit(); - } catch (const MyMoneyException &e) { - KMessageBox::sorry(this, i18n("Cannot set %1 as base currency: %2", baseCurrency.name(), QString::fromLatin1(e.what())), i18n("Set base currency")); - } - } - AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); - KMyMoneyEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); - } -} - void KMyMoneyView::viewAccountList(const QString& /*selectAccount*/) { if (viewFrames[View::Accounts] != currentPage()) showPage(View::Accounts); viewBases[View::Accounts]->show(); } void KMyMoneyView::slotRefreshViews() { showTitleBar(KMyMoneySettings::showTitleBar()); for (auto i = (int)View::Home; i < (int)View::None; ++i) if (viewBases.contains(View(i))) viewBases[View(i)]->executeCustomAction(eView::Action::Refresh); viewBases[View::Payees]->executeCustomAction(eView::Action::ClosePayeeIdentifierSource); } void KMyMoneyView::slotShowTransactionDetail(bool detailed) { KMyMoneySettings::setShowRegisterDetailed(detailed); slotRefreshViews(); } void KMyMoneyView::slotCurrentPageChanged(const QModelIndex current, const QModelIndex previous) { // set the current page's title in the header if (m_header) m_header->setText(m_model->data(current, KPageModel::HeaderRole).toString()); + const auto view = currentPage(); // remember the selected view if there is a real change if (previous.isValid()) { - const KPageWidgetItem* view = currentPage(); QHash::const_iterator it; for(it = viewFrames.cbegin(); it != viewFrames.cend(); ++it) { if ((*it) == view) { emit viewActivated(it.key()); break; } } } + + if (viewBases.contains(View::Ledgers) && view != viewFrames.value(View::Ledgers)) + viewBases[View::Ledgers]->executeCustomAction(eView::Action::DisableViewDepenedendActions); + + pActions[eMenu::Action::Print]->setEnabled(canPrint()); + pActions[eMenu::Action::AccountCreditTransfer]->setEnabled(onlineJobAdministration::instance()->canSendCreditTransfer()); } void KMyMoneyView::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) { // Add the schedule only if one exists // // Remember to modify the first split to reference the newly created account if (!newSchedule.name().isEmpty()) { MyMoneyFileTransaction ft; try { // We assume at least 2 splits in the transaction MyMoneyTransaction t = newSchedule.transaction(); if (t.splitCount() < 2) { throw MYMONEYEXCEPTION_CSTRING("Transaction for schedule has less than 2 splits!"); } // now search the split that does not have an account reference // and set it up to be the one of the account we just added // to the account pool. Note: the schedule code used to leave // this always the first split, but the loan code leaves it as // the second one. So I thought, searching is a good alternative .... foreach (const auto split, t.splits()) { if (split.accountId().isEmpty()) { MyMoneySplit s = split; s.setAccountId(newAccount.id()); t.modifySplit(s); break; } } newSchedule.setTransaction(t); MyMoneyFile::instance()->addSchedule(newSchedule); // in case of a loan account, we keep a reference to this // schedule in the account if (newAccount.isLoan()) { newAccount.setValue("schedule", newSchedule.id()); MyMoneyFile::instance()->modifyAccount(newAccount); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add schedule: %1", QString::fromLatin1(e.what()))); } } } void KMyMoneyView::slotPrintView() { if (viewFrames.contains(View::Reports) && viewFrames[View::Reports] == currentPage()) viewBases[View::Reports]->executeCustomAction(eView::Action::Print); else if (viewFrames.contains(View::Home) && viewFrames[View::Home] == currentPage()) viewBases[View::Home]->executeCustomAction(eView::Action::Print); } -void KMyMoneyView::resetViewSelection(const View) +void KMyMoneyView::resetViewSelection() { - emit aboutToChangeView(); + if (!MyMoneyFile::instance()->storageAttached()) + return; + slotObjectSelected(MyMoneyAccount()); + slotObjectSelected(MyMoneyInstitution()); + slotObjectSelected(MyMoneySchedule()); + slotObjectSelected(MyMoneyTag()); + slotSelectByVariant(QVariantList {QVariant::fromValue(KMyMoneyRegister::SelectedTransactions())}, eView::Intent::SelectRegisterTransactions); } void KMyMoneyView::slotOpenObjectRequested(const MyMoneyObject& obj) { if (typeid(obj) == typeid(MyMoneyAccount)) { const auto& acc = static_cast(obj); // check if we can open this account // currently it make's sense for asset and liability accounts if (!MyMoneyFile::instance()->isStandardAccount(acc.id())) if (viewBases.contains(View::Ledgers)) viewBases[View::Ledgers]->slotSelectByVariant(QVariantList {QVariant(acc.id()), QVariant(QString()) }, eView::Intent::ShowTransaction ); } else if (typeid(obj) == typeid(MyMoneyInstitution)) { // const auto& inst = static_cast(obj); if (viewBases.contains(View::Institutions)) viewBases[View::Institutions]->executeCustomAction(eView::Action::EditInstitution); } else if (typeid(obj) == typeid(MyMoneySchedule)) { if (viewBases.contains(View::Schedules)) viewBases[View::Schedules]->executeCustomAction(eView::Action::EditSchedule); } else if (typeid(obj) == typeid(MyMoneyReport)) { // const auto& rep = static_cast(obj); showPage(View::Reports); if (viewBases.contains(View::Reports)) viewBases[View::Reports]->slotSelectByObject(obj, eView::Intent::OpenObject); } } void KMyMoneyView::slotSelectByObject(const MyMoneyObject& obj, eView::Intent intent) { switch (intent) { case eView::Intent::None: slotObjectSelected(obj); break; case eView::Intent::SynchronizeAccountInInvestmentView: if (viewBases.contains(View::Investments)) viewBases[View::Investments]->slotSelectByObject(obj, intent); break; case eView::Intent::SynchronizeAccountInLedgersView: if (viewBases.contains(View::Ledgers)) viewBases[View::Ledgers]->slotSelectByObject(obj, intent); break; case eView::Intent::OpenObject: slotOpenObjectRequested(obj); break; case eView::Intent::OpenContextMenu: slotContextMenuRequested(obj); break; case eView::Intent::StartEnteringOverdueScheduledTransactions: if (viewBases.contains(View::Schedules)) viewBases[View::Schedules]->slotSelectByObject(obj, intent); break; case eView::Intent::FinishEnteringOverdueScheduledTransactions: if (viewBases.contains(View::Ledgers)) { showPage(View::Ledgers); viewBases[View::Ledgers]->slotSelectByObject(obj, intent); } break; default: break; } } void KMyMoneyView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent) { switch(intent) { case eView::Intent::ReportProgress: if (variant.count() == 2) emit statusProgress(variant.at(0).toInt(), variant.at(1).toInt()); break; case eView::Intent::ReportProgressMessage: if (variant.count() == 1) emit statusMsg(variant.first().toString()); break; case eView::Intent::UpdateNetWorth: if (viewBases.contains(View::Accounts)) viewBases[View::Accounts]->slotSelectByVariant(variant, intent); if (viewBases.contains(View::Institutions)) viewBases[View::Institutions]->slotSelectByVariant(variant, intent); break; case eView::Intent::UpdateProfit: if (viewBases.contains(View::Categories)) viewBases[View::Categories]->slotSelectByVariant(variant, intent); break; case eView::Intent::ShowTransaction: if (viewBases.contains(View::Ledgers)) { showPage(View::Ledgers); viewBases[View::Ledgers]->slotSelectByVariant(variant, intent); } break; case eView::Intent::ToggleColumn: if (variant.count() == 2) slotAccountTreeViewChanged(variant.at(0).value(), variant.at(1).value()); break; case eView::Intent::ShowPayee: if (viewBases.contains(View::Payees)) { showPage(View::Payees); viewBases[View::Payees]->slotSelectByVariant(variant, intent); } break; case eView::Intent::SelectRegisterTransactions: - if (variant.count() == 1) + if (variant.count() == 1) { emit transactionsSelected(variant.at(0).value()); // for plugins + if (viewBases.contains(View::Ledgers)) + viewBases[View::Ledgers]->slotSelectByVariant(variant, intent); + } break; case eView::Intent::AccountReconciled: if (variant.count() == 5) emit accountReconciled(variant.at(0).value(), variant.at(1).value(), variant.at(2).value(), variant.at(3).value(), variant.at(4).value>>()); // for plugins break; default: break; } } void KMyMoneyView::slotCustomActionRequested(View view, eView::Action action) { switch (action) { case eView::Action::AboutToShow: - emit aboutToChangeView(); + resetViewSelection(); break; case eView::Action::SwitchView: showPage(view); break; default: break; } } void KMyMoneyView::slotObjectSelected(const MyMoneyObject& obj) { // carrying some slots over to views isn't easy for all slots... // ...so calls to kmymoney still must be here if (typeid(obj) == typeid(MyMoneyAccount)) { QVector views {View::Investments, View::Categories, View::Accounts, View::Ledgers, View::Reports, View::OnlineJobOutbox}; for (const auto view : views) if (viewBases.contains(view)) viewBases[view]->slotSelectByObject(obj, eView::Intent::UpdateActions); // for plugin only const auto& acc = static_cast(obj); if (!acc.isIncomeExpense() && !MyMoneyFile::instance()->isStandardAccount(acc.id())) emit accountSelected(acc); } else if (typeid(obj) == typeid(MyMoneyInstitution)) { viewBases[View::Institutions]->slotSelectByObject(obj, eView::Intent::UpdateActions); } else if (typeid(obj) == typeid(MyMoneySchedule)) { viewBases[View::Schedules]->slotSelectByObject(obj, eView::Intent::UpdateActions); } } void KMyMoneyView::slotContextMenuRequested(const MyMoneyObject& obj) { if (typeid(obj) == typeid(MyMoneyAccount)) { const auto& acc = static_cast(obj); if (acc.isInvest()) viewBases[View::Investments]->slotSelectByObject(obj, eView::Intent::OpenContextMenu); else if (acc.isIncomeExpense()) viewBases[View::Categories]->slotSelectByObject(obj, eView::Intent::OpenContextMenu); else viewBases[View::Accounts]->slotSelectByObject(obj, eView::Intent::OpenContextMenu); } else if (typeid(obj) == typeid(MyMoneyInstitution)) { viewBases[View::Institutions]->slotSelectByObject(obj, eView::Intent::OpenContextMenu); } else if (typeid(obj) == typeid(MyMoneySchedule)) { viewBases[View::Schedules]->slotSelectByObject(obj, eView::Intent::OpenContextMenu); } } diff --git a/kmymoney/views/kmymoneyview.h b/kmymoney/views/kmymoneyview.h index ded775b88..77aa4132e 100644 --- a/kmymoney/views/kmymoneyview.h +++ b/kmymoney/views/kmymoneyview.h @@ -1,368 +1,327 @@ /*************************************************************************** kmymoneyview.h ------------------- copyright : (C) 2000-2001 by Michael Edwardes 2004 by Thomas Baumgart (C) 2017, 2018 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KMYMONEYVIEW_H #define KMYMONEYVIEW_H #include // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "selectedtransactions.h" #ifdef KF5Activities_FOUND namespace KActivities { class ResourceInstance; } #endif namespace eAccountsModel { enum class Column; } namespace eMenu { enum class Action; } namespace KMyMoneyPlugin { class OnlinePlugin; } -namespace KMyMoneyPlugin { class StoragePlugin; } namespace eDialogs { enum class ScheduleResultCode; } namespace eView { enum class Intent; } namespace eView { enum class Action; } namespace Icons { enum class Icon; } class KMyMoneyApp; class KHomeView; class KAccountsView; class KCategoriesView; class KInstitutionsView; class KPayeesView; class KTagsView; class KBudgetView; class KScheduledView; class KGlobalLedgerView; class IMyMoneyOperationsFormat; class MyMoneyTransaction; class KInvestmentView; class KReportsView; class SimpleLedgerView; class MyMoneySchedule; class MyMoneySecurity; class MyMoneyReport; class TransactionEditor; class KOnlineJobOutbox; class KMyMoneyTitleLabel; class MyMoneyAccount; class MyMoneyMoney; class MyMoneyObject; class QLabel; class KMyMoneyViewBase; /** * This class represents the view of the MyMoneyFile which contains * Banks/Accounts/Transactions, Recurring transactions (or Bills & Deposits) * and scripts (yet to be implemented). Each different aspect of the file * is represented by a tab within the view. * * @author Michael Edwardes 2001 Copyright 2000-2001 * * @short Handles the view of the MyMoneyFile. */ enum class View; class KMyMoneyView : public KPageWidget { Q_OBJECT -public: - // file actions for plugin - enum fileActions { - preOpen, postOpen, preSave, postSave, preClose, postClose - }; - private: - enum menuID { - AccountNew = 1, - AccountOpen, - AccountReconcile, - AccountEdit, - AccountDelete, - AccountOnlineMap, - AccountOnlineUpdate, - AccountOfxConnect, - CategoryNew - }; - - enum storageTypeE { - Memory = 0, - Database - } _storageType; KPageWidgetModel* m_model; QHash viewFrames; QHash viewBases; KMyMoneyTitleLabel* m_header; - QMap* m_storagePlugins; - -private: void viewAccountList(const QString& selectAccount); // Show the accounts view void createSchedule(MyMoneySchedule s, MyMoneyAccount& a); public: /** * The constructor for KMyMoneyView. Just creates all the tabs for the * different aspects of the MyMoneyFile. */ KMyMoneyView(); /** * Destructor */ ~KMyMoneyView(); /** * This method enables the state of all views (except home view) according * to an open file. */ void enableViewsIfFileOpen(bool fileOpen); void switchToDefaultView(); void switchToHomeView(); void addWidget(QWidget* w); void showPageAndFocus(View idView); void showPage(View idView); /** * check if the current view allows to print something * * @retval true Yes, view allows to print * @retval false No, view cannot print */ bool canPrint(); void finishReconciliation(const MyMoneyAccount& account); void showTitleBar(bool show); /** * This method changes the view type according to the settings. */ void updateViewType(); void slotAccountTreeViewChanged(const eAccountsModel::Column column, const bool show); void setOnlinePlugins(QMap& plugins); - void setStoragePlugins(QMap& plugins); // TODO: remove that function /** * ugly proxy function */ eDialogs::ScheduleResultCode enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys); void addView(KMyMoneyViewBase* view, const QString& name, View idView); void removeView(View idView); /** * @brief actionsToBeConnected are actions that need ActionCollection * which is available in KMyMoneyApp * @return QHash of action id and QAction itself */ QHash actionsToBeConnected(); protected: /** * Overwritten because KMyMoney has it's custom header. */ bool showPageHeader() const final override; public Q_SLOTS: /** * This slot writes information about the page passed as argument @a current * in the kmymoney.rc file so that it can be selected automatically when * the application is started again. * * @param current QModelIndex of the current page item * @param previous QModelIndex of the previous page item */ void slotCurrentPageChanged(const QModelIndex current, const QModelIndex previous); /** * Brings up a dialog to change the list(s) settings and saves them into the * class KMyMoneySettings (a singleton). * * @see KListSettingsDlg * Refreshes all views. Used e.g. after settings have been changed or * data has been loaded from external sources (QIF import). **/ void slotRefreshViews(); /** * Called, whenever the payees view should pop up and a specific * transaction in an account should be shown. * * @param payeeId The ID of the payee to be shown * @param accountId The ID of the account to be shown * @param transactionId The ID of the transaction to be selected */ void slotPayeeSelected(const QString& payeeId, const QString& accountId, const QString& transactionId); /** * Called, whenever the tags view should pop up and a specific * transaction in an account should be shown. * * @param tagId The ID of the tag to be shown * @param accountId The ID of the account to be shown * @param transactionId The ID of the transaction to be selected */ void slotTagSelected(const QString& tagId, const QString& accountId, const QString& transactionId); /** * This slot prints the current view. */ void slotPrintView(); /** * Called when the user changes the detail * setting of the transaction register * * @param detailed if true, the register is shown with all details */ void slotShowTransactionDetail(bool detailed); /** * Informs respective views about selected object, so they can * update action states and current object. * @param obj Account, Category, Investment, Stock, Institution */ void slotObjectSelected(const MyMoneyObject& obj); void slotSelectByObject(const MyMoneyObject& obj, eView::Intent intent); void slotSelectByVariant(const QVariantList& variant, eView::Intent intent); void slotCustomActionRequested(View view, eView::Action action); void slotFileOpened(); void slotFileClosed(); private Q_SLOTS: /** * This slots switches the view to the specific page */ void slotShowHomePage(); void slotShowInstitutionsPage(); void slotShowAccountsPage(); void slotShowSchedulesPage(); void slotShowCategoriesPage(); void slotShowTagsPage(); void slotShowPayeesPage(); void slotShowLedgersPage(); void slotShowInvestmentsPage(); void slotShowReportsPage(); void slotShowBudgetPage(); void slotShowForecastPage(); void slotShowOutboxPage(); /** * Opens object in ledgers or edits in case of institution * @param obj Account, Category, Investment, Stock, Institution */ void slotOpenObjectRequested(const MyMoneyObject& obj); /** * Opens context menu based on objects's type * @param obj Account, Category, Investment, Stock, Institution */ void slotContextMenuRequested(const MyMoneyObject& obj); -protected Q_SLOTS: - /** - * eventually replace this with KMyMoneyApp::slotCurrencySetBase(). - * it contains the same code - * - * @deprecated - */ - void slotSetBaseCurrency(const MyMoneySecurity& baseCurrency); - private: /** * Internal method used by slotAccountNew() and slotAccountCategory(). */ void accountNew(const bool createCategory); - void resetViewSelection(const View); + void resetViewSelection(); Q_SIGNALS: /** * This signal is emitted whenever a view is selected. * The parameter @p view is identified as one of KMyMoneyView::viewID. */ void viewActivated(View view); - /** - * This signal is emitted whenever a new view is about to be selected. - */ - void aboutToChangeView(); - void accountSelectedForContextMenu(const MyMoneyAccount& acc); void viewStateChanged(bool enabled); /** * This signal is emitted to inform the kmmFile plugin when various file actions * occur. The Action parameter distinguishes between them. */ void kmmFilePlugin(unsigned int action); /** * @brief proxy signal */ void statusMsg(const QString& txt); /** * @brief proxy signal */ void statusProgress(int cnt, int base); void accountReconciled(const MyMoneyAccount& account, const QDate& date, const MyMoneyMoney& startingBalance, const MyMoneyMoney& endingBalance, const QList >& transactionList); /** * This signal is emitted when a transaction/list of transactions has been selected by * the GUI. If no transaction is selected or the selection is removed, * @p transactions is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void transactionsSelected(const KMyMoneyRegister::SelectedTransactions& transactions); /** * This signal is emitted when a new account has been selected by * the GUI. If no account is selected or the selection is removed, * @a account is identical to MyMoneyAccount(). This signal is used * by plugins to get information about changes. */ void accountSelected(const MyMoneyAccount& account); }; #endif diff --git a/kmymoney/views/viewenums.h b/kmymoney/views/viewenums.h index e13a8d921..cd457e752 100644 --- a/kmymoney/views/viewenums.h +++ b/kmymoney/views/viewenums.h @@ -1,73 +1,74 @@ /*************************************************************************** viewenums.h ------------------- copyright : (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef VIEWENUMS_H #define VIEWENUMS_H #include enum class View { Home = 0, Institutions, Accounts, Schedules, Categories, Tags, Payees, Ledgers, Investments, Reports, Budget, Forecast, OnlineJobOutbox, NewLedgers, None }; inline uint qHash(const View key, uint seed) { return ::qHash(static_cast(key), seed); } namespace eView { enum class Tag { All = 0, Referenced, // used tags Unused, // unused tags Opened, // not closed tags Closed // closed tags }; enum class Intent { None, UpdateActions, OpenContextMenu, OpenObject, ShowPayee, ShowTransaction, SynchronizeAccountInInvestmentView, SynchronizeAccountInLedgersView, ToggleColumn, UpdateNetWorth, UpdateProfit, StartEnteringOverdueScheduledTransactions, FinishEnteringOverdueScheduledTransactions, EnterSchedule, ReportProgress, ReportProgressMessage, SelectRegisterTransactions, AccountReconciled, SetOnlinePlugins }; enum class Action { None, Refresh, SetDefaultFocus, AboutToShow, Print, SwitchView, ClosePayeeIdentifierSource, EditInstitution, EditSchedule, CleanupBeforeFileClose, - InitializeAfterFileOpen + InitializeAfterFileOpen, + DisableViewDepenedendActions }; } #endif diff --git a/kmymoney/wizards/newuserwizard/CMakeLists.txt b/kmymoney/wizards/newuserwizard/CMakeLists.txt index 12841f01b..76c921fa4 100644 --- a/kmymoney/wizards/newuserwizard/CMakeLists.txt +++ b/kmymoney/wizards/newuserwizard/CMakeLists.txt @@ -1,29 +1,28 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) set (libnewuserwizard_a_SOURCES knewuserwizard.cpp kaccountpage.cpp kcategoriespage.cpp kcurrencypage.cpp - kfilepage.cpp kgeneralpage.cpp kintropage.cpp kpreferencepage.cpp ) set (libnewuserwizard_a_UI kaccountpage.ui kcurrencypage.ui - kfilepage.ui kgeneralpage.ui + kgeneralpage.ui kintropage.ui kpreferencepage.ui kpasswordpage.ui ) # The handling of these ui files depends # on libkmymoney.so (the widgets library) ki18n_wrap_ui(libnewuserwizard_a_SOURCES ${libnewuserwizard_a_UI}) add_library(newuserwizard STATIC ${libnewuserwizard_a_SOURCES}) # TODO: clean dependencies target_link_libraries(newuserwizard kmymoneywizard KF5::ConfigGui KF5::KIOWidgets KF5::TextWidgets KF5::Completion KF5::ConfigWidgets Qt5::Widgets Qt5::Xml Alkimia::alkimia) add_dependencies(newuserwizard widgets wizardpages) diff --git a/kmymoney/wizards/newuserwizard/kcurrencypage.cpp b/kmymoney/wizards/newuserwizard/kcurrencypage.cpp index 4ecea0a3d..ce90960a1 100644 --- a/kmymoney/wizards/newuserwizard/kcurrencypage.cpp +++ b/kmymoney/wizards/newuserwizard/kcurrencypage.cpp @@ -1,124 +1,124 @@ /*************************************************************************** kcurrencypage.cpp ------------------- begin : Sat Feb 18 2006 copyright : (C) 2006 Thomas Baumgart email : Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kcurrencypage.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "icons/icons.h" #include "knewuserwizard.h" #include "knewuserwizard_p.h" #include "kaccountpage.h" #include "kaccountpage_p.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "ui_currency.h" #include "ui_kaccountpage.h" #include "wizardpage.h" using namespace Icons; namespace NewUserWizard { class CurrencyPagePrivate : public WizardPagePrivate { Q_DISABLE_COPY(CurrencyPagePrivate) public: CurrencyPagePrivate(QObject* parent) : WizardPagePrivate(parent) { } }; CurrencyPage::CurrencyPage(Wizard* wizard) : Currency(wizard), WizardPage(*new CurrencyPagePrivate(wizard), stepCount++, this, wizard) { QTreeWidgetItem *first = 0; QList list = MyMoneyFile::instance()->availableCurrencyList(); QList::const_iterator it; QString localCurrency(QLocale().currencySymbol(QLocale::CurrencyIsoCode)); - QString baseCurrency = MyMoneyFile::instance()->baseCurrency().id(); + QString baseCurrency = MyMoneyFile::instance()->storageAttached() ? MyMoneyFile::instance()->baseCurrency().id() : QString(); ui->m_currencyList->clear(); for (it = list.constBegin(); it != list.constEnd(); ++it) { QTreeWidgetItem* p = insertCurrency(*it); if ((*it).id() == baseCurrency) { first = p; QIcon icon = Icons::get(Icon::ViewBankAccount); p->setIcon(0, icon); } else { p->setIcon(0, QIcon()); } if (!first && (*it).id() == localCurrency) first = p; } QTreeWidgetItemIterator itemsIt = QTreeWidgetItemIterator(ui->m_currencyList, QTreeWidgetItemIterator::All); if (first == 0) first = *itemsIt; if (first != 0) { ui->m_currencyList->setCurrentItem(first); ui->m_currencyList->setItemSelected(first, true); ui->m_currencyList->scrollToItem(first, QTreeView::PositionAtTop); } } CurrencyPage::~CurrencyPage() { } void CurrencyPage::enterPage() { ui->m_currencyList->setFocus(); } KMyMoneyWizardPage* CurrencyPage::nextPage() const { Q_D(const CurrencyPage); QString selCur = selectedCurrency(); QList currencies = MyMoneyFile::instance()->availableCurrencyList(); foreach (auto currency, currencies) { if (selCur == currency.id()) { d->m_wizard->d_func()->m_baseCurrency = currency; break; } } d->m_wizard->d_func()->m_accountPage->d_func()->ui->m_accountCurrencyLabel->setText(d->m_wizard->d_func()->m_baseCurrency.tradingSymbol()); return d->m_wizard->d_func()->m_accountPage; } } diff --git a/kmymoney/wizards/newuserwizard/kfilepage.cpp b/kmymoney/wizards/newuserwizard/kfilepage.cpp deleted file mode 100644 index 754f9f794..000000000 --- a/kmymoney/wizards/newuserwizard/kfilepage.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/*************************************************************************** - kfilepage.cpp - ------------------- - begin : Sat Feb 18 2006 - copyright : (C) 2006 Thomas Baumgart - email : Thomas Baumgart - (C) 2017 by Łukasz Wojniłowicz - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#include "kfilepage.h" -#include "kfilepage_p.h" - -// ---------------------------------------------------------------------------- -// QT Includes - -#include -#include -#include -#include -#include -#include - -// ---------------------------------------------------------------------------- -// KDE Includes - -#include -#include -#include -#include - -// ---------------------------------------------------------------------------- -// Project Includes - -#include "ui_kfilepage.h" -#include "knewuserwizard.h" -#include "kguiutils.h" - -namespace NewUserWizard -{ - FilePage::FilePage(Wizard* wizard) : - QWidget(wizard), - WizardPage(*new FilePagePrivate(wizard), stepCount++, this, wizard) - { - Q_D(FilePage); - d->ui->setupUi(this); - d->m_mandatoryGroup->add(d->ui->m_dataFileEdit->lineEdit()); - connect(d->m_mandatoryGroup, static_cast(&KMandatoryFieldGroup::stateChanged), object(), &KMyMoneyWizardPagePrivate::completeStateChanged); - - KUser user; - QString folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - if (folder.isEmpty() || !QDir().exists(folder)) - folder = QDir::homePath(); - d->ui->m_dataFileEdit->setStartDir(QUrl::fromLocalFile(folder)); - d->ui->m_dataFileEdit->setUrl(QUrl::fromLocalFile(folder + QLatin1Char('/') + user.loginName() + QLatin1String(".kmy"))); - d->ui->m_dataFileEdit->setFilter(i18n("*.kmy *.xml|KMyMoney files (*.kmy *.xml);;*|All files (*)")); - d->ui->m_dataFileEdit->setMode(KFile::File); - } - - FilePage::~FilePage() - { - } - - bool FilePage::isComplete() const - { - Q_D(const FilePage); - //! @todo Allow to overwrite files - bool rc = d->m_mandatoryGroup->isEnabled(); - d->ui->m_existingFileLabel->hide(); - d->ui->m_finishLabel->show(); - if (rc) { - // if a filename is present, check that - // a) the file does not exist - // b) the directory does exist - // c) the file is stored locally (because we cannot check previous conditions if it is not) - const QUrl fullPath = d->ui->m_dataFileEdit->url(); - QFileInfo directory{fullPath.adjusted(QUrl::RemoveFilename).toLocalFile()}; - qDebug() << "Selected fileptah: " << fullPath << " " << directory.absoluteFilePath() << " dir: " << directory.isDir(); - rc = false; - if (!fullPath.isValid() || !fullPath.isLocalFile()) { - d->ui->m_dataFileEdit->setToolTip(i18n("The path has to be valid and cannot be on a remote location.")); - } else if (QFileInfo::exists(fullPath.toLocalFile())) { - d->ui->m_dataFileEdit->setToolTip(i18n("The file exists already. Please create a new file.")); - } else if (!directory.isDir()) { - d->ui->m_dataFileEdit->setToolTip(i18n("The destination directory does not exist or cannot be written to.")); - } else { - d->ui->m_dataFileEdit->setToolTip(""); - rc = true; - } - - d->ui->m_existingFileLabel->setHidden(rc); - d->ui->m_finishLabel->setVisible(rc); - } - return rc; - } - -} diff --git a/kmymoney/wizards/newuserwizard/kfilepage.h b/kmymoney/wizards/newuserwizard/kfilepage.h deleted file mode 100644 index ac3957186..000000000 --- a/kmymoney/wizards/newuserwizard/kfilepage.h +++ /dev/null @@ -1,58 +0,0 @@ -/*************************************************************************** - kfilepage.h - ------------------- - begin : Sat Feb 18 2006 - copyright : (C) 2006 Thomas Baumgart - email : Thomas Baumgart - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef KFILEPAGE_H -#define KFILEPAGE_H - -// ---------------------------------------------------------------------------- -// QT Includes - -#include - -// ---------------------------------------------------------------------------- -// Project Includes - -#include "wizardpage.h" - -namespace NewUserWizard -{ - class Wizard; - /** - * Wizard page to allow selecting the filename - * - * @author Thomas Baumgart - */ - class FilePagePrivate; - class FilePage : public QWidget, public WizardPage - { - Q_OBJECT - Q_DISABLE_COPY(FilePage) - - public: - explicit FilePage(Wizard* parent); - ~FilePage() override; - - bool isComplete() const override; - - private: - Q_DECLARE_PRIVATE_D(WizardPage::d_ptr, FilePage) - friend class Wizard; - }; - -} // namespace - -#endif diff --git a/kmymoney/wizards/newuserwizard/kfilepage.ui b/kmymoney/wizards/newuserwizard/kfilepage.ui deleted file mode 100644 index df6f191e2..000000000 --- a/kmymoney/wizards/newuserwizard/kfilepage.ui +++ /dev/null @@ -1,92 +0,0 @@ - - - KFilePage - - - - 0 - 0 - 602 - 350 - - - - - - - Qt::NoFocus - - - KMyMoney will store your financial data in a file on the disk. A standard filename within your user environment will be the default. This is just provided for convenience and you can choose any other location here. - - - true - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 30 - - - - - - - - Either the currently selected file exists or the selected directory does not exist. Please make sure, that - -<ul> -<li>the selected directory exists and</li> -<li>the filename is not currently used in this directory.</li> -</ul> - - - Qt::RichText - - - true - - - - - - - Qt::NoFocus - - - This finishes the setup of your KMyMoney environment. You can now press the Finish button and start using KMyMoney to record your financial transactions. - - - Qt::RichText - - - true - - - - - - - - - KUrlRequester - QFrame -
kurlrequester.h
-
-
- - -
diff --git a/kmymoney/wizards/newuserwizard/kfilepage_p.h b/kmymoney/wizards/newuserwizard/kfilepage_p.h deleted file mode 100644 index 7b93bd8a6..000000000 --- a/kmymoney/wizards/newuserwizard/kfilepage_p.h +++ /dev/null @@ -1,57 +0,0 @@ -/*************************************************************************** - kfilepage_p.h - ------------------- - begin : Sat Feb 18 2006 - copyright : (C) 2006 Thomas Baumgart - email : Thomas Baumgart - (C) 2017 by Łukasz Wojniłowicz - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef KFILEPAGE_P_H -#define KFILEPAGE_P_H - -// ---------------------------------------------------------------------------- -// QT Includes - -// ---------------------------------------------------------------------------- -// KDE Includes - -// ---------------------------------------------------------------------------- -// Project Includes - -#include "ui_kfilepage.h" -#include "wizardpage_p.h" - -namespace NewUserWizard -{ - class Wizard; - - class FilePagePrivate : public WizardPagePrivate - { - Q_DISABLE_COPY(FilePagePrivate) - - public: - explicit FilePagePrivate(QObject* parent) : - WizardPagePrivate(parent), - ui(new Ui::KFilePage) - { - } - - ~FilePagePrivate() - { - delete ui; - } - - Ui::KFilePage *ui; - }; -} -#endif diff --git a/kmymoney/wizards/newuserwizard/knewuserwizard.cpp b/kmymoney/wizards/newuserwizard/knewuserwizard.cpp index 17310f807..1a95a2623 100644 --- a/kmymoney/wizards/newuserwizard/knewuserwizard.cpp +++ b/kmymoney/wizards/newuserwizard/knewuserwizard.cpp @@ -1,165 +1,155 @@ /*************************************************************************** knewuserwizard.cpp ------------------- begin : Sat Feb 18 2006 copyright : (C) 2006 Thomas Baumgart email : Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "knewuserwizard.h" #include "knewuserwizard_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "kaccountpage.h" #include "kaccountpage_p.h" #include "kcategoriespage.h" #include "kcurrencypage.h" -#include "kfilepage.h" -#include "kfilepage_p.h" #include "kgeneralpage.h" #include "kintropage.h" #include "kpreferencepage.h" #include "kpreferencepage_p.h" #include "mymoneysecurity.h" #include "mymoneypayee.h" #include "mymoneymoney.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "kmymoneydateinput.h" #include "kmymoneyedit.h" #include "kmymoneysettings.h" #include "mymoneytemplate.h" #include "mymoneyenums.h" namespace NewUserWizard { int stepCount = 0; Wizard::Wizard(QWidget *parent, bool modal, Qt::WindowFlags flags) : KMyMoneyWizard(*new WizardPrivate(this), parent, modal, flags) { Q_D(Wizard); bool isFirstTime = KMyMoneySettings::firstTimeRun(); stepCount = 1; setTitle(i18n("KMyMoney New File Setup")); if (isFirstTime) addStep(i18nc("New file wizard introduction", "Introduction")); addStep(i18n("Personal Data")); addStep(i18n("Select Currency")); addStep(i18n("Select Accounts")); - addStep(i18n("Set preferences")); addStep(i18nc("Finish the wizard", "Finish")); if (isFirstTime) d->m_introPage = new IntroPage(this); d->m_generalPage = new GeneralPage(this); d->m_currencyPage = new CurrencyPage(this); d->m_accountPage = new AccountPage(this); d->m_categoriesPage = new CategoriesPage(this); d->m_preferencePage = new PreferencePage(this); - d->m_filePage = new FilePage(this); d->m_accountPage->d_func()->ui->m_haveCheckingAccountButton->setChecked(true); if (isFirstTime) d->setFirstPage(d->m_introPage); else d->setFirstPage(d->m_generalPage); setHelpContext("firsttime-3"); } Wizard::~Wizard() { } MyMoneyPayee Wizard::user() const { Q_D(const Wizard); return d->m_generalPage->user(); } - QUrl Wizard::url() const - { - Q_D(const Wizard); - return d->m_filePage->d_func()->ui->m_dataFileEdit->url(); - } - MyMoneyInstitution Wizard::institution() const { Q_D(const Wizard); MyMoneyInstitution inst; if (d->m_accountPage->d_func()->ui->m_haveCheckingAccountButton->isChecked()) { if (d->m_accountPage->d_func()->ui->m_institutionNameEdit->text().length()) { inst.setName(d->m_accountPage->d_func()->ui->m_institutionNameEdit->text()); if (d->m_accountPage->d_func()->ui->m_institutionNumberEdit->text().length()) inst.setSortcode(d->m_accountPage->d_func()->ui->m_institutionNumberEdit->text()); } } return inst; } MyMoneyAccount Wizard::account() const { Q_D(const Wizard); MyMoneyAccount acc; if (d->m_accountPage->d_func()->ui->m_haveCheckingAccountButton->isChecked()) { acc.setName(d->m_accountPage->d_func()->ui->m_accountNameEdit->text()); if (d->m_accountPage->d_func()->ui->m_accountNumberEdit->text().length()) acc.setNumber(d->m_accountPage->d_func()->ui->m_accountNumberEdit->text()); acc.setOpeningDate(d->m_accountPage->d_func()->ui->m_openingDateEdit->date()); acc.setCurrencyId(d->m_baseCurrency.id()); acc.setAccountType(eMyMoney::Account::Type::Checkings); } return acc; } MyMoneyMoney Wizard::openingBalance() const { Q_D(const Wizard); return d->m_accountPage->d_func()->ui->m_openingBalanceEdit->value(); } MyMoneySecurity Wizard::baseCurrency() const { Q_D(const Wizard); return d->m_baseCurrency; } QList Wizard::templates() const { Q_D(const Wizard); return d->m_categoriesPage->selectedTemplates(); } bool Wizard::startSettingsAfterFinished() const { Q_D(const Wizard); return d->m_preferencePage->d_func()->ui->m_openConfigAfterFinished->checkState() == Qt::Checked; } } diff --git a/kmymoney/wizards/newuserwizard/knewuserwizard.h b/kmymoney/wizards/newuserwizard/knewuserwizard.h index c2a4fbed5..7d401c558 100644 --- a/kmymoney/wizards/newuserwizard/knewuserwizard.h +++ b/kmymoney/wizards/newuserwizard/knewuserwizard.h @@ -1,121 +1,114 @@ /*************************************************************************** knewuserwizard.h ------------------- begin : Sat Feb 18 2006 copyright : (C) 2006 Thomas Baumgart email : Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KNEWUSERWIZARD_H #define KNEWUSERWIZARD_H // ---------------------------------------------------------------------------- // QT Includes #include -#include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneywizard.h" #include "mymoneysecurity.h" class MyMoneyPayee; class MyMoneyInstitution; class MyMoneyAccount; class MyMoneyMoney; class MyMoneyTemplate; /** * @author Thomas Baumgart */ namespace NewUserWizard { extern int stepCount; /** * @author Thomas Baumgart * * This class implements the new user wizard which is used to gather * some initial information from the user who creates a new KMyMoney * 'file'. */ class WizardPrivate; class Wizard : public KMyMoneyWizard { friend class IntroPage; friend class GeneralPage; friend class CurrencyPage; friend class AccountPage; friend class CategoriesPage; friend class PreferencePage; - friend class FilePage; Q_OBJECT Q_DISABLE_COPY(Wizard) public: explicit Wizard(QWidget *parent = nullptr, bool modal = false, Qt::WindowFlags flags = 0); ~Wizard() override; /** * Returns the personal information of the user (e.g. name, address, etc.) */ MyMoneyPayee user() const; - /** - * Returns the URL that the user has chosen to store the file - */ - QUrl url() const; - /** * Returns the information about an institution if entered by * the user. If the name field is empty, then he did not enter * such information. */ MyMoneyInstitution institution() const; /** * Returns the information about a checking account if entered by * the user. If the name field is empty, then he did not enter * such information. */ MyMoneyAccount account() const; /** * Returns the opening balance value provided by the user. not enter */ MyMoneyMoney openingBalance() const; /** * Returns the security to be used as base currency. */ MyMoneySecurity baseCurrency() const; /** * Returns a list of templates including accounts to be created */ QList templates() const; /** * True if the settings dialog should be launched after the wizard is finished. */ bool startSettingsAfterFinished() const; private: Q_DECLARE_PRIVATE(Wizard) }; } // namespace #endif diff --git a/kmymoney/wizards/newuserwizard/knewuserwizard_p.h b/kmymoney/wizards/newuserwizard/knewuserwizard_p.h index 1ce98397b..f7879babd 100644 --- a/kmymoney/wizards/newuserwizard/knewuserwizard_p.h +++ b/kmymoney/wizards/newuserwizard/knewuserwizard_p.h @@ -1,74 +1,71 @@ /*************************************************************************** knewuserwizard_p.h ------------------- begin : Sat Feb 18 2006 copyright : (C) 2006 Thomas Baumgart email : Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KNEWUSERWIZARD_P_H #define KNEWUSERWIZARD_P_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // Project Includes #include "knewuserwizard.h" #include "kmymoneywizard_p.h" #include "mymoneysecurity.h" namespace NewUserWizard { class IntroPage; class GeneralPage; class CurrencyPage; class AccountPage; class CategoriesPage; class PreferencePage; - class FilePage; class WizardPrivate : public KMyMoneyWizardPrivate { Q_DISABLE_COPY(WizardPrivate) public: explicit WizardPrivate(Wizard *qq): KMyMoneyWizardPrivate(qq), m_introPage(nullptr), m_generalPage(nullptr), m_currencyPage(nullptr), m_accountPage(nullptr), m_categoriesPage(nullptr), - m_preferencePage(nullptr), - m_filePage(nullptr) + m_preferencePage(nullptr) { } ~WizardPrivate() { } MyMoneySecurity m_baseCurrency; IntroPage* m_introPage; GeneralPage* m_generalPage; CurrencyPage* m_currencyPage; AccountPage* m_accountPage; CategoriesPage* m_categoriesPage; PreferencePage* m_preferencePage; - FilePage* m_filePage; }; } // namespace #endif diff --git a/kmymoney/wizards/newuserwizard/kpreferencepage.cpp b/kmymoney/wizards/newuserwizard/kpreferencepage.cpp index 46a153770..18a7d986b 100644 --- a/kmymoney/wizards/newuserwizard/kpreferencepage.cpp +++ b/kmymoney/wizards/newuserwizard/kpreferencepage.cpp @@ -1,57 +1,50 @@ /*************************************************************************** kpreferencepage.cpp ------------------- begin : Sat Feb 18 2006 copyright : (C) 2006 Thomas Baumgart email : Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kpreferencepage.h" #include "kpreferencepage_p.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "ui_kpreferencepage.h" #include "knewuserwizard.h" #include "knewuserwizard_p.h" -#include "kfilepage.h" namespace NewUserWizard { PreferencePage::PreferencePage(Wizard* wizard) : QWidget(wizard), WizardPage(*new PreferencePagePrivate(wizard), stepCount++, this, wizard) { Q_D(PreferencePage); d->ui->setupUi(this); } PreferencePage::~PreferencePage() { } - KMyMoneyWizardPage* PreferencePage::nextPage() const - { - Q_D(const PreferencePage); - return d->m_wizard->d_func()->m_filePage; - } - } diff --git a/kmymoney/wizards/newuserwizard/kpreferencepage.h b/kmymoney/wizards/newuserwizard/kpreferencepage.h index ddfa1eeb9..f5fafe10b 100644 --- a/kmymoney/wizards/newuserwizard/kpreferencepage.h +++ b/kmymoney/wizards/newuserwizard/kpreferencepage.h @@ -1,60 +1,58 @@ /*************************************************************************** kpreferencepage.h ------------------- begin : Sat Feb 18 2006 copyright : (C) 2006 Thomas Baumgart email : Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KPREFERENCEPAGE_H #define KPREFERENCEPAGE_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "wizardpage.h" class KMyMoneyWizardPage; namespace NewUserWizard { class Wizard; /** * Wizard page to allow changing the preferences during setup * * @author Thomas Baumgart */ class PreferencePagePrivate; class PreferencePage : public QWidget, public WizardPage { Q_OBJECT Q_DISABLE_COPY(PreferencePage) public: explicit PreferencePage(Wizard* parent); ~PreferencePage() override; - KMyMoneyWizardPage* nextPage() const override; - private: Q_DECLARE_PRIVATE_D(WizardPage::d_ptr, PreferencePage) friend class Wizard; }; } // namespace #endif