1 /*
2 * Copyright 2004-2018 Thomas Baumgart <tbaumgart@kde.org>
3 * Copyright 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "kcurrencycalculator.h"
20
21 // ----------------------------------------------------------------------------
22 // QT Includes
23
24 #include <QButtonGroup>
25 #include <QLabel>
26 #include <QRadioButton>
27 #include <QCheckBox>
28 #include <QPushButton>
29 #include <QDialogButtonBox>
30 #include <QPointer>
31
32 // ----------------------------------------------------------------------------
33 // KDE Includes
34
35 #include <KLocalizedString>
36
37 // ----------------------------------------------------------------------------
38 // Project Includes
39
40 #include "ui_kcurrencycalculator.h"
41
42 #include "mymoneyfile.h"
43 #include "mymoneyaccount.h"
44 #include "mymoneysecurity.h"
45 #include "mymoneyprice.h"
46 #include "mymoneymoney.h"
47 #include "mymoneyexception.h"
48 #include "mymoneysplit.h"
49 #include "mymoneytransaction.h"
50 #include "kmymoneysettings.h"
51
52 class KCurrencyCalculatorPrivate
53 {
54 Q_DISABLE_COPY(KCurrencyCalculatorPrivate)
55 Q_DECLARE_PUBLIC(KCurrencyCalculator)
56
57 public:
KCurrencyCalculatorPrivate(KCurrencyCalculator * qq,const MyMoneySecurity & from,const MyMoneySecurity & to,const MyMoneyMoney & value,const MyMoneyMoney & shares,const QDate & date,const signed64 resultFraction)58 explicit KCurrencyCalculatorPrivate(KCurrencyCalculator *qq,
59 const MyMoneySecurity& from,
60 const MyMoneySecurity& to,
61 const MyMoneyMoney& value,
62 const MyMoneyMoney& shares,
63 const QDate& date,
64 const signed64 resultFraction) :
65 q_ptr(qq),
66 ui(new Ui::KCurrencyCalculator),
67 m_fromCurrency(from),
68 m_toCurrency(to),
69 m_result(shares.abs()),
70 m_value(value.abs()),
71 m_date(date),
72 m_resultFraction(resultFraction)
73 {
74 }
75
~KCurrencyCalculatorPrivate()76 ~KCurrencyCalculatorPrivate()
77 {
78 delete ui;
79 }
80
init()81 void init()
82 {
83 Q_Q(KCurrencyCalculator);
84 ui->setupUi(q);
85 auto file = MyMoneyFile::instance();
86
87 //set main widget of QDialog
88 ui->buttonGroup1->setId(ui->m_amountButton, 0);
89 ui->buttonGroup1->setId(ui->m_rateButton, 1);
90
91 ui->m_dateFrame->hide();
92 if (m_date.isValid())
93 ui->m_dateEdit->setDate(m_date);
94 else
95 ui->m_dateEdit->setDate(QDate::currentDate());
96
97 ui->m_fromCurrencyText->setText(QString(MyMoneySecurity::securityTypeToString(m_fromCurrency.securityType()) + ' ' + (m_fromCurrency.isCurrency() ? m_fromCurrency.id() : m_fromCurrency.tradingSymbol())));
98 ui->m_toCurrencyText->setText(QString(MyMoneySecurity::securityTypeToString(m_toCurrency.securityType()) + ' ' + (m_toCurrency.isCurrency() ? m_toCurrency.id() : m_toCurrency.tradingSymbol())));
99
100 //set bold font
101 auto boldFont = ui->m_fromCurrencyText->font();
102 boldFont.setBold(true);
103 ui->m_fromCurrencyText->setFont(boldFont);
104 boldFont = ui->m_toCurrencyText->font();
105 boldFont.setBold(true);
106 ui->m_toCurrencyText->setFont(boldFont);
107
108 ui->m_fromAmount->setText(m_value.formatMoney(QString(), MyMoneyMoney::denomToPrec(m_fromCurrency.smallestAccountFraction())));
109
110 ui->m_dateText->setText(QLocale().toString(m_date));
111
112 ui->m_updateButton->setChecked(KMyMoneySettings::priceHistoryUpdate());
113
114 // setup initial result
115 if (m_result == MyMoneyMoney() && !m_value.isZero()) {
116 const MyMoneyPrice &pr = file->price(m_fromCurrency.id(), m_toCurrency.id(), m_date);
117 if (pr.isValid()) {
118 m_result = m_value * pr.rate(m_toCurrency.id());
119 }
120 }
121
122 // fill in initial values
123 ui->m_toAmount->setPrecision(MyMoneyMoney::denomToPrec(m_resultFraction));
124 ui->m_toAmount->setValue(m_result);
125
126 ui->m_conversionRate->setPrecision(m_fromCurrency.pricePrecision());
127
128 q->connect(ui->m_amountButton, &QAbstractButton::clicked, q, &KCurrencyCalculator::slotSetToAmount);
129 q->connect(ui->m_rateButton, &QAbstractButton::clicked, q, &KCurrencyCalculator::slotSetExchangeRate);
130
131 q->connect(ui->m_toAmount, &AmountEdit::valueChanged, q, &KCurrencyCalculator::slotUpdateResult);
132 q->connect(ui->m_conversionRate, &AmountEdit::valueChanged, q, &KCurrencyCalculator::slotUpdateRate);
133
134 // use this as the default
135 ui->m_amountButton->animateClick();
136 q->slotUpdateResult(ui->m_toAmount->text());
137
138 // If the from security is not a currency, we only allow entering a price
139 if (!m_fromCurrency.isCurrency()) {
140 ui->m_rateButton->animateClick();
141 ui->m_amountButton->hide();
142 ui->m_toAmount->hide();
143 }
144 }
145
updateExample(const MyMoneyMoney & price)146 void updateExample(const MyMoneyMoney& price)
147 {
148 QString msg;
149 if (price.isZero()) {
150 msg = QString("1 %1 = ? %2").arg(m_fromCurrency.tradingSymbol())
151 .arg(m_toCurrency.tradingSymbol());
152 if (m_fromCurrency.isCurrency()) {
153 msg += QString("\n");
154 msg += QString("1 %1 = ? %2").arg(m_toCurrency.tradingSymbol())
155 .arg(m_fromCurrency.tradingSymbol());
156 }
157 } else {
158 msg = QString("1 %1 = %2 %3").arg(m_fromCurrency.tradingSymbol())
159 .arg(price.formatMoney(QString(), m_fromCurrency.pricePrecision()))
160 .arg(m_toCurrency.tradingSymbol());
161 if (m_fromCurrency.isCurrency()) {
162 msg += QString("\n");
163 msg += QString("1 %1 = %2 %3").arg(m_toCurrency.tradingSymbol())
164 .arg((MyMoneyMoney::ONE / price).formatMoney(QString(), m_toCurrency.pricePrecision()))
165 .arg(m_fromCurrency.tradingSymbol());
166 }
167 }
168 ui->m_conversionExample->setText(msg);
169 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!price.isZero());
170 }
171
172 KCurrencyCalculator *q_ptr;
173 Ui::KCurrencyCalculator *ui;
174 MyMoneySecurity m_fromCurrency;
175 MyMoneySecurity m_toCurrency;
176 MyMoneyMoney m_result;
177 MyMoneyMoney m_value;
178 QDate m_date;
179 signed64 m_resultFraction;
180 };
181
KCurrencyCalculator(const MyMoneySecurity & from,const MyMoneySecurity & to,const MyMoneyMoney & value,const MyMoneyMoney & shares,const QDate & date,const signed64 resultFraction,QWidget * parent)182 KCurrencyCalculator::KCurrencyCalculator(const MyMoneySecurity& from,
183 const MyMoneySecurity& to,
184 const MyMoneyMoney& value,
185 const MyMoneyMoney& shares,
186 const QDate& date,
187 const signed64 resultFraction,
188 QWidget *parent) :
189 QDialog(parent),
190 d_ptr(new KCurrencyCalculatorPrivate(this,
191 from,
192 to,
193 value,
194 shares,
195 date,
196 resultFraction))
197 {
198 Q_D(KCurrencyCalculator);
199 d->init();
200 }
201
~KCurrencyCalculator()202 KCurrencyCalculator::~KCurrencyCalculator()
203 {
204 Q_D(KCurrencyCalculator);
205 delete d;
206 }
207
setupSplitPrice(MyMoneyMoney & shares,const MyMoneyTransaction & t,const MyMoneySplit & s,const QMap<QString,MyMoneyMoney> & priceInfo,QWidget * parentWidget)208 bool KCurrencyCalculator::setupSplitPrice(MyMoneyMoney& shares,
209 const MyMoneyTransaction& t,
210 const MyMoneySplit& s,
211 const QMap<QString,
212 MyMoneyMoney>& priceInfo,
213 QWidget* parentWidget)
214 {
215 auto rc = true;
216 auto file = MyMoneyFile::instance();
217
218 if (!s.value().isZero()) {
219 auto cat = file->account(s.accountId());
220 MyMoneySecurity toCurrency;
221 toCurrency = file->security(cat.currencyId());
222 // determine the fraction required for this category/account
223 int fract = cat.fraction(toCurrency);
224
225 if (cat.currencyId() != t.commodity()) {
226
227 MyMoneyMoney toValue;
228 auto fromCurrency = file->security(t.commodity());
229 // display only positive values to the user
230 auto fromValue = s.value().abs();
231
232 // if we had a price info in the beginning, we use it here
233 if (priceInfo.find(cat.currencyId()) != priceInfo.end()) {
234 toValue = (fromValue * priceInfo[cat.currencyId()]).convert(fract);
235 }
236 // if the shares are still 0, we need to change that
237 if (toValue.isZero()) {
238 const MyMoneyPrice &price = file->price(fromCurrency.id(), toCurrency.id(), t.postDate());
239 // if the price is valid calculate the shares. If it is invalid
240 // assume a conversion rate of 1.0
241 if (price.isValid()) {
242 toValue = (price.rate(toCurrency.id()) * fromValue).convert(fract);
243 } else {
244 toValue = fromValue;
245 }
246 }
247
248 // now present all that to the user
249 QPointer<KCurrencyCalculator> calc =
250 new KCurrencyCalculator(fromCurrency,
251 toCurrency,
252 fromValue,
253 toValue,
254 t.postDate(),
255 fract,
256 parentWidget);
257
258 if (calc->exec() == QDialog::Rejected) {
259 rc = false;
260 } else
261 shares = (s.value() * calc->price()).convert(fract);
262
263 delete calc;
264
265 } else {
266 shares = s.value().convert(fract);
267 }
268 } else
269 shares = s.value();
270
271 return rc;
272 }
273
setupPriceEditor()274 void KCurrencyCalculator::setupPriceEditor()
275 {
276 Q_D(KCurrencyCalculator);
277 d->ui->m_dateFrame->show();
278 d->ui->m_amountDateFrame->hide();
279 d->ui->m_updateButton->setChecked(true);
280 d->ui->m_updateButton->hide();
281 }
282
slotSetToAmount()283 void KCurrencyCalculator::slotSetToAmount()
284 {
285 Q_D(KCurrencyCalculator);
286 d->ui->m_rateButton->setChecked(false);
287 d->ui->m_toAmount->setEnabled(true);
288 d->ui->m_conversionRate->setEnabled(false);
289 }
290
slotSetExchangeRate()291 void KCurrencyCalculator::slotSetExchangeRate()
292 {
293 Q_D(KCurrencyCalculator);
294 d->ui->m_amountButton->setChecked(false);
295 d->ui->m_toAmount->setEnabled(false);
296 d->ui->m_conversionRate->setEnabled(true);
297 }
298
slotUpdateResult(const QString &)299 void KCurrencyCalculator::slotUpdateResult(const QString& /*txt*/)
300 {
301 Q_D(KCurrencyCalculator);
302 MyMoneyMoney result = d->ui->m_toAmount->value();
303 MyMoneyMoney price(0, 1);
304
305 if (result.isNegative()) {
306 d->ui->m_toAmount->setValue(-result);
307 slotUpdateResult(QString());
308 return;
309 }
310
311 if (!result.isZero()) {
312 price = result / d->m_value;
313
314 d->ui->m_conversionRate->setValue(price);
315 d->m_result = (d->m_value * price).convert(d->m_resultFraction);
316 d->ui->m_toAmount->setValue(d->m_result);
317 }
318 d->updateExample(price);
319 }
320
slotUpdateRate(const QString &)321 void KCurrencyCalculator::slotUpdateRate(const QString& /*txt*/)
322 {
323 Q_D(KCurrencyCalculator);
324 auto price = d->ui->m_conversionRate->value();
325
326 if (price.isNegative()) {
327 d->ui->m_conversionRate->setValue(-price);
328 slotUpdateRate(QString());
329 return;
330 }
331
332 if (!price.isZero()) {
333 d->ui->m_conversionRate->setValue(price);
334 d->m_result = (d->m_value * price).convert(d->m_resultFraction);
335 d->ui->m_toAmount->setValue(d->m_result);
336 }
337 d->updateExample(price);
338 }
339
accept()340 void KCurrencyCalculator::accept()
341 {
342 Q_D(KCurrencyCalculator);
343 if (d->ui->m_conversionRate->isEnabled())
344 slotUpdateRate(QString());
345 else
346 slotUpdateResult(QString());
347
348 if (d->ui->m_updateButton->isChecked()) {
349 auto pr = MyMoneyFile::instance()->price(d->m_fromCurrency.id(), d->m_toCurrency.id(), d->ui->m_dateEdit->date());
350 if (!pr.isValid()
351 || pr.date() != d->ui->m_dateEdit->date()
352 || (pr.date() == d->ui->m_dateEdit->date() && pr.rate(d->m_fromCurrency.id()) != price())) {
353 pr = MyMoneyPrice(d->m_fromCurrency.id(), d->m_toCurrency.id(), d->ui->m_dateEdit->date(), price(), i18n("User"));
354 MyMoneyFileTransaction ft;
355 try {
356 MyMoneyFile::instance()->addPrice(pr);
357 ft.commit();
358 } catch (const MyMoneyException &) {
359 qDebug("Cannot add price");
360 }
361 }
362 }
363
364 // remember setting for next round
365 KMyMoneySettings::setPriceHistoryUpdate(d->ui->m_updateButton->isChecked());
366 QDialog::accept();
367 }
368
price() const369 MyMoneyMoney KCurrencyCalculator::price() const
370 {
371 Q_D(const KCurrencyCalculator);
372 // This should fix https://bugs.kde.org/show_bug.cgi?id=205254 and
373 // https://bugs.kde.org/show_bug.cgi?id=325953 as well as
374 // https://bugs.kde.org/show_bug.cgi?id=300965
375 if (d->ui->m_amountButton->isChecked())
376 return d->ui->m_toAmount->value().abs() / d->m_value.abs();
377 else
378 return d->ui->m_conversionRate->value();
379 }
380