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"