~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to plasma/generic/runners/calculator/calculatorrunner.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
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>
 
6
 *
 
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
 
10
 *
 
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
 
15
 *
 
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.
 
20
 */
 
21
 
 
22
#include "calculatorrunner.h"
 
23
 
 
24
#ifdef ENABLE_QALCULATE
 
25
#include "qalculate_engine.h"
 
26
#else
 
27
#include <QScriptEngine>
 
28
#endif
 
29
 
 
30
#include <KIcon>
 
31
#include <KDebug>
 
32
 
 
33
#include <Plasma/QueryMatch>
 
34
 
 
35
CalculatorRunner::CalculatorRunner( QObject* parent, const QVariantList &args )
 
36
    : Plasma::AbstractRunner(parent, args)
 
37
{
 
38
    Q_UNUSED(args)
 
39
 
 
40
    #ifdef ENABLE_QALCULATE
 
41
    m_engine = new QalculateEngine;
 
42
    setSpeed(SlowSpeed);
 
43
    #endif
 
44
 
 
45
    setObjectName( QLatin1String("Calculator" ));
 
46
    setIgnoredTypes(Plasma::RunnerContext::Directory | Plasma::RunnerContext::File |
 
47
                         Plasma::RunnerContext::NetworkLocation | Plasma::RunnerContext::Executable |
 
48
                         Plasma::RunnerContext::ShellCommand);
 
49
 
 
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));
 
54
}
 
55
 
 
56
CalculatorRunner::~CalculatorRunner()
 
57
{
 
58
    #ifdef ENABLE_QALCULATE
 
59
    delete m_engine;
 
60
    #endif
 
61
}
 
62
 
 
63
void CalculatorRunner::powSubstitutions(QString& cmd)
 
64
{
 
65
    if (cmd.contains("e+", Qt::CaseInsensitive)) {
 
66
        cmd=cmd.replace("e+", "*10^", Qt::CaseInsensitive);
 
67
    }
 
68
 
 
69
    if (cmd.contains("e-", Qt::CaseInsensitive)) {
 
70
        cmd=cmd.replace("e-", "*10^-", Qt::CaseInsensitive);
 
71
    }
 
72
 
 
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;
 
80
        int count = 0;
 
81
 
 
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);
 
86
 
 
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;
 
92
            if (current == ')') {
 
93
                count++;
 
94
            } else if (current == '(') {
 
95
                count--;
 
96
            } else {
 
97
                if (((next <= '9' ) && (next >= '0')) || next == decimalSymbol) {
 
98
                    preIndex--;
 
99
                    continue;
 
100
                }
 
101
            }
 
102
            if (count == 0) {
 
103
                //check for functions
 
104
                if (!((next <= 'z' ) && (next >= 'a'))) {
 
105
                    break;
 
106
                }
 
107
            }
 
108
            preIndex--;
 
109
        }
 
110
 
 
111
       //go forwards looking for the end of the number or expression
 
112
        count = 0;
 
113
        while (postIndex != cmd.size() - 1) {
 
114
            QChar current=cmd.at(postIndex);
 
115
            QChar next=cmd.at(postIndex + 1);
 
116
 
 
117
            //check for functions
 
118
            if ((count == 0) && (current <= 'z') && (current >= 'a')) {
 
119
                postIndex++;
 
120
                continue;
 
121
            }
 
122
 
 
123
            if (current == '(') {
 
124
                count++;
 
125
            } else if (current == ')') {
 
126
                count--;
 
127
            } else {
 
128
                if (((next <= '9' ) && (next >= '0')) || next == decimalSymbol) {
 
129
                    postIndex++;
 
130
                    continue;
 
131
                 }
 
132
            }
 
133
            if (count == 0) {
 
134
                break;
 
135
            }
 
136
            postIndex++;
 
137
        }
 
138
 
 
139
        preIndex = qMax(0, preIndex);
 
140
        postIndex = qMin(postIndex, cmd.length());
 
141
 
 
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;
 
146
    }
 
147
}
 
148
 
 
149
void CalculatorRunner::hexSubstitutions(QString& cmd)
 
150
{
 
151
    if (cmd.contains("0x")) {
 
152
        //Append +0 so that the calculator can serve also as a hex converter
 
153
        cmd.append("+0");
 
154
        bool ok;
 
155
        int pos = 0;
 
156
        QString hex;
 
157
 
 
158
        while (cmd.contains("0x")) {
 
159
            hex.clear();
 
160
            pos = cmd.indexOf("0x", pos);
 
161
 
 
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
 
165
                    hex[q] = current;
 
166
                } else {
 
167
                    break;
 
168
                }
 
169
            }
 
170
            cmd = cmd.replace(pos, 2+hex.length(), QString::number(hex.toInt(&ok,16))); //replace hex with decimal
 
171
        }
 
172
    }
 
173
}
 
174
 
 
175
void CalculatorRunner::userFriendlySubstitutions(QString& cmd)
 
176
{
 
177
    if (cmd.contains(KGlobal::locale()->decimalSymbol(), Qt::CaseInsensitive)) {
 
178
         cmd=cmd.replace(KGlobal::locale()->decimalSymbol(), QChar('.'), Qt::CaseInsensitive);
 
179
    }
 
180
 
 
181
    // the following substitutions are not needed with libqalculate
 
182
    #ifndef ENABLE_QALCULATE
 
183
    hexSubstitutions(cmd);
 
184
    powSubstitutions(cmd);
 
185
 
 
186
    if (cmd.contains(QRegExp("\\d+and\\d+"))) {
 
187
         cmd = cmd.replace(QRegExp("(\\d+)and(\\d+)"), "\\1&\\2");
 
188
    }
 
189
    if (cmd.contains(QRegExp("\\d+or\\d+"))) {
 
190
         cmd = cmd.replace(QRegExp("(\\d+)or(\\d+)"), "\\1|\\2");
 
191
    }
 
192
    if (cmd.contains(QRegExp("\\d+xor\\d+"))) {
 
193
         cmd = cmd.replace(QRegExp("(\\d+)xor(\\d+)"), "\\1^\\2");
 
194
    }
 
195
    #endif
 
196
}
 
197
 
 
198
 
 
199
void CalculatorRunner::match(Plasma::RunnerContext &context)
 
200
{
 
201
    const QString term = context.query();
 
202
    QString cmd = term;
 
203
 
 
204
    //no meanless space between friendly guys: helps simplify code
 
205
    cmd = cmd.trimmed().remove(' ');
 
206
 
 
207
    if (cmd.length() < 4) {
 
208
        return;
 
209
    }
 
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);
 
217
        return;
 
218
    }
 
219
 
 
220
    bool toHex = cmd.startsWith(QLatin1String("hex="));
 
221
    bool startsWithEquals = !toHex && cmd[0] == '=';
 
222
 
 
223
    if (toHex || startsWithEquals) {
 
224
        cmd.remove(0, cmd.indexOf('=') + 1);
 
225
    } else if (cmd.endsWith('=')) {
 
226
        cmd.chop(1);
 
227
    } else {
 
228
        // we don't have an actionable equation here
 
229
        return;
 
230
    }
 
231
 
 
232
    if (cmd.isEmpty()) {
 
233
        return;
 
234
    }
 
235
 
 
236
    userFriendlySubstitutions(cmd);
 
237
    #ifndef ENABLE_QALCULATE
 
238
    cmd.replace(QRegExp("([a-zA-Z]+)"), "Math.\\1"); //needed for accessing math funktions like sin(),....
 
239
    #endif
 
240
 
 
241
    QString result = calculate(cmd);
 
242
 
 
243
    if (!result.isEmpty() && result != cmd) {
 
244
        if (toHex) {
 
245
            result = "0x" + QString::number(result.toInt(), 16).toUpper();
 
246
        }
 
247
 
 
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);
 
255
    }
 
256
}
 
257
 
 
258
QString CalculatorRunner::calculate(const QString& term)
 
259
{
 
260
    #ifdef ENABLE_QALCULATE
 
261
    QString result = "";
 
262
 
 
263
    try {
 
264
        result = m_engine->evaluate(term);
 
265
    } catch(std::exception& e) {
 
266
        kDebug() << "qalculate error: " << e.what();
 
267
    }
 
268
 
 
269
    return result.replace('.', KGlobal::locale()->decimalSymbol(), Qt::CaseInsensitive);
 
270
    #else
 
271
    //kDebug() << "calculating" << term;
 
272
    QScriptEngine eng;
 
273
    QScriptValue result = eng.evaluate(" var result ="+term+"; result");
 
274
 
 
275
    if (result.isError()) {
 
276
        return QString();
 
277
    }
 
278
 
 
279
    const QString resultString = result.toString();
 
280
    if (resultString.isEmpty()) {
 
281
        return QString();
 
282
    }
 
283
 
 
284
    if (!resultString.contains('.')) {
 
285
            return resultString;
 
286
        }
 
287
 
 
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();
 
293
 
 
294
    roundedResultString.replace('.', KGlobal::locale()->decimalSymbol(), Qt::CaseInsensitive);
 
295
 
 
296
    return roundedResultString;
 
297
    #endif
 
298
}
 
299
 
 
300
QMimeData * CalculatorRunner::mimeDataForMatch(const Plasma::QueryMatch *match)
 
301
{
 
302
    //kDebug();
 
303
    QMimeData * result = new QMimeData();
 
304
    result->setText(match->text());
 
305
    return result;
 
306
}
 
307
 
 
308
#include "calculatorrunner.moc"