File indexing completed on 2024-04-14 03:40:11

0001 /*************************************************************************************
0002  *  Copyright (C) 2007-2017 by Aleix Pol <aleixpol@kde.org>                          *
0003  *                                                                                   *
0004  *  This program is free software; you can redistribute it and/or                    *
0005  *  modify it under the terms of the GNU General Public License                      *
0006  *  as published by the Free Software Foundation; either version 2                   *
0007  *  of the License, or (at your option) any later version.                           *
0008  *                                                                                   *
0009  *  This program is distributed in the hope that it will be useful,                  *
0010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of                   *
0011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                    *
0012  *  GNU General Public License for more details.                                     *
0013  *                                                                                   *
0014  *  You should have received a copy of the GNU General Public License                *
0015  *  along with this program; if not, write to the Free Software                      *
0016  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA   *
0017  *************************************************************************************/
0018 
0019 #include "consolemodel.h"
0020 
0021 #include <KLocalizedString>
0022 #include <QFile>
0023 #include <QFontMetrics>
0024 #include <QGuiApplication>
0025 #include <QPalette>
0026 #include <QUrl>
0027 #include <QUrlQuery>
0028 
0029 using namespace Qt::Literals::StringLiterals;
0030 
0031 Q_GLOBAL_STATIC_WITH_ARGS(QByteArray,
0032                           s_css,
0033                           ("<style type=\"text/css\">\n"
0034                            "\thtml { background-color: "
0035                            + qGuiApp->palette().color(QPalette::Active, QPalette::Base).name().toLatin1()
0036                            + "; }\n"
0037                              "\t.error { border-style: solid; border-width: 1px; border-color: #ff3b21; background-color: #ffe9c4; padding:7px;}\n"
0038                              "\t.last  { border-style: solid; border-width: 1px; border-color: #2020ff; background-color: #e0e0ff; padding:7px;}\n"
0039                              "\t.before { text-align:right; }\n"
0040                              "\t.op  { font-weight: bold; }\n"
0041                              //     "\t.normal:hover  { border-style: solid; border-width: 1px; border-color: #777; }\n";
0042                              "\t.normal:hover  { background-color: #f7f7f7; }\n"
0043                              "\t.cont { color: #560000; }\n"
0044                              "\t.num { color: #0000C4; }\n"
0045                              "\t.sep { font-weight: bold; color: #0000FF; }\n"
0046                              "\t.var { color: #640000; }\n"
0047                              "\t.keyword { color: #000064; }\n"
0048                              "\t.func { color: #008600; }\n"
0049                              "\t.result { padding-left: 10%; }\n"
0050                              "\t.options { font-size: small; text-align:right }\n"
0051                              "\t.string { color: #bb0000 }\n"
0052                              "\tli { padding-left: 12px; padding-bottom: 4px; list-style-position: inside; }\n"
0053                              "\t.exp { color: #000000 }\n"
0054                              "\ta { color: #0000ff }\n"
0055                              "\ta:link {text-decoration:none;}\n"
0056                              "\ta:visited {text-decoration:none;}\n"
0057                              "\ta:hover {text-decoration:underline;}\n"
0058                              "\ta:active {text-decoration:underline;}\n"
0059                              "\tp { font-size: "
0060                            + QByteArray::number(QFontMetrics(QGuiApplication::font()).height())
0061                            + "px; }\n"
0062                              "</style>\n"))
0063 
0064 ConsoleModel::ConsoleModel(QObject *parent)
0065     : QObject(parent)
0066 {
0067 }
0068 
0069 bool ConsoleModel::addOperation(const QString &input)
0070 {
0071     return addOperation(Analitza::Expression(input), input);
0072 }
0073 
0074 bool ConsoleModel::addOperation(const Analitza::Expression &e, const QString &input)
0075 {
0076     Analitza::Expression res;
0077 
0078     a.setExpression(e);
0079     if (a.isCorrect()) {
0080         if (m_mode == ConsoleModel::Evaluation) {
0081             res = a.evaluate();
0082         } else {
0083             res = a.calculate();
0084         }
0085     }
0086 
0087     if (a.isCorrect()) {
0088         a.insertVariable(u"ans"_s, res);
0089         m_script += e; // Script won't have the errors
0090         Q_EMIT operationSuccessful(e, res);
0091 
0092         const auto result = res.toHtml();
0093         addMessage(QStringLiteral("<a title='%1' href='kalgebra:/query?id=copy&func=%2'><span class='exp'>%3</span></a><br />= <a title='kalgebra:%1' "
0094                                   "href='kalgebra:/query?id=copy&func=%4'><span class='result'>%5</span></a>")
0095                        .arg(i18n("Paste to Input"), e.toString(), e.toHtml(), res.toString(), result),
0096                    e,
0097                    res);
0098     } else {
0099         addMessage(i18n("<ul class='error'>Error: <b>%1</b><li>%2</li></ul>", input.toHtmlEscaped(), a.errors().join(u"</li>\n<li>"_s)), {}, {});
0100     }
0101 
0102     return a.isCorrect();
0103 }
0104 
0105 bool ConsoleModel::loadScript(const QUrl &path)
0106 {
0107     Q_ASSERT(!path.isEmpty() && path.isLocalFile());
0108 
0109     // FIXME: We have expression-only script support
0110     bool correct = false;
0111     QFile file(path.toLocalFile());
0112 
0113     if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
0114         QTextStream stream(&file);
0115 
0116         a.importScript(&stream);
0117         correct = a.isCorrect();
0118     }
0119 
0120     if (correct)
0121         addMessage(i18n("Imported: %1", path.toDisplayString()), {}, {});
0122     else
0123         addMessage(i18n("<ul class='error'>Error: Could not load %1. <br /> %2</ul>", path.toDisplayString(), a.errors().join(u"<br/>"_s)), {}, {});
0124 
0125     return correct;
0126 }
0127 
0128 bool ConsoleModel::saveScript(const QUrl &savePath)
0129 {
0130     Q_ASSERT(!savePath.isEmpty());
0131 
0132     QFile file(savePath.toLocalFile());
0133     bool correct = file.open(QIODevice::WriteOnly | QIODevice::Text);
0134 
0135     if (correct) {
0136         QTextStream out(&file);
0137         for (const Analitza::Expression &exp : std::as_const(m_script)) {
0138             out << exp.toString() << QLatin1Char('\n');
0139         }
0140     }
0141 
0142     return correct;
0143 }
0144 
0145 void ConsoleModel::setMode(ConsoleMode mode)
0146 {
0147     if (m_mode != mode) {
0148         m_mode = mode;
0149         Q_EMIT modeChanged(mode);
0150     }
0151 }
0152 
0153 void ConsoleModel::setVariables(const QSharedPointer<Analitza::Variables> &vars)
0154 {
0155     a.setVariables(vars);
0156 }
0157 
0158 void ConsoleModel::addMessage(const QString &msg, const Analitza::Expression &operation, const Analitza::Expression &result)
0159 {
0160     m_htmlLog += msg.toUtf8();
0161     Q_EMIT updateView();
0162     Q_EMIT message(msg, operation, result);
0163 }
0164 
0165 bool ConsoleModel::saveLog(const QUrl &savePath) const
0166 {
0167     Q_ASSERT(savePath.isLocalFile());
0168     // FIXME: We have to choose between txt and html
0169     QFile file(savePath.toLocalFile());
0170     bool correct = file.open(QIODevice::WriteOnly | QIODevice::Text);
0171 
0172     if (correct) {
0173         QTextStream out(&file);
0174         out << "<html>\n<head>" << *s_css << "</head>" << QLatin1Char('\n');
0175         out << "<body>" << QLatin1Char('\n');
0176         for (const QByteArray &entry : std::as_const(m_htmlLog)) {
0177             out << "<p>" << entry << "</p>" << QLatin1Char('\n');
0178         }
0179         out << "</body>\n</html>" << QLatin1Char('\n');
0180     }
0181 
0182     return correct;
0183 }
0184 
0185 void ConsoleModel::clear()
0186 {
0187     m_script.clear();
0188     m_htmlLog.clear();
0189 }
0190 
0191 QByteArray ConsoleModel::css() const
0192 {
0193     return *s_css;
0194 }
0195 
0196 QString ConsoleModel::readContent(const QUrl &url)
0197 {
0198     return QUrlQuery(url).queryItemValue(u"func"_s);
0199 }