File indexing completed on 2024-05-19 09:31:37
0001 /* 0002 SPDX-FileCopyrightText: 2007 Barış Metin <baris@pardus.org.tr> 0003 SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org> 0004 SPDX-FileCopyrightText: 2007 Richard Moore <rich@kde.org> 0005 SPDX-FileCopyrightText: 2010 Matteo Agostinelli <agostinelli@gmail.com> 0006 SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de> 0007 0008 SPDX-License-Identifier: LGPL-2.0-only 0009 */ 0010 0011 #include "calculatorrunner.h" 0012 0013 #include "qalculate_engine.h" 0014 0015 #include <QApplication> 0016 #include <QClipboard> 0017 #include <QDebug> 0018 #include <QIcon> 0019 #include <QRegularExpression> 0020 0021 #include <KLocalizedString> 0022 #include <krunner/querymatch.h> 0023 0024 K_PLUGIN_CLASS_WITH_JSON(CalculatorRunner, "plasma-runner-calculator.json") 0025 0026 CalculatorRunner::CalculatorRunner(QObject *parent, const KPluginMetaData &metaData) 0027 : KRunner::AbstractRunner(parent, metaData) 0028 , m_actions({KRunner::Action(QStringLiteral("copy"), QStringLiteral("edit-copy"), i18n("Copy to Clipboard"))}) 0029 { 0030 QString description = i18n( 0031 "Calculates the value of :q: when :q: is made up of numbers and " 0032 "mathematical symbols such as +, -, /, *, ! and ^."); 0033 addSyntax(QStringLiteral(":q:"), description); 0034 addSyntax(QStringLiteral("=:q:"), description); 0035 addSyntax(QStringLiteral(":q:="), description); 0036 addSyntax(QStringLiteral("sqrt(4)"), i18n("Enter a common math function")); 0037 0038 setMinLetterCount(2); 0039 } 0040 0041 CalculatorRunner::~CalculatorRunner() = default; 0042 0043 void CalculatorRunner::userFriendlySubstitutions(QString &cmd) 0044 { 0045 if (QLocale().decimalPoint() != QLatin1Char('.')) { 0046 cmd.replace(QLocale().decimalPoint(), QLatin1String("."), Qt::CaseInsensitive); 0047 } else if (!cmd.contains(QLatin1Char('[')) && !cmd.contains(QLatin1Char(']'))) { 0048 // If we are sure that the user does not want to use vectors we can replace this char 0049 // Especially when switching between locales that use a different decimal separator 0050 // this ensures that the results are valid, see BUG: 406388 0051 cmd.replace(QLatin1Char(','), QLatin1Char('.'), Qt::CaseInsensitive); 0052 } 0053 } 0054 0055 void CalculatorRunner::match(KRunner::RunnerContext &context) 0056 { 0057 const QString term = context.query(); 0058 QString cmd = term; 0059 0060 // no meanless space between friendly guys: helps simplify code 0061 cmd = std::move(cmd).trimmed(); 0062 cmd.remove(QLatin1Char(' ')); 0063 0064 if (cmd.length() < 2) { 0065 return; 0066 } 0067 0068 if (cmd.compare(QLatin1String("universe"), Qt::CaseInsensitive) == 0 || cmd.compare(QLatin1String("life"), Qt::CaseInsensitive) == 0) { 0069 KRunner::QueryMatch match(this); 0070 match.setCategoryRelevance(KRunner::QueryMatch::CategoryRelevance::Moderate); 0071 match.setIconName(QStringLiteral("accessories-calculator")); 0072 match.setText(QStringLiteral("42")); 0073 match.setData(QStringLiteral("42")); 0074 match.setId(term); 0075 context.addMatch(match); 0076 return; 0077 } 0078 0079 int base = 10; 0080 QString customBase; 0081 0082 bool foundPrefix = false; // is there `=` or `hex=` or other base prefix in cmd 0083 0084 int equalSignPosition = cmd.indexOf(QLatin1Char('=')); 0085 if (equalSignPosition != -1 && equalSignPosition != cmd.length() - 1) { 0086 foundPrefix = QalculateEngine::findPrefix(cmd.left(equalSignPosition), &base, &customBase); 0087 } 0088 0089 const static QRegularExpression hexRegex(QStringLiteral("0x[0-9a-f]+"), QRegularExpression::CaseInsensitiveOption); 0090 const bool parseHex = cmd.contains(hexRegex); 0091 if (!parseHex) { 0092 userFriendlyMultiplication(cmd); 0093 } 0094 0095 const static QRegularExpression functionName(QStringLiteral("^([a-zA-Z]+)\\(.+\\)")); 0096 if (foundPrefix) { 0097 cmd.remove(0, cmd.indexOf(QLatin1Char('=')) + 1); 0098 } else if (cmd.endsWith(QLatin1Char('='))) { 0099 cmd.chop(1); 0100 } else if (auto match = functionName.match(cmd); match.hasMatch()) { // BUG: 467418 0101 if (!m_engine) { 0102 m_engine = std::make_unique<QalculateEngine>(); 0103 } 0104 const QString functionName = match.captured(1); 0105 if (!m_engine->isKnownFunction(functionName)) { 0106 return; 0107 } 0108 0109 } else if (!parseHex) { 0110 bool foundDigit = false; 0111 for (int i = 0; i < cmd.length(); ++i) { 0112 QChar c = cmd.at(i); 0113 if (c.isLetter() && c != QLatin1Char('!')) { 0114 // not just numbers and symbols, so we return 0115 return; 0116 } 0117 if (c.isDigit()) { 0118 foundDigit = true; 0119 } 0120 } 0121 if (!foundDigit) { 0122 return; 0123 } 0124 } 0125 0126 if (cmd.isEmpty()) { 0127 return; 0128 } 0129 0130 userFriendlySubstitutions(cmd); 0131 0132 bool isApproximate = false; 0133 QString result = calculate(cmd, &isApproximate, base, customBase); 0134 if (!result.isEmpty() && (foundPrefix || result != cmd)) { 0135 KRunner::QueryMatch match(this); 0136 match.setCategoryRelevance(KRunner::QueryMatch::CategoryRelevance::High); 0137 match.setIconName(QStringLiteral("accessories-calculator")); 0138 match.setText(result); 0139 if (isApproximate) { 0140 match.setSubtext(i18nc("The result of the calculation is only an approximation", "Approximation")); 0141 } 0142 match.setData(result); 0143 match.setId(term); 0144 match.setActions(m_actions); 0145 context.addMatch(match); 0146 } 0147 } 0148 0149 QString CalculatorRunner::calculate(const QString &term, bool *isApproximate, int base, const QString &customBase) 0150 { 0151 { 0152 if (!m_engine) { 0153 m_engine = std::make_unique<QalculateEngine>(); 0154 } 0155 } 0156 0157 QString result; 0158 try { 0159 result = m_engine->evaluate(term, isApproximate, base, customBase); 0160 } catch (std::exception &e) { 0161 qDebug() << "qalculate error: " << e.what(); 0162 } 0163 0164 return result.replace(QLatin1Char('.'), QLocale().decimalPoint(), Qt::CaseInsensitive); 0165 } 0166 0167 void CalculatorRunner::run(const KRunner::RunnerContext &context, const KRunner::QueryMatch &match) 0168 { 0169 if (match.selectedAction()) { 0170 QApplication::clipboard()->setText(match.text()); 0171 } else { 0172 context.requestQueryStringUpdate(match.text(), match.text().length()); 0173 } 0174 } 0175 0176 QMimeData *CalculatorRunner::mimeDataForMatch(const KRunner::QueryMatch &match) 0177 { 0178 QMimeData *result = new QMimeData(); 0179 result->setText(match.text()); 0180 return result; 0181 } 0182 0183 void CalculatorRunner::userFriendlyMultiplication(QString &cmd) 0184 { 0185 // convert multiplication sign to * 0186 cmd.replace(QChar(U'\u00D7'), QChar('*')); 0187 0188 for (int i = 0; i < cmd.length(); ++i) { 0189 if (i == 0 || i == cmd.length() - 1) { 0190 continue; 0191 } 0192 const QChar prev = cmd.at(i - 1); 0193 const QChar current = cmd.at(i); 0194 const QChar next = cmd.at(i + 1); 0195 if (current == QLatin1Char('x')) { 0196 if (prev.isDigit() && (next.isDigit() || next == QLatin1Char(',') || next == QLatin1Char('.'))) { 0197 cmd[i] = '*'; 0198 } 0199 } 0200 } 0201 } 0202 0203 #include "calculatorrunner.moc"