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