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 }