consolehtml.cpp 9.99 KB
Newer Older
1
/*************************************************************************************
2
 *  Copyright (C) 2007 by Aleix Pol <aleixpol@kde.org>                               *
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *                                                                                   *
 *  This program is free software; you can redistribute it and/or                    *
 *  modify it under the terms of the GNU General Public License                      *
 *  as published by the Free Software Foundation; either version 2                   *
 *  of the License, or (at your option) any later version.                           *
 *                                                                                   *
 *  This program 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 General Public License for more details.                                     *
 *                                                                                   *
 *  You should have received a copy of the GNU General Public License                *
 *  along with this program; if not, write to the Free Software                      *
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA   *
 *************************************************************************************/

#include "consolehtml.h"
20

21
#include <QClipboard>
Laurent Montel's avatar
Laurent Montel committed
22
#include <QApplication>
23
#include <qevent.h>
24 25
#include <QUrlQuery>
#include <QTemporaryFile>
26
#include <QDir>
27

28
#include <KStandardAction>
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
29 30
#include <QMenu>
#include <KLocalizedString>
31
#include <QAction>
32 33
#include <kio/copyjob.h>
#include <kio/job.h>
34 35 36

#include <analitza/variables.h>
#include <analitza/expression.h>
37

38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, s_css, (
    "<style type=\"text/css\">\n"
        "\thtml { background-color: " + qApp->palette().color(QPalette::Active, QPalette::Base).name().toLatin1() + "; }\n"
        "\t.error { border-style: solid; border-width: 1px; border-color: #ff3b21; background-color: #ffe9c4; padding:7px;}\n"
        "\t.last  { border-style: solid; border-width: 1px; border-color: #2020ff; background-color: #e0e0ff; padding:7px;}\n"
        "\t.before { text-align:right; }\n"
        "\t.op  { font-weight: bold; }\n"
    //     "\t.normal:hover  { border-style: solid; border-width: 1px; border-color: #777; }\n";
        "\t.normal:hover  { background-color: #f7f7f7; }\n"
        "\t.cont { color: #560000; }\n"
        "\t.num { color: #0000C4; }\n"
        "\t.sep { font-weight: bold; color: #0000FF; }\n"
        "\t.var { color: #640000; }\n"
        "\t.keyword { color: #000064; }\n"
        "\t.func { color: #008600; }\n"
        "\t.result { padding-left: 10%; }\n"
        "\t.options { font-size: small; text-align:right }\n"
        "\t.string { color: #bb0000 }\n"
        "\tli { padding-left: 12px; padding-bottom: 4px; list-style-position: inside; }"
        "\t.exp { color: #000000 }\n"
        "\ta { color: #0000ff }\n"
        "\ta:link {text-decoration:none;}\n"
        "\ta:visited {text-decoration:none;}\n"
        "\ta:hover {text-decoration:underline;}\n"
        "\ta:active {text-decoration:underline;}\n"
        "\tp { font-size: " +QByteArray::number(QFontMetrics(QApplication::font()).height())+ "px; }\n"
        "</style>\n"))

66
static QUrl temporaryPath()
67
{
68 69 70 71
    QTemporaryFile temp(QStringLiteral("consolelog"));
    temp.open();
    temp.close();
    temp.setAutoRemove(false);
72
    return QUrl::fromLocalFile(temp.fileName());
73
}
74

75
static QUrl retrieve(const QUrl& remoteUrl)
76
{
77 78
    const QUrl path = temporaryPath();
    KIO::CopyJob* job=KIO::copyAs(remoteUrl, path);
79

80
    job->exec();
81

82 83
    return path;
}
84

85 86 87 88 89
class ConsolePage : public QWebEnginePage
{
public:
    ConsolePage(ConsoleHtml* parent) : QWebEnginePage(parent), m_console(parent) {}

90
    bool acceptNavigationRequest(const QUrl &url, NavigationType /*type*/, bool /*isMainFrame*/) override {
91 92 93 94 95 96 97
        m_console->openClickedUrl(url);
        return false;
    }

    ConsoleHtml* m_console;
};

98
ConsoleHtml::ConsoleHtml(QWidget *parent)
99
    : QWebEngineView(parent)
100
    , m_model(new ConsoleModel(this, false))
101
{
102
    connect(m_model.data(), &ConsoleModel::updateView, this, &ConsoleHtml::updateView);
103
    connect(m_model.data(), &ConsoleModel::operationSuccessful, this, &ConsoleHtml::includeOperation);
104
    connect(m_model.data(), &ConsoleModel::errorMessage, this, &ConsoleHtml::addMessage);
105
    setPage(new ConsolePage(this));
106 107 108 109
}

ConsoleHtml::~ConsoleHtml()
{
110
    qDeleteAll(m_options);
111 112
}

113
void ConsoleHtml::addMessage(const QString& message)
114
{
115
    m_htmlLog += message;
116
}
117

118
void ConsoleHtml::openClickedUrl(const QUrl& url)
119
{
120
    QUrlQuery query(url);
121 122
    QString id =query.queryItemValue(QStringLiteral("id"));
    QString exp=query.queryItemValue(QStringLiteral("func"));
123
    
124 125 126
    if(id=="copy") {
        emit paste(exp);
    } else foreach(InlineOptions* opt, m_options) {
127 128 129 130
        if(opt->id() == id) {
            opt->triggerOption(Analitza::Expression(exp, false));
        }
    }
131 132
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
133
bool ConsoleHtml::addOperation(const Analitza::Expression& e, const QString& input)
134
{
135
    return m_model->addOperation(e, input);
136 137
}

138
ConsoleModel::ConsoleMode ConsoleHtml::mode() const
139
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
140
    return m_model->mode();
141 142
}

143
void ConsoleHtml::setMode(ConsoleModel::ConsoleMode newMode)
144
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
145
    m_model->setMode(newMode);
146 147 148 149
}

Analitza::Analyzer* ConsoleHtml::analitza()
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
150
    return m_model->analyzer();
151 152
}

153
bool ConsoleHtml::loadScript(const QUrl& path)
154
{
155
    return m_model->loadScript(path.isLocalFile() ? path : retrieve(path));
156 157
}

158
bool ConsoleHtml::saveScript(const QUrl & path) const
159
{
160
    const QUrl savePath=path.isLocalFile() ? path : temporaryPath();
161 162
    bool correct = m_model->saveScript(savePath);
    if(!path.isLocalFile()) {
163
        KIO::CopyJob* job=KIO::move(savePath, path);
164 165 166
        correct=job->exec();
    }
    return correct;
167 168
}

169
bool ConsoleHtml::saveLog(const QUrl& path) const
170
{
171 172
    Q_ASSERT(!path.isEmpty());
    //FIXME: We have to choose between txt and html
173 174
    const QUrl savePath=path.isLocalFile() ? path : temporaryPath();
    QFile file(savePath.toLocalFile());
175 176
    bool correct = file.open(QIODevice::WriteOnly | QIODevice::Text);

177 178
    if(correct) {
        QTextStream out(&file);
179
        out << "<html>\n<head>" << *s_css << "</head>" << endl;
180 181 182 183 184
        out << "<body>" << endl;
        foreach(const QString &entry, m_htmlLog)
            out << "<p>" << entry << "</p>" << endl;
        out << "</body>\n</html>" << endl;
    }
185

186
    if(!path.isLocalFile()) {
187
        KIO::CopyJob* job=KIO::move(savePath, path);
188 189 190
        correct=job->exec();
    }
    return correct;
191 192
}

193
void ConsoleHtml::includeOperation(const Analitza::Expression& e, const Analitza::Expression& res)
194
{
195
    QString options, newEntry;
196
    if (res.isCorrect()) {
197 198 199 200
        const auto result = res.toHtml();
        newEntry = QStringLiteral("<a title='%1' href='kalgebra:/query?id=copy&func=%2'><span class='exp'>%3</span></a><br />=<a title='kalgebra:%1' href='/query?id=copy&func=%4'><span class='result'>%5</span>")
            .arg(i18n("Paste to Input"), e.toString(), e.toHtml(), res.toString(), result);

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
201
        Analitza::Analyzer lambdifier(m_model->variables());
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
        lambdifier.setExpression(res);
        Analitza::Expression lambdaexp = lambdifier.dependenciesToLambda();
        lambdifier.setExpression(lambdaexp);

        foreach(InlineOptions* opt, m_options) {
            if(opt->matchesExpression(lambdaexp)) {
                QUrl url(QStringLiteral("/query"));
                QUrlQuery query(url);
                query.addQueryItem(QStringLiteral("id"), opt->id());
                query.addQueryItem(QStringLiteral("func"), lambdaexp.toString());
                url.setQuery(query);

                options += i18n(" <a href='kalgebra:%1'>%2</a>", url.toString(), opt->caption());
            }
        }

        if(!options.isEmpty())
            options = "<div class='options'>"+i18n("Options: %1", options)+"</div>";
    }
221 222
    updateViewWithOptions(newEntry, options);
}
223

224 225 226 227 228 229 230
void ConsoleHtml::updateView(const QString& newEntry)
{
    updateViewWithOptions(newEntry, {});
}

void ConsoleHtml::updateViewWithOptions(const QString& newEntry, const QString &options)
{
231 232 233
    QByteArray code;
    code += "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n";
    code += "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n<head>\n\t<title> :) </title>\n";
234
    code += *s_css;
235 236 237
    code += "</head>\n<body>";
    foreach(const QString &entry, m_htmlLog)
        code += "<p class='normal'>"+entry.toUtf8()+"</p>";
238

239 240 241 242 243 244
    if(!newEntry.isEmpty()) {
        m_htmlLog += newEntry;
        code += options.toUtf8();
        code += "<p class='last'>"+newEntry.toUtf8()+"</p>";
    }
    code += "</body></html>";
245

246
    page()->setHtml(code);
247 248
    
    emit changed();
249

250 251 252 253 254
    QObject* o = new QObject;
    connect(this, &QWebEngineView::loadFinished, o, [this, o](){
        page()->runJavaScript(QStringLiteral("window.scrollTo(0, document.body.scrollHeight);"));
        delete o;
    });
255 256
}

257 258
void ConsoleHtml::copy() const
{
259
    QApplication::clipboard()->setText(selectedText());
260 261
}

262
void ConsoleHtml::contextMenuEvent(QContextMenuEvent* ev)
263
{
264 265 266
    QMenu popup;
    if(hasSelection()) {
        popup.addAction(KStandardAction::copy(this, SLOT(copy()), &popup));
267
        QAction *act=new QAction(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("Paste \"%1\" to input", selectedText()), &popup);
268 269 270 271 272 273 274
        connect(act, SIGNAL(triggered()), SLOT(paste()));
        popup.addAction(act);
        popup.addSeparator();
    }
    popup.addAction(KStandardAction::clear(this, SLOT(clear()), &popup));
    
    popup.exec(ev->pos());
275 276
}

277 278
void ConsoleHtml::clear()
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
279
    m_model->clear();
280
    m_htmlLog.clear();
281
    updateView(QString());
282 283
}

284
void ConsoleHtml::modifyVariable(const QString& name, const Analitza::Expression& exp)
285
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
286
    m_model->variables()->modify(name, exp);
287 288 289 290
}

void ConsoleHtml::removeVariable(const QString & name)
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
291
    m_model->variables()->remove(name);
292 293
}

294 295
void ConsoleHtml::paste()
{
296
    emit paste(selectedText());
297
}