diff --git a/kmymoney/dialogs/kcurrencycalculator.cpp b/kmymoney/dialogs/kcurrencycalculator.cpp index bf5874d2e..47d89a558 100644 --- a/kmymoney/dialogs/kcurrencycalculator.cpp +++ b/kmymoney/dialogs/kcurrencycalculator.cpp @@ -1,381 +1,379 @@ /* * Copyright 2004-2018 Thomas Baumgart * Copyright 2017 Ł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 "kcurrencycalculator.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kcurrencycalculator.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" -#include "kmymoneyedit.h" -#include "kmymoneydateinput.h" #include "mymoneyprice.h" #include "mymoneymoney.h" #include "mymoneyexception.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "kmymoneysettings.h" class KCurrencyCalculatorPrivate { Q_DISABLE_COPY(KCurrencyCalculatorPrivate) Q_DECLARE_PUBLIC(KCurrencyCalculator) public: explicit KCurrencyCalculatorPrivate(KCurrencyCalculator *qq, const MyMoneySecurity& from, const MyMoneySecurity& to, const MyMoneyMoney& value, const MyMoneyMoney& shares, const QDate& date, const signed64 resultFraction) : q_ptr(qq), ui(new Ui::KCurrencyCalculator), m_fromCurrency(from), m_toCurrency(to), m_result(shares.abs()), m_value(value.abs()), m_date(date), m_resultFraction(resultFraction) { } ~KCurrencyCalculatorPrivate() { delete ui; } void init() { Q_Q(KCurrencyCalculator); ui->setupUi(q); auto file = MyMoneyFile::instance(); //set main widget of QDialog ui->buttonGroup1->setId(ui->m_amountButton, 0); ui->buttonGroup1->setId(ui->m_rateButton, 1); ui->m_dateFrame->hide(); if (m_date.isValid()) ui->m_dateEdit->setDate(m_date); else ui->m_dateEdit->setDate(QDate::currentDate()); ui->m_fromCurrencyText->setText(QString(MyMoneySecurity::securityTypeToString(m_fromCurrency.securityType()) + ' ' + (m_fromCurrency.isCurrency() ? m_fromCurrency.id() : m_fromCurrency.tradingSymbol()))); ui->m_toCurrencyText->setText(QString(MyMoneySecurity::securityTypeToString(m_toCurrency.securityType()) + ' ' + (m_toCurrency.isCurrency() ? m_toCurrency.id() : m_toCurrency.tradingSymbol()))); //set bold font auto boldFont = ui->m_fromCurrencyText->font(); boldFont.setBold(true); ui->m_fromCurrencyText->setFont(boldFont); boldFont = ui->m_toCurrencyText->font(); boldFont.setBold(true); ui->m_toCurrencyText->setFont(boldFont); ui->m_fromAmount->setText(m_value.formatMoney(QString(), MyMoneyMoney::denomToPrec(m_fromCurrency.smallestAccountFraction()))); ui->m_dateText->setText(QLocale().toString(m_date)); ui->m_updateButton->setChecked(KMyMoneySettings::priceHistoryUpdate()); // setup initial result if (m_result == MyMoneyMoney() && !m_value.isZero()) { const MyMoneyPrice &pr = file->price(m_fromCurrency.id(), m_toCurrency.id(), m_date); if (pr.isValid()) { m_result = m_value * pr.rate(m_toCurrency.id()); } } // fill in initial values - ui->m_toAmount->loadText(m_result.formatMoney(QString(), MyMoneyMoney::denomToPrec(m_resultFraction))); ui->m_toAmount->setPrecision(MyMoneyMoney::denomToPrec(m_resultFraction)); + ui->m_toAmount->setValue(m_result); ui->m_conversionRate->setPrecision(m_fromCurrency.pricePrecision()); q->connect(ui->m_amountButton, &QAbstractButton::clicked, q, &KCurrencyCalculator::slotSetToAmount); q->connect(ui->m_rateButton, &QAbstractButton::clicked, q, &KCurrencyCalculator::slotSetExchangeRate); - q->connect(ui->m_toAmount, &KMyMoneyEdit::valueChanged, q, &KCurrencyCalculator::slotUpdateResult); - q->connect(ui->m_conversionRate, &KMyMoneyEdit::valueChanged, q, &KCurrencyCalculator::slotUpdateRate); + q->connect(ui->m_toAmount, &AmountEdit::valueChanged, q, &KCurrencyCalculator::slotUpdateResult); + q->connect(ui->m_conversionRate, &AmountEdit::valueChanged, q, &KCurrencyCalculator::slotUpdateRate); // use this as the default ui->m_amountButton->animateClick(); q->slotUpdateResult(ui->m_toAmount->text()); // If the from security is not a currency, we only allow entering a price if (!m_fromCurrency.isCurrency()) { ui->m_rateButton->animateClick(); ui->m_amountButton->hide(); ui->m_toAmount->hide(); } } void updateExample(const MyMoneyMoney& price) { QString msg; if (price.isZero()) { msg = QString("1 %1 = ? %2").arg(m_fromCurrency.tradingSymbol()) .arg(m_toCurrency.tradingSymbol()); if (m_fromCurrency.isCurrency()) { msg += QString("\n"); msg += QString("1 %1 = ? %2").arg(m_toCurrency.tradingSymbol()) .arg(m_fromCurrency.tradingSymbol()); } } else { msg = QString("1 %1 = %2 %3").arg(m_fromCurrency.tradingSymbol()) .arg(price.formatMoney(QString(), m_fromCurrency.pricePrecision())) .arg(m_toCurrency.tradingSymbol()); if (m_fromCurrency.isCurrency()) { msg += QString("\n"); msg += QString("1 %1 = %2 %3").arg(m_toCurrency.tradingSymbol()) .arg((MyMoneyMoney::ONE / price).formatMoney(QString(), m_toCurrency.pricePrecision())) .arg(m_fromCurrency.tradingSymbol()); } } ui->m_conversionExample->setText(msg); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!price.isZero()); } KCurrencyCalculator *q_ptr; Ui::KCurrencyCalculator *ui; MyMoneySecurity m_fromCurrency; MyMoneySecurity m_toCurrency; MyMoneyMoney m_result; MyMoneyMoney m_value; QDate m_date; signed64 m_resultFraction; }; KCurrencyCalculator::KCurrencyCalculator(const MyMoneySecurity& from, const MyMoneySecurity& to, const MyMoneyMoney& value, const MyMoneyMoney& shares, const QDate& date, const signed64 resultFraction, QWidget *parent) : QDialog(parent), d_ptr(new KCurrencyCalculatorPrivate(this, from, to, value, shares, date, resultFraction)) { Q_D(KCurrencyCalculator); d->init(); } KCurrencyCalculator::~KCurrencyCalculator() { Q_D(KCurrencyCalculator); delete d; } bool KCurrencyCalculator::setupSplitPrice(MyMoneyMoney& shares, const MyMoneyTransaction& t, const MyMoneySplit& s, const QMap& priceInfo, QWidget* parentWidget) { auto rc = true; auto file = MyMoneyFile::instance(); if (!s.value().isZero()) { auto cat = file->account(s.accountId()); MyMoneySecurity toCurrency; toCurrency = file->security(cat.currencyId()); // determine the fraction required for this category/account int fract = cat.fraction(toCurrency); if (cat.currencyId() != t.commodity()) { MyMoneyMoney toValue; auto fromCurrency = file->security(t.commodity()); // display only positive values to the user auto fromValue = s.value().abs(); // if we had a price info in the beginning, we use it here if (priceInfo.find(cat.currencyId()) != priceInfo.end()) { toValue = (fromValue * priceInfo[cat.currencyId()]).convert(fract); } // if the shares are still 0, we need to change that if (toValue.isZero()) { const MyMoneyPrice &price = file->price(fromCurrency.id(), toCurrency.id(), t.postDate()); // if the price is valid calculate the shares. If it is invalid // assume a conversion rate of 1.0 if (price.isValid()) { toValue = (price.rate(toCurrency.id()) * fromValue).convert(fract); } else { toValue = fromValue; } } // now present all that to the user QPointer calc = new KCurrencyCalculator(fromCurrency, toCurrency, fromValue, toValue, t.postDate(), fract, parentWidget); if (calc->exec() == QDialog::Rejected) { rc = false; } else shares = (s.value() * calc->price()).convert(fract); delete calc; } else { shares = s.value().convert(fract); } } else shares = s.value(); return rc; } void KCurrencyCalculator::setupPriceEditor() { Q_D(KCurrencyCalculator); d->ui->m_dateFrame->show(); d->ui->m_amountDateFrame->hide(); d->ui->m_updateButton->setChecked(true); d->ui->m_updateButton->hide(); } void KCurrencyCalculator::slotSetToAmount() { Q_D(KCurrencyCalculator); d->ui->m_rateButton->setChecked(false); d->ui->m_toAmount->setEnabled(true); d->ui->m_conversionRate->setEnabled(false); } void KCurrencyCalculator::slotSetExchangeRate() { Q_D(KCurrencyCalculator); d->ui->m_amountButton->setChecked(false); d->ui->m_toAmount->setEnabled(false); d->ui->m_conversionRate->setEnabled(true); } void KCurrencyCalculator::slotUpdateResult(const QString& /*txt*/) { Q_D(KCurrencyCalculator); MyMoneyMoney result = d->ui->m_toAmount->value(); MyMoneyMoney price(0, 1); if (result.isNegative()) { d->ui->m_toAmount->setValue(-result); slotUpdateResult(QString()); return; } if (!result.isZero()) { price = result / d->m_value; - d->ui->m_conversionRate->loadText(price.formatMoney(QString(), d->m_fromCurrency.pricePrecision())); + d->ui->m_conversionRate->setValue(price); d->m_result = (d->m_value * price).convert(d->m_resultFraction); - d->ui->m_toAmount->loadText(d->m_result.formatMoney(d->m_resultFraction)); + d->ui->m_toAmount->setValue(d->m_result); } d->updateExample(price); } void KCurrencyCalculator::slotUpdateRate(const QString& /*txt*/) { Q_D(KCurrencyCalculator); auto price = d->ui->m_conversionRate->value(); if (price.isNegative()) { d->ui->m_conversionRate->setValue(-price); slotUpdateRate(QString()); return; } if (!price.isZero()) { - d->ui->m_conversionRate->loadText(price.formatMoney(QString(), d->m_fromCurrency.pricePrecision())); + d->ui->m_conversionRate->setValue(price); d->m_result = (d->m_value * price).convert(d->m_resultFraction); - d->ui->m_toAmount->loadText(d->m_result.formatMoney(QString(), MyMoneyMoney::denomToPrec(d->m_resultFraction))); + d->ui->m_toAmount->setValue(d->m_result); } d->updateExample(price); } void KCurrencyCalculator::accept() { Q_D(KCurrencyCalculator); if (d->ui->m_conversionRate->isEnabled()) slotUpdateRate(QString()); else slotUpdateResult(QString()); if (d->ui->m_updateButton->isChecked()) { auto pr = MyMoneyFile::instance()->price(d->m_fromCurrency.id(), d->m_toCurrency.id(), d->ui->m_dateEdit->date()); if (!pr.isValid() || pr.date() != d->ui->m_dateEdit->date() || (pr.date() == d->ui->m_dateEdit->date() && pr.rate(d->m_fromCurrency.id()) != price())) { pr = MyMoneyPrice(d->m_fromCurrency.id(), d->m_toCurrency.id(), d->ui->m_dateEdit->date(), price(), i18n("User")); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->addPrice(pr); ft.commit(); } catch (const MyMoneyException &) { qDebug("Cannot add price"); } } } // remember setting for next round KMyMoneySettings::setPriceHistoryUpdate(d->ui->m_updateButton->isChecked()); QDialog::accept(); } MyMoneyMoney KCurrencyCalculator::price() const { Q_D(const KCurrencyCalculator); // This should fix https://bugs.kde.org/show_bug.cgi?id=205254 and // https://bugs.kde.org/show_bug.cgi?id=325953 as well as // https://bugs.kde.org/show_bug.cgi?id=300965 if (d->ui->m_amountButton->isChecked()) return d->ui->m_toAmount->value().abs() / d->m_value.abs(); else return d->ui->m_conversionRate->value(); } diff --git a/kmymoney/dialogs/kcurrencycalculator.ui b/kmymoney/dialogs/kcurrencycalculator.ui index a4b72c361..8a46056ed 100644 --- a/kmymoney/dialogs/kcurrencycalculator.ui +++ b/kmymoney/dialogs/kcurrencycalculator.ui @@ -1,400 +1,406 @@ KCurrencyCalculator true 0 0 387 - 268 + 336 Exchange Rate/Price Editor true QFrame::NoFrame QFrame::Plain 0 0 0 0 Amount false xxx false Qt::Horizontal QSizePolicy::Expanding 80 20 Date false xxx false Qt::Horizontal QSizePolicy::Expanding 145 20 Convert 0 Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter false to Qt::AlignCenter false Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false - - - true 0 0 0 0 QFrame::NoFrame QFrame::Plain 0 0 0 0 To a&mount buttonGroup1 Exchange &rate / Price buttonGroup1 + + + + + + + + + + xx +xx + + + false + + + QFrame::NoFrame QFrame::Plain 0 0 0 0 Date false - + + + true + + - - - - xx -xx - - - false - - - - - - Update price history true Qt::Vertical QSizePolicy::Expanding 20 16 QDialogButtonBox::Cancel|QDialogButtonBox::Ok - KMyMoneyDateInput - QWidget -
kmymoneydateinput.h
- 1 + AmountEdit + QLineEdit +
amountedit.h
- KMyMoneyEdit - QWidget -
kmymoneyedit.h
+ KMyMoneyDateEdit + QDateEdit +
kmymoneydateedit.h
m_amountButton m_rateButton + m_toAmount + m_conversionRate + m_dateEdit m_updateButton buttonBox accepted() KCurrencyCalculator accept() 189 233 193 133 buttonBox rejected() KCurrencyCalculator reject() 189 233 193 133
diff --git a/kmymoney/widgets/amountedit.cpp b/kmymoney/widgets/amountedit.cpp index 37ebe3573..f38839094 100644 --- a/kmymoney/widgets/amountedit.cpp +++ b/kmymoney/widgets/amountedit.cpp @@ -1,438 +1,440 @@ /* * Copyright 2010-2017 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 "amountedit.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "amountvalidator.h" #include "kmymoneycalculator.h" #include "mymoneymoney.h" #include "mymoneysecurity.h" #include "icons/icons.h" using namespace Icons; class AmountEditPrivate { Q_DISABLE_COPY(AmountEditPrivate) Q_DECLARE_PUBLIC(AmountEdit) public: explicit AmountEditPrivate(AmountEdit* qq) : q_ptr(qq), m_calculatorFrame(nullptr), m_calculator(nullptr), m_calculatorButton(nullptr), m_prec(2), m_allowEmpty(false) { Q_Q(AmountEdit); m_calculatorFrame = new QFrame(q); m_calculatorFrame->setWindowFlags(Qt::Popup); m_calculatorFrame->setFrameStyle(QFrame::Panel | QFrame::Raised); m_calculatorFrame->setLineWidth(3); m_calculator = new KMyMoneyCalculator(m_calculatorFrame); m_calculatorFrame->hide(); } void init() { Q_Q(AmountEdit); // Yes, just a simple double validator ! auto validator = new AmountValidator(q); q->setValidator(validator); q->setAlignment(Qt::AlignRight | Qt::AlignVCenter); int height = q->sizeHint().height(); int btnSize = q->sizeHint().height() - 5; m_calculatorButton = new QToolButton(q); m_calculatorButton->setIcon(Icons::get(Icon::AccessoriesCalculator)); m_calculatorButton->setCursor(Qt::ArrowCursor); m_calculatorButton->setStyleSheet("QToolButton { border: none; padding: 2px}"); m_calculatorButton->setFixedSize(btnSize, btnSize); + m_calculatorButton->setFocusPolicy(Qt::ClickFocus); m_calculatorButton->show(); int frameWidth = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); q->setStyleSheet(QString("QLineEdit { padding-right: %1px }") .arg(btnSize - frameWidth)); q->setMinimumHeight(height); q->connect(m_calculatorButton, &QAbstractButton::clicked, q, &AmountEdit::slotCalculatorOpen); KSharedConfig::Ptr kconfig = KSharedConfig::openConfig(); KConfigGroup grp = kconfig->group("General Options"); if (grp.readEntry("DontShowCalculatorButton", false) == true) q->setCalculatorButtonVisible(false); q->connect(q, &QLineEdit::textChanged, q, &AmountEdit::theTextChanged); q->connect(m_calculator, &KMyMoneyCalculator::signalResultAvailable, q, &AmountEdit::slotCalculatorResult); q->connect(m_calculator, &KMyMoneyCalculator::signalQuit, q, &AmountEdit::slotCalculatorClose); } /** * Internal helper function for value() and ensureFractionalPart(). */ void ensureFractionalPart(QString& s) const { s = MyMoneyMoney(s).formatMoney(QString(), m_prec, false); } /** * This method opens the calculator and replays the key * event pointed to by @p ev. If @p ev is 0, then no key * event is replayed. * * @param ev pointer to QKeyEvent that started the calculator. */ void calculatorOpen(QKeyEvent* k) { Q_Q(AmountEdit); m_calculator->setInitialValues(q->text(), k); auto h = m_calculatorFrame->height(); auto w = m_calculatorFrame->width(); // usually, the calculator widget is shown underneath the MoneyEdit widget // if it does not fit on the screen, we show it above this widget auto p = q->mapToGlobal(QPoint(0, 0)); if (p.y() + q->height() + h > QApplication::desktop()->height()) p.setY(p.y() - h); else p.setY(p.y() + q->height()); // usually, it is shown left aligned. If it does not fit, we align it // to the right edge of the widget if (p.x() + w > QApplication::desktop()->width()) p.setX(p.x() + q->width() - w); + m_calculatorFrame->show(); + QRect r = m_calculator->geometry(); r.moveTopLeft(p); m_calculatorFrame->setGeometry(r); - m_calculatorFrame->show(); m_calculator->setFocus(); } AmountEdit* q_ptr; QFrame* m_calculatorFrame; KMyMoneyCalculator* m_calculator; QToolButton* m_calculatorButton; int m_prec; bool m_allowEmpty; QString m_previousText; // keep track of what has been typed QString m_text; // keep track of what was the original value /** * This holds the number of precision to be used * when no other information (e.g. from account) * is available. * * @sa setStandardPrecision() */ static int standardPrecision; }; int AmountEditPrivate::standardPrecision = 2; AmountEdit::AmountEdit(QWidget *parent, const int prec) : QLineEdit(parent), d_ptr(new AmountEditPrivate(this)) { Q_D(AmountEdit); d->m_prec = prec; if (prec < -1 || prec > 20) { d->m_prec = AmountEditPrivate::standardPrecision; } d->init(); } AmountEdit::AmountEdit(const MyMoneySecurity& sec, QWidget *parent) : QLineEdit(parent), d_ptr(new AmountEditPrivate(this)) { Q_D(AmountEdit); d->m_prec = MyMoneyMoney::denomToPrec(sec.smallestAccountFraction()); d->init(); } AmountEdit::~AmountEdit() { Q_D(AmountEdit); delete d; } void AmountEdit::setStandardPrecision(int prec) { if (prec >= 0 && prec < 20) { AmountEditPrivate::standardPrecision = prec; } } void AmountEdit::resizeEvent(QResizeEvent* event) { Q_D(AmountEdit); Q_UNUSED(event); const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); d->m_calculatorButton->move(width() - d->m_calculatorButton->width() - frameWidth - 2, 2); } void AmountEdit::focusOutEvent(QFocusEvent* event) { Q_D(AmountEdit); QLineEdit::focusOutEvent(event); // make sure we have a zero value in case the current text // is empty but this is not allowed if (text().isEmpty() && !d->m_allowEmpty) { QLineEdit::setText(QLatin1String("0")); } // make sure we have a fractional part if (!text().isEmpty()) ensureFractionalPart(); // in case the widget contains a different value we emit // the valueChanged signal if (MyMoneyMoney(text()) != MyMoneyMoney(d->m_text)) { emit valueChanged(text()); } } void AmountEdit::keyPressEvent(QKeyEvent* event) { Q_D(AmountEdit); switch(event->key()) { case Qt::Key_Plus: case Qt::Key_Minus: if (hasSelectedText()) { cut(); } if (text().length() == 0) { QLineEdit::keyPressEvent(event); break; } // in case of '-' we do not enter the calculator when // the current position is the beginning and there is // no '-' sign at the first position. if (event->key() == Qt::Key_Minus) { if (cursorPosition() == 0 && text()[0] != '-') { QLineEdit::keyPressEvent(event); break; } } // intentional fall through case Qt::Key_Slash: case Qt::Key_Asterisk: case Qt::Key_Percent: if (hasSelectedText()) { // remove the selected text cut(); } d->calculatorOpen(event); break; default: QLineEdit::keyPressEvent(event); break; } } void AmountEdit::setPrecision(const int prec) { Q_D(AmountEdit); if (prec >= -1 && prec <= 20) { if (prec != d->m_prec) { d->m_prec = prec; // update current display setValue(value()); } } } int AmountEdit::precision() const { Q_D(const AmountEdit); return d->m_prec; } bool AmountEdit::isValid() const { return !(text().isEmpty()); } QString AmountEdit::numericalText() const { return value().toString(); } MyMoneyMoney AmountEdit::value() const { Q_D(const AmountEdit); MyMoneyMoney money(text()); if (d->m_prec != -1) money = money.convert(MyMoneyMoney::precToDenom(d->m_prec)); return money; } void AmountEdit::setValue(const MyMoneyMoney& value) { Q_D(AmountEdit); // load the value into the widget but don't use thousandsSeparators setText(value.formatMoney(QString(), d->m_prec, false)); } void AmountEdit::setText(const QString& txt) { Q_D(AmountEdit); d->m_text = txt; if (isEnabled() && !txt.isEmpty()) d->ensureFractionalPart(d->m_text); QLineEdit::setText(d->m_text); #if 0 m_resetButton->setEnabled(false); #endif } void AmountEdit::resetText() { #if 0 Q_D(AmountEdit); setText(d->m_text); m_resetButton->setEnabled(false); #endif } void AmountEdit::theTextChanged(const QString & theText) { Q_D(AmountEdit); QLocale locale; QString dec = locale.groupSeparator(); QString l_text = theText; QString nsign, psign; nsign = locale.negativeSign(); psign = locale.positiveSign(); auto i = 0; if (isEnabled()) { QValidator::State state = validator()->validate(l_text, i); if (state == QValidator::Intermediate) { if (l_text.length() == 1) { if (l_text != dec && l_text != nsign && l_text != psign) state = QValidator::Invalid; } } if (state == QValidator::Invalid) QLineEdit::setText(d->m_previousText); else { d->m_previousText = l_text; emit validatedTextChanged(text()); } } } void AmountEdit::slotCalculatorOpen() { Q_D(AmountEdit); d->calculatorOpen(0); } void AmountEdit::slotCalculatorClose() { Q_D(AmountEdit); if (d->m_calculator != 0) { d->m_calculatorFrame->hide(); } } void AmountEdit::slotCalculatorResult() { Q_D(AmountEdit); slotCalculatorClose(); if (d->m_calculator != 0) { setText(d->m_calculator->result()); ensureFractionalPart(); #if 0 // I am not sure if getting a result from the calculator // is a good event to emit a value changed signal. We // should do this only on focusOutEvent() emit valueChanged(text()); d->m_text = text(); #endif } } void AmountEdit::setCalculatorButtonVisible(const bool show) { Q_D(AmountEdit); d->m_calculatorButton->setVisible(show); } void AmountEdit::setAllowEmpty(bool allowed) { Q_D(AmountEdit); d->m_allowEmpty = allowed; } bool AmountEdit::isEmptyAllowed() const { Q_D(const AmountEdit); return d->m_allowEmpty; } bool AmountEdit::isCalculatorButtonVisible() const { Q_D(const AmountEdit); return d->m_calculatorButton->isVisible(); } void AmountEdit::ensureFractionalPart() { Q_D(AmountEdit); QString s(text()); d->ensureFractionalPart(s); // by setting the text only when it's different then the one that it is already there // we preserve the edit widget's state (like the selection for example) during a // call to ensureFractionalPart() that does not change anything if (s != text()) QLineEdit::setText(s); } diff --git a/kmymoney/widgets/kmymoneyedit.cpp b/kmymoney/widgets/kmymoneyedit.cpp index 886e4b4fe..d228a584d 100644 --- a/kmymoney/widgets/kmymoneyedit.cpp +++ b/kmymoney/widgets/kmymoneyedit.cpp @@ -1,595 +1,596 @@ /* * Copyright 2000-2002 Michael Edwardes * Copyright 2002-2017 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 "kmymoneyedit.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneymoneyvalidator.h" #include "kmymoneylineedit.h" #include "kmymoneycalculator.h" #include "mymoneymoney.h" #include "mymoneysecurity.h" #include "icons.h" using namespace Icons; // converted image from kde3.5.1/share/apps/kdevdesignerpart/pics/designer_resetproperty.png static const uchar resetButtonImage[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x08, 0x06, 0x00, 0x00, 0x00, 0x0F, 0x0E, 0x84, 0x76, 0x00, 0x00, 0x00, 0x06, 0x62, 0x4B, 0x47, 0x44, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xA0, 0xBD, 0xA7, 0x93, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0B, 0x13, 0x00, 0x00, 0x0B, 0x13, 0x01, 0x00, 0x9A, 0x9C, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4D, 0x45, 0x07, 0xD6, 0x06, 0x10, 0x09, 0x36, 0x0C, 0x58, 0x91, 0x11, 0x7C, 0x00, 0x00, 0x00, 0x64, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x65, 0xC9, 0xA1, 0x0D, 0x02, 0x41, 0x18, 0x84, 0xD1, 0xF7, 0x5F, 0x13, 0x04, 0x9A, 0x39, 0x43, 0x68, 0x81, 0x02, 0x10, 0xB8, 0x13, 0x74, 0x80, 0xC1, 0x21, 0x76, 0x1D, 0xDD, 0xD0, 0x01, 0x65, 0x10, 0x34, 0x9A, 0x0C, 0x66, 0x83, 0x61, 0x92, 0x2F, 0x23, 0x5E, 0x25, 0x01, 0xBD, 0x6A, 0xC6, 0x1D, 0x9B, 0x25, 0x79, 0xC2, 0x34, 0xE0, 0x30, 0x00, 0x56, 0xBD, 0x6A, 0x0D, 0xD5, 0x38, 0xE1, 0xEA, 0x7F, 0xE7, 0x4A, 0xA2, 0x57, 0x1D, 0x71, 0xC1, 0x07, 0xBB, 0x81, 0x8F, 0x09, 0x96, 0xE4, 0x86, 0x3D, 0xDE, 0x78, 0x8D, 0x48, 0xF2, 0xAB, 0xB1, 0x1D, 0x9F, 0xC6, 0xFC, 0x05, 0x46, 0x68, 0x28, 0x6B, 0x58, 0xEE, 0x72, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; class KMyMoneyEditPrivate { Q_DISABLE_COPY(KMyMoneyEditPrivate) Q_DECLARE_PUBLIC(KMyMoneyEdit) public: explicit KMyMoneyEditPrivate(KMyMoneyEdit *qq) : q_ptr(qq), m_calculator(nullptr), m_calculatorFrame(nullptr), m_edit(nullptr), m_calcButton(nullptr), m_resetButton(nullptr), m_prec(2), allowEmpty(true) { } ~KMyMoneyEditPrivate() { } /** * Force geometry update of a hidden widget, * see https://stackoverflow.com/a/3996525 for details * * @parem widget widget to force update */ void forceUpdate(QWidget *widget) { widget->setAttribute(Qt::WA_DontShowOnScreen); widget->show(); widget->updateGeometry(); widget->hide(); widget->setAttribute(Qt::WA_DontShowOnScreen, false); } void init() { Q_Q(KMyMoneyEdit); QHBoxLayout *editLayout = new QHBoxLayout(q); editLayout->setSpacing(0); editLayout->setContentsMargins(0, 0, 0, 0); allowEmpty = false; m_edit = new KMyMoneyLineEdit(q, true); m_edit->installEventFilter(q); + q->setFocusPolicy(m_edit->focusPolicy()); q->setFocusProxy(m_edit); editLayout->addWidget(m_edit); // Yes, just a simple double validator ! KMyMoneyMoneyValidator *validator = new KMyMoneyMoneyValidator(q); m_edit->setValidator(validator); m_edit->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_calculatorFrame = new QWidget; QVBoxLayout *calculatorFrameVBoxLayout = new QVBoxLayout(m_calculatorFrame); calculatorFrameVBoxLayout->setMargin(0); m_calculatorFrame->setWindowFlags(Qt::Popup); m_calculator = new KMyMoneyCalculator(m_calculatorFrame); calculatorFrameVBoxLayout->addWidget(m_calculator); forceUpdate(m_calculatorFrame); m_calcButton = new QPushButton(Icons::get(Icon::AccessoriesCalculator), QString(), q); m_calcButton->setFocusProxy(m_edit); editLayout->addWidget(m_calcButton); QPixmap pixmap; pixmap.loadFromData(resetButtonImage, sizeof(resetButtonImage), "PNG", 0); m_resetButton = new QPushButton(pixmap, QString(QString()), q); m_resetButton->setEnabled(false); m_resetButton->setFocusProxy(m_edit); editLayout->addWidget(m_resetButton); KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup grp = kconfig->group("General Options"); if (grp.readEntry("DontShowCalculatorButton", false) == true) q->setCalculatorButtonVisible(false); q->connect(m_edit, &QLineEdit::textChanged, q, &KMyMoneyEdit::theTextChanged); q->connect(m_calculator, &KMyMoneyCalculator::signalResultAvailable, q, &KMyMoneyEdit::slotCalculatorResult); q->connect(m_calcButton, &QAbstractButton::clicked, q, &KMyMoneyEdit::slotCalculatorOpen); q->connect(m_resetButton, &QAbstractButton::clicked, q, &KMyMoneyEdit::resetText); } /** * This method ensures that the text version contains a * fractional part. */ void ensureFractionalPart() { QString s(m_edit->text()); ensureFractionalPart(s); // by setting the text only when it's different then the one that it is already there // we preserve the edit widget's state (like the selection for example) during a // call to ensureFractionalPart() that does not change anything if (s != m_edit->text()) m_edit->setText(s); } /** * Internal helper function for value() and ensureFractionalPart(). */ void ensureFractionalPart(QString& s) const { QString decimalSymbol = QLocale().decimalPoint(); if (decimalSymbol.isEmpty()) decimalSymbol = '.'; // If text contains no 'monetaryDecimalSymbol' then add it // followed by the required number of 0s if (!s.isEmpty()) { if (m_prec > 0) { if (!s.contains(decimalSymbol)) { s += decimalSymbol; for (auto i = 0; i < m_prec; ++i) s += '0'; } } else if (m_prec == 0) { while (s.contains(decimalSymbol)) { int pos = s.lastIndexOf(decimalSymbol); if (pos != -1) { s.truncate(pos); } } } else if (s.contains(decimalSymbol)) { // m_prec == -1 && fraction // no trailing zeroes while (s.endsWith('0')) { s.truncate(s.length() - 1); } // no trailing decimalSymbol if (s.endsWith(decimalSymbol)) s.truncate(s.length() - 1); } } } /** * This method opens the calculator and replays the key * event pointed to by @p ev. If @p ev is 0, then no key * event is replayed. * * @param ev pointer to QKeyEvent that started the calculator. */ void calculatorOpen(QKeyEvent* k) { Q_Q(KMyMoneyEdit); m_calculator->setInitialValues(m_edit->text(), k); int h = m_calculatorFrame->height(); int w = m_calculatorFrame->width(); // usually, the calculator widget is shown underneath the MoneyEdit widget // if it does not fit on the screen, we show it above this widget QPoint p = q->mapToGlobal(QPoint(0, 0)); if (p.y() + q->height() + h > QApplication::desktop()->height()) p.setY(p.y() - h); else p.setY(p.y() + q->height()); // usually, it is shown left aligned. If it does not fit, we align it // to the right edge of the widget if (p.x() + w > QApplication::desktop()->width()) p.setX(p.x() + q->width() - w); QRect r = m_calculator->geometry(); r.moveTopLeft(p); m_calculatorFrame->setGeometry(r); m_calculatorFrame->show(); m_calculator->setFocus(); } KMyMoneyEdit *q_ptr; QString previousText; // keep track of what has been typed QString m_text; // keep track of what was the original value KMyMoneyCalculator* m_calculator; QWidget* m_calculatorFrame; KMyMoneyLineEdit* m_edit; QPushButton* m_calcButton; QPushButton* m_resetButton; int m_prec; bool allowEmpty; /** * This holds the number of precision to be used * when no other information (e.g. from account) * is available. * * @sa setStandardPrecision() */ static int standardPrecision; }; int KMyMoneyEditPrivate::standardPrecision = 2; KMyMoneyEdit::KMyMoneyEdit(QWidget *parent, const int prec) : QWidget(parent), d_ptr(new KMyMoneyEditPrivate(this)) { Q_D(KMyMoneyEdit); d->m_prec = prec; if (prec < -1 || prec > 20) d->m_prec = KMyMoneyEditPrivate::standardPrecision; d->init(); } KMyMoneyEdit::KMyMoneyEdit(const MyMoneySecurity& sec, QWidget *parent) : QWidget(parent), d_ptr(new KMyMoneyEditPrivate(this)) { Q_D(KMyMoneyEdit); d->m_prec = MyMoneyMoney::denomToPrec(sec.smallestAccountFraction()); d->init(); } void KMyMoneyEdit::setStandardPrecision(int prec) { if (prec >= 0 && prec < 20) { KMyMoneyEditPrivate::standardPrecision = prec; } } void KMyMoneyEdit::setValidator(const QValidator* v) { Q_D(KMyMoneyEdit); d->m_edit->setValidator(v); } KMyMoneyEdit::~KMyMoneyEdit() { Q_D(KMyMoneyEdit); delete d; } KLineEdit* KMyMoneyEdit::lineedit() const { Q_D(const KMyMoneyEdit); return d->m_edit; } QString KMyMoneyEdit::text() const { return value().toString(); } void KMyMoneyEdit::setMinimumWidth(int w) { Q_D(KMyMoneyEdit); d->m_edit->setMinimumWidth(w); } void KMyMoneyEdit::setPrecision(const int prec) { Q_D(KMyMoneyEdit); if (prec >= -1 && prec <= 20) { if (prec != d->m_prec) { d->m_prec = prec; // update current display setValue(value()); } } } int KMyMoneyEdit::precision() const { Q_D(const KMyMoneyEdit); return d->m_prec; } bool KMyMoneyEdit::isValid() const { Q_D(const KMyMoneyEdit); return !(d->m_edit->text().isEmpty()); } MyMoneyMoney KMyMoneyEdit::value() const { Q_D(const KMyMoneyEdit); auto txt = d->m_edit->text(); d->ensureFractionalPart(txt); MyMoneyMoney money(txt); if (d->m_prec != -1) money = money.convert(MyMoneyMoney::precToDenom(d->m_prec)); return money; } void KMyMoneyEdit::setValue(const MyMoneyMoney& value) { Q_D(KMyMoneyEdit); // load the value into the widget but don't use thousandsSeparators auto txt = value.formatMoney(QString(), d->m_prec, false); loadText(txt); } void KMyMoneyEdit::loadText(const QString& txt) { Q_D(KMyMoneyEdit); d->m_edit->setText(txt); if (isEnabled() && !txt.isEmpty()) d->ensureFractionalPart(); d->m_text = d->m_edit->text(); d->m_resetButton->setEnabled(false); } void KMyMoneyEdit::clearText() { Q_D(KMyMoneyEdit); d->m_text.clear(); d->m_edit->setText(d->m_text); } void KMyMoneyEdit::setText(const QString& txt) { setValue(MyMoneyMoney(txt)); } void KMyMoneyEdit::resetText() { Q_D(KMyMoneyEdit); d->m_edit->setText(d->m_text); d->m_resetButton->setEnabled(false); } void KMyMoneyEdit::theTextChanged(const QString & theText) { Q_D(KMyMoneyEdit); QString txt = QLocale().decimalPoint(); QString l_text = theText; QString nsign, psign; #if 0 KLocale * l = KLocale::global(); if (l->negativeMonetarySignPosition() == KLocale::ParensAround || l->positiveMonetarySignPosition() == KLocale::ParensAround) { nsign = psign = '('; } else { nsign = l->negativeSign(); psign = l->positiveSign(); } #else nsign = "-"; psign = QString(); #endif auto i = 0; if (isEnabled()) { QValidator::State state = d->m_edit->validator()->validate(l_text, i); if (state == QValidator::Intermediate) { if (l_text.length() == 1) { if (l_text != txt && l_text != nsign && l_text != psign) state = QValidator::Invalid; } } if (state == QValidator::Invalid) d->m_edit->setText(d->previousText); else { d->previousText = l_text; emit textChanged(d->m_edit->text()); d->m_resetButton->setEnabled(true); } } } bool KMyMoneyEdit::eventFilter(QObject * /* o */ , QEvent *event) { Q_D(KMyMoneyEdit); auto rc = false; // we want to catch some keys that are usually handled by // the base class (e.g. '+', '-', etc.) if (event->type() == QEvent::KeyPress) { QKeyEvent *k = static_cast(event); rc = true; switch (k->key()) { case Qt::Key_Plus: case Qt::Key_Minus: if (d->m_edit->hasSelectedText()) { d->m_edit->cut(); } if (d->m_edit->text().length() == 0) { rc = false; break; } // in case of '-' we do not enter the calculator when // the current position is the beginning and there is // no '-' sign at the first position. if (k->key() == Qt::Key_Minus) { if (d->m_edit->cursorPosition() == 0 && d->m_edit->text()[0] != '-') { rc = false; break; } } // intentional fall through case Qt::Key_Slash: case Qt::Key_Asterisk: case Qt::Key_Percent: if (d->m_edit->hasSelectedText()) { // remove the selected text d->m_edit->cut(); } d->calculatorOpen(k); break; default: rc = false; break; } } else if (event->type() == QEvent::FocusOut) { if (!d->m_edit->text().isEmpty() || !d->allowEmpty) d->ensureFractionalPart(); if (MyMoneyMoney(d->m_edit->text()) != MyMoneyMoney(d->m_text) && !d->m_calculator->isVisible()) { emit valueChanged(d->m_edit->text()); } d->m_text = d->m_edit->text(); } return rc; } void KMyMoneyEdit::slotCalculatorOpen() { Q_D(KMyMoneyEdit); d->calculatorOpen(0); } void KMyMoneyEdit::slotCalculatorResult() { Q_D(KMyMoneyEdit); if (d->m_calculator != 0) { d->m_calculatorFrame->hide(); d->m_edit->setText(d->m_calculator->result()); d->ensureFractionalPart(); emit valueChanged(d->m_edit->text()); d->m_text = d->m_edit->text(); } } QWidget* KMyMoneyEdit::focusWidget() const { Q_D(const KMyMoneyEdit); QWidget* w = d->m_edit; while (w->focusProxy()) w = w->focusProxy(); return w; } void KMyMoneyEdit::setCalculatorButtonVisible(const bool show) { Q_D(KMyMoneyEdit); d->m_calcButton->setVisible(show); } void KMyMoneyEdit::setResetButtonVisible(const bool show) { Q_D(KMyMoneyEdit); d->m_resetButton->setVisible(show); } void KMyMoneyEdit::setAllowEmpty(bool allowed) { Q_D(KMyMoneyEdit); d->allowEmpty = allowed; } bool KMyMoneyEdit::isCalculatorButtonVisible() const { Q_D(const KMyMoneyEdit); return d->m_calcButton->isVisible(); } bool KMyMoneyEdit::isResetButtonVisible() const { Q_D(const KMyMoneyEdit); return d->m_resetButton->isVisible(); } bool KMyMoneyEdit::isEmptyAllowed() const { Q_D(const KMyMoneyEdit); return d->allowEmpty; } void KMyMoneyEdit::setPlaceholderText(const QString& hint) const { Q_D(const KMyMoneyEdit); if (d->m_edit) d->m_edit->setPlaceholderText(hint); } bool KMyMoneyEdit::isReadOnly() const { Q_D(const KMyMoneyEdit); if (d->m_edit) return d->m_edit->isReadOnly(); return false; } void KMyMoneyEdit::setReadOnly(bool readOnly) { Q_D(KMyMoneyEdit); // we use the QLineEdit::setReadOnly() method directly to avoid // changing the background between readonly and read/write mode // as it is done by the KLineEdit code. if (d->m_edit) d->m_edit->QLineEdit::setReadOnly(readOnly); //krazy:exclude=qclasses }