diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ Quick Qml Widgets + Test ) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS 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,23 @@ 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 Qt5::Widgets) + install(TARGETS krunner_converter DESTINATION ${KDE_INSTALL_PLUGINDIR}) install(FILES plasma-runner-converter.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) + +add_library(krunner_converter_test STATIC ${krunner_converter_SRCS}) +target_link_libraries(krunner_converter_test + KF5::I18n + KF5::Runner + KF5::UnitConversion + Qt5::Widgets + Qt5::Test + ) + +if(BUILD_TESTING) + add_subdirectory(autotests) +endif() diff --git a/runners/converter/autotests/CMakeLists.txt b/runners/converter/autotests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/runners/converter/autotests/CMakeLists.txt @@ -0,0 +1,5 @@ +remove_definitions(-DQT_NO_CAST_FROM_ASCII) + +include(ECMAddTests) + +ecm_add_test(converterrunnertest.cpp TEST_NAME converterrunnertest LINK_LIBRARIES Qt5::Test krunner_converter_test) diff --git a/runners/converter/autotests/converterrunnertest.cpp b/runners/converter/autotests/converterrunnertest.cpp new file mode 100644 --- /dev/null +++ b/runners/converter/autotests/converterrunnertest.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2020 Alexander Lohnau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include + +#include "../converterrunner.h" + +using namespace KUnitConversion; + +class ConverterRunnerTest : public QObject +{ +Q_OBJECT +private Q_SLOTS: + void initTestCase(); + + void testMostCommonUnits(); + void testSpecificTargetUnit(); + void testUnitsCaseInsensitive(); + void testCaseSensitiveUnits(); + void testCurrency(); + void testLettersAndCurrency(); + void testInvalidCurrency(); + void testFractions(); + void testInvalidFractions(); + void testSymbolsInUnits(); + void testNegativeValue(); + +private: + ConverterRunner *runner = nullptr; +}; + +void ConverterRunnerTest::initTestCase() +{ + setlocale(LC_ALL, "C.utf8"); + qputenv("LANG", "en_US"); + runner = new ConverterRunner(this, QVariantList()); + runner->init(); +} + +/** + * Test if the most common units are displayed + */ +void ConverterRunnerTest::testMostCommonUnits() +{ + Plasma::RunnerContext context; + context.setQuery(QStringLiteral("1m")); + runner->match(context); + + Converter converter; + const auto lengthCategory = converter.category(KUnitConversion::LengthCategory); + QCOMPARE(context.matches().count(), lengthCategory.mostCommonUnits().count() -1); +} + +/* + * Test if specifying a target unit works + */ +void ConverterRunnerTest::testSpecificTargetUnit() +{ + Plasma::RunnerContext context; + context.setQuery(QStringLiteral("1m > cm")); + runner->match(context); + + QCOMPARE(context.matches().count(), 1); + QCOMPARE(context.matches().first().text(), QStringLiteral("100 centimeters (cm)")); +} + +/** + * Test if the units are case insensitive + */ +void ConverterRunnerTest::testUnitsCaseInsensitive() +{ + Plasma::RunnerContext context; + context.setQuery(QStringLiteral("1Liter in ML")); + runner->match(context); + + QCOMPARE(context.matches().count(), 1); +} + +/** + * Test if the units that are case sensitive are correctly parsed + */ +void ConverterRunnerTest::testCaseSensitiveUnits() +{ + Plasma::RunnerContext context; + context.setQuery(QStringLiteral("1Ms as ms")); + runner->match(context); + + QCOMPARE(context.matches().count(), 1); + QCOMPARE(context.matches().first().text(), QStringLiteral("1.000.000.000 milliseconds (ms)")); + + Plasma::RunnerContext context2; + context2.setQuery(QStringLiteral("1.000.000.000milliseconds>Ms")); + runner->match(context2); + QCOMPARE(context2.matches().count(), 1); + QCOMPARE(context2.matches().first().text(), "1 megasecond (Ms)"); +} + +/** + * Test of a currency gets converted to the most common currencies + */ +void ConverterRunnerTest::testCurrency() +{ + Plasma::RunnerContext context; + context.setQuery(QStringLiteral("1$")); + runner->match(context); + + Converter converter; + const auto currencyCategory = converter.category(KUnitConversion::CurrencyCategory); + QList currencyUnits = currencyCategory.mostCommonUnits(); + + const QString currencyIsoCode = QLocale().currencySymbol(QLocale::CurrencyIsoCode); + const KUnitConversion::Unit localCurrency = currencyCategory.unit(currencyIsoCode); + if (localCurrency.isValid() && !currencyUnits.contains(localCurrency)) { + currencyUnits << localCurrency; + } + QCOMPARE(context.matches().count(), currencyUnits.count() - 1); + +} + +/** + * Test a combination of currency symbols and letters that is not directly supported by the conversion backend + */ +void ConverterRunnerTest::testLettersAndCurrency() +{ + Plasma::RunnerContext context; + context.setQuery(QStringLiteral("4us$>ca$")); + runner->match(context); + + QCOMPARE(context.matches().count(), 1); + QVERIFY(context.matches().first().text().contains(QLatin1String("Canadian dollars (CAD)"))); +} + +/** + * Test a query that matches the regex but is not valid + */ +void ConverterRunnerTest::testInvalidCurrency() +{ + Plasma::RunnerContext context; + context.setQuery(QStringLiteral("4us$>abc$")); + runner->match(context); + + QCOMPARE(context.matches().count(), 0); +} + +/** + * Test if the factions are correctly parsed + */ +void ConverterRunnerTest::testFractions() +{ + Plasma::RunnerContext context; + context.setQuery(QStringLiteral("6/3m>cm")); + runner->match(context); + + QCOMPARE(context.matches().count(), 1); + QCOMPARE(context.matches().first().text(), QStringLiteral("200 centimeters (cm)")); +} + +/** + * Test if an invalid query with a fraction gets rejected + */ +void ConverterRunnerTest::testInvalidFractions() +{ + Plasma::RunnerContext context; + context.setQuery(QStringLiteral("4/4>cm")); + runner->match(context); + + QCOMPARE(context.matches().count(), 0); +} + +/** + * Test if symbols (other than currencies) are accepted + */ +void ConverterRunnerTest::testSymbolsInUnits() +{ + Plasma::RunnerContext context; + context.setQuery(QStringLiteral("1000 µs as year")); + runner->match(context); + + QCOMPARE(context.matches().count(), 1); +} + +/** + * Test if negative values are accepted + */ +void ConverterRunnerTest::testNegativeValue() +{ + Plasma::RunnerContext context; + context.setQuery(QStringLiteral("-4m as cm")); + runner->match(context); + + QCOMPARE(context.matches().count(), 1); + QCOMPARE(context.matches().first().text(), "-400 centimeters (cm)"); +} + +QTEST_MAIN(ConverterRunnerTest) + +#include "converterrunnertest.moc" 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,47 @@ #ifndef CONVERTERRUNNER_H #define CONVERTERRUNNER_H -#include +#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); + void init() override; + void insertCompatibleUnits(); ~ConverterRunner() override; void match(Plasma::RunnerContext &context) override; + QList actionsForMatch(const Plasma::QueryMatch &match) override; void run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) override; private: - QStringList m_separators; + KUnitConversion::Converter converter; + const QLocale locale; + QRegularExpression valueRegex; + QRegularExpression unitSeperatorRegex; + /** To convert currency symbols back to ISO string and handle case sensitive units */ + QMap compatibleUnits; + + QList actionList; + QLatin1String copyActionId = QLatin1String("copy"); + QLatin1String copyUnitActionId = QLatin1String("copy-unit"); + + QPair stringToDouble(const QStringRef &value); + QPair getValidatedNumberValue(const QString &value); + QList createResultUnits(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,303 +16,246 @@ * along with this program. If not, see . */ + #include "converterrunner.h" + #include #include #include -#include #include #include -#include -#include #include -#define CONVERSION_CHAR QLatin1Char( '>' ) - K_EXPORT_PLASMA_RUNNER(converterrunner, ConverterRunner) -class StringParser +ConverterRunner::ConverterRunner(QObject *parent, const QVariantList &args) + : Plasma::AbstractRunner(parent, args) { -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; + setObjectName(QStringLiteral("Converter")); + //can not ignore commands: we have things like m4 + setIgnoredTypes(Plasma::RunnerContext::Directory | Plasma::RunnerContext::File | + 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)); +} + +void ConverterRunner::init() +{ + valueRegex = QRegularExpression(QStringLiteral("^([0-9,./+-]+)")); + const QStringList conversionWords = i18nc("list of words that can used as amount of 'unit1' [in|to|as] 'unit2'", + "in;to;as").split(QLatin1Char(';')); + QString conversionRegex; + for (const auto &word: conversionWords) { + conversionRegex.append(QLatin1Char(' ') + word + QStringLiteral(" |")); + } + conversionRegex.append(QStringLiteral(" ?> ?")); + unitSeperatorRegex = QRegularExpression(conversionRegex); + valueRegex.optimize(); + unitSeperatorRegex.optimize(); + + insertCompatibleUnits(); + + addAction(copyActionId, QIcon::fromTheme(QStringLiteral("edit-copy")), + QStringLiteral("Copy number")); + addAction(copyUnitActionId, QIcon::fromTheme(QStringLiteral("edit-copy")), + QStringLiteral("Copy unit and number")); + actionList = {action(copyActionId), action(copyUnitActionId)}; +} + +ConverterRunner::~ConverterRunner() = default; + +void ConverterRunner::match(Plasma::RunnerContext &context) +{ + const QString term = context.query(); + if (term.size() < 2 || !context.isValid()) { + return; + } + + const QRegularExpressionMatch valueRegexMatch = valueRegex.match(context.query()); + if (!valueRegexMatch.hasMatch()) { + return; } + const QString inputValueString = valueRegexMatch.captured(1); - bool isNumber(const QChar &ch) - { - if (ch.isNumber()) { - return true; + // Get the different units by splitting up the query with the regex + QStringList unitStrings = context.query().simplified().remove(valueRegex).split(unitSeperatorRegex); + if (unitStrings.isEmpty()) { + return; + } + // Check if unit is valid, otherwise check for the value in the compatibleUnits map + QString inputUnitString = unitStrings.first().simplified(); + KUnitConversion::UnitCategory inputCategory = converter.categoryForUnit(inputUnitString); + if (inputCategory.id() == KUnitConversion::InvalidCategory) { + inputUnitString = compatibleUnits.value(inputUnitString.toUpper()); + if (inputUnitString.isEmpty()) { + return; } - if (QStringLiteral(".,-+/").contains(ch)) { - return true; + inputCategory = converter.categoryForUnit(inputUnitString); + if (inputCategory.id() == KUnitConversion::InvalidCategory) { + return; } - return false; } - QString rest() - { - return m_s.mid(m_index).simplified(); + QString outputUnitString; + if (unitStrings.size() == 2) { + outputUnitString = unitStrings.at(1).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; - } - } + 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; } -private: - void passWhiteSpace() - { - while (next().isSpace()) { - ++m_index; + const double numberValue = numberDataPair.second; + QList matches; + for (const KUnitConversion::Unit &outputUnit: outputUnits) { + KUnitConversion::Value outputValue = inputCategory.convert( + KUnitConversion::Value(numberValue, inputUnit), outputUnit); + if (!outputValue.isValid() || inputUnit == outputUnit) { + continue; } - } - QChar next() - { - if (m_index >= m_s.size()) { - return QChar::Null; + Plasma::QueryMatch match(this); + match.setType(Plasma::QueryMatch::InformationalMatch); + match.setIconName(QStringLiteral("accessories-calculator")); + if (outputUnit.categoryId() == KUnitConversion::CurrencyCategory) { + outputValue.round(2); + match.setText(QStringLiteral("%1 (%2)").arg(outputValue.toString(0, 'f', 2), outputUnit.symbol())); + } else { + match.setText(QStringLiteral("%1 (%2)").arg(outputValue.toString(), outputUnit.symbol())); } - return m_s.at(m_index); + match.setData(outputValue.number()); + match.setRelevance(1.0 - std::abs(std::log10(outputValue.number())) / 50.0); + matches.append(match); } - int m_index; - QString m_s; -}; + context.addMatches(matches); +} -ConverterRunner::ConverterRunner(QObject* parent, const QVariantList &args) - : Plasma::AbstractRunner(parent, args) +QList ConverterRunner::actionsForMatch(const Plasma::QueryMatch &match) { - Q_UNUSED(args) - setObjectName(QLatin1String( "Converter" )); + Q_UNUSED(match) - m_separators << QString( CONVERSION_CHAR ); - m_separators << i18nc("list of words that can used as amount of 'unit1' [in|to|as] 'unit2'", - "in;to;as").split(QLatin1Char( ';' )); + return actionList; +} - //can not ignore commands: we have things like m4 - setIgnoredTypes(Plasma::RunnerContext::Directory | Plasma::RunnerContext::File | - Plasma::RunnerContext::NetworkLocation); +void ConverterRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) +{ + Q_UNUSED(context) - 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)); + if (match.selectedAction() == action(copyActionId)) { + QGuiApplication::clipboard()->setText(match.data().toString()); + } else { + QGuiApplication::clipboard()->setText(match.text().split(QLatin1String(" (")).first()); + } } -ConverterRunner::~ConverterRunner() +QPair ConverterRunner::stringToDouble(const QStringRef &value) { + bool ok; + double numberValue = locale.toDouble(value, &ok); + if (!ok) { + numberValue = value.toDouble(&ok); + } + return {ok, numberValue}; } -void ConverterRunner::match(Plasma::RunnerContext &context) +QPair ConverterRunner::getValidatedNumberValue(const QString &value) { - const QString term = context.query(); - if (term.size() < 2) { - return; + const auto fractionParts = value.splitRef(QLatin1Char('/'), QString::SkipEmptyParts); + if (fractionParts.isEmpty() || fractionParts.count() > 2) { + return {false, 0}; } - StringParser cmd(term); - QString unit1; - QString value; - QString unit2; - - unit1 = cmd.get(StringParser::GetString); - value = cmd.get(StringParser::GetDigit); - if (value.isEmpty()) { - return; - } - if (unit1.isEmpty()) { - unit1 = cmd.get(StringParser::GetString | StringParser::GetDigit); - if (unit1.isEmpty()) { - return; + if (fractionParts.count() == 2) { + const QPair doubleFirstResults = stringToDouble(fractionParts.first()); + if (!doubleFirstResults.first) { + return {false, 0}; } - } - - const QString s = cmd.get(StringParser::GetString); - - if (!s.isEmpty() && !m_separators.contains(s)) { - unit1 += QLatin1Char( ' ' ) + s; - } - if (s.isEmpty() || !m_separators.contains(s)) { - cmd.pass(m_separators); - } - 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; - } + const QPair doubleSecondResult = stringToDouble(fractionParts.last()); + if (!doubleSecondResult.first || qFuzzyIsNull(doubleSecondResult.second)) { + return {false, 0}; } - if (!found) { - return; + 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(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; + // Autocompletion for the target units + outputUnitString = outputUnitString.toUpper(); + for (const auto &unitStringKey: compatibleUnits.keys()) { + if (unitStringKey.startsWith(outputUnitString)) { + outputUnit = category.unit(compatibleUnits.value(unitStringKey)); + 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) { + return units; +} +void ConverterRunner::insertCompatibleUnits() +{ + // Add all currency symbols to the map, if their ISO code is supported by backend + const QList allLocales = QLocale::matchingLocales( + QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry); + KUnitConversion::UnitCategory currencyCategory = converter.category(QStringLiteral("Currency")); + const QStringList availableISOCodes = currencyCategory.allUnits(); + QRegularExpression hasCurrencyRegex = QRegularExpression(QStringLiteral("\\p{Sc}")); + hasCurrencyRegex.optimize(); + for (const auto ¤cyLocale: allLocales) { + const QString symbol = currencyLocale.currencySymbol(QLocale::CurrencySymbol); + const QString isoCode = currencyLocale.currencySymbol(QLocale::CurrencyIsoCode); + + if (isoCode.isEmpty() || !symbol.contains(hasCurrencyRegex)) { 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; - } + if (availableISOCodes.contains(isoCode)) { + compatibleUnits.insert(symbol.toUpper(), isoCode); } - - 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); + // Add all units as uppercase in the map + for (const auto &category: converter.categories()) { + for (const auto &unit: category.allUnits()) { + compatibleUnits.insert(unit.toUpper(), unit); + } } } diff --git a/runners/spellchecker/spellcheck.h b/runners/spellchecker/spellcheck.h --- a/runners/spellchecker/spellcheck.h +++ b/runners/spellchecker/spellcheck.h @@ -46,17 +46,18 @@ QList actionsForMatch(const Plasma::QueryMatch &match) override; QMimeData * mimeDataForMatch(const Plasma::QueryMatch &match) override; - void loaddata(); + void loadData(); void destroydata(); private: - QString findlang(const QStringList &terms); + QString findLang(const QStringList &terms); QString m_triggerWord; QMap m_languages;//key=language name, value=language code bool m_requireTriggerWord; QMap > m_spellers; //spellers QMutex m_spellLock; //Lock held when constructing a new speller + QList m_actions; }; #endif diff --git a/runners/spellchecker/spellcheck.cpp b/runners/spellchecker/spellcheck.cpp --- a/runners/spellchecker/spellcheck.cpp +++ b/runners/spellchecker/spellcheck.cpp @@ -28,38 +28,32 @@ #include -namespace { -namespace ActionIds { -inline QString copyToClipboard() { return QStringLiteral("copyToClipboard"); } -} -} - SpellCheckRunner::SpellCheckRunner(QObject* parent, const QVariantList &args) : Plasma::AbstractRunner(parent, args) { Q_UNUSED(args) - setObjectName(QLatin1String( "Spell Checker" )); + setObjectName(QStringLiteral("Spell Checker")); setIgnoredTypes(Plasma::RunnerContext::FileSystem | Plasma::RunnerContext::NetworkLocation); setSpeed(AbstractRunner::SlowSpeed); - - addAction(ActionIds::copyToClipboard(), QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action", "Copy to Clipboard")); } -SpellCheckRunner::~SpellCheckRunner() -{ -} +SpellCheckRunner::~SpellCheckRunner() = default; void SpellCheckRunner::init() { - Plasma::AbstractRunner::init(); + m_actions = {addAction(QStringLiteral("copyToClipboard"), + QIcon::fromTheme(QStringLiteral("edit-copy")), + i18nc("@action", "Copy to Clipboard"))}; //Connect prepare and teardown signals - connect(this, SIGNAL(prepare()), this, SLOT(loaddata())); - connect(this, SIGNAL(teardown()), this, SLOT(destroydata())); + connect(this, &SpellCheckRunner::prepare, this, &SpellCheckRunner::loadData); + connect(this, &SpellCheckRunner::teardown, this, &SpellCheckRunner::destroydata); + + reloadConfiguration(); } //Load a default dictionary and some locale names -void SpellCheckRunner::loaddata() +void SpellCheckRunner::loadData() { //Load the default speller, with the default language auto defaultSpellerIt = m_spellers.find(QString()); @@ -75,12 +69,12 @@ //name (eg. 'german') with one sub-code. QSet families; //First get the families - foreach (const QString &code, avail) { + for (const QString &code: avail) { families +=code.left(2); } //Now for each family figure out which is the main code. - foreach (const QString &fcode,families) { - QStringList family = avail.filter(fcode); + for (const QString &fcode: qAsConst(families)) { + const QStringList family = avail.filter(fcode); QString code; //If we only have one code, use it. //If a string is the default language, use it @@ -106,7 +100,6 @@ if (!name.isEmpty()) { m_languages[name.toLower()] = code; } -// kDebug() << "SPELL lang: " << fcode<< "::"<< name << " : " << code; } } @@ -119,32 +112,29 @@ void SpellCheckRunner::reloadConfiguration() { - m_triggerWord = config().readEntry("trigger", i18n("spell")); + const KConfigGroup cfg = config(); + m_triggerWord = cfg.readEntry("trigger", i18n("spell")); //Processing will be triggered by "keyword " + m_requireTriggerWord = cfg.readEntry("requireTriggerWord", true) && !m_triggerWord.isEmpty(); m_triggerWord += QLatin1Char( ' ' ); - m_requireTriggerWord = config().readEntry("requireTriggerWord", true); - Plasma::RunnerSyntax s(i18nc("Spelling checking runner syntax, first word is trigger word, e.g. \"spell\".", - "%1:q:", m_triggerWord), - i18n("Checks the spelling of :q:.")); + "%1:q:", m_triggerWord), i18n("Checks the spelling of :q:.")); if (!m_requireTriggerWord) { - s.addExampleQuery(QLatin1String( ":q:" )); + s.addExampleQuery(QStringLiteral(":q:")); } - QList syns; - syns << s; - setSyntaxes(syns); + setSyntaxes({s}); } /* Take the input query, split into a list, and see if it contains a language to spell in. * Return the empty string if we can't match a language. */ -QString SpellCheckRunner::findlang(const QStringList& terms) +QString SpellCheckRunner::findLang(const QStringList& terms) { - auto defaultSpeller = m_spellers[QString()]; + const auto &defaultSpeller = m_spellers[QString()]; //If first term is a language code (like en_GB), set it as the spell-check language - if (terms.count() >= 1 && defaultSpeller->availableLanguages().contains(terms[0])) { + if (!terms.isEmpty() && defaultSpeller->availableLanguages().contains(terms[0])) { return terms[0]; } //If we have two terms and the first is a language name (eg 'french'), @@ -201,7 +191,7 @@ if (speller->isValid()) { QStringList terms = query.split(QLatin1Char(' '), QString::SkipEmptyParts); - QString lang = findlang(terms); + const QString lang = findLang(terms); //If we found a language, create a new speller object using it. if (!lang.isEmpty()) { //First term is the language @@ -216,7 +206,7 @@ } speller = m_spellers[lang]; //Rejoin the strings - query = terms.join(QLatin1String(" ")); + query = terms.join(QLatin1Char(' ')); } } @@ -258,18 +248,15 @@ void SpellCheckRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match) { Q_UNUSED(context) - if (match.selectedAction() == action(ActionIds::copyToClipboard())) { - //Copy words to clipboard - const QString text = match.data().toString(); - QGuiApplication::clipboard()->setText(text); - } + + QGuiApplication::clipboard()->setText(match.data().toString()); } QList SpellCheckRunner::actionsForMatch(const Plasma::QueryMatch &match) { Q_UNUSED(match) - return {action(ActionIds::copyToClipboard())}; + return m_actions; } QMimeData * SpellCheckRunner::mimeDataForMatch(const Plasma::QueryMatch &match) diff --git a/runners/spellchecker/spellcheck_config.cpp b/runners/spellchecker/spellcheck_config.cpp --- a/runners/spellchecker/spellcheck_config.cpp +++ b/runners/spellchecker/spellcheck_config.cpp @@ -43,27 +43,21 @@ layout->addWidget(m_ui, 0, 0); - connect(m_ui->m_requireTriggerWord, SIGNAL(stateChanged(int)), this, SLOT(changed())); - connect(m_ui->m_requireTriggerWord, SIGNAL(stateChanged(int)), this, SLOT(toggleTriggerWord(int))); - connect(m_ui->m_triggerWord, SIGNAL(textChanged(QString)), this, SLOT(changed())); - - m_ui->m_openKcmButton->setIcon(QIcon::fromTheme(QStringLiteral("tools-check-spelling"))); + connect(m_ui->m_requireTriggerWord, &QCheckBox::stateChanged, this, &SpellCheckConfig::markAsChanged); + connect(m_ui->m_requireTriggerWord, &QCheckBox::stateChanged, this, &SpellCheckConfig::toggleTriggerWord); + connect(m_ui->m_triggerWord, &QLineEdit::textChanged, this, &SpellCheckConfig::markAsChanged); connect(m_ui->m_openKcmButton, &QPushButton::clicked, this, &SpellCheckConfig::openKcm); - load(); + m_ui->m_openKcmButton->setIcon(QIcon::fromTheme(QStringLiteral("tools-check-spelling"))); } SpellCheckConfig::~SpellCheckConfig() { } void SpellCheckConfig::toggleTriggerWord(int state) { - if (state == Qt::Unchecked) { - m_ui->m_triggerWord->setEnabled(false); - } else { - m_ui->m_triggerWord->setEnabled(true); - } + m_ui->m_triggerWord->setEnabled(state == Qt::Checked); } void SpellCheckConfig::openKcm() @@ -76,29 +70,27 @@ KCModule::load(); //FIXME: This shouldn't be hardcoded! - KSharedConfig::Ptr cfg = KSharedConfig::openConfig( QLatin1String( "krunnerrc" ) ); - KConfigGroup conf = cfg->group( "Runners" ); - KConfigGroup grp = KConfigGroup( &conf, "Spell Checker"); + const KSharedConfig::Ptr cfg = KSharedConfig::openConfig(QStringLiteral("krunnerrc")); + const KConfigGroup grp = cfg->group("Runners").group("Spell Checker"); const bool requireTrigger = grp.readEntry("requireTriggerWord", true); const QString trigger = grp.readEntry("trigger", i18n("spell")); if (!requireTrigger) { m_ui->m_triggerWord->setEnabled(false); } - m_ui->m_requireTriggerWord->setCheckState( (requireTrigger) ? Qt::Checked : Qt::Unchecked ); - m_ui->m_triggerWord->setText( trigger ); + m_ui->m_requireTriggerWord->setCheckState((requireTrigger) ? Qt::Checked : Qt::Unchecked); + m_ui->m_triggerWord->setText(trigger); emit changed(false); } void SpellCheckConfig::save() { //FIXME: This shouldn't be hardcoded! - KSharedConfig::Ptr cfg = KSharedConfig::openConfig( QLatin1String( "krunnerrc" ) ); - KConfigGroup conf = cfg->group( "Runners" ); - KConfigGroup grp = KConfigGroup( &conf, "Spell Checker"); + KSharedConfig::Ptr cfg = KSharedConfig::openConfig(QStringLiteral("krunnerrc")); + KConfigGroup grp = cfg->group("Runners").group("Spell Checker"); bool requireTrigger = m_ui->m_requireTriggerWord->checkState() == Qt::Checked; if (requireTrigger) {