File indexing completed on 2024-05-12 17:21:07

0001 /*
0002  * SPDX-FileCopyrightText: 2020-2021 Han Young <hanyoung@protonmail.com>
0003  * SPDX-FileCopyrightText: 2021-2022 Rohan Asokan <rohan.asokan@students.iiit.ac.in>
0004  *
0005  * SPDX-License-Identifier: GPL-3.0-or-later
0006  */
0007 #include "inputmanager.h"
0008 #include "historymanager.h"
0009 #include "mathengine.h"
0010 #include <QDebug>
0011 #include <QLocale>
0012 
0013 InputManager::InputManager()
0014 {
0015 
0016     KNumber::setDefaultFloatOutput(true);
0017 
0018     QLocale locale;
0019     m_groupSeparator = locale.groupSeparator();
0020 
0021     // change non breaking space into classic space, because UI converts it to classic space
0022     if (m_groupSeparator == QString(QChar(160))) {
0023         m_groupSeparator = QLatin1Char(32);
0024     }
0025     m_decimalPoint = locale.decimalPoint();
0026 }
0027 
0028 InputManager *InputManager::inst()
0029 {
0030     static InputManager singleton;
0031     return &singleton;
0032 }
0033 
0034 const QString &InputManager::expression() const
0035 {
0036     return m_expression;
0037 }
0038 void InputManager::setExpression(const QString &expression)
0039 {
0040     m_expression = expression;
0041 }
0042 
0043 const QString &InputManager::result() const
0044 {
0045     return m_result;
0046 }
0047 
0048 const QString &InputManager::binaryResult() const
0049 {
0050     return m_binaryResult;
0051 }
0052 
0053 const QString &InputManager::hexResult() const
0054 {
0055     return m_hexResult;
0056 }
0057 
0058 bool InputManager::moveFromResult() const
0059 {
0060     return m_moveFromResult;
0061 }
0062 
0063 int InputManager::getCursorPosition() const
0064 {
0065     int position = m_inputPosition;
0066 
0067     // account for group separators in expression
0068     int i = 0;
0069     while (i < position && i < m_expression.size()) {
0070         if (m_expression.at(i) == m_groupSeparator) {
0071             position++;
0072         }
0073         i++;
0074     }
0075     return position;
0076 }
0077 
0078 void InputManager::setCursorPosition(int position)
0079 {
0080     m_inputPosition = position;
0081 
0082     int i = 0;
0083     while (i < position && i < m_expression.size()) {
0084         if (m_expression.at(i) == m_groupSeparator) {
0085             m_inputPosition--;
0086         }
0087         i++;
0088     }
0089 }
0090 
0091 int InputManager::idealCursorPosition(int position) const
0092 {
0093     // position cursor ahead of group separator
0094     if (position > 1 && position < m_expression.size()) {
0095         if (m_expression.at(position - 1) == m_groupSeparator) {
0096             position++;
0097             return position;
0098         }
0099     }
0100 
0101     // position cursor around functions not between
0102     QRegularExpression re(QStringLiteral(R"([^\d\+−×÷\!,\^\(\) ]{2,})"));
0103     QRegularExpressionMatch match = re.match(m_expression.mid(position - 1, 2));
0104     if (match.hasMatch()) {
0105         if (position == m_expression.size()) {
0106             // at end, do nothing
0107         } else if (m_expression.at(position - 1) == m_expression.at(position)) {
0108             // same char, do nothing
0109         } else {
0110             // check nearest left
0111             int posLeft = position - 1;
0112             while (posLeft > 0) {
0113                 if (m_expression.at(posLeft).isDigit()) {
0114                     break;
0115                 }
0116                 posLeft--;
0117             }
0118 
0119             // check nearest right
0120             int posRight = position + 1;
0121             while (posRight < m_expression.size()) {
0122                 if (m_expression.at(posRight).isDigit()) {
0123                     break;
0124                 }
0125                 posRight++;
0126             }
0127 
0128             // prefer the closest side
0129             if (position - posLeft < posRight - position) {
0130                 position -= position - posLeft - 1;
0131             } else {
0132                 position += posRight - position;
0133             }
0134 
0135             return position;
0136         }
0137     }
0138 
0139     return position;
0140 }
0141 
0142 void InputManager::append(const QString &subexpression)
0143 {
0144     // if expression was from result and input is numeric, clear expression
0145     if (m_moveFromResult && subexpression.size() == 1 && m_inputPosition == m_input.size()) {
0146         if(subexpression.at(0).isDigit() || subexpression.at(0) == QLatin1Char('.'))
0147         {
0148             m_input.clear();
0149         }
0150     }
0151     m_moveFromResult = false;
0152 
0153     QString temp = m_input;
0154     temp.insert(m_inputPosition, subexpression);
0155     temp.remove(m_groupSeparator);
0156 
0157     // Call the corresponding parser based on the type of expression.
0158     MathEngine * engineInstance = MathEngine::inst();
0159     if (m_isBinaryMode) {
0160         engineInstance->parseBinaryExpression(temp);
0161     } else {
0162         engineInstance->parse(temp);
0163     }
0164 
0165     if (!MathEngine::inst()->error()) {
0166         KNumber result = MathEngine::inst()->result();
0167         m_output = result.toQString();
0168         m_binaryResult = result.toBinaryString(0);
0169         m_hexResult = result.toHexString(0);
0170         m_input = temp;
0171         m_inputPosition += subexpression.size();
0172         m_expression = formatNumbers(m_input);
0173         m_result = formatNumbers(m_output);
0174 
0175         Q_EMIT resultChanged();
0176         Q_EMIT binaryResultChanged();
0177         Q_EMIT hexResultChanged();
0178         Q_EMIT expressionChanged();
0179     }
0180 }
0181 
0182 void InputManager::backspace()
0183 {
0184     if (m_inputPosition > 0) {
0185         // delete entire function
0186         QRegularExpression re(QStringLiteral(R"([^\d\+−×÷\!,\^ ]{2,})"));
0187         QRegularExpressionMatch match = re.match(m_input.mid(m_inputPosition - 2, 2));
0188         if (match.hasMatch()) {
0189             // check backwards
0190             int posBack = m_inputPosition - 2;
0191             while (posBack >= 0) {
0192                 if (m_input.at(posBack).isDigit() || m_input.at(posBack) == QStringLiteral("(")) {
0193                     break;
0194                 } else if (posBack > 0 && m_input.at(posBack - 1) == m_input.at(posBack)) {
0195                     posBack--;
0196                     break;
0197                 }
0198                 posBack--;
0199             }
0200 
0201             const int diff = m_inputPosition - posBack;
0202             m_input.remove(m_inputPosition - diff + 1, diff - 1);
0203             m_inputPosition = m_inputPosition - diff + 1;
0204         } else {
0205             m_input.remove(m_inputPosition - 1, 1);
0206             m_inputPosition--;
0207         }
0208 
0209         m_expression = formatNumbers(m_input);
0210 
0211         Q_EMIT expressionChanged();
0212 
0213         if (m_input.length() == 0) {
0214             clear();
0215             return;
0216         }
0217 
0218         // Call the corresponding parser based on the type of expression.
0219         MathEngine * engineInstance = MathEngine::inst();
0220         if (m_isBinaryMode) {
0221             engineInstance->parseBinaryExpression(m_input);
0222         } else {
0223             engineInstance->parse(m_input);
0224         }
0225 
0226         if(!MathEngine::inst()->error())
0227         {
0228             KNumber result = MathEngine::inst()->result();
0229             m_output = result.toQString();
0230             m_binaryResult = result.toBinaryString(0);
0231             m_hexResult = result.toHexString(0);
0232             m_result = formatNumbers(m_output);
0233             Q_EMIT resultChanged();
0234             Q_EMIT binaryResultChanged();
0235             Q_EMIT hexResultChanged();
0236         }
0237     }
0238 }
0239 
0240 void InputManager::equal()
0241 {
0242     HistoryManager::inst()->addHistory(m_expression + QStringLiteral(" = ") + m_result);
0243 
0244     m_input = m_output;
0245     m_output.clear();
0246     m_expression = m_result;
0247     m_result.clear();
0248     m_binaryResult.clear();
0249     m_hexResult.clear();
0250 
0251     m_moveFromResult = true;
0252     m_inputPosition = m_input.size();
0253     Q_EMIT expressionChanged();
0254     Q_EMIT resultChanged();
0255     Q_EMIT binaryResultChanged();
0256     Q_EMIT hexResultChanged();
0257 }
0258 
0259 void InputManager::clear()
0260 {
0261     m_input.clear();
0262     m_output.clear();
0263     m_expression.clear();
0264     m_result.clear();
0265     m_binaryResult.clear();
0266     m_hexResult.clear();
0267 
0268     m_inputPosition = 0;
0269     Q_EMIT expressionChanged();
0270     Q_EMIT resultChanged();
0271     Q_EMIT binaryResultChanged();
0272     Q_EMIT hexResultChanged();
0273 }
0274 
0275 void InputManager::fromHistory(const QString &result)
0276 {
0277     m_input = result;
0278     m_input.remove(m_groupSeparator);
0279     m_output.clear();
0280     m_expression = result;
0281     m_result.clear();
0282     m_binaryResult.clear();
0283     m_hexResult.clear();
0284 
0285     m_moveFromResult = true;
0286     m_inputPosition = m_input.size();
0287     Q_EMIT expressionChanged();
0288     Q_EMIT resultChanged();
0289     Q_EMIT binaryResultChanged();
0290     Q_EMIT hexResultChanged();
0291 }
0292 
0293 void InputManager::setBinaryMode(bool active) {
0294     m_isBinaryMode = active;
0295     clear();
0296 }
0297 bool InputManager::binaryMode()
0298 {
0299     return m_isBinaryMode;
0300 }
0301 
0302 QString InputManager::formatNumbers(const QString &text)
0303 {
0304     QString formatted;
0305     QString number;
0306     for (const auto ch : text) {
0307         if (ch.isDigit() || ch == m_decimalPoint) {
0308             number.append(ch);
0309         } else {
0310             if (!number.isEmpty()) {
0311                 addNumberSeparators(number);
0312                 formatted.append(number);
0313                 number.clear();
0314             }
0315             formatted.append(ch);
0316         }
0317     }
0318 
0319     if (!number.isEmpty()) {
0320         addNumberSeparators(number);
0321         formatted.append(number);
0322     }
0323 
0324     formatted.replace(QStringLiteral("log10"), QStringLiteral("log₁₀"));
0325     formatted.replace(QStringLiteral("log2"), QStringLiteral("log₂"));
0326 
0327     return formatted;
0328 }
0329 
0330 void InputManager::addNumberSeparators(QString &number)
0331 {
0332     const int idx = number.indexOf(m_decimalPoint);
0333 
0334     if (idx >= 0 && idx < number.size() - 3) {
0335         QString left = number.left(idx);
0336         QString right = number.right(number.size() - idx);
0337         left.replace(QRegularExpression(QStringLiteral(R"(\B(?=(\d{3})+(?!\d)))")), m_groupSeparator);
0338         number = left + right;
0339     } else if (number.size() > 3) {
0340         number.replace(QRegularExpression(QStringLiteral(R"(\B(?=(\d{3})+(?!\d)))")), m_groupSeparator);
0341     }
0342 }