File indexing completed on 2024-05-05 17:18:59

0001 /***************************************************************************
0002  * SPDX-FileCopyrightText: 2022 S. MANKOWSKI stephane@mankowski.fr
0003  * SPDX-FileCopyrightText: 2022 G. DE BURE support@mankowski.fr
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  ***************************************************************************/
0006 /** @file
0007  * A QLineEdit with calculator included.
0008  *
0009  * @author Stephane MANKOWSKI / Guillaume DE BURE
0010  */
0011 #include "skgcalculatoredit.h"
0012 
0013 #include <kcolorscheme.h>
0014 
0015 #include <qcompleter.h>
0016 #include <qevent.h>
0017 #include <qscriptengine.h>
0018 #include <qstringlistmodel.h>
0019 #include <qvalidator.h>
0020 
0021 #include "skgservices.h"
0022 #include "skgtraces.h"
0023 
0024 SKGCalculatorEdit::SKGCalculatorEdit(QWidget* iParent)
0025     : QLineEdit(iParent), m_lastValue(0), m_lastOperator(0), m_currentMode(EXPRESSION)
0026 {
0027     setMode(CALCULATOR);
0028     m_fontColor = palette().color(QPalette::Text);
0029 }
0030 
0031 SKGCalculatorEdit::~SKGCalculatorEdit()
0032     = default;
0033 
0034 double SKGCalculatorEdit::value()
0035 {
0036     bool test;
0037     return getEvaluatedValue(test);
0038 }
0039 
0040 int SKGCalculatorEdit::sign() const
0041 {
0042     QString t = text();
0043     if (!t.isEmpty() && t[0] == '+') {
0044         return 1;
0045     }
0046     if (!t.isEmpty() && t[0] == '-') {
0047         return -1;
0048     }
0049     return 0;
0050 }
0051 
0052 SKGCalculatorEdit::Mode SKGCalculatorEdit::mode() const
0053 {
0054     return m_currentMode;
0055 }
0056 
0057 void SKGCalculatorEdit::setMode(Mode iMode)
0058 {
0059     if (m_currentMode != iMode) {
0060         m_currentMode = iMode;
0061         if (iMode == CALCULATOR) {
0062             auto newValidator = new QDoubleValidator(this);
0063             setValidator(newValidator);
0064             setAlignment(Qt::AlignRight);
0065         } else {
0066             setValidator(nullptr);
0067         }
0068         Q_EMIT modified();
0069     }
0070 }
0071 
0072 void SKGCalculatorEdit::setValue(double iValue)
0073 {
0074     setText(SKGServices::doubleToString(iValue));
0075 }
0076 
0077 void SKGCalculatorEdit::setText(const QString& iText)
0078 {
0079     // Set default color
0080     QPalette field_palette = palette();
0081     field_palette.setColor(QPalette::Text, m_fontColor);
0082     setPalette(field_palette);
0083 
0084     // Set text (to be sure than keyPressEvent is able to get it)
0085     QLineEdit::setText(iText);
0086 
0087     // Simulate a validation
0088     if (mode() == EXPRESSION) {
0089         bool previous = this->blockSignals(true);
0090         keyPressEvent(Qt::Key_Return);
0091         this->blockSignals(previous);
0092     }
0093 
0094     // Set text (to display the input value)
0095     if (valid()) {
0096         QLineEdit::setText(iText);
0097     }
0098     Q_EMIT modified();
0099 }
0100 
0101 bool SKGCalculatorEdit::valid()
0102 {
0103     bool test;
0104     getEvaluatedValue(test);
0105     return test;
0106 }
0107 
0108 QString SKGCalculatorEdit::formula()
0109 {
0110     return m_formula;
0111 }
0112 
0113 void SKGCalculatorEdit::addParameterValue(const QString& iParameter, double iValue)
0114 {
0115     m_parameters.insert(iParameter, iValue);
0116     QStringList list;
0117     auto keys = m_parameters.keys();
0118     list.reserve(keys.count());
0119     for (const auto& a : qAsConst(keys)) {
0120         list.append('=' % a);
0121     }
0122 
0123     // Refresh completion
0124     auto comp = new QCompleter(list);
0125     comp->setCaseSensitivity(Qt::CaseInsensitive);
0126     comp->setFilterMode(Qt::MatchContains);
0127     setCompleter(comp);
0128 }
0129 
0130 void SKGCalculatorEdit::keyPressEvent(QKeyEvent* iEvent)
0131 {
0132     if (iEvent != nullptr) {
0133         int key = iEvent->key();
0134         if (mode() == CALCULATOR) {
0135             bool hasText = !text().isEmpty() && selectedText() != text();
0136 
0137             if (iEvent->count() == 1 && ((key == Qt::Key_Plus && hasText) || (key == Qt::Key_Minus && hasText) || key == Qt::Key_Asterisk || key == Qt::Key_Slash || key == Qt::Key_Return || key == Qt::Key_Enter)) {
0138                 keyPressEvent(key);
0139                 iEvent->accept();
0140             } else {
0141                 QLineEdit::keyPressEvent(iEvent);
0142             }
0143         } else {
0144             // Set default color
0145             QPalette field_palette = palette();
0146             field_palette.setColor(QPalette::Text, m_fontColor);
0147             setPalette(field_palette);
0148 
0149             keyPressEvent(key);
0150             QLineEdit::keyPressEvent(iEvent);
0151         }
0152     }
0153 }
0154 
0155 void SKGCalculatorEdit::focusOutEvent(QFocusEvent* iEvent)
0156 {
0157     if (iEvent->reason() != Qt::ActiveWindowFocusReason) {
0158         keyPressEvent(Qt::Key_Return);
0159     }
0160     QLineEdit::focusOutEvent(iEvent);
0161 }
0162 
0163 void SKGCalculatorEdit::keyPressEvent(int key)
0164 {
0165     if (mode() == CALCULATOR) {
0166         if (m_lastOperator != 0) {
0167             if (m_lastOperator == Qt::Key_Plus) {
0168                 m_lastValue += value();
0169                 setValue(m_lastValue);
0170             } else if (m_lastOperator == Qt::Key_Minus) {
0171                 m_lastValue -= value();
0172                 setValue(m_lastValue);
0173             } else if (m_lastOperator == Qt::Key_Asterisk) {
0174                 m_lastValue *= value();
0175                 setValue(m_lastValue);
0176             } else if (m_lastOperator == Qt::Key_Slash && value() != 0) {
0177                 m_lastValue /= value();
0178                 setValue(m_lastValue);
0179             }
0180 
0181         } else {
0182             m_lastValue = value();
0183         }
0184 
0185         if (key == Qt::Key_Return || key == Qt::Key_Enter) {
0186             m_lastOperator = 0;
0187             m_lastValue = 0;
0188         } else {
0189             m_lastOperator = key;
0190             QLineEdit::setText(QLatin1String(""));
0191         }
0192     } else {
0193         if (key == Qt::Key_Return || key == Qt::Key_Enter) {
0194             bool test;
0195             double v = getEvaluatedValue(test);
0196             if (test) {
0197                 QString t = text();
0198                 QLineEdit::setText((!t.isEmpty() && t[0] == '+' && v > 0 ? "+" : "") % SKGServices::doubleToString(v));
0199             } else {
0200                 QPalette field_palette = palette();
0201                 field_palette.setColor(QPalette::Text, KColorScheme(QPalette::Normal).foreground(KColorScheme::NegativeText).color());
0202                 setPalette(field_palette);
0203             }
0204             emit textChanged(text());
0205         }
0206     }
0207 }
0208 
0209 double SKGCalculatorEdit::getEvaluatedValue(bool& iOk)
0210 {
0211     qsreal output = 0;
0212     iOk = false;
0213 
0214     QString t = text().trimmed();
0215     if (!t.isEmpty()) {
0216         m_formula = t;
0217         t = t.replace(',', '.');  // Replace comma by a point in case of typo
0218         t = t.remove(' ');  // Remove space to support this kind of values 3 024,25
0219         if (!QLocale().groupSeparator().isNull()) {
0220             t = t.replace(QLocale().groupSeparator(), '.');
0221         }
0222 
0223         // Remove double . in numbers
0224         int toRemoveIndex = -1;
0225         int nbc = t.count();
0226         for (int i = 0; i < nbc; ++i) {
0227             if (t.at(i) == '.') {
0228                 if (toRemoveIndex != -1) {
0229                     t = t.remove(toRemoveIndex, 1);
0230                     --nbc;
0231                     --i;
0232                     toRemoveIndex = i;
0233                 } else {
0234                     toRemoveIndex = i;
0235                 }
0236             } else if (t.at(i) < '0' || t.at(i) > '9') {
0237                 toRemoveIndex = -1;
0238             }
0239         }
0240         if (t.startsWith(QLatin1String("="))) {
0241             t = t.right(t.length() - 1);
0242             QMapIterator<QString, double> i(m_parameters);
0243             while (i.hasNext()) {
0244                 i.next();
0245                 t.replace(i.key(), SKGServices::doubleToString(i.value()));
0246             }
0247 
0248         } else {
0249             m_formula = QLatin1String("");
0250         }
0251 
0252         QScriptEngine myEngine;
0253         QScriptValue result = myEngine.evaluate(t);
0254         if (result.isNumber()) {
0255             output = result.toNumber();
0256             iOk = true;
0257         }
0258     }
0259     return output;
0260 }
0261 
0262 
0263