File indexing completed on 2024-04-28 17:06:56
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;