File indexing completed on 2024-04-21 03:41:33

0001 // SPDX-FileCopyrightText: 2007 by Aleix Pol <aleixpol@kde.org>
0002 // SPDX-License-Identifier: GPL-2.0-or-later
0003 
0004 #include "consolehtml.h"
0005 
0006 #include <QApplication>
0007 #include <QClipboard>
0008 #include <QContextMenuEvent>
0009 #include <QTemporaryFile>
0010 #include <QUrlQuery>
0011 
0012 #include <KLocalizedString>
0013 #include <KStandardAction>
0014 #include <QAction>
0015 #include <QMenu>
0016 #include <kio/copyjob.h>
0017 #include <kio/job.h>
0018 
0019 #include <analitza/expression.h>
0020 #include <analitza/variables.h>
0021 
0022 using namespace Qt::Literals::StringLiterals;
0023 
0024 static QUrl temporaryPath()
0025 {
0026     QTemporaryFile temp(QStringLiteral("consolelog"));
0027     temp.open();
0028     temp.close();
0029     temp.setAutoRemove(false);
0030     return QUrl::fromLocalFile(temp.fileName());
0031 }
0032 
0033 static QUrl retrieve(const QUrl &remoteUrl)
0034 {
0035     const QUrl path = temporaryPath();
0036     KIO::CopyJob *job = KIO::copyAs(remoteUrl, path);
0037 
0038     job->exec();
0039 
0040     return path;
0041 }
0042 
0043 class ConsolePage : public QWebEnginePage
0044 {
0045 public:
0046     ConsolePage(ConsoleHtml *parent)
0047         : QWebEnginePage(parent)
0048         , m_console(parent)
0049     {
0050     }
0051 
0052     bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool /*isMainFrame*/) override
0053     {
0054         m_console->setActualUrl(url);
0055 
0056         if (url.scheme() == QLatin1String("data"))
0057             return true;
0058 
0059         qDebug() << "navigating to" << url << type;
0060         m_console->openClickedUrl(url);
0061         return false;
0062     }
0063 
0064     ConsoleHtml *m_console;
0065 };
0066 
0067 ConsoleHtml::ConsoleHtml(QWidget *parent)
0068     : QWebEngineView(parent)
0069     , m_actualUrl()
0070     , m_model(new ConsoleModel(this))
0071 {
0072     connect(m_model.data(), &ConsoleModel::updateView, this, &ConsoleHtml::updateView);
0073     connect(m_model.data(), &ConsoleModel::operationSuccessful, this, &ConsoleHtml::includeOperation);
0074     setPage(new ConsolePage(this));
0075 }
0076 
0077 ConsoleHtml::~ConsoleHtml()
0078 {
0079     qDeleteAll(m_options);
0080 }
0081 
0082 void ConsoleHtml::setActualUrl(const QUrl &url)
0083 {
0084     m_actualUrl = url;
0085 }
0086 
0087 void ConsoleHtml::openClickedUrl(const QUrl &url)
0088 {
0089     QUrlQuery query(url);
0090     QString id = query.queryItemValue(QStringLiteral("id"));
0091     QString exp = query.queryItemValue(QStringLiteral("func"));
0092 
0093     if (id == QLatin1String("copy")) {
0094         Q_EMIT paste(exp);
0095     } else
0096         for (InlineOptions *opt : std::as_const(m_options)) {
0097             if (opt->id() == id) {
0098                 opt->triggerOption(Analitza::Expression(exp, false));
0099             }
0100         }
0101 }
0102 
0103 bool ConsoleHtml::addOperation(const Analitza::Expression &e, const QString &input)
0104 {
0105     return m_model->addOperation(e, input);
0106 }
0107 
0108 ConsoleModel::ConsoleMode ConsoleHtml::mode() const
0109 {
0110     return m_model->mode();
0111 }
0112 
0113 void ConsoleHtml::setMode(ConsoleModel::ConsoleMode newMode)
0114 {
0115     m_model->setMode(newMode);
0116 }
0117 
0118 Analitza::Analyzer *ConsoleHtml::analitza()
0119 {
0120     return m_model->analyzer();
0121 }
0122 
0123 bool ConsoleHtml::loadScript(const QUrl &path)
0124 {
0125     return m_model->loadScript(path.isLocalFile() ? path : retrieve(path));
0126 }
0127 
0128 bool ConsoleHtml::saveScript(const QUrl &path) const
0129 {
0130     const QUrl savePath = path.isLocalFile() ? path : temporaryPath();
0131     bool correct = m_model->saveScript(savePath);
0132     if (!path.isLocalFile()) {
0133         KIO::CopyJob *job = KIO::move(savePath, path);
0134         correct = job->exec();
0135     }
0136     return correct;
0137 }
0138 
0139 bool ConsoleHtml::saveLog(const QUrl &path) const
0140 {
0141     const QUrl savePath = path.isLocalFile() ? path : temporaryPath();
0142     bool correct = m_model->saveLog(savePath);
0143     if (!path.isLocalFile()) {
0144         KIO::CopyJob *job = KIO::move(savePath, path);
0145         correct = job->exec();
0146     }
0147     return correct;
0148 }
0149 
0150 void ConsoleHtml::includeOperation(const Analitza::Expression & /*e*/, const Analitza::Expression &res)
0151 {
0152     m_optionsString.clear();
0153     if (res.isCorrect()) {
0154         Analitza::Analyzer lambdifier(m_model->variables());
0155         lambdifier.setExpression(res);
0156         Analitza::Expression lambdaexp = lambdifier.dependenciesToLambda();
0157         lambdifier.setExpression(lambdaexp);
0158 
0159         for (InlineOptions *opt : std::as_const(m_options)) {
0160             if (opt->matchesExpression(lambdaexp)) {
0161                 QUrl url(QStringLiteral("/query"));
0162                 QUrlQuery query(url);
0163                 query.addQueryItem(QStringLiteral("id"), opt->id());
0164                 query.addQueryItem(QStringLiteral("func"), lambdaexp.toString());
0165                 url.setQuery(query);
0166 
0167                 m_optionsString += i18n(" <a href='kalgebra:%1'>%2</a>", url.toString(), opt->caption());
0168             }
0169         }
0170 
0171         if (!m_optionsString.isEmpty()) {
0172             m_optionsString = u"<div class='options'>"_s + i18n("Options: %1", m_optionsString) + u"</div>"_s;
0173         }
0174     }
0175 }
0176 
0177 void ConsoleHtml::updateView()
0178 {
0179     QByteArray code;
0180     code += "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n";
0181     code += "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n<head>\n\t<title> :) </title>\n";
0182     code += m_model->css();
0183     code += "</head>\n<body>\n";
0184 
0185     auto log = m_model->htmlLog();
0186     if (!log.isEmpty()) {
0187         const auto newEntry = log.takeLast();
0188         for (const QByteArray &entry : std::as_const(log))
0189             code += "<p class='normal'>" + entry + "</p>\n";
0190 
0191         code += m_optionsString.toUtf8();
0192         if (newEntry.startsWith("<ul class='error'>"))
0193             code += newEntry;
0194         else
0195             code += "<p class='last'>" + newEntry + "</p>\n";
0196     }
0197     code += "</body></html>";
0198 
0199     page()->setHtml(QString::fromUtf8(code));
0200 
0201     Q_EMIT changed();
0202 
0203     connect(this, &QWebEngineView::loadFinished, this, [this](bool ok) {
0204         if (!ok && (m_actualUrl.scheme() != QLatin1String("kalgebra"))) {
0205             qWarning() << "error loading page" << m_actualUrl;
0206         }
0207 
0208         page()->runJavaScript(QStringLiteral("window.scrollTo(0, document.body.scrollHeight);"));
0209     });
0210 }
0211 
0212 void ConsoleHtml::copy() const
0213 {
0214     QApplication::clipboard()->setText(selectedText());
0215 }
0216 
0217 void ConsoleHtml::contextMenuEvent(QContextMenuEvent *ev)
0218 {
0219     QMenu popup;
0220     if (hasSelection()) {
0221         popup.addAction(KStandardAction::copy(this, SLOT(copy()), &popup));
0222         QAction *act = new QAction(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("Paste \"%1\" to input", selectedText().trimmed()), &popup);
0223         connect(act, SIGNAL(triggered()), SLOT(paste()));
0224         popup.addAction(act);
0225         popup.addSeparator();
0226     }
0227     popup.addAction(KStandardAction::clear(this, SLOT(clear()), &popup));
0228 
0229     popup.exec(mapToGlobal(ev->pos()));
0230 }
0231 
0232 void ConsoleHtml::clear()
0233 {
0234     m_model->clear();
0235     updateView();
0236 }
0237 
0238 void ConsoleHtml::modifyVariable(const QString &name, const Analitza::Expression &exp)
0239 {
0240     m_model->variables()->modify(name, exp);
0241 }
0242 
0243 void ConsoleHtml::removeVariable(const QString &name)
0244 {
0245     m_model->variables()->remove(name);
0246 }
0247 
0248 void ConsoleHtml::paste()
0249 {
0250     Q_EMIT paste(selectedText().trimmed());
0251 }