File indexing completed on 2024-04-28 05:52:25

0001 /*
0002  * Copyright 2010-2012 Bart Kroon <bart@tarmack.eu>
0003  * 
0004  * Redistribution and use in source and binary forms, with or without
0005  * modification, are permitted provided that the following conditions
0006  * are met:
0007  * 
0008  * 1. Redistributions of source code must retain the above copyright
0009  *   notice, this list of conditions and the following disclaimer.
0010  * 2. Redistributions in binary form must reproduce the above copyright
0011  *   notice, this list of conditions and the following disclaimer in the
0012  *   documentation and/or other materials provided with the distribution.
0013  * 
0014  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
0015  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0016  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
0017  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
0018  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
0019  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
0020  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
0021  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0022  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
0023  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0024  */
0025 
0026 #include "Units.h"
0027 
0028 #include <QRegularExpression>
0029 #include <QClipboard>
0030 #include <QApplication>
0031 #include <klocalizedstring.h>
0032 #include <KUnitConversion/Converter>
0033 #include <cmath>
0034 #include <QDebug>
0035 #include <QThread>
0036 #include <QTimer>
0037 #include <QElapsedTimer>
0038 
0039 #include "calculator/evaluator.h"
0040 
0041 void CurrencyRefresher::init()
0042 {
0043     m_timer = new QTimer(this);
0044     m_timer->setInterval(86390 * 1000);
0045     refresh();
0046     connect(thread(), &QThread::finished, m_timer, &QTimer::stop);
0047     m_timer->start();
0048 }
0049 
0050 void CurrencyRefresher::refresh()
0051 {
0052     QElapsedTimer timer; timer.start();
0053     qDebug() << "Warming up currency converter";
0054     KUnitConversion::Converter converter;
0055     KUnitConversion::UnitCategory currency = converter.category(KUnitConversion::CurrencyCategory);
0056     const KUnitConversion::Unit nok = currency.unit(KUnitConversion::UnitId::Nok);
0057     const KUnitConversion::Value value = KUnitConversion::Value(1, nok);
0058     converter.convert(value, KUnitConversion::UnitId::Usd);
0059     if (timer.elapsed() > 0) {
0060         qDebug() << "Currency converter warmed in" << timer.elapsed() << "ms";
0061     }
0062 }
0063 
0064 Units::Units(QObject *parent) :
0065     Provider(parent)
0066 {
0067     m_refresher = new CurrencyRefresher;
0068     m_refresherThread = new QThread(this);
0069     m_refresher->moveToThread(m_refresherThread);
0070     m_refresherThread->start();
0071     QMetaObject::invokeMethod(m_refresher, &CurrencyRefresher::init);
0072 }
0073 
0074 Units::~Units()
0075 {
0076     m_refresherThread->quit();
0077     m_refresherThread->wait();
0078 }
0079 
0080 ProviderResult *Units::createResult(const KUnitConversion::Unit &inputUnit, const KUnitConversion::Value &inputValue, const KUnitConversion::Unit &outputUnit)
0081 {
0082     const KUnitConversion::Value outputValue = m_converter.convert(inputValue, outputUnit);
0083 
0084     int inputPrecision = 1;
0085     if (inputValue.number() < 1 || inputUnit.categoryId() != KUnitConversion::CurrencyCategory) {
0086         qreal calculationResult = inputValue.number();
0087 
0088         if (calculationResult < 100) {
0089             inputPrecision = 5;
0090         }
0091 
0092         double scaledResult = calculationResult * std::pow(10, inputPrecision);
0093         while (inputPrecision > 0 && std::floor(scaledResult) == std::ceil(scaledResult)) {
0094             inputPrecision--;
0095             scaledResult = calculationResult * std::pow(10, inputPrecision);
0096         }
0097     }
0098     int outputPrecision = 1;
0099     if (outputValue.number() < 1 && outputUnit.categoryId() != KUnitConversion::CurrencyCategory) {
0100         qreal calculationResult = outputValue.number();
0101 
0102         if (calculationResult < 100) {
0103             outputPrecision = 5;
0104         }
0105 
0106         double scaledResult = calculationResult * std::pow(10, outputPrecision);
0107         while (outputPrecision > 0 && std::floor(scaledResult) == std::ceil(scaledResult)) {
0108             outputPrecision--;
0109             scaledResult = calculationResult * std::pow(10, outputPrecision);
0110         }
0111     }
0112 
0113     QString inputString = QLocale::system().toString(inputValue.number(), 'f', inputPrecision + 1);
0114     const QString outputString = QLocale::system().toString(outputValue.number(), 'f', outputPrecision + 1);
0115 
0116     while (!inputString.isEmpty() && inputString.endsWith('0')) {
0117         inputString.chop(1);
0118     }
0119     if (inputString.endsWith(QLocale::system().decimalPoint())) {
0120         inputString.chop(1);
0121     }
0122 
0123     ProviderResult *result = new ProviderResult;
0124     result->icon = "exchange-positions";
0125     result->object = this;
0126     result->name = i18nc("conversion from one unit to another", "%1 %2 is %3 %4", inputString, inputValue.unit().symbol(), outputString, outputValue.unit().symbol());
0127     result->program = outputString;
0128     result->completion = result->name;
0129     result->type = i18n("Unit conversion");
0130     result->isCalculation = true;
0131 
0132     return result;
0133 }
0134 
0135 QList<ProviderResult *> Units::getResults(QString query)
0136 {
0137 
0138     QRegularExpression pattern(R"raw((.+?)(\w+)\s+(?:\=|to|is|in)\s+(\w+)$)raw", QRegularExpression::CaseInsensitiveOption);
0139     QRegularExpressionMatch match = pattern.match(query);
0140     if (!match.hasMatch()) {
0141         return {};
0142     }
0143 
0144     QString sourceAmount = match.captured(1);
0145     bool ok = false;
0146     double inputNumber = sourceAmount.toDouble(&ok);
0147     if (!ok) {
0148         Evaluator *ev = Evaluator::instance();
0149         sourceAmount = ev->autoFix(sourceAmount);
0150         ev->setExpression(sourceAmount);
0151         const Quantity quantity = ev->evalNoAssign();
0152         if (!ev->error().isEmpty()) {
0153             return {};
0154         }
0155         if (!quantity.isReal()) {
0156             return {};
0157         }
0158         const Rational rationalNumber(quantity.numericValue().real);
0159         if (!rationalNumber.isValid()) {
0160             return {};
0161         }
0162         inputNumber = rationalNumber.toDouble();
0163     }
0164 
0165     QList<ProviderResult*> list;
0166 
0167     QSet<KUnitConversion::UnitId> usedInputs;
0168     QSet<KUnitConversion::UnitId> usedOutputs;
0169     int priority = 0;
0170     for (const KUnitConversion::Unit &inputUnit : resolveUnitName(match.captured(2))) {
0171         if (usedInputs.contains(inputUnit.id())) {
0172             continue;
0173         }
0174         usedInputs.insert(inputUnit.id());
0175         for (const KUnitConversion::Unit &outputUnit : resolveUnitName(match.captured(3), inputUnit.category())) {
0176             if (usedOutputs.contains(outputUnit.id())) {
0177                 continue;
0178             }
0179             usedOutputs.insert(outputUnit.id());
0180 
0181             const KUnitConversion::Value inputValue(inputNumber, inputUnit);
0182 
0183             ProviderResult *result = createResult(inputUnit, inputValue, outputUnit);
0184             result->priority = priority++;
0185             list.append(result);
0186         }
0187     }
0188 
0189     return list;
0190 }
0191 
0192 int Units::launch(const QString &exec)
0193 {
0194     QClipboard* clipboard = QApplication::clipboard();
0195     clipboard->setText(exec);
0196     return 0;
0197 }
0198 
0199 QList<KUnitConversion::Unit> Units::resolveUnitName(const QString &name, const KUnitConversion::UnitCategory &category)
0200 {
0201     KUnitConversion::Unit unit;
0202 
0203     if (!category.isNull()) {
0204         unit = category.unit(name);
0205     } else {
0206         unit = m_converter.unit(name);
0207     }
0208 
0209     if (unit.isValid()) {
0210         return {unit};
0211     }
0212 
0213     QList<KUnitConversion::Unit> units;
0214 
0215     // Didn't match directly, try to match without case sensitivity
0216 
0217     if (!category.isNull()) {
0218         // try only common first
0219         units.append(findCompatibleUnits(name, category, OnlyCommonUnits));
0220 
0221         if (!units.isEmpty()) {
0222             return units;
0223         }
0224 
0225         // If not common, try something else
0226         units.append(findCompatibleUnits(name, category, AllUnits));
0227     } else {
0228         for (const KUnitConversion::UnitCategory &candidateCategory : m_converter.categories()) {
0229             units.append(findCompatibleUnits(name, candidateCategory, OnlyCommonUnits));
0230         }
0231         if (!units.isEmpty()) {
0232             return units;
0233         }
0234 
0235         // Was not a common unit, try all units
0236         for (const KUnitConversion::UnitCategory &candidateCategory : m_converter.categories()) {
0237             units.append(findCompatibleUnits(name, candidateCategory, AllUnits));
0238         }
0239     }
0240 
0241     return units;
0242 }
0243 
0244 QList<KUnitConversion::Unit> Units::findCompatibleUnits(const QString &name, const KUnitConversion::UnitCategory &category, const UnitMatchingLevel level)
0245 {
0246     if (category.isNull()) {
0247         return {};
0248     }
0249 
0250     QSet<KUnitConversion::UnitId> commonIds;
0251     if (level == OnlyCommonUnits) {
0252         for (const KUnitConversion::Unit &unit : category.mostCommonUnits()) {
0253             commonIds.insert(unit.id());
0254         }
0255     }
0256 
0257     QList<KUnitConversion::Unit> units;
0258     static const QRegularExpression nonAlphaRegex("([^A-Za-z]+)");
0259     QString simpleName = name;
0260     simpleName.remove(nonAlphaRegex);
0261 
0262     for (const QString &candidateName : category.allUnits()) {
0263         QString simpleCandidateName = candidateName.toLower();
0264         simpleCandidateName.remove(nonAlphaRegex);
0265         if (simpleCandidateName.startsWith(simpleName)) {
0266             units.append(category.unit(candidateName));
0267         }
0268 
0269         if (name.compare(candidateName, Qt::CaseInsensitive)) {
0270             continue;
0271         }
0272 
0273         KUnitConversion::Unit candidate = category.unit(candidateName);
0274         if (level == OnlyCommonUnits && !commonIds.contains(candidate.id())) {
0275             continue;
0276         }
0277 
0278         if (candidate.isValid()) {
0279             return {candidate};
0280         }
0281     }
0282 
0283     return units;
0284 }
0285 
0286 // kate: indent-mode cstyle; space-indent on; indent-width 4;