diff --git a/runners/converter/CMakeLists.txt b/runners/converter/CMakeLists.txt --- a/runners/converter/CMakeLists.txt +++ b/runners/converter/CMakeLists.txt @@ -1,11 +1,9 @@ add_definitions(-DTRANSLATION_DOMAIN=\"plasma_runner_converterrunner\") -set(krunner_converter_SRCS - converterrunner.cpp -) +set(krunner_converter_SRCS converterrunner.cpp) add_library(krunner_converter MODULE ${krunner_converter_SRCS}) -target_link_libraries(krunner_converter KF5::UnitConversion KF5::KIOCore KF5::I18n KF5::Runner) +target_link_libraries(krunner_converter KF5::UnitConversion KF5::I18n KF5::Runner) + install(TARGETS krunner_converter DESTINATION ${KDE_INSTALL_PLUGINDIR}) install(FILES plasma-runner-converter.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) - diff --git a/runners/converter/converterrunner.h b/runners/converter/converterrunner.h --- a/runners/converter/converterrunner.h +++ b/runners/converter/converterrunner.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2007,2008 Petri Damstén + * Copyright (C) 2020 Alexander Lohnau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -18,24 +19,37 @@ #ifndef CONVERTERRUNNER_H #define CONVERTERRUNNER_H -#include +#include +#include +#include +#include +#include + /** * This class converts values to different units. */ -class ConverterRunner : public Plasma::AbstractRunner +class ConverterRunner: public Plasma::AbstractRunner { Q_OBJECT public: - ConverterRunner(QObject* parent, const QVariantList &args); + ConverterRunner(QObject *parent, const QVariantList &args); ~ConverterRunner() override; void match(Plasma::RunnerContext &context) override; void run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) override; private: QStringList m_separators; + KUnitConversion::Converter converter; + const QLocale locale; + QRegularExpression matchRegex; + + QPair stringToDouble(const QStringRef &value); + QPair getValidatedNumberValue(const QString &value); + QList createResultUnits(const QString &outputUnitString, + const KUnitConversion::UnitCategory &category); }; #endif diff --git a/runners/converter/converterrunner.cpp b/runners/converter/converterrunner.cpp --- a/runners/converter/converterrunner.cpp +++ b/runners/converter/converterrunner.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2007,2008 Petri Damstén + * Copyright (C) 2020 Alexander Lohnau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -15,131 +16,47 @@ * along with this program. If not, see . */ + #include "converterrunner.h" + +// Qt #include #include #include #include #include +// KF #include -#include -#include #include -#define CONVERSION_CHAR QLatin1Char( '>' ) - -K_EXPORT_PLASMA_RUNNER(converterrunner, ConverterRunner) - -class StringParser -{ -public: - enum GetType - { - GetString = 1, - GetDigit = 2 - }; - - StringParser(const QString &s) : m_index(0), m_s(s) {} - ~StringParser() {} - - QString get(int type) - { - QChar current; - QString result; - - passWhiteSpace(); - while (true) { - current = next(); - if (current.isNull()) { - break; - } - if (current.isSpace()) { - break; - } - bool number = isNumber(current); - if (type == GetDigit && !number) { - break; - } - if (type == GetString && number) { - break; - } - if(current == QLatin1Char( CONVERSION_CHAR )) { - break; - } - ++m_index; - result += current; - } - return result; - } - - bool isNumber(const QChar &ch) - { - if (ch.isNumber()) { - return true; - } - if (QStringLiteral(".,-+/").contains(ch)) { - return true; - } - return false; - } +K_EXPORT_PLASMA_RUNNER(converterrunner, ConverterRunner - QString rest() - { - return m_s.mid(m_index).simplified(); - } +) - void pass(const QStringList &strings) - { - passWhiteSpace(); - const QString temp = m_s.mid(m_index); - - foreach (const QString& s, strings) { - if (temp.startsWith(s)) { - m_index += s.length(); - return; - } - } - } - -private: - void passWhiteSpace() - { - while (next().isSpace()) { - ++m_index; - } - } - - QChar next() - { - if (m_index >= m_s.size()) { - return QChar::Null; - } - return m_s.at(m_index); - } - - int m_index; - QString m_s; -}; - -ConverterRunner::ConverterRunner(QObject* parent, const QVariantList &args) +ConverterRunner::ConverterRunner(QObject *parent, const QVariantList &args) : Plasma::AbstractRunner(parent, args) { Q_UNUSED(args) - setObjectName(QLatin1String( "Converter" )); + setObjectName(QStringLiteral("Converter")); - m_separators << QString( CONVERSION_CHAR ); + m_separators << QString(QLatin1Char('<')); m_separators << i18nc("list of words that can used as amount of 'unit1' [in|to|as] 'unit2'", - "in;to;as").split(QLatin1Char( ';' )); + "in;to;as").split(QLatin1Char(';')); //can not ignore commands: we have things like m4 setIgnoredTypes(Plasma::RunnerContext::Directory | Plasma::RunnerContext::File | - Plasma::RunnerContext::NetworkLocation); - - QString description = i18n("Converts the value of :q: when :q: is made up of " - "\"value unit [>, to, as, in] unit\". You can use the " - "Unit converter applet to find all available units."); - addSyntax(Plasma::RunnerSyntax(QLatin1String(":q:"), description)); + Plasma::RunnerContext::NetworkLocation); + + const QString description = i18n("Converts the value of :q: when :q: is made up of " + "\"value unit [>, to, as, in] unit\". You can use the " + "Unit converter applet to find all available units."); + addSyntax(Plasma::RunnerSyntax(QStringLiteral(":q:"), description)); + // Construct match regex, see https://regex101.com/r/EUDzQi/11 for demo + const QString valueGroup = QStringLiteral("([0-9,./]+)"); + const QString unitGroup = QStringLiteral("([a-zA-Z/\"'^0-9$£¥€]+)"); + const QString separatorsGroup = QStringLiteral("(?: in | to | as | ?> ?)"); + matchRegex = QRegularExpression(QStringLiteral("^%1 ?%2(?:%3%2)?$").arg(valueGroup, unitGroup, separatorsGroup)); } ConverterRunner::~ConverterRunner() @@ -149,170 +66,127 @@ void ConverterRunner::match(Plasma::RunnerContext &context) { const QString term = context.query(); - if (term.size() < 2) { + if (term.size() < 2 || !context.isValid()) { return; } - StringParser cmd(term); - QString unit1; - QString value; - QString unit2; - - unit1 = cmd.get(StringParser::GetString); - value = cmd.get(StringParser::GetDigit); - if (value.isEmpty()) { + const QRegularExpressionMatch regexMatch = matchRegex.match(context.query()); + if (!regexMatch.hasMatch() || regexMatch.capturedTexts().size() < 2) { + return; + } + const QString inputValueString = regexMatch.captured(1); + const QString inputUnitString = regexMatch.captured(2); + const QString outputUnitString = regexMatch.lastCapturedIndex() == 3 ? regexMatch.captured(3) : QString(); + + KUnitConversion::UnitCategory inputCategory = converter.categoryForUnit(inputUnitString); + const KUnitConversion::Unit inputUnit = inputCategory.unit(inputUnitString); + const QList outputUnits = createResultUnits(outputUnitString, inputCategory); + const auto numberDataPair = getValidatedNumberValue(inputValueString); + // Return on invalid user input + if (!numberDataPair.first) { return; } - if (unit1.isEmpty()) { - unit1 = cmd.get(StringParser::GetString | StringParser::GetDigit); - if (unit1.isEmpty()) { - return; + + const double numberValue = numberDataPair.second; + QList matches; + for (const KUnitConversion::Unit &outputUnit: outputUnits) { + const KUnitConversion::Value &outputValue = inputCategory.convert( + KUnitConversion::Value(numberValue, inputUnit), outputUnit); + if (!outputValue.isValid() || inputUnit == outputUnit) { + continue; } + + Plasma::QueryMatch match(this); + match.setType(Plasma::QueryMatch::ExactMatch); + match.setIconName(QStringLiteral("edit-copy")); + match.setText(QStringLiteral("%1 (%2)").arg(outputValue.toString(), outputUnit.symbol())); + match.setData(outputValue.number()); + match.setRelevance(1.0 - std::abs(std::log10(outputValue.number())) / 50.0); + matches.append(match); } - const QString s = cmd.get(StringParser::GetString); + context.addMatches(matches); +} + +void ConverterRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) +{ + Q_UNUSED(context) + + QGuiApplication::clipboard()->setText(match.data().toString()); +} - if (!s.isEmpty() && !m_separators.contains(s)) { - unit1 += QLatin1Char( ' ' ) + s; +QPair ConverterRunner::stringToDouble(const QStringRef &value) +{ + bool ok; + double numberValue = locale.toDouble(value, &ok); + if (!ok) { + numberValue = value.toDouble(&ok); } - if (s.isEmpty() || !m_separators.contains(s)) { - cmd.pass(m_separators); + return {ok, numberValue}; +} + +QPair ConverterRunner::getValidatedNumberValue(const QString &value) +{ + const auto fractionParts = value.splitRef(QLatin1Char('/'), QString::SkipEmptyParts); + if (fractionParts.isEmpty() || fractionParts.count() > 2) { + return {false, 0}; } - unit2 = cmd.rest(); - KUnitConversion::Converter converter; - KUnitConversion::UnitCategory category = converter.categoryForUnit(unit1); - bool found = false; - if (category.id() == KUnitConversion::InvalidCategory) { - foreach (category, converter.categories()) { - foreach (const QString& s, category.allUnits()) { - if (s.compare(unit1, Qt::CaseInsensitive) == 0) { - unit1 = s; - found = true; - break; - } - } - if (found) { - break; - } + if (fractionParts.count() == 2) { + const QPair doubleFirstResults = stringToDouble(fractionParts.first()); + if (!doubleFirstResults.first) { + return {false, 0}; } - if (!found) { - return; + const QPair doubleSecondResult = stringToDouble(fractionParts.last()); + if (!doubleSecondResult.first || qFuzzyIsNull(doubleSecondResult.second)) { + return {false, 0}; } + + return {true, doubleFirstResults.second / doubleSecondResult.second}; + } else if (fractionParts.count() == 1) { + const QPair doubleResult = stringToDouble(fractionParts.first()); + if (!doubleResult.first) { + return {false, 0}; + } + return {true, doubleResult.second}; + } else { + return {true, 0}; } +} +QList ConverterRunner::createResultUnits(const QString &outputUnitString, + const KUnitConversion::UnitCategory &category) +{ QList units; - - if (!unit2.isEmpty()) { - KUnitConversion::Unit u = category.unit(unit2); - if (!u.isNull() && u.isValid()) { - units.append(u); - config().writeEntry(category.name(), u.symbol()); + if (!outputUnitString.isEmpty()) { + KUnitConversion::Unit outputUnit = category.unit(outputUnitString); + if (!outputUnit.isNull() && outputUnit.isValid()) { + units.append(outputUnit); } else { const QStringList unitStrings = category.allUnits(); - QList matchingUnits; - foreach (const QString& s, unitStrings) { - if (s.startsWith(unit2, Qt::CaseInsensitive)) { - u = category.unit(s); - if (!matchingUnits.contains(u)) { - matchingUnits << u; + for (const QString &unitString: unitStrings) { + if (unitString.startsWith(outputUnitString, Qt::CaseInsensitive)) { + outputUnit = category.unit(unitString); + if (!units.contains(outputUnit)) { + units << outputUnit; } } } - units = matchingUnits; - if (units.count() == 1) { - config().writeEntry(category.name(), units[0].symbol()); - } } } else { units = category.mostCommonUnits(); - KUnitConversion::Unit u = category.unit(config().readEntry(category.name())); - if (!u.isNull() && units.indexOf(u) < 0) { - units << u; - } - // suggest converting to the user's local currency if (category.id() == KUnitConversion::CurrencyCategory) { const QString ¤cyIsoCode = QLocale().currencySymbol(QLocale::CurrencyIsoCode); - KUnitConversion::Unit localCurrency = category.unit(currencyIsoCode); + const KUnitConversion::Unit localCurrency = category.unit(currencyIsoCode); if (localCurrency.isValid() && !units.contains(localCurrency)) { units << localCurrency; } } } - QList matches; - - QLocale locale; - auto stringToDouble = [&locale](const QStringRef &value, bool *ok) { - double numberValue = locale.toDouble(value, ok); - if (!(*ok)) { - numberValue = value.toDouble(ok); - } - return numberValue; - }; - - KUnitConversion::Unit u1 = category.unit(unit1); - foreach (const KUnitConversion::Unit& u, units) { - if (u1 == u) { - continue; - } - - double numberValue = 0.0; - - const auto fractionParts = value.splitRef(QLatin1Char('/'), QString::SkipEmptyParts); - if (fractionParts.isEmpty() || fractionParts.count() > 2) { - continue; - } - - if (fractionParts.count() == 2) { - bool ok; - const double numerator = stringToDouble(fractionParts.first(), &ok); - if (!ok) { - continue; - } - const double denominator = stringToDouble(fractionParts.last(), &ok); - if (!ok || qFuzzyIsNull(denominator)) { - continue; - } - - numberValue = numerator / denominator; - } else if (fractionParts.count() == 1) { - bool ok; - numberValue = stringToDouble(fractionParts.first(), &ok); - if (!ok) { - continue; - } - } - - KUnitConversion::Value v = category.convert(KUnitConversion::Value(numberValue, u1), u); - - if (!v.isValid()) { - continue; - } - - Plasma::QueryMatch match(this); - match.setType(Plasma::QueryMatch::InformationalMatch); - match.setIconName(QStringLiteral("edit-copy")); - match.setText(QStringLiteral("%1 (%2)").arg(v.toString(), u.symbol())); - match.setData(v.number()); - match.setRelevance(1.0 - std::abs(std::log10(v.number())) / 50.0); - matches.append(match); - } - - context.addMatches(matches); -} - -void ConverterRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) -{ - Q_UNUSED(context) - const QString data = match.data().toString(); - if (data.startsWith(QLatin1String("http://"))) { - QDesktopServices::openUrl(QUrl(data)); - } else { - QGuiApplication::clipboard()->setText(data); - } + return units; } #include "converterrunner.moc"