File indexing completed on 2024-05-05 17:45:01
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 <QDebug> 0016 #include <QIcon> 0017 #include <QMutex> 0018 #include <QRegularExpression> 0019 0020 #include <KLocalizedString> 0021 #include <krunner/querymatch.h> 0022 0023 K_PLUGIN_CLASS_WITH_JSON(CalculatorRunner, "plasma-runner-calculator.json") 0024 0025 static QMutex s_initMutex; 0026 0027 CalculatorRunner::CalculatorRunner(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args) 0028 : Plasma::AbstractRunner(parent, metaData, args) 0029 { 0030 setObjectName(QStringLiteral("Calculator")); 0031 0032 QString description = i18n( 0033 "Calculates the value of :q: when :q: is made up of numbers and " 0034 "mathematical symbols such as +, -, /, *, ! and ^."); 0035 addSyntax(Plasma::RunnerSyntax(QStringLiteral(":q:"), description)); 0036 addSyntax(Plasma::RunnerSyntax(QStringLiteral("=:q:"), description)); 0037 addSyntax(Plasma::RunnerSyntax(QStringLiteral(":q:="), description)); 0038 0039 m_actions = {new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy to Clipboard"), this)}; 0040 setMinLetterCount(2); 0041 } 0042 0043 CalculatorRunner::~CalculatorRunner() 0044 { 0045 } 0046 0047 void CalculatorRunner::userFriendlySubstitutions(QString &cmd) 0048 { 0049 if (QLocale().decimalPoint() != QLatin1Char('.')) { 0050 cmd.replace(QLocale().decimalPoint(), QLatin1String("."), Qt::CaseInsensitive); 0051 } else if (!cmd.contains(QLatin1Char('[')) && !cmd.contains(QLatin1Char(']'))) { 0052 // If we are sure that the user does not want to use vectors we can replace this char 0053 // Especially when switching between locales that use a different decimal separator 0054 // this ensures that the results are valid, see BUG: 406388 0055 cmd.replace(QLatin1Char(','), QLatin1Char('.'), Qt::CaseInsensitive); 0056 } 0057 } 0058 0059 void CalculatorRunner::match(Plasma::RunnerContext &context) 0060 { 0061 const QString term = context.query(); 0062 QString cmd = term; 0063 0064 // no meanless space between friendly guys: helps simplify code 0065 cmd = cmd.trimmed().remove(QLatin1Char(' ')); 0066 0067 if (cmd.length() < 2) { 0068 return; 0069 } 0070 0071 if (cmd.toLower() == QLatin1String("universe") || cmd.toLower() == QLatin1String("life")) { 0072 Plasma::QueryMatch match(this); 0073 match.setType(Plasma::QueryMatch::PossibleMatch); 0074 match.setIconName(QStringLiteral("accessories-calculator")); 0075 match.setText(QStringLiteral("42")); 0076 match.setData(QStringLiteral("42")); 0077 match.setId(term); 0078 context.addMatch(match); 0079 return; 0080 } 0081 0082 int base = 10; 0083 QString customBase; 0084 0085 bool foundPrefix = false; // is there `=` or `hex=` or other base prefix in cmd 0086 0087 int equalSignPosition = cmd.indexOf(QLatin1Char('=')); 0088 if (equalSignPosition != -1 && equalSignPosition != cmd.length() - 1) { 0089 foundPrefix = QalculateEngine::findPrefix(cmd.left(equalSignPosition), &base, &customBase); 0090 } 0091 0092 const static QRegularExpression hexRegex(QStringLiteral("0x[0-9a-f]+"), QRegularExpression::CaseInsensitiveOption); 0093 const bool parseHex = cmd.contains(hexRegex); 0094 if (!parseHex) { 0095 userFriendlyMultiplication(cmd); 0096 } 0097 0098 if (foundPrefix) { 0099 cmd.remove(0, cmd.indexOf(QLatin1Char('=')) + 1); 0100 } else if (cmd.endsWith(QLatin1Char('='))) { 0101 cmd.chop(1); 0102 } else if (!parseHex) { 0103 bool foundDigit = false; 0104 for (int i = 0; i < cmd.length(); ++i) { 0105 QChar c = cmd.at(i); 0106 if (c.isLetter() && c != QLatin1Char('!')) { 0107 // not just numbers and symbols, so we return 0108 return; 0109 } 0110 if (c.isDigit()) { 0111 foundDigit = true; 0112 } 0113 } 0114 if (!foundDigit) { 0115 return; 0116 } 0117 } 0118 0119 if (cmd.isEmpty()) { 0120 return; 0121 } 0122 0123 userFriendlySubstitutions(cmd); 0124 0125 bool isApproximate = false; 0126 QString result = calculate(cmd, &isApproximate, base, customBase); 0127 if (!result.isEmpty() && (foundPrefix || result != cmd)) { 0128 Plasma::QueryMatch match(this); 0129 match.setType(Plasma::QueryMatch::HelperMatch); 0130 match.setIconName(QStringLiteral("accessories-calculator")); 0131 match.setText(result); 0132 if (isApproximate) { 0133 match.setSubtext(i18nc("The result of the calculation is only an approximation", "Approximation")); 0134 } 0135 match.setData(result); 0136 match.setId(term); 0137 match.setActions(m_actions); 0138 context.addMatch(match); 0139 } 0140 } 0141 0142 QString CalculatorRunner::calculate(const QString &term, bool *isApproximate, int base, const QString &customBase) 0143 { 0144 { 0145 QMutexLocker lock(&s_initMutex); 0146 if (!m_engine) { 0147 m_engine = std::make_unique<QalculateEngine>(); 0148 } 0149 } 0150 0151 QString result; 0152 try { 0153 result = m_engine->evaluate(term, isApproximate, base, customBase); 0154 } catch (std::exception &e) { 0155 qDebug() << "qalculate error: " << e.what(); 0156 } 0157 0158 return result.replace(QLatin1Char('.'), QLocale().decimalPoint(), Qt::CaseInsensitive); 0159 } 0160 0161 void CalculatorRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) 0162 { 0163 if (match.selectedAction()) { 0164 m_engine->copyToClipboard(); 0165 } else { 0166 context.requestQueryStringUpdate(match.text(), match.text().length()); 0167 } 0168 } 0169 0170 QMimeData *CalculatorRunner::mimeDataForMatch(const Plasma::QueryMatch &match) 0171 { 0172 QMimeData *result = new QMimeData(); 0173 result->setText(match.text()); 0174 return result; 0175 } 0176 0177 void CalculatorRunner::userFriendlyMultiplication(QString &cmd) 0178 { 0179 // convert multiplication sign to * 0180 cmd.replace(QChar(U'\u00D7'), QChar('*')); 0181 0182 for (int i = 0; i < cmd.length(); ++i) { 0183 if (i == 0 || i == cmd.length() - 1) { 0184 continue; 0185 } 0186 const QChar prev = cmd.at(i - 1); 0187 const QChar current = cmd.at(i); 0188 const QChar next = cmd.at(i + 1); 0189 if (current == QLatin1Char('x')) { 0190 if (prev.isDigit() && (next.isDigit() || next == QLatin1Char(',') || next == QLatin1Char('.'))) { 0191 cmd[i] = '*'; 0192 } 0193 } 0194 } 0195 } 0196 0197 #include "calculatorrunner.moc"