/* SPDX-FileCopyrightText: 2007 Barış Metin SPDX-FileCopyrightText: 2006 David Faure SPDX-FileCopyrightText: 2007 Richard Moore SPDX-FileCopyrightText: 2010 Matteo Agostinelli SPDX-FileCopyrightText: 2021 Alexander Lohnau SPDX-License-Identifier: LGPL-2.0-only */ #include "calculatorrunner.h" #include "qalculate_engine.h" #include #include #include #include #include #include #include K_PLUGIN_CLASS_WITH_JSON(CalculatorRunner, "plasma-runner-calculator.json") CalculatorRunner::CalculatorRunner(QObject *parent, const KPluginMetaData &metaData) : KRunner::AbstractRunner(parent, metaData) , m_actions({KRunner::Action(QStringLiteral("copy"), QStringLiteral("edit-copy"), i18n("Copy to Clipboard"))}) { QString description = i18n( "Calculates the value of :q: when :q: is made up of numbers and " "mathematical symbols such as +, -, /, *, ! and ^."); addSyntax(QStringLiteral(":q:"), description); addSyntax(QStringLiteral("=:q:"), description); addSyntax(QStringLiteral(":q:="), description); addSyntax(QStringLiteral("sqrt(4)"), i18n("Enter a common math function")); setMinLetterCount(2); } CalculatorRunner::~CalculatorRunner() = default; void CalculatorRunner::userFriendlySubstitutions(QString &cmd) { if (QLocale().decimalPoint() != QLatin1Char('.')) { cmd.replace(QLocale().decimalPoint(), QLatin1String("."), Qt::CaseInsensitive); } else if (!cmd.contains(QLatin1Char('[')) && !cmd.contains(QLatin1Char(']'))) { // If we are sure that the user does not want to use vectors we can replace this char // Especially when switching between locales that use a different decimal separator // this ensures that the results are valid, see BUG: 406388 cmd.replace(QLatin1Char(','), QLatin1Char('.'), Qt::CaseInsensitive); } } void CalculatorRunner::match(KRunner::RunnerContext &context) { const QString term = context.query(); QString cmd = term; // no meanless space between friendly guys: helps simplify code cmd = std::move(cmd).trimmed(); cmd.remove(QLatin1Char(' ')); if (cmd.length() < 2) { return; } if (cmd.compare(QLatin1String("universe"), Qt::CaseInsensitive) == 0 || cmd.compare(QLatin1String("life"), Qt::CaseInsensitive) == 0) { KRunner::QueryMatch match(this); match.setCategoryRelevance(KRunner::QueryMatch::CategoryRelevance::Moderate); match.setIconName(QStringLiteral("accessories-calculator")); match.setText(QStringLiteral("42")); match.setData(QStringLiteral("42")); match.setId(term); context.addMatch(match); return; } int base = 10; QString customBase; bool foundPrefix = false; // is there `=` or `hex=` or other base prefix in cmd int equalSignPosition = cmd.indexOf(QLatin1Char('=')); if (equalSignPosition != -1 && equalSignPosition != cmd.length() - 1) { foundPrefix = QalculateEngine::findPrefix(cmd.left(equalSignPosition), &base, &customBase); } const static QRegularExpression hexRegex(QStringLiteral("0x[0-9a-f]+"), QRegularExpression::CaseInsensitiveOption); const bool parseHex = cmd.contains(hexRegex); if (!parseHex) { userFriendlyMultiplication(cmd); } const static QRegularExpression functionName(QStringLiteral("^([a-zA-Z]+)\\(.+\\)")); if (foundPrefix) { cmd.remove(0, cmd.indexOf(QLatin1Char('=')) + 1); } else if (cmd.endsWith(QLatin1Char('='))) { cmd.chop(1); } else if (auto match = functionName.match(cmd); match.hasMatch()) { // BUG: 467418 if (!m_engine) { m_engine = std::make_unique(); } const QString functionName = match.captured(1); if (!m_engine->isKnownFunction(functionName)) { return; } } else if (!parseHex) { bool foundDigit = false; for (int i = 0; i < cmd.length(); ++i) { QChar c = cmd.at(i); if (c.isLetter() && c != QLatin1Char('!')) { // not just numbers and symbols, so we return return; } if (c.isDigit()) { foundDigit = true; } } if (!foundDigit) { return; } } if (cmd.isEmpty()) { return; } userFriendlySubstitutions(cmd); bool isApproximate = false; QString result = calculate(cmd, &isApproximate, base, customBase); if (!result.isEmpty() && (foundPrefix || result != cmd)) { KRunner::QueryMatch match(this); match.setCategoryRelevance(KRunner::QueryMatch::CategoryRelevance::High); match.setIconName(QStringLiteral("accessories-calculator")); match.setText(result); if (isApproximate) { match.setSubtext(i18nc("The result of the calculation is only an approximation", "Approximation")); } match.setData(result); match.setId(term); match.setActions(m_actions); context.addMatch(match); } } QString CalculatorRunner::calculate(const QString &term, bool *isApproximate, int base, const QString &customBase) { { if (!m_engine) { m_engine = std::make_unique(); } } QString result; try { result = m_engine->evaluate(term, isApproximate, base, customBase); } catch (std::exception &e) { qDebug() << "qalculate error: " << e.what(); } return result.replace(QLatin1Char('.'), QLocale().decimalPoint(), Qt::CaseInsensitive); } void CalculatorRunner::run(const KRunner::RunnerContext &context, const KRunner::QueryMatch &match) { if (match.selectedAction()) { QApplication::clipboard()->setText(match.text()); } else { context.requestQueryStringUpdate(match.text(), match.text().length()); } } QMimeData *CalculatorRunner::mimeDataForMatch(const KRunner::QueryMatch &match) { QMimeData *result = new QMimeData(); result->setText(match.text()); return result; } void CalculatorRunner::userFriendlyMultiplication(QString &cmd) { // convert multiplication sign to * cmd.replace(QChar(U'\u00D7'), QChar(u'*')); for (int i = 0; i < cmd.length(); ++i) { if (i == 0 || i == cmd.length() - 1) { continue; } const QChar prev = cmd.at(i - 1); const QChar current = cmd.at(i); const QChar next = cmd.at(i + 1); if (current == QLatin1Char('x')) { if (prev.isDigit() && (next.isDigit() || next == QLatin1Char(',') || next == QLatin1Char('.'))) { cmd[i] = u'*'; } } } } #include "calculatorrunner.moc"