2
* Copyright (C) 2007 Barış Metin <baris@pardus.org.tr>
3
* Copyright (C) 2006 David Faure <faure@kde.org>
4
* Copyright (C) 2007 Richard Moore <rich@kde.org>
5
* Copyright (C) 2010 Matteo Agostinelli <agostinelli@gmail.com>
7
* This program is free software; you can redistribute it and/or modify
8
* it under the terms of the GNU Library General Public License version 2 as
9
* published by the Free Software Foundation
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details
16
* You should have received a copy of the GNU Library General Public
17
* License along with this program; if not, write to the
18
* Free Software Foundation, Inc.,
19
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
#include "calculatorrunner.h"
24
#ifdef ENABLE_QALCULATE
25
#include "qalculate_engine.h"
27
#include <QScriptEngine>
33
#include <Plasma/QueryMatch>
35
CalculatorRunner::CalculatorRunner( QObject* parent, const QVariantList &args )
36
: Plasma::AbstractRunner(parent, args)
40
#ifdef ENABLE_QALCULATE
41
m_engine = new QalculateEngine;
45
setObjectName( QLatin1String("Calculator" ));
46
setIgnoredTypes(Plasma::RunnerContext::Directory | Plasma::RunnerContext::File |
47
Plasma::RunnerContext::NetworkLocation | Plasma::RunnerContext::Executable |
48
Plasma::RunnerContext::ShellCommand);
50
QString description = i18n("Calculates the value of :q: when :q: is made up of numbers and "
51
"mathematical symbols such as +, -, /, * and ^.");
52
addSyntax(Plasma::RunnerSyntax("=:q:", description));
53
addSyntax(Plasma::RunnerSyntax(":q:=", description));
56
CalculatorRunner::~CalculatorRunner()
58
#ifdef ENABLE_QALCULATE
63
void CalculatorRunner::powSubstitutions(QString& cmd)
65
if (cmd.contains("e+", Qt::CaseInsensitive)) {
66
cmd=cmd.replace("e+", "*10^", Qt::CaseInsensitive);
69
if (cmd.contains("e-", Qt::CaseInsensitive)) {
70
cmd=cmd.replace("e-", "*10^-", Qt::CaseInsensitive);
73
// the below code is scary mainly because we have to honor priority
74
// honor decimal numbers and parenthesis.
75
while (cmd.contains('^')) {
76
int where = cmd.indexOf('^');
77
cmd = cmd.replace(where, 1, ',');
78
int preIndex = where - 1;
79
int postIndex = where + 1;
82
QChar decimalSymbol = KGlobal::locale()->decimalSymbol().at(0);
83
//avoid out of range on weird commands
84
preIndex = qMax(0, preIndex);
85
postIndex = qMin(postIndex, cmd.length()-1);
87
//go backwards looking for the beginning of the number or expression
88
while (preIndex != 0) {
89
QChar current = cmd.at(preIndex);
90
QChar next = cmd.at(preIndex-1);
91
//kDebug() << "index " << preIndex << " char " << current;
94
} else if (current == '(') {
97
if (((next <= '9' ) && (next >= '0')) || next == decimalSymbol) {
103
//check for functions
104
if (!((next <= 'z' ) && (next >= 'a'))) {
111
//go forwards looking for the end of the number or expression
113
while (postIndex != cmd.size() - 1) {
114
QChar current=cmd.at(postIndex);
115
QChar next=cmd.at(postIndex + 1);
117
//check for functions
118
if ((count == 0) && (current <= 'z') && (current >= 'a')) {
123
if (current == '(') {
125
} else if (current == ')') {
128
if (((next <= '9' ) && (next >= '0')) || next == decimalSymbol) {
139
preIndex = qMax(0, preIndex);
140
postIndex = qMin(postIndex, cmd.length());
142
cmd.insert(preIndex,"pow(");
143
// +1 +4 == next position to the last number after we add 4 new characters pow(
144
cmd.insert(postIndex + 1 + 4, ')');
145
//kDebug() << "from" << preIndex << " to " << postIndex << " got: " << cmd;
149
void CalculatorRunner::hexSubstitutions(QString& cmd)
151
if (cmd.contains("0x")) {
152
//Append +0 so that the calculator can serve also as a hex converter
158
while (cmd.contains("0x")) {
160
pos = cmd.indexOf("0x", pos);
162
for (int q = 0; q < cmd.size(); q++) {//find end of hex number
163
QChar current = cmd[pos+q+2];
164
if (((current <= '9' ) && (current >= '0')) || ((current <= 'F' ) && (current >= 'A')) || ((current <= 'f' ) && (current >= 'a'))) { //Check if valid hex sign
170
cmd = cmd.replace(pos, 2+hex.length(), QString::number(hex.toInt(&ok,16))); //replace hex with decimal
175
void CalculatorRunner::userFriendlySubstitutions(QString& cmd)
177
if (cmd.contains(KGlobal::locale()->decimalSymbol(), Qt::CaseInsensitive)) {
178
cmd=cmd.replace(KGlobal::locale()->decimalSymbol(), QChar('.'), Qt::CaseInsensitive);
181
// the following substitutions are not needed with libqalculate
182
#ifndef ENABLE_QALCULATE
183
hexSubstitutions(cmd);
184
powSubstitutions(cmd);
186
if (cmd.contains(QRegExp("\\d+and\\d+"))) {
187
cmd = cmd.replace(QRegExp("(\\d+)and(\\d+)"), "\\1&\\2");
189
if (cmd.contains(QRegExp("\\d+or\\d+"))) {
190
cmd = cmd.replace(QRegExp("(\\d+)or(\\d+)"), "\\1|\\2");
192
if (cmd.contains(QRegExp("\\d+xor\\d+"))) {
193
cmd = cmd.replace(QRegExp("(\\d+)xor(\\d+)"), "\\1^\\2");
199
void CalculatorRunner::match(Plasma::RunnerContext &context)
201
const QString term = context.query();
204
//no meanless space between friendly guys: helps simplify code
205
cmd = cmd.trimmed().remove(' ');
207
if (cmd.length() < 4) {
210
if (cmd.toLower() == "universe" || cmd.toLower() == "life") {
211
Plasma::QueryMatch match(this);
212
match.setType(Plasma::QueryMatch::InformationalMatch);
213
match.setIcon(KIcon("accessories-calculator"));
214
match.setText("= 42");
215
match.setId(QString());
216
context.addMatch(term, match);
220
bool toHex = cmd.startsWith(QLatin1String("hex="));
221
bool startsWithEquals = !toHex && cmd[0] == '=';
223
if (toHex || startsWithEquals) {
224
cmd.remove(0, cmd.indexOf('=') + 1);
225
} else if (cmd.endsWith('=')) {
228
// we don't have an actionable equation here
236
userFriendlySubstitutions(cmd);
237
#ifndef ENABLE_QALCULATE
238
cmd.replace(QRegExp("([a-zA-Z]+)"), "Math.\\1"); //needed for accessing math funktions like sin(),....
241
QString result = calculate(cmd);
243
if (!result.isEmpty() && result != cmd) {
245
result = "0x" + QString::number(result.toInt(), 16).toUpper();
248
Plasma::QueryMatch match(this);
249
match.setType(Plasma::QueryMatch::InformationalMatch);
250
match.setIcon(KIcon("accessories-calculator"));
251
match.setText(result);
252
match.setData(QString::fromLatin1("= %1").arg(result));
253
match.setId(QString());
254
context.addMatch(term, match);
258
QString CalculatorRunner::calculate(const QString& term)
260
#ifdef ENABLE_QALCULATE
264
result = m_engine->evaluate(term);
265
} catch(std::exception& e) {
266
kDebug() << "qalculate error: " << e.what();
269
return result.replace('.', KGlobal::locale()->decimalSymbol(), Qt::CaseInsensitive);
271
//kDebug() << "calculating" << term;
273
QScriptValue result = eng.evaluate(" var result ="+term+"; result");
275
if (result.isError()) {
279
const QString resultString = result.toString();
280
if (resultString.isEmpty()) {
284
if (!resultString.contains('.')) {
288
//ECMAScript has issues with the last digit in simple rational computations
289
//This script rounds off the last digit; see bug 167986
290
QString roundedResultString = eng.evaluate("var exponent = 14-(1+Math.floor(Math.log(Math.abs(result))/Math.log(10)));\
291
var order=Math.pow(10,exponent);\
292
(order > 0? Math.round(result*order)/order : 0)").toString();
294
roundedResultString.replace('.', KGlobal::locale()->decimalSymbol(), Qt::CaseInsensitive);
296
return roundedResultString;
300
QMimeData * CalculatorRunner::mimeDataForMatch(const Plasma::QueryMatch *match)
303
QMimeData * result = new QMimeData();
304
result->setText(match->text());
308
#include "calculatorrunner.moc"