File indexing completed on 2024-05-19 05:06:50

0001 /*
0002     SPDX-FileCopyrightText: 2004-2021 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "kcurrencycalculator.h"
0008 
0009 // ----------------------------------------------------------------------------
0010 // QT Includes
0011 
0012 #include <QButtonGroup>
0013 #include <QLabel>
0014 #include <QRadioButton>
0015 #include <QCheckBox>
0016 #include <QPushButton>
0017 #include <QDialogButtonBox>
0018 #include <QPointer>
0019 
0020 // ----------------------------------------------------------------------------
0021 // KDE Includes
0022 
0023 #include <KLocalizedString>
0024 
0025 // ----------------------------------------------------------------------------
0026 // Project Includes
0027 
0028 #include "ui_kcurrencycalculator.h"
0029 
0030 #include "kmymoneysettings.h"
0031 #include "mymoneyaccount.h"
0032 #include "mymoneyexception.h"
0033 #include "mymoneyfile.h"
0034 #include "mymoneymoney.h"
0035 #include "mymoneyprice.h"
0036 #include "mymoneysecurity.h"
0037 #include "mymoneysplit.h"
0038 #include "mymoneytransaction.h"
0039 #include "mymoneyutils.h"
0040 
0041 class KCurrencyCalculatorPrivate
0042 {
0043     Q_DISABLE_COPY(KCurrencyCalculatorPrivate)
0044     Q_DECLARE_PUBLIC(KCurrencyCalculator)
0045 
0046 public:
0047     explicit KCurrencyCalculatorPrivate(KCurrencyCalculator* qq)
0048         : q_ptr(qq)
0049         , ui(new Ui::KCurrencyCalculator)
0050         , m_resultFraction(100)
0051     {
0052     }
0053 
0054     KCurrencyCalculatorPrivate(KCurrencyCalculator* qq,
0055                                const MyMoneySecurity& from,
0056                                const MyMoneySecurity& to,
0057                                const MyMoneyMoney& fromAmount,
0058                                const MyMoneyMoney& toAmount,
0059                                const QDate& date,
0060                                const signed64 resultFraction)
0061         : q_ptr(qq)
0062         , ui(new Ui::KCurrencyCalculator)
0063         , m_fromCurrency(from)
0064         , m_toCurrency(to)
0065         , m_toAmount(toAmount.abs())
0066         , m_fromAmount(fromAmount.abs())
0067         , m_date(date)
0068         , m_resultFraction(resultFraction)
0069     {
0070     }
0071 
0072     ~KCurrencyCalculatorPrivate()
0073     {
0074         delete ui;
0075     }
0076 
0077     void init()
0078     {
0079         Q_Q(KCurrencyCalculator);
0080         ui->setupUi(q);
0081         auto file = MyMoneyFile::instance();
0082 
0083         // set main widget of QDialog
0084         ui->buttonGroup1->setId(ui->m_amountButton, 0);
0085         ui->buttonGroup1->setId(ui->m_rateButton, 1);
0086 
0087         ui->m_dateFrame->hide();
0088 
0089         // set bold font
0090         auto boldFont = ui->m_fromCurrencyText->font();
0091         boldFont.setBold(true);
0092         ui->m_fromCurrencyText->setFont(boldFont);
0093         ui->m_toCurrencyText->setFont(boldFont);
0094 
0095         ui->m_updateButton->setChecked(KMyMoneySettings::priceHistoryUpdate());
0096 
0097         // setup initial result
0098         if (m_toAmount.isZero() && !m_fromAmount.isZero()) {
0099             const MyMoneyPrice &pr = file->price(m_fromCurrency.id(), m_toCurrency.id(), m_date);
0100             if (pr.isValid()) {
0101                 m_toAmount = m_fromAmount * pr.rate(m_toCurrency.id());
0102             }
0103         }
0104 
0105         // fill in initial values
0106         ui->m_toAmount->setCommodity(MyMoneySecurity());
0107         ui->m_toAmount->setPrecision(-1);
0108         ui->m_toAmount->setValue(m_toAmount);
0109 
0110         ui->m_conversionRate->setCommodity(MyMoneySecurity());
0111         ui->m_conversionRate->setPrecision(-1);
0112 
0113         q->connect(ui->m_amountButton, &QAbstractButton::clicked, q, &KCurrencyCalculator::slotSetToAmount);
0114         q->connect(ui->m_rateButton, &QAbstractButton::clicked, q, &KCurrencyCalculator::slotSetExchangeRate);
0115 
0116         q->connect(ui->m_toAmount, &AmountEdit::textChanged, q, &KCurrencyCalculator::slotUpdateResult);
0117         q->connect(ui->m_conversionRate, &AmountEdit::textChanged, q, &KCurrencyCalculator::slotUpdateRate);
0118 
0119         q->connect(ui->m_toAmount, &AmountEdit::returnPressed, q, &KCurrencyCalculator::accept);
0120         q->connect(ui->m_conversionRate, &AmountEdit::returnPressed, q, &KCurrencyCalculator::accept);
0121 
0122         // use this as the default
0123         ui->m_amountButton->animateClick();
0124         q->slotUpdateResult(ui->m_toAmount->text());
0125 
0126         // If the from security is not a currency, we only allow entering a price
0127         if (!m_fromCurrency.isCurrency()) {
0128             ui->m_rateButton->animateClick();
0129             ui->m_amountButton->hide();
0130             ui->m_toAmount->hide();
0131         }
0132 
0133         updateWidgets();
0134     }
0135 
0136     void updateExample(const MyMoneyMoney& price)
0137     {
0138         QString msg;
0139         if (price.isZero()) {
0140             msg = QString("1 %1 = ? %2").arg(m_fromCurrency.tradingSymbol())
0141                   .arg(m_toCurrency.tradingSymbol());
0142             if (m_fromCurrency.isCurrency()) {
0143                 msg += QString("\n");
0144                 msg += QString("1 %1 = ? %2").arg(m_toCurrency.tradingSymbol())
0145                        .arg(m_fromCurrency.tradingSymbol());
0146             }
0147         } else {
0148             msg = QString("1 %1 = %2 %3").arg(m_fromCurrency.tradingSymbol())
0149                   .arg(price.formatMoney(QString(), m_fromCurrency.pricePrecision()))
0150                   .arg(m_toCurrency.tradingSymbol());
0151             if (m_fromCurrency.isCurrency()) {
0152                 msg += QString("\n");
0153                 msg += QString("1 %1 = %2 %3").arg(m_toCurrency.tradingSymbol())
0154                        .arg((MyMoneyMoney::ONE / price).formatMoney(QString(), m_toCurrency.pricePrecision()))
0155                        .arg(m_fromCurrency.tradingSymbol());
0156             }
0157         }
0158         ui->m_conversionExample->setText(msg);
0159         ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!price.isZero());
0160     }
0161 
0162     void updateWidgets()
0163     {
0164         if (m_date.isValid())
0165             ui->m_dateEdit->setDate(m_date);
0166         else
0167             ui->m_dateEdit->setDate(QDate::currentDate());
0168 
0169         ui->m_dateText->setText(MyMoneyUtils::formatDate(m_date));
0170 
0171         ui->m_fromCurrencyText->setText(QStringLiteral("%1 %2").arg(MyMoneySecurity::securityTypeToString(m_fromCurrency.securityType()),
0172                                                                     (m_fromCurrency.isCurrency() ? m_fromCurrency.id() : m_fromCurrency.tradingSymbol())));
0173         ui->m_toCurrencyText->setText(QStringLiteral("%1 %2").arg(MyMoneySecurity::securityTypeToString(m_toCurrency.securityType()),
0174                                                                   (m_toCurrency.isCurrency() ? m_toCurrency.id() : m_toCurrency.tradingSymbol())));
0175 
0176         ui->m_fromAmount->setText(m_fromAmount.formatMoney(QString(), MyMoneyMoney::denomToPrec(m_fromCurrency.smallestAccountFraction())));
0177     }
0178 
0179     KCurrencyCalculator* q_ptr;
0180     Ui::KCurrencyCalculator *ui;
0181     MyMoneySecurity m_fromCurrency;
0182     MyMoneySecurity m_toCurrency;
0183     MyMoneyMoney m_toAmount;
0184     MyMoneyMoney m_fromAmount;
0185     QDate m_date;
0186     signed64 m_resultFraction;
0187 };
0188 
0189 KCurrencyCalculator::KCurrencyCalculator(QWidget* parent)
0190     : QDialog(parent)
0191     , d_ptr(new KCurrencyCalculatorPrivate(this))
0192 {
0193     Q_D(KCurrencyCalculator);
0194     d->init();
0195 }
0196 
0197 KCurrencyCalculator::KCurrencyCalculator(const MyMoneySecurity& from,
0198         const MyMoneySecurity& to,
0199         const MyMoneyMoney& value,
0200         const MyMoneyMoney& shares,
0201         const QDate& date,
0202         const signed64 resultFraction,
0203         QWidget *parent) :
0204     QDialog(parent),
0205     d_ptr(new KCurrencyCalculatorPrivate(this,
0206                                          from,
0207                                          to,
0208                                          value,
0209                                          shares,
0210                                          date,
0211                                          resultFraction))
0212 {
0213     Q_D(KCurrencyCalculator);
0214     d->init();
0215 }
0216 
0217 KCurrencyCalculator::~KCurrencyCalculator()
0218 {
0219     Q_D(KCurrencyCalculator);
0220     delete d;
0221 }
0222 
0223 void KCurrencyCalculator::setDate(const QDate& date)
0224 {
0225     Q_D(KCurrencyCalculator);
0226     d->m_date = date;
0227     if (date.isValid())
0228         d->ui->m_dateEdit->setDate(date);
0229     else
0230         d->ui->m_dateEdit->setDate(QDate::currentDate());
0231 
0232     d->ui->m_dateText->setText(MyMoneyUtils::formatDate(date));
0233 }
0234 
0235 void KCurrencyCalculator::setFromCurrency(const MyMoneySecurity& sec)
0236 {
0237     Q_D(KCurrencyCalculator);
0238     d->m_fromCurrency = sec;
0239     d->ui->m_fromCurrencyText->setText(
0240         QString(MyMoneySecurity::securityTypeToString(sec.securityType()) + ' ' + (sec.isCurrency() ? sec.id() : sec.tradingSymbol())));
0241 
0242     // If the from security is not a currency, we only allow entering a price
0243     if (!sec.isCurrency()) {
0244         d->ui->m_rateButton->animateClick();
0245         d->ui->m_amountButton->hide();
0246         d->ui->m_toAmount->hide();
0247     } else {
0248         d->ui->m_amountButton->show();
0249         d->ui->m_toAmount->show();
0250     }
0251     d->updateWidgets();
0252 }
0253 
0254 void KCurrencyCalculator::setToCurrency(const MyMoneySecurity& sec)
0255 {
0256     Q_D(KCurrencyCalculator);
0257     d->m_toCurrency = sec;
0258     d->ui->m_toCurrencyText->setText(
0259         QString(MyMoneySecurity::securityTypeToString(sec.securityType()) + ' ' + (sec.isCurrency() ? sec.id() : sec.tradingSymbol())));
0260     d->updateWidgets();
0261 }
0262 
0263 void KCurrencyCalculator::setFromAmount(const MyMoneyMoney& amount)
0264 {
0265     Q_D(KCurrencyCalculator);
0266     d->m_fromAmount = amount;
0267     d->updateWidgets();
0268 }
0269 
0270 void KCurrencyCalculator::setToAmount(const MyMoneyMoney& amount)
0271 {
0272     Q_D(KCurrencyCalculator);
0273     d->m_toAmount = amount;
0274     d->updateWidgets();
0275 }
0276 
0277 void KCurrencyCalculator::setResultFraction(signed64 fraction)
0278 {
0279     Q_D(KCurrencyCalculator);
0280     d->m_resultFraction = fraction;
0281     d->updateWidgets();
0282 }
0283 
0284 bool KCurrencyCalculator::setupSplitPrice(MyMoneyMoney& shares,
0285         const MyMoneyTransaction& t,
0286         const MyMoneySplit& s,
0287         const QMap<QString,
0288         MyMoneyMoney>& priceInfo,
0289         QWidget* parentWidget)
0290 {
0291     auto rc = true;
0292     auto file = MyMoneyFile::instance();
0293 
0294     if (!s.value().isZero()) {
0295         auto cat = file->account(s.accountId());
0296         MyMoneySecurity toCurrency;
0297         toCurrency = file->security(cat.currencyId());
0298         // determine the fraction required for this category/account
0299         int fract = cat.fraction(toCurrency);
0300 
0301         if (cat.currencyId() != t.commodity()) {
0302 
0303             MyMoneyMoney toValue;
0304             auto fromCurrency = file->security(t.commodity());
0305             // display only positive values to the user
0306             auto fromValue = s.value().abs();
0307 
0308             // if we had a price info in the beginning, we use it here
0309             if (priceInfo.find(cat.currencyId()) != priceInfo.end()) {
0310                 toValue = (fromValue * priceInfo[cat.currencyId()]).convert(fract);
0311             }
0312             // if the shares are still 0, we need to change that
0313             if (toValue.isZero()) {
0314                 const MyMoneyPrice& storedPrice = file->price(fromCurrency.id(), toCurrency.id(), t.postDate());
0315                 // if the price is valid calculate the shares. If it is invalid
0316                 // assume a conversion rate of 1.0
0317                 if (storedPrice.isValid()) {
0318                     toValue = (storedPrice.rate(toCurrency.id()) * fromValue).convert(fract);
0319                 } else {
0320                     toValue = fromValue;
0321                 }
0322             }
0323 
0324             // now present all that to the user
0325             QPointer<KCurrencyCalculator> calc =
0326                 new KCurrencyCalculator(fromCurrency,
0327                                         toCurrency,
0328                                         fromValue,
0329                                         toValue,
0330                                         t.postDate(),
0331                                         10000000000,
0332                                         parentWidget);
0333 
0334             if (calc->exec() == QDialog::Rejected) {
0335                 rc = false;
0336             } else
0337                 shares = (s.value() * calc->price()).convert(fract);
0338 
0339             delete calc;
0340 
0341         } else {
0342             shares = s.value().convert(fract);
0343         }
0344     } else
0345         shares = s.value();
0346 
0347     return rc;
0348 }
0349 
0350 void KCurrencyCalculator::setupPriceEditor()
0351 {
0352     Q_D(KCurrencyCalculator);
0353     d->ui->m_dateFrame->show();
0354     d->ui->m_amountDateFrame->hide();
0355     d->ui->m_updateButton->setChecked(true);
0356     d->ui->m_updateButton->hide();
0357 }
0358 
0359 void KCurrencyCalculator::slotSetToAmount()
0360 {
0361     Q_D(KCurrencyCalculator);
0362     d->ui->m_rateButton->setChecked(false);
0363     d->ui->m_toAmount->setEnabled(true);
0364     d->ui->m_conversionRate->setEnabled(false);
0365 }
0366 
0367 void KCurrencyCalculator::slotSetExchangeRate()
0368 {
0369     Q_D(KCurrencyCalculator);
0370     d->ui->m_amountButton->setChecked(false);
0371     d->ui->m_toAmount->setEnabled(false);
0372     d->ui->m_conversionRate->setEnabled(true);
0373 }
0374 
0375 void KCurrencyCalculator::slotUpdateResult(const QString& /*txt*/)
0376 {
0377     Q_D(KCurrencyCalculator);
0378     MyMoneyMoney result = d->ui->m_toAmount->value();
0379     MyMoneyMoney calculatedPrice(MyMoneyMoney::ONE);
0380 
0381     if (result.isNegative()) {
0382         d->ui->m_toAmount->setValue(-result);
0383         slotUpdateResult(QString());
0384         return;
0385     }
0386 
0387     if (!result.isZero() && !d->m_fromAmount.isZero()) {
0388         calculatedPrice = result / d->m_fromAmount;
0389 
0390         d->ui->m_conversionRate->setValue(calculatedPrice);
0391         d->m_toAmount = (d->m_fromAmount * calculatedPrice).convert(d->m_resultFraction);
0392 
0393         d->ui->m_toAmount->setValue(d->m_toAmount);
0394     }
0395     d->updateExample(calculatedPrice);
0396 }
0397 
0398 void KCurrencyCalculator::slotUpdateRate(const QString& /*txt*/)
0399 {
0400     Q_D(KCurrencyCalculator);
0401     auto calculatedPrice = d->ui->m_conversionRate->value();
0402 
0403     if (calculatedPrice.isNegative()) {
0404         d->ui->m_conversionRate->setValue(-calculatedPrice);
0405         slotUpdateRate(QString());
0406         return;
0407     }
0408 
0409     if (!calculatedPrice.isZero()) {
0410         d->ui->m_conversionRate->setValue(calculatedPrice);
0411         d->m_toAmount = (d->m_fromAmount * calculatedPrice).convert(d->m_resultFraction);
0412         d->ui->m_toAmount->setValue(d->m_toAmount);
0413     }
0414     d->updateExample(calculatedPrice);
0415 }
0416 
0417 void KCurrencyCalculator::accept()
0418 {
0419     Q_D(KCurrencyCalculator);
0420     if (d->ui->m_conversionRate->isEnabled())
0421         slotUpdateRate(QString());
0422     else
0423         slotUpdateResult(QString());
0424 
0425     if (d->ui->m_updateButton->isChecked()) {
0426         auto pr = MyMoneyFile::instance()->price(d->m_fromCurrency.id(), d->m_toCurrency.id(), d->ui->m_dateEdit->date());
0427         if (!pr.isValid() //
0428                 || pr.date() != d->ui->m_dateEdit->date() //
0429                 || (pr.date() == d->ui->m_dateEdit->date() && pr.rate(d->m_fromCurrency.id()) != price())) {
0430             pr = MyMoneyPrice(d->m_fromCurrency.id(), d->m_toCurrency.id(), d->ui->m_dateEdit->date(), price(), i18nc("@info price source", "User"));
0431             MyMoneyFileTransaction ft;
0432             try {
0433                 MyMoneyFile::instance()->addPrice(pr);
0434                 ft.commit();
0435             } catch (const MyMoneyException &) {
0436                 qDebug("Cannot add price");
0437             }
0438         }
0439     }
0440 
0441     // remember setting for next round
0442     KMyMoneySettings::setPriceHistoryUpdate(d->ui->m_updateButton->isChecked());
0443     QDialog::accept();
0444 }
0445 
0446 MyMoneyMoney KCurrencyCalculator::price() const
0447 {
0448     Q_D(const KCurrencyCalculator);
0449     // This should fix https://bugs.kde.org/show_bug.cgi?id=205254 and
0450     // https://bugs.kde.org/show_bug.cgi?id=325953 as well as
0451     // https://bugs.kde.org/show_bug.cgi?id=300965
0452     if (d->ui->m_amountButton->isChecked()) {
0453         if (!d->m_fromAmount.isZero()) {
0454             return d->ui->m_toAmount->value().abs() / d->m_fromAmount.abs();
0455         }
0456         return MyMoneyMoney::ONE;
0457     } else
0458         return d->ui->m_conversionRate->value();
0459 }
0460 
0461 void KCurrencyCalculator::updateConversion(MultiCurrencyEdit* amountEdit, const QDate date)
0462 {
0463     // in case the widget does not have multiple currencies we're done
0464     if (!amountEdit->hasMultipleCurrencies())
0465         return;
0466 
0467     const auto file = MyMoneyFile::instance();
0468     QPointer<KCurrencyCalculator> calc;
0469 
0470     MyMoneyMoney fromValue;
0471     MyMoneyMoney toValue;
0472     MyMoneySecurity fromSecurity;
0473     MyMoneySecurity toSecurity;
0474 
0475     const auto state = amountEdit->displayState();
0476     int fraction = MyMoneyMoney::precToDenom(amountEdit->precision(state));
0477 
0478     switch (state) {
0479     case MultiCurrencyEdit::DisplayShares:
0480         fromValue = amountEdit->shares();
0481         toValue = amountEdit->value();
0482         fromSecurity = amountEdit->sharesCommodity();
0483         toSecurity = amountEdit->valueCommodity();
0484         break;
0485 
0486     case MultiCurrencyEdit::DisplayValue:
0487         fromValue = amountEdit->value();
0488         toValue = amountEdit->shares();
0489         fromSecurity = amountEdit->valueCommodity();
0490         toSecurity = amountEdit->sharesCommodity();
0491         break;
0492     }
0493 
0494     // in case the two values are identical, we search for
0495     // a matching conversion rate in the price list and
0496     // apply it.
0497     if (fromValue == toValue) {
0498         const auto rate = file->price(fromSecurity.id(), toSecurity.id(), date).rate(toSecurity.id());
0499         toValue *= rate;
0500     }
0501 
0502     calc = new KCurrencyCalculator(fromSecurity, toSecurity, fromValue, toValue, date, fraction, amountEdit->widget());
0503 
0504     if (calc->exec() == QDialog::Accepted) {
0505         if (calc) {
0506             switch (state) {
0507             case MultiCurrencyEdit::DisplayShares:
0508                 amountEdit->setValue(fromValue * calc->price());
0509                 amountEdit->setShares(fromValue);
0510                 break;
0511             case MultiCurrencyEdit::DisplayValue:
0512                 amountEdit->setValue(fromValue);
0513                 amountEdit->setShares(fromValue * calc->price());
0514                 break;
0515             }
0516         }
0517     }
0518     delete calc;
0519 }