File indexing completed on 2024-05-19 05:51:34

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 "qalculateengine.h"
0010 
0011 #include <QLocale>
0012 #include <QRegularExpression>
0013 
0014 constexpr QStringView ZERO_WIDTH_SPACE = u"\u200B";
0015 
0016 InputManager::InputManager()
0017     : m_engine(QalculateEngine::inst())
0018 {
0019     QLocale locale;
0020     m_groupSeparator = locale.groupSeparator();
0021 
0022     // change non breaking space into classic space, because UI converts it to classic space
0023     if (m_groupSeparator == QString(QChar(160))) {
0024         m_groupSeparator = QLatin1Char(32);
0025     }
0026     m_decimalPoint = locale.decimalPoint();
0027 }
0028 
0029 InputManager *InputManager::inst()
0030 {
0031     static InputManager singleton;
0032     return &singleton;
0033 }
0034 
0035 const QString &InputManager::expression() const
0036 {
0037     return m_expression;
0038 }
0039 
0040 const QString &InputManager::result() const
0041 {
0042     return m_result;
0043 }
0044 
0045 const QString &InputManager::binaryResult() const
0046 {
0047     return m_binaryResult;
0048 }
0049 
0050 const QString &InputManager::hexResult() const
0051 {
0052     return m_hexResult;
0053 }
0054 
0055 bool InputManager::moveFromResult() const
0056 {
0057     return m_moveFromResult;
0058 }
0059 
0060 int InputManager::getCursorPosition() const
0061 {
0062     int position = m_inputPosition;
0063 
0064     // account for group separators in expression
0065     int i = 0;
0066     while (i < position && i < m_expression.size()) {
0067         if (m_expression.at(i) == m_groupSeparator) {
0068             position++;
0069         }
0070         i++;
0071     }
0072     return position;
0073 }
0074 
0075 void InputManager::setCursorPosition(int position)
0076 {
0077     m_inputPosition = position;
0078 
0079     int i = 0;
0080     while (i < position && i < m_expression.size()) {
0081         if (m_expression.at(i) == m_groupSeparator) {
0082             m_inputPosition--;
0083         }
0084         i++;
0085     }
0086 }
0087 
0088 int InputManager::idealCursorPosition(int position, int arrow) const
0089 {
0090     // position cursor ahead of group separator
0091     if (position > 1 && position < m_expression.size()) {
0092         if (m_expression.at(position - 1) == m_groupSeparator) {
0093             arrow == -1 ? position-- : position++;
0094             return position;
0095         }
0096     }
0097 
0098     // position cursor ahead of zero width space
0099     if (position > 1 && position < m_expression.size()) {
0100         if (m_expression.at(position - 1) == ZERO_WIDTH_SPACE.toString()) {
0101             arrow == -1 ? position-- : position++;
0102             return position;
0103         }
0104     }
0105 
0106     // position cursor around functions not between
0107     QRegularExpression re(QStringLiteral(R"([^\d+−\-×÷!%π∫√∛ˆ,\^()ˆ⁰¹²³⁴⁵⁶⁷⁸⁹ ]{2,})"));
0108     QRegularExpressionMatch match = re.match(m_expression.mid(position - 1, 2));
0109     if (match.hasMatch()) {
0110         if (position == m_expression.size()) {
0111             // at end, do nothing
0112         } else if (m_expression.at(position - 1) == m_expression.at(position)) {
0113             // same char, do nothing
0114         } else {
0115             // check nearest left
0116             int posLeft = position - 1;
0117             while (posLeft >= 0) {
0118                 if (m_expression.at(posLeft).isDigit() || m_expression.at(posLeft).isSymbol() || m_expression.at(posLeft) == QLatin1Char('(')) {
0119                     posLeft++;
0120                     break;
0121                 } else if (posLeft == 0) {
0122                     break;
0123                 } else {
0124                     posLeft--;
0125                 }
0126             }
0127 
0128             // check nearest right
0129             int posRight = position + 1;
0130             while (posRight < m_expression.size()) {
0131                 if (m_expression.at(posRight).isDigit() || m_expression.at(posRight).isSymbol() || m_expression.at(posRight) == QLatin1Char('(')) {
0132                     break;
0133                 }
0134                 posRight++;
0135             }
0136 
0137             // prefer the closest side
0138             if (arrow != 1 && (position - posLeft < posRight - position || arrow == -1)) {
0139                 position -= position - posLeft;
0140             } else {
0141                 position += posRight - position;
0142             }
0143 
0144             return position;
0145         }
0146     }
0147 
0148     return position;
0149 }
0150 
0151 void InputManager::append(const QString &subexpression)
0152 {
0153     // if expression was from result and input is numeric, clear expression
0154     if (m_moveFromResult && subexpression.size() == 1 && m_inputPosition == m_input.size()) {
0155         if(subexpression.at(0).isDigit() || subexpression.at(0) == QLatin1Char('.'))
0156         {
0157             m_input.clear();
0158         }
0159     }
0160     m_moveFromResult = false;
0161 
0162     QString temp = subexpression;
0163     temp.remove(m_groupSeparator);
0164 
0165     // auto parentheses
0166     if (temp == QStringLiteral("(  )")) {
0167         temp = QStringLiteral("(");
0168 
0169         if (m_input.size() > 0 && m_input.at(m_inputPosition - 1) != QLatin1Char('(')) {
0170             QStringView left = m_input.left(m_inputPosition);
0171             if (left.count(QStringLiteral("(")) > left.count(QStringLiteral(")"))) {
0172                 temp = QStringLiteral(")");
0173             }
0174         }
0175     } else if (temp == QStringLiteral("rand()")) {
0176         bool isAprox = false;
0177         temp = m_engine->evaluate(temp, &isAprox);
0178     }
0179 
0180     // prevent invalid duplicate operators
0181     if (QStringLiteral("+×*÷/").contains(temp) && m_inputPosition > 0 && m_input.size() > 0 && m_input.at(m_inputPosition - 1) == temp) {
0182         return;
0183     }
0184 
0185     m_input.insert(m_inputPosition, temp);
0186     m_inputPosition += temp.size();
0187 
0188     calculate();
0189 
0190     store();
0191 }
0192 
0193 void InputManager::backspace()
0194 {
0195     if (m_inputPosition < 1) {
0196         return;
0197     }
0198 
0199     // delete entire function
0200     if (m_input.size() > 2) {
0201         int posBack = m_inputPosition - 2;
0202         while (posBack >= 0) {
0203             if (m_input.at(posBack).isDigit() || m_input.at(posBack).isSymbol() || m_input.at(posBack).isPunct() || m_input.at(posBack).isSpace()
0204                 || m_input.at(posBack + 1) == m_input.at(posBack)) {
0205                 break;
0206             }
0207             posBack--;
0208         }
0209 
0210         const int diff = m_inputPosition - posBack;
0211         m_input.remove(m_inputPosition - diff + 1, diff - 1);
0212         m_inputPosition = m_inputPosition - diff + 1;
0213     } else {
0214         m_input.remove(m_inputPosition - 1, 1);
0215         m_inputPosition--;
0216     }
0217 
0218     calculate();
0219 
0220     store();
0221 }
0222 
0223 void InputManager::equal()
0224 {
0225     if (m_output.isEmpty()) {
0226         // if the output is empty, either the input is empty or user has pressed equal twice
0227         return;
0228     }
0229     HistoryManager::inst()->addHistory(m_expression + QStringLiteral(" = ") + m_result);
0230 
0231     QString savedResult = m_isBinaryMode ? m_binaryResult : m_result;
0232 
0233     if (m_isApproximate) {
0234         // Show fraction representation of result
0235         calculate(true);
0236         m_input = QStringLiteral("(") + m_output + QStringLiteral(")");
0237         if (savedResult == m_result) {
0238             m_result.clear();
0239             Q_EMIT resultChanged();
0240         }
0241     } else {
0242         m_input = m_output;
0243         m_result.clear();
0244         Q_EMIT resultChanged();
0245     }
0246 
0247     m_output.clear();
0248     m_expression = savedResult;
0249     m_binaryResult.clear();
0250     m_hexResult.clear();
0251 
0252     m_moveFromResult = true;
0253     m_inputPosition = m_input.size();
0254     Q_EMIT expressionChanged();
0255     Q_EMIT binaryResultChanged();
0256     Q_EMIT hexResultChanged();
0257 
0258     store();
0259 }
0260 
0261 void InputManager::clear(bool save)
0262 {
0263     m_input.clear();
0264     m_output.clear();
0265     m_expression.clear();
0266     m_result.clear();
0267     m_binaryResult.clear();
0268     m_hexResult.clear();
0269 
0270     m_inputPosition = 0;
0271     Q_EMIT expressionChanged();
0272     Q_EMIT resultChanged();
0273     Q_EMIT binaryResultChanged();
0274     Q_EMIT hexResultChanged();
0275 
0276     if (save) {
0277         store();
0278     }
0279 }
0280 
0281 void InputManager::setHistoryIndex(const int &index)
0282 {
0283     m_historyIndex = index;
0284 }
0285 
0286 int InputManager::historyIndex() const
0287 {
0288     return m_historyIndex;
0289 }
0290 
0291 void InputManager::fromHistory(const QString &result)
0292 {
0293     m_input = result;
0294     m_input.remove(m_groupSeparator);
0295     m_output.clear();
0296     m_expression = result;
0297     m_result.clear();
0298     m_binaryResult.clear();
0299     m_hexResult.clear();
0300 
0301     m_moveFromResult = true;
0302     m_inputPosition = m_input.size();
0303     Q_EMIT expressionChanged();
0304     Q_EMIT resultChanged();
0305     Q_EMIT binaryResultChanged();
0306     Q_EMIT hexResultChanged();
0307 }
0308 
0309 void InputManager::store()
0310 {
0311     if (m_undoStack.size() > m_undoPos) {
0312         m_undoStack.resize(m_undoPos);
0313         m_undoStack.shrink_to_fit();
0314     }
0315 
0316     m_undoStack.push_back(m_input);
0317     m_undoPos++;
0318 
0319     Q_EMIT canUndoChanged();
0320     Q_EMIT canRedoChanged();
0321 }
0322 
0323 void InputManager::undo()
0324 {
0325     if (m_undoPos <= 0) {
0326         return;
0327     }
0328 
0329     m_undoPos--;
0330     if (m_undoPos >= 1) {
0331         m_input = m_undoStack.at(m_undoPos - 1);
0332         m_inputPosition += m_input.size() - m_undoStack.at(m_undoPos).size();
0333     } else {
0334         m_input = QString();
0335     }
0336 
0337     if (m_inputPosition > m_input.size()) {
0338         m_inputPosition = m_input.size();
0339     }
0340 
0341     calculate();
0342 
0343     Q_EMIT canUndoChanged();
0344     Q_EMIT canRedoChanged();
0345 }
0346 
0347 void InputManager::redo()
0348 {
0349     if (m_undoPos >= m_undoStack.size()) {
0350         return;
0351     }
0352 
0353     m_inputPosition += m_undoStack.at(m_undoPos).size() - m_input.size();
0354     m_input = m_undoStack.at(m_undoPos);
0355     m_undoPos++;
0356 
0357     if (m_inputPosition > m_input.size()) {
0358         m_inputPosition = m_input.size();
0359     }
0360 
0361     calculate();
0362 
0363     Q_EMIT canUndoChanged();
0364     Q_EMIT canRedoChanged();
0365 }
0366 
0367 bool InputManager::canUndo()
0368 {
0369     return m_undoPos > 0;
0370 }
0371 
0372 bool InputManager::canRedo()
0373 {
0374     return m_undoPos < m_undoStack.size();
0375 }
0376 
0377 void InputManager::setBinaryMode(bool active) {
0378     m_isBinaryMode = active;
0379     clear();
0380 }
0381 bool InputManager::binaryMode()
0382 {
0383     return m_isBinaryMode;
0384 }
0385 
0386 QString InputManager::formatNumbers(const QString &text)
0387 {
0388     QString temp = text;
0389 
0390     // show exponents as superscripts
0391     if (temp.contains(QStringLiteral("^"))) {
0392         QRegularExpression re(QStringLiteral(R"((?<base>\w+|\)|!|π|%)?(?<exponent>\^-?[\d.]+(?!\!)))"));
0393         QRegularExpressionMatchIterator i = re.globalMatch(temp);
0394         while (i.hasNext()) {
0395             QRegularExpressionMatch match = i.next();
0396             QString base = match.captured(QStringLiteral("base"));
0397             QString exponent = match.captured(QStringLiteral("exponent"));
0398             if (!base.isEmpty()) {
0399                 exponent.replace(QStringLiteral("^"), ZERO_WIDTH_SPACE.toString());
0400             }
0401             replaceWithSuperscript(exponent);
0402 
0403             // replace only the first occurence
0404             size_t index = temp.indexOf(match.captured(0));
0405             temp.replace(index, match.captured(0).size(), base + exponent);
0406         }
0407     }
0408 
0409     QString formatted;
0410     QString number;
0411     for (const auto ch : temp) {
0412         if (ch.isDigit() || ch == m_decimalPoint || ch == FRACTION_SLASH.toString()) {
0413             number.append(ch);
0414         } else {
0415             // do not add number separators if contains fraction
0416             if (!number.isEmpty()) {
0417                 if (!number.contains(FRACTION_SLASH.toString())) {
0418                     addNumberSeparators(number);
0419                 }
0420                 formatted.append(number);
0421                 number.clear();
0422             }
0423             formatted.append(ch);
0424         }
0425     }
0426 
0427     // do not add number separators if contains fraction
0428     if (!number.isEmpty()) {
0429         if (!number.contains(FRACTION_SLASH.toString())) {
0430             addNumberSeparators(number);
0431         }
0432         formatted.append(number);
0433     }
0434 
0435     formatted.replace(QStringLiteral("log10"), QStringLiteral("log₁₀"));
0436     formatted.replace(QStringLiteral("log2"), QStringLiteral("log₂"));
0437 
0438     return formatted;
0439 }
0440 
0441 void InputManager::replaceWithSuperscript(QString &text)
0442 {
0443     text.replace(QStringLiteral("^"), QStringLiteral("ˆ"));
0444     text.replace(QStringLiteral("0"), QStringLiteral("⁰"));
0445     text.replace(QStringLiteral("1"), QStringLiteral("¹"));
0446     text.replace(QStringLiteral("2"), QStringLiteral("²"));
0447     text.replace(QStringLiteral("3"), QStringLiteral("³"));
0448     text.replace(QStringLiteral("4"), QStringLiteral("⁴"));
0449     text.replace(QStringLiteral("5"), QStringLiteral("⁵"));
0450     text.replace(QStringLiteral("6"), QStringLiteral("⁶"));
0451     text.replace(QStringLiteral("7"), QStringLiteral("⁷"));
0452     text.replace(QStringLiteral("8"), QStringLiteral("⁸"));
0453     text.replace(QStringLiteral("9"), QStringLiteral("⁹"));
0454     text.replace(QStringLiteral("−"), QStringLiteral("⁻"));
0455     text.replace(QStringLiteral("-"), QStringLiteral("⁻"));
0456 }
0457 
0458 void InputManager::addNumberSeparators(QString &number)
0459 {
0460     const int idx = number.indexOf(m_decimalPoint);
0461 
0462     if (idx >= 0 && idx < number.size() - 3) {
0463         QString left = number.left(idx);
0464         QString right = number.right(number.size() - idx);
0465         left.replace(QRegularExpression(QStringLiteral(R"(\B(?=(\d{3})+(?!\d)))")), m_groupSeparator);
0466         number = left + right;
0467     } else if (number.size() > 3) {
0468         number.replace(QRegularExpression(QStringLiteral(R"(\B(?=(\d{3})+(?!\d)))")), m_groupSeparator);
0469     }
0470 }
0471 
0472 void InputManager::calculate(bool exact, const int minExp)
0473 {
0474     if (m_input.length() == 0) {
0475         clear(false);
0476         return;
0477     }
0478 
0479     m_expression = m_isBinaryMode ? m_input : formatNumbers(m_input);
0480     Q_EMIT expressionChanged();
0481 
0482     QString input = m_input.trimmed();
0483     m_isApproximate = false;
0484     if (m_isBinaryMode) {
0485         m_output = m_engine->evaluate(input, &m_isApproximate, 2, 10, exact, minExp);
0486         m_binaryResult = m_engine->evaluate(input, &m_isApproximate, 2, 2, exact, minExp);
0487         m_hexResult = m_engine->evaluate(input, &m_isApproximate, 2, 16, exact, minExp);
0488         Q_EMIT binaryResultChanged();
0489         Q_EMIT hexResultChanged();
0490     } else {
0491         m_output = m_engine->evaluate(input, &m_isApproximate, 10, 10, exact, minExp);
0492     }
0493 
0494     m_result = formatNumbers(m_output);
0495     Q_EMIT resultChanged();
0496 }
0497 
0498 #include "moc_inputmanager.cpp"