diff --git a/kmymoney/dialogs/investactivities.cpp b/kmymoney/dialogs/investactivities.cpp index 288ab4089..efcf71685 100644 --- a/kmymoney/dialogs/investactivities.cpp +++ b/kmymoney/dialogs/investactivities.cpp @@ -1,985 +1,985 @@ /* * Copyright 2007-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 "investactivities.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "investtransactioneditor.h" #include "mymoneymoney.h" #include "kmymoneycategory.h" #include "kmymoneyedit.h" #include "kmymoneyaccountselector.h" #include "kmymoneycompletion.h" #include #include "mymoneyfile.h" #include "mymoneysplit.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "dialogenums.h" #include "mymoneyenums.h" using namespace Invest; using namespace KMyMoneyRegister; class Invest::ActivityPrivate { Q_DISABLE_COPY(ActivityPrivate) public: ActivityPrivate() : m_parent(nullptr), m_memoChanged(false) { } InvestTransactionEditor *m_parent; QMap m_priceInfo; bool m_memoChanged; QString m_memoText; }; Activity::Activity(InvestTransactionEditor* editor) : d_ptr(new ActivityPrivate) { Q_D(Activity); d->m_memoChanged = false; d->m_parent = editor; } Activity::~Activity() { Q_D(Activity); delete d; } bool& Activity::memoChanged() { Q_D(Activity); return d->m_memoChanged; } QString& Activity::memoText() { Q_D(Activity); return d->m_memoText; } bool Activity::isComplete(QString& reason) const { Q_D(const Activity); Q_UNUSED(reason) auto rc = false; auto security = dynamic_cast(haveWidget("security")); if (security && !security->currentText().isEmpty()) { rc = (security->selector()->contains(security->currentText()) || (isMultiSelection() && d->m_memoChanged)); } return rc; } QWidget* Activity::haveWidget(const QString& name) const { Q_D(const Activity); return d->m_parent->haveWidget(name); } bool Activity::haveAssetAccount() const { auto rc = true; auto cat = dynamic_cast(haveWidget("asset-account")); if (!cat) return false; if (!isMultiSelection()) rc = !cat->currentText().isEmpty(); if (rc && !cat->currentText().isEmpty()) rc = cat->selector()->contains(cat->currentText()); return rc; } bool Activity::haveCategoryAndAmount(const QString& category, const QString& amount, bool optional) const { Q_D(const Activity); auto cat = dynamic_cast(haveWidget(category)); auto rc = true; if (cat && !cat->currentText().isEmpty()) { rc = cat->selector()->contains(cat->currentText()) || cat->isSplitTransaction(); if (rc && !amount.isEmpty() && !isMultiSelection()) { if (cat->isSplitTransaction()) { QList::const_iterator split; QList::const_iterator splitEnd; if (category == "fee-account") { split = d->m_parent->feeSplits().cbegin(); splitEnd = d->m_parent->feeSplits().cend(); } else if (category == "interest-account") { split = d->m_parent->interestSplits().cbegin(); splitEnd = d->m_parent->interestSplits().cend(); } for (; split != splitEnd; ++split) { if ((*split).value().isZero()) rc = false; } } else { if (auto valueWidget = dynamic_cast(haveWidget(amount))) rc = !valueWidget->value().isZero(); } } } else if (!isMultiSelection() && !optional) { rc = false; } return rc; } bool Activity::haveFees(bool optional) const { return haveCategoryAndAmount("fee-account", "fee-amount", optional); } bool Activity::haveInterest(bool optional) const { return haveCategoryAndAmount("interest-account", "interest-amount", optional); } bool Activity::haveShares() const { if (auto amount = dynamic_cast(haveWidget("shares"))) { if (isMultiSelection() && amount->value().isZero()) return true; return !amount->value().isZero(); } return false; } bool Activity::havePrice() const { if (auto amount = dynamic_cast(haveWidget("price"))) { if (isMultiSelection() && amount->value().isZero()) return true; return !amount->value().isZero(); } return false; } bool Activity::isMultiSelection() const { Q_D(const Activity); return d->m_parent->isMultiSelection(); } bool Activity::createCategorySplits(const MyMoneyTransaction& t, KMyMoneyCategory* cat, KMyMoneyEdit* amount, MyMoneyMoney factor, QList&splits, const QList& osplits) const { Q_D(const Activity); auto rc = true; - if (!isMultiSelection() || (isMultiSelection() && !cat->currentText().isEmpty())) { + if (!isMultiSelection() || !cat->currentText().isEmpty()) { if (!cat->isSplitTransaction()) { splits.clear(); MyMoneySplit s1; QString categoryId; categoryId = cat->selectedItem(); if (!categoryId.isEmpty()) { s1.setAccountId(categoryId); s1.setValue(amount->value() * factor); if (!s1.value().isZero()) { rc = d->m_parent->setupPrice(t, s1); } splits.append(s1); } } else { splits = osplits; } } return rc; } void Activity::createAssetAccountSplit(MyMoneySplit& split, const MyMoneySplit& stockSplit) const { auto cat = dynamic_cast(haveWidget("asset-account")); - if (cat && (!isMultiSelection() || (isMultiSelection() && !cat->currentText().isEmpty()))) { + if (cat && (!isMultiSelection() || !cat->currentText().isEmpty())) { auto categoryId = cat->selectedItem(); split.setAccountId(categoryId); } split.setMemo(stockSplit.memo()); } MyMoneyMoney Activity::sumSplits(const MyMoneySplit& s0, const QList& feeSplits, const QList& interestSplits) const { auto total = s0.value(); foreach (const auto feeSplit, feeSplits) total += feeSplit.value(); foreach (const auto interestSplit, interestSplits) total += interestSplit.value(); return total; } void Activity::setLabelText(const QString& idx, const QString& txt) const { auto w = dynamic_cast(haveWidget(idx)); if (w) { w->setText(txt); } else { if (KMyMoneySettings::transactionForm()) { // labels are only used in the transaction form qDebug("Unknown QLabel named '%s'", qPrintable(idx)); } } } void Activity::preloadAssetAccount() { Q_D(Activity); auto cat = dynamic_cast(haveWidget("asset-account")); if (cat && cat->isVisible()) { if (cat->currentText().isEmpty()) { MyMoneyAccount acc = MyMoneyFile::instance()->accountByName(i18n("%1 (Brokerage)", d->m_parent->account().name())); if (!acc.id().isEmpty()) { bool blocked = cat->signalsBlocked(); // block signals, so that the focus does not go crazy cat->blockSignals(true); cat->completion()->setSelected(acc.id()); cat->slotItemSelected(acc.id()); cat->blockSignals(blocked); } } } } void Activity::setWidgetVisibility(const QStringList& widgetIds, bool visible) const { for (QStringList::const_iterator it_w = widgetIds.constBegin(); it_w != widgetIds.constEnd(); ++it_w) { auto w = haveWidget(*it_w); if (w) { if (visible) { w->show(); } else { w->hide(); } } } } eDialogs::PriceMode Activity::priceMode() const { Q_D(const Activity); return d->m_parent->priceMode(); } QString Activity::priceLabel() const { QString label; if (priceMode() == eDialogs::PriceMode::Price) { label = i18n("Price"); } else if (priceMode() == eDialogs::PriceMode::PricePerShare) { label = i18n("Price/share"); } else if (priceMode() == eDialogs::PriceMode::PricePerTransaction) { label = i18n("Transaction amount"); } return label; } Buy::Buy(InvestTransactionEditor* editor) : Activity(editor) { } Buy::~Buy() { } eMyMoney::Split::InvestmentTransactionType Buy::type() const { return eMyMoney::Split::InvestmentTransactionType::BuyShares; } void Buy::showWidgets() const { static const QStringList visibleWidgetIds = QStringList() << "asset-account" << "shares" << "price" << "total" << "interest-account" << "fee-account"; setWidgetVisibility(visibleWidgetIds, true); setLabelText("interest-amount-label", i18n("Interest")); setLabelText("interest-label", i18n("Interest")); setLabelText("fee-label", i18n("Fees")); setLabelText("asset-label", i18n("Account")); setLabelText("shares-label", i18n("Shares")); if (dynamic_cast(haveWidget("price-label"))) setLabelText("price-label", priceLabel()); setLabelText("total-label", i18nc("Total value", "Total")); } bool Buy::isComplete(QString& reason) const { auto rc = Activity::isComplete(reason); rc &= haveAssetAccount(); rc &= haveFees(true); rc &= haveShares(); rc &= havePrice(); return rc; } bool Buy::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList& feeSplits, QList& m_feeSplits, QList& interestSplits, QList& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) { Q_D(Activity); Q_UNUSED(m_interestSplits); Q_UNUSED(security); Q_UNUSED(currency); QString reason; if (!isComplete(reason)) return false; auto sharesEdit = dynamic_cast(haveWidget("shares")); auto priceEdit = dynamic_cast(haveWidget("price")); s0.setAction(eMyMoney::Split::InvestmentTransactionType::BuyShares); MyMoneyMoney shares = s0.shares(); MyMoneyMoney price; if (!s0.shares().isZero()) price = (s0.value() / s0.shares()).reduce(); - if (sharesEdit && (!isMultiSelection() || (isMultiSelection() && !sharesEdit->value().isZero()))) { + if (sharesEdit && (!isMultiSelection() || !sharesEdit->value().isZero())) { shares = sharesEdit->value().abs(); s0.setShares(shares); s0.setValue((shares * price).reduce()); s0.setPrice(price); } - if (priceEdit && (!isMultiSelection() || (isMultiSelection() && !priceEdit->value().isZero()))) { + if (priceEdit && (!isMultiSelection() || !priceEdit->value().isZero())) { price = priceEdit->value().abs(); if (priceMode() == eDialogs::PriceMode::PricePerTransaction) { s0.setValue(price.reduce()); if (!s0.shares().isZero()) s0.setPrice((price / s0.shares()).reduce()); } else { s0.setValue((shares * price).reduce()); s0.setPrice(price); } } auto feeAccountWidget = dynamic_cast(haveWidget("fee-account")); auto feeAmountWidget = dynamic_cast(haveWidget("fee-amount")); if (!feeAccountWidget || !feeAmountWidget || !createCategorySplits(t, feeAccountWidget, feeAmountWidget, MyMoneyMoney::ONE, feeSplits, m_feeSplits)) return false; createAssetAccountSplit(assetAccountSplit, s0); MyMoneyMoney total = sumSplits(s0, feeSplits, QList()); // Clear any leftover value from previous Dividend. interestSplits.clear(); assetAccountSplit.setValue(-total); if (!d->m_parent->setupPrice(t, assetAccountSplit)) return false; return true; } Sell::Sell(InvestTransactionEditor* editor) : Activity(editor) { } Sell::~Sell() { } eMyMoney::Split::InvestmentTransactionType Sell::type() const { return eMyMoney::Split::InvestmentTransactionType::SellShares; } void Sell::showWidgets() const { Q_D(const Activity); static const QStringList visibleWidgetIds = QStringList() << "asset-account" << "interest-amount" << "shares" << "price" << "total" << "interest-account" << "fee-account"; setWidgetVisibility(visibleWidgetIds, true); if (auto shareEdit = dynamic_cast(haveWidget("shares"))) shareEdit->setPrecision(MyMoneyMoney::denomToPrec(d->m_parent->security().smallestAccountFraction())); setLabelText("interest-amount-label", i18n("Interest")); setLabelText("interest-label", i18n("Interest")); setLabelText("fee-label", i18n("Fees")); setLabelText("asset-label", i18n("Account")); setLabelText("shares-label", i18n("Shares")); if (dynamic_cast(haveWidget("price-label"))) setLabelText("price-label", priceLabel()); setLabelText("total-label", i18nc("Total value", "Total")); } bool Sell::isComplete(QString& reason) const { auto rc = Activity::isComplete(reason); rc &= haveAssetAccount(); rc &= haveFees(true); rc &= haveInterest(true); rc &= haveShares(); rc &= havePrice(); return rc; } bool Sell::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList& feeSplits, QList& m_feeSplits, QList& interestSplits, QList& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) { Q_D(Activity); Q_UNUSED(m_interestSplits); Q_UNUSED(security); Q_UNUSED(currency); QString reason; if (!isComplete(reason)) return false; auto sharesEdit = dynamic_cast(haveWidget("shares")); auto priceEdit = dynamic_cast(haveWidget("price")); s0.setAction(eMyMoney::Split::InvestmentTransactionType::BuyShares); MyMoneyMoney shares = s0.shares(); MyMoneyMoney price; if (!s0.shares().isZero()) price = (s0.value() / s0.shares()).reduce(); - if (sharesEdit && (!isMultiSelection() || (isMultiSelection() && !sharesEdit->value().isZero()))) { + if (sharesEdit && (!isMultiSelection() || !sharesEdit->value().isZero())) { shares = -sharesEdit->value().abs(); s0.setShares(shares); s0.setValue((shares * price).reduce()); s0.setPrice(price); } - if (priceEdit && (!isMultiSelection() || (isMultiSelection() && !priceEdit->value().isZero()))) { + if (priceEdit && (!isMultiSelection() || !priceEdit->value().isZero())) { price = priceEdit->value().abs(); if (priceMode() == eDialogs::PriceMode::PricePerTransaction) { price = -price; s0.setValue(price.reduce()); if (!s0.shares().isZero()) s0.setPrice((price / s0.shares()).reduce()); } else { s0.setValue((shares * price).reduce()); s0.setPrice(price); } } auto feeAccountWidget = dynamic_cast(haveWidget("fee-account")); auto feeAmountWidget = dynamic_cast(haveWidget("fee-amount")); if (!feeAccountWidget || !feeAmountWidget || !createCategorySplits(t, feeAccountWidget, feeAmountWidget, MyMoneyMoney::ONE, feeSplits, m_feeSplits)) return false; auto interestAccountWidget = dynamic_cast(haveWidget("interest-account")); auto interestAmountWidget = dynamic_cast(haveWidget("interest-amount")); if (!interestAccountWidget || !interestAmountWidget || !createCategorySplits(t, interestAccountWidget, interestAmountWidget, MyMoneyMoney::MINUS_ONE, interestSplits, m_interestSplits)) return false; createAssetAccountSplit(assetAccountSplit, s0); MyMoneyMoney total = sumSplits(s0, feeSplits, interestSplits); assetAccountSplit.setValue(-total); if (!d->m_parent->setupPrice(t, assetAccountSplit)) return false; return true; } Div::Div(InvestTransactionEditor* editor) : Activity(editor) { } Div::~Div() { } eMyMoney::Split::InvestmentTransactionType Div::type() const { return eMyMoney::Split::InvestmentTransactionType::Dividend; } void Div::showWidgets() const { static const QStringList visibleWidgetIds = QStringList() << "asset-account" << "interest-amount" << "total" << "interest-account" << "fee-account"; setWidgetVisibility(visibleWidgetIds, true); static const QStringList hiddenWidgetIds = QStringList() << "shares" << "price"; setWidgetVisibility(hiddenWidgetIds, false); setLabelText("interest-amount-label", i18n("Interest")); setLabelText("interest-label", i18n("Interest")); setLabelText("fee-label", i18n("Fees")); setLabelText("asset-label", i18n("Account")); setLabelText("total-label", i18nc("Total value", "Total")); } bool Div::isComplete(QString& reason) const { Q_UNUSED(reason) auto rc = Activity::isComplete(reason); rc &= haveAssetAccount(); rc &= haveCategoryAndAmount("interest-account", QString(), false); rc &= haveInterest(false); return rc; } bool Div::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList& feeSplits, QList& m_feeSplits, QList& interestSplits, QList& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) { Q_D(Activity); Q_UNUSED(m_feeSplits); Q_UNUSED(security); Q_UNUSED(currency); QString reason; if (!isComplete(reason)) return false; s0.setAction(eMyMoney::Split::InvestmentTransactionType::Dividend); // for dividends, we only use the stock split as a marker MyMoneyMoney shares; s0.setShares(shares); s0.setValue(shares); s0.setPrice(MyMoneyMoney::ONE); auto feeAccountWidget = dynamic_cast(haveWidget("fee-account")); auto feeAmountWidget = dynamic_cast(haveWidget("fee-amount")); if (!feeAccountWidget || !feeAmountWidget || !createCategorySplits(t, feeAccountWidget, feeAmountWidget, MyMoneyMoney::ONE, feeSplits, m_feeSplits)) return false; auto interestAccountWidget = dynamic_cast(haveWidget("interest-account")); auto interestAmountWidget = dynamic_cast(haveWidget("interest-amount")); if (!interestAccountWidget || !interestAmountWidget || !createCategorySplits(t, interestAccountWidget, interestAmountWidget, MyMoneyMoney::MINUS_ONE, interestSplits, m_interestSplits)) return false; createAssetAccountSplit(assetAccountSplit, s0); MyMoneyMoney total = sumSplits(s0, feeSplits, interestSplits); assetAccountSplit.setValue(-total); if (!d->m_parent->setupPrice(t, assetAccountSplit)) return false; return true; } Reinvest::Reinvest(InvestTransactionEditor* editor) : Activity(editor) { } Reinvest::~Reinvest() { } eMyMoney::Split::InvestmentTransactionType Reinvest::type() const { return eMyMoney::Split::InvestmentTransactionType::ReinvestDividend; } void Reinvest::showWidgets() const { Q_D(const Activity); static const QStringList visibleWidgetIds = QStringList() << "price" << "fee-account" << "interest-account"; setWidgetVisibility(visibleWidgetIds, true); if (auto shareEdit = dynamic_cast(haveWidget("shares"))) { shareEdit->show(); shareEdit->setPrecision(MyMoneyMoney::denomToPrec(d->m_parent->security().smallestAccountFraction())); } if (auto intAmount = dynamic_cast(haveWidget("interest-amount"))) { intAmount->hide(); setLabelText("interest-amount-label", QString()); intAmount->setValue(MyMoneyMoney()); } setLabelText("fee-label", i18n("Fees")); setLabelText("interest-label", i18n("Interest")); setLabelText("shares-label", i18n("Shares")); if (dynamic_cast(haveWidget("price-label"))) setLabelText("price-label", priceLabel()); setLabelText("total-label", i18nc("Total value", "Total")); } bool Reinvest::isComplete(QString& reason) const { auto rc = Activity::isComplete(reason); rc &= haveCategoryAndAmount("interest-account", QString(), false); rc &= haveFees(true); rc &= haveShares(); rc &= havePrice(); return rc; } bool Reinvest::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList& feeSplits, QList& m_feeSplits, QList& interestSplits, QList& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) { Q_D(Activity); Q_UNUSED(assetAccountSplit); Q_UNUSED(security); Q_UNUSED(currency); QString reason; if (!isComplete(reason)) return false; auto sharesEdit = dynamic_cast(haveWidget("shares")); auto priceEdit = dynamic_cast(haveWidget("price")); s0.setAction(eMyMoney::Split::InvestmentTransactionType::ReinvestDividend); MyMoneyMoney shares = s0.shares(); MyMoneyMoney price; if (!s0.shares().isZero()) price = (s0.value() / s0.shares()).reduce(); - if (sharesEdit && (!isMultiSelection() || (isMultiSelection() && !sharesEdit->value().isZero()))) { + if (sharesEdit && (!isMultiSelection() || !sharesEdit->value().isZero())) { shares = sharesEdit->value().abs(); s0.setShares(shares); s0.setValue((shares * price).reduce()); s0.setPrice(price); } - if (priceEdit && (!isMultiSelection() || (isMultiSelection() && !priceEdit->value().isZero()))) { + if (priceEdit && (!isMultiSelection() || !priceEdit->value().isZero())) { price = priceEdit->value().abs(); if (priceMode() == eDialogs::PriceMode::PricePerTransaction) { s0.setValue(price.reduce()); if (!s0.shares().isZero()) s0.setPrice((price / s0.shares()).reduce()); } else { s0.setValue((shares * price).reduce()); s0.setPrice(price); } } auto feeAccountWidget = dynamic_cast(haveWidget("fee-account")); auto feeAmountWidget = dynamic_cast(haveWidget("fee-amount")); if (feeAmountWidget && feeAccountWidget) { if (!createCategorySplits(t, feeAccountWidget, feeAmountWidget, MyMoneyMoney::ONE, feeSplits, m_feeSplits)) return false; } auto interestAccountWidget = dynamic_cast(haveWidget("interest-account")); auto interestAmountWidget = dynamic_cast(haveWidget("interest-amount")); if (!interestAccountWidget || !interestAmountWidget || !createCategorySplits(t, interestAccountWidget, interestAmountWidget, MyMoneyMoney::MINUS_ONE, interestSplits, m_interestSplits)) return false; if (interestSplits.count() != 1) { qDebug("more or less than one interest split in Reinvest::createTransaction. Not created."); return false; } assetAccountSplit.setAccountId(QString()); MyMoneySplit& s1 = interestSplits[0]; MyMoneyMoney total = sumSplits(s0, feeSplits, QList()); s1.setValue(-total); if (!d->m_parent->setupPrice(t, s1)) return false; return true; } Add::Add(InvestTransactionEditor* editor) : Activity(editor) { } Add::~Add() { } eMyMoney::Split::InvestmentTransactionType Add::type() const { return eMyMoney::Split::InvestmentTransactionType::AddShares; } void Add::showWidgets() const { Q_D(const Activity); if (auto shareEdit = dynamic_cast(haveWidget("shares"))) { shareEdit->show(); shareEdit->setPrecision(MyMoneyMoney::denomToPrec(d->m_parent->security().smallestAccountFraction())); } setLabelText("shares-label", i18n("Shares")); } bool Add::isComplete(QString& reason) const { auto rc = Activity::isComplete(reason); rc &= haveShares(); return rc; } bool Add::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList& feeSplits, QList& m_feeSplits, QList& interestSplits, QList& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) { Q_UNUSED(t); Q_UNUSED(assetAccountSplit); Q_UNUSED(m_feeSplits); Q_UNUSED(m_interestSplits); Q_UNUSED(security); Q_UNUSED(currency); QString reason; if (!isComplete(reason)) return false; auto sharesEdit = dynamic_cast(haveWidget("shares")); s0.setAction(eMyMoney::Split::InvestmentTransactionType::AddShares); if (sharesEdit) s0.setShares(sharesEdit->value().abs()); s0.setValue(MyMoneyMoney()); s0.setPrice(MyMoneyMoney()); assetAccountSplit.setValue(MyMoneyMoney());// Clear any leftover value from previous Dividend. feeSplits.clear(); interestSplits.clear(); return true; } Remove::Remove(InvestTransactionEditor* editor) : Activity(editor) { } Remove::~Remove() { } eMyMoney::Split::InvestmentTransactionType Remove::type() const { return eMyMoney::Split::InvestmentTransactionType::RemoveShares; } void Remove::showWidgets() const { Q_D(const Activity); if (auto shareEdit = dynamic_cast(haveWidget("shares"))) { shareEdit->show(); shareEdit->setPrecision(MyMoneyMoney::denomToPrec(d->m_parent->security().smallestAccountFraction())); } setLabelText("shares-label", i18n("Shares")); } bool Remove::isComplete(QString& reason) const { auto rc = Activity::isComplete(reason); rc &= haveShares(); return rc; } bool Remove::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList& feeSplits, QList& m_feeSplits, QList& interestSplits, QList& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) { Q_UNUSED(t); Q_UNUSED(assetAccountSplit); Q_UNUSED(m_feeSplits); Q_UNUSED(m_interestSplits); Q_UNUSED(security); Q_UNUSED(currency); QString reason; if (!isComplete(reason)) return false; s0.setAction(eMyMoney::Split::InvestmentTransactionType::AddShares); if (auto sharesEdit = dynamic_cast(haveWidget("shares"))) s0.setShares(-(sharesEdit->value().abs())); s0.setValue(MyMoneyMoney()); s0.setPrice(MyMoneyMoney()); assetAccountSplit.setValue(MyMoneyMoney());// Clear any leftover value from previous Dividend. feeSplits.clear(); interestSplits.clear(); return true; } Invest::Split::Split(InvestTransactionEditor* editor) : Activity(editor) { } Invest::Split::~Split() { } eMyMoney::Split::InvestmentTransactionType Invest::Split::type() const { return eMyMoney::Split::InvestmentTransactionType::SplitShares; } void Invest::Split::showWidgets() const { // TODO do we need a special split ratio widget? // TODO maybe yes, currently the precision is the one of the fraction and might differ from it if (auto shareEdit = dynamic_cast(haveWidget("shares"))) { shareEdit->show(); shareEdit->setPrecision(-1); } setLabelText("shares-label", i18n("Ratio 1/")); } bool Invest::Split::isComplete(QString& reason) const { auto rc = Activity::isComplete(reason); rc &= haveShares(); return rc; } bool Invest::Split::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList& feeSplits, QList& m_feeSplits, QList& interestSplits, QList& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) { Q_UNUSED(t); Q_UNUSED(assetAccountSplit); Q_UNUSED(m_feeSplits); Q_UNUSED(m_interestSplits); Q_UNUSED(security); Q_UNUSED(currency); auto sharesEdit = dynamic_cast(haveWidget("shares")); KMyMoneyCategory* cat; cat = dynamic_cast(haveWidget("interest-account")); if (cat) cat->parentWidget()->hide(); cat = dynamic_cast(haveWidget("fee-account")); if (cat) cat->parentWidget()->hide(); s0.setAction(eMyMoney::Split::InvestmentTransactionType::SplitShares); if (sharesEdit) s0.setShares(sharesEdit->value().abs()); s0.setValue(MyMoneyMoney()); s0.setPrice(MyMoneyMoney()); feeSplits.clear(); interestSplits.clear(); return true; } IntInc::IntInc(InvestTransactionEditor* editor) : Activity(editor) { } IntInc::~IntInc() { } eMyMoney::Split::InvestmentTransactionType IntInc::type() const { return eMyMoney::Split::InvestmentTransactionType::InterestIncome; } void IntInc::showWidgets() const { static const QStringList visibleWidgetIds = QStringList() << "asset-account" << "interest-amount" << "total" << "interest-account" << "fee-account"; setWidgetVisibility(visibleWidgetIds, true); static const QStringList hiddenWidgetIds = QStringList() << "shares" << "price" << "fee-amount"; setWidgetVisibility(hiddenWidgetIds, false); setLabelText("interest-amount-label", i18n("Interest")); setLabelText("interest-label", i18n("Interest")); setLabelText("fee-label", i18n("Fees")); setLabelText("asset-label", i18n("Account")); setLabelText("total-label", i18nc("Total value", "Total")); } bool IntInc::isComplete(QString& reason) const { Q_UNUSED(reason) auto rc = Activity::isComplete(reason); rc &= haveAssetAccount(); rc &= haveCategoryAndAmount("interest-account", QString(), false); rc &= haveInterest(false); return rc; } bool IntInc::createTransaction(MyMoneyTransaction& t, MyMoneySplit& s0, MyMoneySplit& assetAccountSplit, QList& feeSplits, QList& m_feeSplits, QList& interestSplits, QList& m_interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency) { Q_D(Activity); Q_UNUSED(security); Q_UNUSED(currency); QString reason; if (!isComplete(reason)) return false; s0.setAction(eMyMoney::Split::InvestmentTransactionType::InterestIncome); // for dividends, we only use the stock split as a marker MyMoneyMoney shares; s0.setShares(shares); s0.setValue(shares); s0.setPrice(MyMoneyMoney::ONE); auto feeAccountWidget = dynamic_cast(haveWidget("fee-account")); auto feeAmountWidget = dynamic_cast(haveWidget("fee-amount")); if (!feeAccountWidget || !feeAmountWidget || !createCategorySplits(t, feeAccountWidget, feeAmountWidget, MyMoneyMoney::ONE, feeSplits, m_feeSplits)) return false; auto interestAccountWidget = dynamic_cast(haveWidget("interest-account")); auto interestAmountWidget = dynamic_cast(haveWidget("interest-amount")); if (!interestAccountWidget || !interestAmountWidget || !createCategorySplits(t, interestAccountWidget, interestAmountWidget, MyMoneyMoney::MINUS_ONE, interestSplits, m_interestSplits)) return false; createAssetAccountSplit(assetAccountSplit, s0); MyMoneyMoney total = sumSplits(s0, feeSplits, interestSplits); assetAccountSplit.setValue(-total); if (!d->m_parent->setupPrice(t, assetAccountSplit)) return false; return true; } diff --git a/kmymoney/main.cpp b/kmymoney/main.cpp index a3d56a054..1ed97bcea 100644 --- a/kmymoney/main.cpp +++ b/kmymoney/main.cpp @@ -1,378 +1,386 @@ /*************************************************************************** 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 #include #include #ifdef KMM_DBUS #include #include #include #endif // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoney/mymoneyfile.h" #include "mymoneyexception.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. Otherwise, QUrl prepends // "http://" and uses the full path which will not work. // On MS-Windows we also need to check if the filename starts with a // drive letter or the backslash variants. // // The handling might be different on other OSes if (!fileUrls.isEmpty()) { fname = fileUrls.front(); QFileInfo fi(fname); auto needLeadIn = fi.isFile(); #ifdef Q_OS_WIN QRegularExpression exp("^[a-z]:", QRegularExpression::CaseInsensitiveOption); needLeadIn &= !exp.match(fname).hasMatch() && !fname.startsWith(QLatin1String(".\\")) && !fname.startsWith(QLatin1String("\\")); #endif needLeadIn &= !fname.startsWith(QLatin1String("file://")) && !fname.startsWith(QLatin1String("./")) && !fname.startsWith(QLatin1String("/")); if (needLeadIn) { 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) { + if (importfile.isEmpty()) { + KTipDialog::showTip(kmymoney, QString(), false); + } kmymoney->slotFileOpenRecent(url); + } else if (KMyMoneySettings::firstTimeRun()) { - // resetting the splash here is needed for windows to have access + // resetting the splash here is needed for ms-windows to have access // to the new file wizard splash.reset(); kmymoney->slotFileNew(); } + KMyMoneySettings::setFirstTimeRun(false); - if (!importfile.isEmpty()) + if (!importfile.isEmpty()) { + // resetting the splash here is needed for ms-windows to have access + // to the web connect widgets + splash.reset(); kmymoney->webConnect(importfile, QByteArray()); + } } else { // the instantQuit flag is set, so we force the app to quit right away kmymoney->slotFileQuit(); } 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/plugins/xml/mymoneystorageanon.cpp b/kmymoney/plugins/xml/mymoneystorageanon.cpp index 180a95b3e..907b80716 100644 --- a/kmymoney/plugins/xml/mymoneystorageanon.cpp +++ b/kmymoney/plugins/xml/mymoneystorageanon.cpp @@ -1,323 +1,345 @@ /* * Copyright 2004-2006 Ace Jones * Copyright 2005-2017 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. * * 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 "mymoneystorageanon.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes +#include + // ---------------------------------------------------------------------------- // Project Includes #include "mymoneystoragemgr.h" #include "mymoneyreport.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneybudget.h" #include "mymoneytransaction.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "mymoneykeyvaluecontainer.h" #include "mymoneyexception.h" +#include "mymoneyenums.h" QStringList MyMoneyStorageANON::zKvpNoModify = QString("kmm-baseCurrency,OpeningBalanceAccount,PreferredAccount,Tax,fixed-interest,interest-calculation,payee,schedule,term,kmm-online-source,kmm-brokerage-account,kmm-sort-reconcile,kmm-sort-std,kmm-iconpos,mm-closed,payee,schedule,term,lastImportedTransactionDate,VatAccount,VatRate,kmm-matched-tx,Imported,priceMode").split(','); QStringList MyMoneyStorageANON::zKvpXNumber = QString("final-payment,loan-amount,periodic-payment,lastStatementBalance").split(','); MyMoneyStorageANON::MyMoneyStorageANON() : MyMoneyStorageXML() { // Choose a quasi-random 0.0-100.0 factor which will be applied to all splits this time // around. int msec; do { msec = QTime::currentTime().msec(); } while (msec == 0); m_factor = MyMoneyMoney(msec, 10).reduce(); } MyMoneyStorageANON::~MyMoneyStorageANON() { } void MyMoneyStorageANON::readFile(QIODevice* , MyMoneyStorageMgr*) { throw MYMONEYEXCEPTION_CSTRING("Cannot read a file through MyMoneyStorageANON!!"); } void MyMoneyStorageANON::writeUserInformation(QDomElement& userInfo) { MyMoneyPayee user = m_storage->user(); userInfo.setAttribute(QString("name"), hideString(user.name())); userInfo.setAttribute(QString("email"), hideString(user.email())); QDomElement address = m_doc->createElement("ADDRESS"); address.setAttribute(QString("street"), hideString(user.address())); address.setAttribute(QString("city"), hideString(user.city())); address.setAttribute(QString("county"), hideString(user.state())); address.setAttribute(QString("zipcode"), hideString(user.postcode())); address.setAttribute(QString("telephone"), hideString(user.telephone())); userInfo.appendChild(address); } void MyMoneyStorageANON::writeInstitution(QDomElement& institution, const MyMoneyInstitution& _i) { MyMoneyInstitution i(_i); // mangle fields i.setName(i.id()); i.setManager(hideString(i.manager())); i.setSortcode(hideString(i.sortcode())); i.setStreet(hideString(i.street())); i.setCity(hideString(i.city())); i.setPostcode(hideString(i.postcode())); i.setTelephone(hideString(i.telephone())); MyMoneyStorageXML::writeInstitution(institution, i); } void MyMoneyStorageANON::writePayee(QDomElement& payee, const MyMoneyPayee& _p) { MyMoneyPayee p(_p); p.setName(p.id()); p.setReference(hideString(p.reference())); p.setAddress(hideString(p.address())); p.setCity(hideString(p.city())); p.setPostcode(hideString(p.postcode())); p.setState(hideString(p.state())); p.setTelephone(hideString(p.telephone())); p.setNotes(hideString(p.notes())); bool ignoreCase; QStringList keys; auto matchType = p.matchData(ignoreCase, keys); QRegExp exp("[A-Za-z]"); p.setMatchData(matchType, ignoreCase, keys.join(";").replace(exp, "x").split(';')); // Data from plugins cannot be estranged, yet. p.resetPayeeIdentifiers(); MyMoneyStorageXML::writePayee(payee, p); } void MyMoneyStorageANON::writeTag(QDomElement& tag, const MyMoneyTag& _ta) { MyMoneyTag ta(_ta); ta.setName(ta.id()); ta.setNotes(hideString(ta.notes())); MyMoneyStorageXML::writeTag(tag, ta); } +void MyMoneyStorageANON::writeAccounts(QDomElement& accounts) +{ + // keep an account list to allow changing brokerage accounts accordingly + m_storage->accountList(m_accountList); + MyMoneyStorageXML::writeAccounts(accounts); +} + void MyMoneyStorageANON::writeAccount(QDomElement& account, const MyMoneyAccount& _p) { MyMoneyAccount p(_p); + const auto isBrokerageAccount = p.name().contains(i18n(" (Brokerage)")); p.setNumber(hideString(p.number())); p.setName(p.id()); + if (isBrokerageAccount) { + // search the name of the corresponding investment account + // and setup the name according to the rule of brokerage accounts + foreach(const auto acc, m_accountList) { + if (acc.accountType() == eMyMoney::Account::Type::Investment + && _p.name() == i18n("%1 (Brokerage)", acc.name()) ) { + p.setName(i18n("%1 (Brokerage)", acc.id())); + break; + } + } + } p.setDescription(hideString(p.description())); fakeKeyValuePair(p); // Remove the online banking settings entirely. p.setOnlineBankingSettings(MyMoneyKeyValueContainer()); MyMoneyStorageXML::writeAccount(account, p); } void MyMoneyStorageANON::fakeTransaction(MyMoneyTransaction& tx) { MyMoneyTransaction tn = tx; // hide transaction data tn.setMemo(tx.id()); tn.setBankID(hideString(tx.bankID())); // hide split data foreach (const auto split, tx.splits()) { MyMoneySplit s = split; s.setMemo(QString("%1/%2").arg(tn.id()).arg(s.id())); if (s.value() != MyMoneyMoney::autoCalc) { s.setValue((s.value() * m_factor)); s.setShares((s.shares() * m_factor)); } s.setNumber(hideString(s.number())); // obfuscate a possibly matched transaction as well if (s.isMatched()) { MyMoneyTransaction t = s.matchedTransaction(); fakeTransaction(t); s.removeMatch(); s.addMatch(t); } tn.modifySplit(s); } tx = tn; fakeKeyValuePair(tx); } void MyMoneyStorageANON::fakeKeyValuePair(MyMoneyKeyValueContainer& kvp) { QMap pairs; QMap::const_iterator it; for (it = kvp.pairs().constBegin(); it != kvp.pairs().constEnd(); ++it) { if (zKvpXNumber.contains(it.key()) || it.key().left(3) == "ir-") pairs[it.key()] = hideNumber(MyMoneyMoney(it.value())).toString(); else if (zKvpNoModify.contains(it.key())) pairs[it.key()] = it.value(); else pairs[it.key()] = hideString(it.value()); } kvp.setPairs(pairs); } void MyMoneyStorageANON::writeTransaction(QDomElement& transactions, const MyMoneyTransaction& tx) { MyMoneyTransaction tn = tx; fakeTransaction(tn); MyMoneyStorageXML::writeTransaction(transactions, tn); } void MyMoneyStorageANON::writeSchedule(QDomElement& scheduledTx, const MyMoneySchedule& sx) { MyMoneySchedule sn = sx; MyMoneyTransaction tn = sn.transaction(); fakeTransaction(tn); sn.setName(sx.id()); sn.setTransaction(tn, true); MyMoneyStorageXML::writeSchedule(scheduledTx, sn); } void MyMoneyStorageANON::writeSecurity(QDomElement& securityElement, const MyMoneySecurity& security) { MyMoneySecurity s = security; s.setName(security.id()); fakeKeyValuePair(s); MyMoneyStorageXML::writeSecurity(securityElement, s); } QString MyMoneyStorageANON::hideString(const QString& _in) const { return QString(_in).fill('x'); } MyMoneyMoney MyMoneyStorageANON::hideNumber(const MyMoneyMoney& _in) const { MyMoneyMoney result; static MyMoneyMoney counter = MyMoneyMoney(100, 100); // preserve sign if (_in.isNegative()) result = MyMoneyMoney::MINUS_ONE; else result = MyMoneyMoney::ONE; result = result * counter; counter += MyMoneyMoney("10/100"); // preserve > 1000 if (_in >= MyMoneyMoney(1000, 1)) result = result * MyMoneyMoney(1000, 1); if (_in <= MyMoneyMoney(-1000, 1)) result = result * MyMoneyMoney(1000, 1); return result.convert(); } void MyMoneyStorageANON::fakeBudget(MyMoneyBudget& bx) { MyMoneyBudget bn; bn.setName(bx.id()); bn.setBudgetStart(bx.budgetStart()); bn = MyMoneyBudget(bx.id(), bn); QList list = bx.getaccounts(); QList::iterator it; for (it = list.begin(); it != list.end(); ++it) { // only add the account if there is a budget entered if (!(*it).balance().isZero()) { MyMoneyBudget::AccountGroup account; account.setId((*it).id()); account.setBudgetLevel((*it).budgetLevel()); account.setBudgetSubaccounts((*it).budgetSubaccounts()); QMap plist = (*it).getPeriods(); QMap::const_iterator it_p; for (it_p = plist.constBegin(); it_p != plist.constEnd(); ++it_p) { MyMoneyBudget::PeriodGroup pGroup; pGroup.setAmount((*it_p).amount() * m_factor); pGroup.setStartDate((*it_p).startDate()); account.addPeriod(pGroup.startDate(), pGroup); } bn.setAccount(account, account.id()); } } bx = bn; } void MyMoneyStorageANON::writeBudget(QDomElement& budgets, const MyMoneyBudget& b) { MyMoneyBudget bn = b; fakeBudget(bn); MyMoneyStorageXML::writeBudget(budgets, bn); } void MyMoneyStorageANON::writeReport(QDomElement& reports, const MyMoneyReport& r) { MyMoneyReport rn = r; rn.setName(rn.id()); rn.setComment(hideString(rn.comment())); MyMoneyStorageXML::writeReport(reports, rn); } void MyMoneyStorageANON::writeOnlineJob(QDomElement& onlineJobs, const onlineJob& job) { Q_UNUSED(onlineJobs); Q_UNUSED(job); } diff --git a/kmymoney/plugins/xml/mymoneystorageanon.h b/kmymoney/plugins/xml/mymoneystorageanon.h index 73768572a..aff80ad37 100644 --- a/kmymoney/plugins/xml/mymoneystorageanon.h +++ b/kmymoney/plugins/xml/mymoneystorageanon.h @@ -1,114 +1,116 @@ /* * Copyright 2004-2006 Ace Jones * Copyright 2005-2017 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. * * 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 MYMONEYSTORAGEANON_H #define MYMONEYSTORAGEANON_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneystoragexml.h" #include "mymoneymoney.h" class MyMoneyKeyValueContainer; /** * @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 /** * This class provides storage of an anonymized version of the current * file. Any object with an ID (account, transaction, etc) is renamed * with that ID. Any other string value the user typed in is replaced with * x's equal in length to the original string. Any numeric value is * replaced with an arbitrary number which matches the sign of the original. * * The purpose of this class is to give users a way to send a developer * their file without compromising their financial data. If a user * encounters an error, they should try saving the anonymous version of the * file and see if the error is still there. If so, they should notify the * list of the problem, and then when requested, send the anonymous file * privately to the developer who takes the problem. I still don't think * it's wise to post the file to the public list...maybe I'm just paranoid. * * @author Ace Jones */ class MyMoneyStorageANON : public MyMoneyStorageXML { public: MyMoneyStorageANON(); virtual ~MyMoneyStorageANON(); protected: void writeUserInformation(QDomElement& userInfo) final override; void writeInstitution(QDomElement& institutions, const MyMoneyInstitution& i) final override; void writePayee(QDomElement& payees, const MyMoneyPayee& p) final override; void writeTag(QDomElement& tags, const MyMoneyTag& ta) final override; void writeAccount(QDomElement& accounts, const MyMoneyAccount& p) final override; void writeTransaction(QDomElement& transactions, const MyMoneyTransaction& tx) final override; void writeSchedule(QDomElement& scheduledTx, const MyMoneySchedule& tx) final override; void writeBudget(QDomElement& budgets, const MyMoneyBudget& b) final override; void writeReport(QDomElement& reports, const MyMoneyReport& r) final override; void readFile(QIODevice* s, MyMoneyStorageMgr* storage) final override; void writeSecurity(QDomElement& securityElement, const MyMoneySecurity& security) final override; /** Cannot remove prive data from plugins, yet. It is simply doing nothing. */ void writeOnlineJob(QDomElement& onlineJobs, const onlineJob& job) final override; QDomElement findChildElement(const QString& name, const QDomElement& root); + void writeAccounts(QDomElement& accounts) final override; private: /** * The list of key-value pairs to not modify */ static QStringList zKvpNoModify; /** * The list of key-value pairs which are numbers to be hidden */ static QStringList zKvpXNumber; QString hideString(const QString&) const; MyMoneyMoney hideNumber(const MyMoneyMoney&) const; void fakeTransaction(MyMoneyTransaction& tn); void fakeBudget(MyMoneyBudget& bn); void fakeKeyValuePair(MyMoneyKeyValueContainer& _kvp); - MyMoneyMoney m_factor; + MyMoneyMoney m_factor; + QList m_accountList; }; #endif diff --git a/kmymoney/plugins/xml/xmlstorage.cpp b/kmymoney/plugins/xml/xmlstorage.cpp index 559f8fb16..f8d6f1ed1 100644 --- a/kmymoney/plugins/xml/xmlstorage.cpp +++ b/kmymoney/plugins/xml/xmlstorage.cpp @@ -1,553 +1,552 @@ /* * 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"; // 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 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")); // For information, announce that we have been loaded. qDebug("Plugins: xmlstorage loaded"); checkRecoveryKeyValidity(); } XMLStorage::~XMLStorage() { qDebug("Plugins: xmlstorage unloaded"); } MyMoneyStorageMgr *XMLStorage::open(const QUrl &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); QRegExp kmyexp(""); QByteArray txt(qbaFileHeader, 70); if (kmyexp.indexIn(txt) == -1) return nullptr; // attach the storage before reading the file, since the online // onlineJobAdministration object queries the engine during // loading. 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); // make sure we setup the encryption key correctly 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.toLocalFile(); 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"); 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 &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; } bool XMLStorage::saveAs() { 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("*.xml")).arg(i18nc("XML (Filefilter)", "XML 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; } eKMyMoney::StorageType XMLStorage::storageType() const { return eKMyMoney::StorageType::XML; } QString XMLStorage::fileExtension() const { 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; + QTemporaryFile tmpFile(writeFile); 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. // on Windows QTemporaryFile does not release file handle even after close() // so QFile::rename(writeFile, localFile) will fail since Windows does not allow moving files in use // as a workaround QFile::copy is used instead of QFile::rename below // writeFile (i.e. tmpFile) will be deleted by QTemporaryFile dtor when it falls out of scope if (!QFile::remove(localFile) || !QFile::copy(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::checkRecoveryKeyValidity() { // 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 = kconfig->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(nullptr, 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")); } } } } } K_PLUGIN_FACTORY_WITH_JSON(XMLStorageFactory, "xmlstorage.json", registerPlugin();) #include "xmlstorage.moc" diff --git a/kmymoney/wizards/newuserwizard/kaccountpage.cpp b/kmymoney/wizards/newuserwizard/kaccountpage.cpp index f5091ef12..8a2e31a04 100644 --- a/kmymoney/wizards/newuserwizard/kaccountpage.cpp +++ b/kmymoney/wizards/newuserwizard/kaccountpage.cpp @@ -1,76 +1,81 @@ /*************************************************************************** kaccountpage.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 "kaccountpage.h" #include "kaccountpage_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kaccountpage.h" #include "kmymoneydateinput.h" #include "knewuserwizard.h" #include "knewuserwizard_p.h" #include "kcategoriespage.h" #include "wizardpage.h" #include "kguiutils.h" namespace NewUserWizard { AccountPage::AccountPage(Wizard* wizard) : QWidget(wizard), WizardPage(*new AccountPagePrivate(wizard), stepCount, this, wizard) // don't inc. the step count here { Q_D(AccountPage); d->ui->setupUi(this); d->m_mandatoryGroup->add(d->ui->m_accountNameEdit); connect(d->m_mandatoryGroup, static_cast(&KMandatoryFieldGroup::stateChanged), object(), &KMyMoneyWizardPagePrivate::completeStateChanged); connect(d->ui->m_haveCheckingAccountButton, &QAbstractButton::toggled, object(), &KMyMoneyWizardPagePrivate::completeStateChanged); - d->ui->m_accountNameEdit->setFocus(); d->ui->m_openingDateEdit->setDate(QDate(QDate::currentDate().year(), 1, 1)); } AccountPage::~AccountPage() { } + void AccountPage::enterPage() + { + Q_D(AccountPage); + d->ui->m_accountNameEdit->setFocus(); + } + KMyMoneyWizardPage* AccountPage::nextPage() const { Q_D(const AccountPage); return d->m_wizard->d_func()->m_categoriesPage; } bool AccountPage::isComplete() const { Q_D(const AccountPage); return !d->ui->m_haveCheckingAccountButton->isChecked() || d->m_mandatoryGroup->isEnabled(); } } diff --git a/kmymoney/wizards/newuserwizard/kaccountpage.h b/kmymoney/wizards/newuserwizard/kaccountpage.h index 8111bb025..2964e91bc 100644 --- a/kmymoney/wizards/newuserwizard/kaccountpage.h +++ b/kmymoney/wizards/newuserwizard/kaccountpage.h @@ -1,61 +1,63 @@ /*************************************************************************** kaccountpage.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 KACCOUNTPAGE_H #define KACCOUNTPAGE_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "wizardpage.h" class KMyMoneyWizardPage; namespace NewUserWizard { class Wizard; /** * Wizard page collecting information about the checking account */ class AccountPagePrivate; class AccountPage : public QWidget, public WizardPage { Q_OBJECT Q_DISABLE_COPY(AccountPage) public: explicit AccountPage(Wizard* parent); ~AccountPage() override; KMyMoneyWizardPage* nextPage() const override; bool isComplete() const override; + void enterPage() override; + private: Q_DECLARE_PRIVATE_D(WizardPage::d_ptr, AccountPage) friend class Wizard; friend class CurrencyPage; }; } // namespace #endif diff --git a/kmymoney/wizards/newuserwizard/kgeneralpage.cpp b/kmymoney/wizards/newuserwizard/kgeneralpage.cpp index 6a5d06d00..46c251dc6 100644 --- a/kmymoney/wizards/newuserwizard/kgeneralpage.cpp +++ b/kmymoney/wizards/newuserwizard/kgeneralpage.cpp @@ -1,114 +1,120 @@ /*************************************************************************** kgeneralpage.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 "kgeneralpage.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "userinfo.h" #include "ui_userinfo.h" #include "knewuserwizard.h" #include "knewuserwizard_p.h" #include "mymoneycontact.h" #include "kcurrencypage.h" class KMyMoneyWizardPage; namespace NewUserWizard { class GeneralPagePrivate : public WizardPagePrivate { Q_DISABLE_COPY(GeneralPagePrivate) public: GeneralPagePrivate(QObject* parent) : WizardPagePrivate(parent), m_contact(nullptr) { } MyMoneyContact *m_contact; }; GeneralPage::GeneralPage(Wizard* wizard) : UserInfo(wizard), WizardPage(*new GeneralPagePrivate(wizard), stepCount++, this, wizard) { Q_D(GeneralPage); d->m_contact = new MyMoneyContact(this); - ui->m_userNameEdit->setFocus(); ui->m_loadAddressButton->setEnabled(d->m_contact->ownerExists()); connect(ui->m_loadAddressButton, &QAbstractButton::clicked, this, &GeneralPage::slotLoadFromAddressBook); } GeneralPage::~GeneralPage() { } + void GeneralPage::enterPage() + { + Q_D(GeneralPage); + ui->m_userNameEdit->setFocus(); + } + + void GeneralPage::slotLoadFromAddressBook() { Q_D(GeneralPage); ui->m_userNameEdit->setText(d->m_contact->ownerFullName()); ui->m_emailEdit->setText(d->m_contact->ownerEmail()); if (ui->m_emailEdit->text().isEmpty()) { KMessageBox::sorry(this, i18n("Unable to load data, because no contact has been associated with the owner of the standard address book."), i18n("Address book import")); return; } ui->m_loadAddressButton->setEnabled(false); connect(d->m_contact, &MyMoneyContact::contactFetched, this, &GeneralPage::slotContactFetched); d->m_contact->fetchContact(ui->m_emailEdit->text()); } void GeneralPage::slotContactFetched(const ContactData &identity) { ui->m_loadAddressButton->setEnabled(true); if (identity.email.isEmpty()) return; ui->m_telephoneEdit->setText(identity.phoneNumber); QString sep; if (!identity.country.isEmpty() && !identity.region.isEmpty()) sep = " / "; ui->m_countyEdit->setText(QString("%1%2%3").arg(identity.country, sep, identity.region)); ui->m_postcodeEdit->setText(identity.postalCode); ui->m_townEdit->setText(identity.locality); ui->m_streetEdit->setText(identity.street); } KMyMoneyWizardPage* GeneralPage::nextPage() const { Q_D(const GeneralPage); return d->m_wizard->d_func()->m_currencyPage; } } diff --git a/kmymoney/wizards/newuserwizard/kgeneralpage.h b/kmymoney/wizards/newuserwizard/kgeneralpage.h index dd1dfc6e6..5c080997e 100644 --- a/kmymoney/wizards/newuserwizard/kgeneralpage.h +++ b/kmymoney/wizards/newuserwizard/kgeneralpage.h @@ -1,63 +1,65 @@ /*************************************************************************** kgeneralpage.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 KGENERALPAGE_H #define KGENERALPAGE_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // Project Includes #include "wizardpage.h" #include "userinfo.h" class KMyMoneyWizardPage; struct ContactData; namespace NewUserWizard { class Wizard; /** * Wizard page collecting information about the user * * @author Thomas Baumgart */ class GeneralPagePrivate; class GeneralPage : public UserInfo, public WizardPage { Q_OBJECT Q_DISABLE_COPY(GeneralPage) public: explicit GeneralPage(Wizard* parent); ~GeneralPage() override; KMyMoneyWizardPage* nextPage() const override; + void enterPage() override; + protected Q_SLOTS: void slotLoadFromAddressBook(); void slotContactFetched(const ContactData &identity); private: Q_DECLARE_PRIVATE_D(WizardPage::d_ptr, GeneralPage) friend class Wizard; }; } // namespace #endif