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 }