File indexing completed on 2024-04-28 11:20:55

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2009 Alexander Rieder <alexanderrieder@gmail.com>
0004     SPDX-FileCopyrightText: 2012 Martin Kuettler <martin.kuettler@gmail.com>
0005     SPDX-FileCopyrightText: 2016-2021 Alexander Semke <alexander.semke@web.de>
0006 */
0007 
0008 #include "commandentry.h"
0009 #include "resultitem.h"
0010 #include "loadedexpression.h"
0011 #include "worksheetview.h"
0012 #include "lib/jupyterutils.h"
0013 #include "lib/result.h"
0014 #include "lib/helpresult.h"
0015 #include "lib/epsresult.h"
0016 #include "lib/latexresult.h"
0017 #include "lib/completionobject.h"
0018 #include "lib/syntaxhelpobject.h"
0019 #include "lib/session.h"
0020 
0021 #include <QGuiApplication>
0022 #include <QDebug>
0023 #include <QActionGroup>
0024 #include <QFontDatabase>
0025 #include <QFontDialog>
0026 #include <QScreen>
0027 #include <QTimer>
0028 #include <QToolTip>
0029 #include <QPropertyAnimation>
0030 #include <QJsonArray>
0031 #include <QJsonObject>
0032 #include <QTextBlock>
0033 #include <QTextDocumentFragment>
0034 #include <QPainter>
0035 
0036 #include <KLocalizedString>
0037 #include <KColorScheme>
0038 
0039 const QString CommandEntry::Prompt     = QLatin1String(">>> ");
0040 const QString CommandEntry::MidPrompt  = QLatin1String(">>  ");
0041 const QString CommandEntry::HidePrompt = QLatin1String(">   ");
0042 const double CommandEntry::VerticalSpacing = 4;
0043 
0044 
0045 CommandEntry::CommandEntry(Worksheet* worksheet) : WorksheetEntry(worksheet),
0046     m_promptItem(new WorksheetTextItem(this, Qt::NoTextInteraction)),
0047     m_commandItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)),
0048     m_resultsCollapsed(false),
0049     m_errorItem(nullptr),
0050     m_expression(nullptr),
0051     m_completionObject(nullptr),
0052     m_syntaxHelpObject(nullptr),
0053     m_evaluationOption(DoNothing),
0054     m_menusInitialized(false),
0055     m_textColorCustom(false),
0056     m_backgroundColorCustom(false),
0057     m_backgroundColorActionGroup(nullptr),
0058     m_backgroundColorMenu(nullptr),
0059     m_textColorActionGroup(nullptr),
0060     m_textColorMenu(nullptr),
0061     m_fontMenu(nullptr),
0062     m_isExecutionEnabled(true)
0063 {
0064     m_promptItem->setPlainText(Prompt);
0065     m_promptItem->setItemDragable(true);
0066     m_commandItem->enableCompletion(true);
0067 
0068     KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View);
0069     m_commandItem->setBackgroundColor(scheme.background(KColorScheme::AlternateBackground).color());
0070 
0071     m_promptItemAnimation = new QPropertyAnimation(m_promptItem, "opacity", this);
0072     m_promptItemAnimation->setDuration(600);
0073     m_promptItemAnimation->setStartValue(1);
0074     m_promptItemAnimation->setKeyValueAt(0.5, 0);
0075     m_promptItemAnimation->setEndValue(1);
0076     connect(m_promptItemAnimation, &QPropertyAnimation::finished, this, &CommandEntry::animatePromptItem);
0077 
0078     m_promptItem->setDoubleClickBehaviour(WorksheetTextItem::DoubleClickEventBehaviour::Simple);
0079     connect(m_promptItem, &WorksheetTextItem::doubleClick, this, &CommandEntry::changeResultCollapsingAction);
0080 
0081     connect(&m_controlElement, &WorksheetControlItem::doubleClick, this, &CommandEntry::changeResultCollapsingAction);
0082 
0083     connect(m_commandItem, &WorksheetTextItem::tabPressed, this, &CommandEntry::handleTabPress);
0084     connect(m_commandItem, &WorksheetTextItem::backtabPressed, this, &CommandEntry::handleBacktabPress);
0085     connect(m_commandItem, &WorksheetTextItem::applyCompletion, this, &CommandEntry::applySelectedCompletion);
0086     connect(m_commandItem, &WorksheetTextItem::execute, this, [=]() { evaluate();} );
0087     connect(m_commandItem, &WorksheetTextItem::moveToPrevious, this, &CommandEntry::moveToPreviousItem);
0088     connect(m_commandItem, &WorksheetTextItem::moveToNext, this, &CommandEntry::moveToNextItem);
0089     connect(m_commandItem, &WorksheetTextItem::receivedFocus, worksheet, &Worksheet::highlightItem);
0090     connect(m_promptItem, &WorksheetTextItem::drag, this, &CommandEntry::startDrag);
0091     connect(worksheet, &Worksheet::updatePrompt, this, [=]() { updatePrompt();} );
0092 
0093     m_defaultDefaultTextColor = m_commandItem->defaultTextColor();
0094 }
0095 
0096 CommandEntry::~CommandEntry()
0097 {
0098     if (m_completionBox)
0099         m_completionBox->deleteLater();
0100 
0101     if (m_menusInitialized)
0102     {
0103         m_backgroundColorMenu->deleteLater();
0104         m_textColorMenu->deleteLater();
0105         m_fontMenu->deleteLater();
0106     }
0107 }
0108 
0109 int CommandEntry::type() const
0110 {
0111     return Type;
0112 }
0113 
0114 void CommandEntry::initMenus() {
0115     //background color
0116     m_backgroundColorActionGroup = new QActionGroup(this);
0117     m_backgroundColorActionGroup->setExclusive(true);
0118     connect(m_backgroundColorActionGroup, &QActionGroup::triggered, this, &CommandEntry::backgroundColorChanged);
0119 
0120     m_backgroundColorMenu = new QMenu(i18n("Background Color"));
0121     m_backgroundColorMenu->setIcon(QIcon::fromTheme(QLatin1String("format-fill-color")));
0122 
0123     QPixmap pix(16,16);
0124     QPainter p(&pix);
0125 
0126     // Create default action
0127     KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View);
0128     p.fillRect(pix.rect(), scheme.background(KColorScheme::AlternateBackground).color());
0129     QAction* action = new QAction(QIcon(pix), i18n("Default"), m_backgroundColorActionGroup);
0130     action->setCheckable(true);
0131     m_backgroundColorMenu->addAction(action);
0132     if (!m_backgroundColorCustom)
0133         action->setChecked(true);
0134 
0135     for (int i=0; i<colorsCount; ++i) {
0136         p.fillRect(pix.rect(), colors[i]);
0137         action = new QAction(QIcon(pix), colorNames[i], m_backgroundColorActionGroup);
0138         action->setCheckable(true);
0139         m_backgroundColorMenu->addAction(action);
0140 
0141         const QColor& backgroundColor = (m_isExecutionEnabled ? m_commandItem->backgroundColor() : m_activeExecutionBackgroundColor);
0142         if (m_backgroundColorCustom && backgroundColor == colors[i])
0143             action->setChecked(true);
0144     }
0145 
0146     //text color
0147     m_textColorActionGroup = new QActionGroup(this);
0148     m_textColorActionGroup->setExclusive(true);
0149     connect(m_textColorActionGroup, &QActionGroup::triggered, this, &CommandEntry::textColorChanged);
0150 
0151     m_textColorMenu = new QMenu(i18n("Text Color"));
0152     m_textColorMenu->setIcon(QIcon::fromTheme(QLatin1String("format-text-color")));
0153 
0154     // Create default action
0155     p.fillRect(pix.rect(), m_defaultDefaultTextColor);
0156     action = new QAction(QIcon(pix), i18n("Default"), m_textColorActionGroup);
0157     action->setCheckable(true);
0158     m_textColorMenu->addAction(action);
0159     if (!m_textColorCustom)
0160         action->setChecked(true);
0161 
0162     for (int i=0; i<colorsCount; ++i) {
0163         QAction* action;
0164         p.fillRect(pix.rect(), colors[i]);
0165         action = new QAction(QIcon(pix), colorNames[i], m_textColorActionGroup);
0166         action->setCheckable(true);
0167         m_textColorMenu->addAction(action);
0168 
0169         const QColor& textColor = (m_isExecutionEnabled ? m_commandItem->defaultTextColor() : m_activeExecutionTextColor);
0170         if (m_textColorCustom && textColor == colors[i])
0171             action->setChecked(true);
0172     }
0173 
0174     //font
0175     QFont font = m_commandItem->font();
0176     m_fontMenu = new QMenu(i18n("Font"));
0177     m_fontMenu->setIcon(QIcon::fromTheme(QLatin1String("preferences-desktop-font")));
0178 
0179     action = new QAction(QIcon::fromTheme(QLatin1String("format-text-bold")), i18n("Bold"));
0180     action->setCheckable(true);
0181     connect(action, &QAction::triggered, this, &CommandEntry::fontBoldTriggered);
0182     m_fontMenu->addAction(action);
0183     if (font.bold())
0184         action->setChecked(true);
0185 
0186     action = new QAction(QIcon::fromTheme(QLatin1String("format-text-italic")), i18n("Italic"));
0187     action->setCheckable(true);
0188     connect(action, &QAction::triggered, this, &CommandEntry::fontItalicTriggered);
0189     m_fontMenu->addAction(action);
0190     if (font.italic())
0191         action->setChecked(true);
0192     m_fontMenu->addSeparator();
0193 
0194     action = new QAction(QIcon::fromTheme(QLatin1String("format-font-size-less")), i18n("Increase Size"));
0195     connect(action, &QAction::triggered, this, &CommandEntry::fontIncreaseTriggered);
0196     m_fontMenu->addAction(action);
0197 
0198     action = new QAction(QIcon::fromTheme(QLatin1String("format-font-size-more")), i18n("Decrease Size"));
0199     connect(action, &QAction::triggered, this, &CommandEntry::fontDecreaseTriggered);
0200     m_fontMenu->addAction(action);
0201     m_fontMenu->addSeparator();
0202 
0203     action = new QAction(QIcon::fromTheme(QLatin1String("preferences-desktop-font")), i18n("Select"));
0204     connect(action, &QAction::triggered, this, &CommandEntry::fontSelectTriggered);
0205     m_fontMenu->addAction(action);
0206 
0207     action = new QAction(QIcon::fromTheme(QLatin1String("preferences-desktop-font")), i18n("Reset to Default"));
0208     connect(action, &QAction::triggered, this, &CommandEntry::resetFontTriggered);
0209     m_fontMenu->addAction(action);
0210 
0211     m_menusInitialized = true;
0212 }
0213 
0214 void CommandEntry::backgroundColorChanged(QAction* action) {
0215     int index = m_backgroundColorActionGroup->actions().indexOf(action);
0216     if (index == -1 || index>=colorsCount)
0217         index = 0;
0218 
0219     QColor color;
0220     if (index == 0)
0221     {
0222         KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View);
0223         color = scheme.background(KColorScheme::AlternateBackground).color();
0224     }
0225     else
0226         color = colors[index-1];
0227 
0228     if (m_isExecutionEnabled)
0229         m_commandItem->setBackgroundColor(color);
0230     else
0231         m_activeExecutionBackgroundColor = color;
0232 }
0233 
0234 void CommandEntry::textColorChanged(QAction* action) {
0235     int index = m_textColorActionGroup->actions().indexOf(action);
0236     if (index == -1 || index>=colorsCount)
0237         index = 0;
0238 
0239     QColor color;
0240     if (index == 0)
0241     {
0242         color = m_defaultDefaultTextColor;
0243     }
0244     else
0245         color = colors[index-1];
0246 
0247     if (m_isExecutionEnabled)
0248         m_commandItem->setDefaultTextColor(color);
0249     else
0250         m_activeExecutionTextColor = color;
0251 }
0252 
0253 void CommandEntry::fontBoldTriggered()
0254 {
0255     QAction* action = static_cast<QAction*>(QObject::sender());
0256     QFont font = m_commandItem->font();
0257     font.setBold(action->isChecked());
0258     m_commandItem->setFont(font);
0259 }
0260 
0261 void CommandEntry::resetFontTriggered()
0262 {
0263     m_commandItem->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0264 }
0265 
0266 void CommandEntry::fontItalicTriggered()
0267 {
0268     QAction* action = static_cast<QAction*>(QObject::sender());
0269     QFont font = m_commandItem->font();
0270     font.setItalic(action->isChecked());
0271     m_commandItem->setFont(font);
0272 }
0273 
0274 void CommandEntry::fontIncreaseTriggered()
0275 {
0276     QFont font = m_commandItem->font();
0277     const int currentSize = font.pointSize();
0278     QFontDatabase fdb;
0279     QList<int> sizes = fdb.pointSizes(font.family(), font.styleName());
0280 
0281     for (int i = 0; i < sizes.size(); ++i)
0282     {
0283         const int size = sizes.at(i);
0284         if (currentSize == size)
0285         {
0286             if (i + 1 < sizes.size())
0287             {
0288                 font.setPointSize(sizes.at(i+1));
0289                 m_commandItem->setFont(font);
0290             }
0291 
0292             break;
0293         }
0294     }
0295 }
0296 
0297 void CommandEntry::fontDecreaseTriggered()
0298 {
0299     QFont font = m_commandItem->font();
0300     const int currentSize = font.pointSize();
0301     QFontDatabase fdb;
0302     QList<int> sizes = fdb.pointSizes(font.family(), font.styleName());
0303 
0304     for (int i = 0; i < sizes.size(); ++i)
0305     {
0306         const int size = sizes.at(i);
0307         if (currentSize == size)
0308         {
0309             if (i - 1 >= 0)
0310             {
0311                 font.setPointSize(sizes.at(i-1));
0312                 m_commandItem->setFont(font);
0313             }
0314 
0315             break;
0316         }
0317     }
0318 }
0319 
0320 void CommandEntry::fontSelectTriggered()
0321 {
0322     bool ok;
0323     QFont font = QFontDialog::getFont(&ok, m_commandItem->font(), nullptr);
0324 
0325     if (ok)
0326         m_commandItem->setFont(font);
0327 }
0328 
0329 void CommandEntry::populateMenu(QMenu* menu, QPointF pos)
0330 {
0331     if (!m_menusInitialized)
0332         initMenus();
0333 
0334     if (!m_resultItems.isEmpty()) {
0335         if (m_resultsCollapsed)
0336             menu->addAction(i18n("Show Results"), this, &CommandEntry::expandResults);
0337         else
0338             menu->addAction(i18n("Hide Results"), this, &CommandEntry::collapseResults);
0339     }
0340 
0341     if (!m_commandItem->toPlainText().simplified().isEmpty())
0342     {
0343         QAction* action = new QAction(QIcon::fromTheme(QLatin1String("help-hint")), i18n("Show Help"));
0344         connect(action, &QAction::triggered, this, &CommandEntry::showHelp);
0345         menu->addAction(action);
0346         menu->addSeparator();
0347     }
0348 
0349     QAction* enabledAction = new QAction(QIcon::fromTheme(QLatin1String("checkmark")), i18n("Enabled"));
0350     enabledAction->setCheckable(true);
0351     enabledAction->setChecked(m_isExecutionEnabled);
0352     menu->addSeparator();
0353     menu->addAction(enabledAction);
0354     connect(enabledAction, &QAction::triggered, this, &CommandEntry::toggleEnabled);
0355 
0356     QMenu* appearanceMenu = new QMenu(i18n("Appearance"));
0357     appearanceMenu->setIcon(QIcon::fromTheme(QLatin1String("configure")));
0358     appearanceMenu->addMenu(m_backgroundColorMenu);
0359     appearanceMenu->addMenu(m_textColorMenu);
0360     appearanceMenu->addMenu(m_fontMenu);
0361     menu->addMenu(appearanceMenu);
0362     menu->addSeparator();
0363 
0364     WorksheetEntry::populateMenu(menu, pos);
0365     menu->addSeparator();
0366 }
0367 
0368 void CommandEntry::moveToNextItem(int pos, qreal x)
0369 {
0370     WorksheetTextItem* item = qobject_cast<WorksheetTextItem*>(sender());
0371 
0372     if (!item)
0373         return;
0374 
0375     if (item == m_commandItem) {
0376         if (m_informationItems.isEmpty() ||
0377             !currentInformationItem()->isEditable())
0378             moveToNextEntry(pos, x);
0379         else
0380             currentInformationItem()->setFocusAt(pos, x);
0381     } else if (item == currentInformationItem()) {
0382         moveToNextEntry(pos, x);
0383     }
0384 }
0385 
0386 void CommandEntry::moveToPreviousItem(int pos, qreal x)
0387 {
0388     WorksheetTextItem* item = qobject_cast<WorksheetTextItem*>(sender());
0389 
0390     if (!item)
0391         return;
0392 
0393     if (item == m_commandItem || item == nullptr) {
0394         moveToPreviousEntry(pos, x);
0395     } else if (item == currentInformationItem()) {
0396         m_commandItem->setFocusAt(pos, x);
0397     }
0398 }
0399 
0400 QString CommandEntry::command()
0401 {
0402     QString cmd = m_commandItem->toPlainText();
0403     cmd.replace(QChar::ParagraphSeparator, QLatin1Char('\n')); //Replace the U+2029 paragraph break by a Normal Newline
0404     cmd.replace(QChar::LineSeparator, QLatin1Char('\n')); //Replace the line break by a Normal Newline
0405     return cmd;
0406 }
0407 
0408 void CommandEntry::setExpression(Cantor::Expression* expr)
0409 {
0410     /*
0411     if ( m_expression ) {
0412         if (m_expression->status() == Cantor::Expression::Computing) {
0413             qDebug() << "OLD EXPRESSION STILL ACTIVE";
0414             m_expression->interrupt();
0415         }
0416         m_expression->deleteLater();
0417         }*/
0418 
0419     // Delete any previous error
0420     if(m_errorItem)
0421     {
0422         m_errorItem->deleteLater();
0423         m_errorItem = nullptr;
0424     }
0425 
0426     for (auto* item : m_informationItems)
0427     {
0428         item->deleteLater();
0429     }
0430     m_informationItems.clear();
0431 
0432     // Delete any previous result
0433     clearResultItems();
0434 
0435     m_expression = expr;
0436     m_resultsCollapsed = false;
0437 
0438     connect(expr, &Cantor::Expression::gotResult, this, &CommandEntry::updateEntry);
0439     connect(expr, &Cantor::Expression::resultsCleared, this, &CommandEntry::clearResultItems);
0440     connect(expr, &Cantor::Expression::resultRemoved, this, &CommandEntry::removeResultItem);
0441     connect(expr, &Cantor::Expression::resultReplaced, this, &CommandEntry::replaceResultItem);
0442     connect(expr, &Cantor::Expression::idChanged, this,  [=]() { updatePrompt();} );
0443     connect(expr, &Cantor::Expression::statusChanged, this, &CommandEntry::expressionChangedStatus);
0444     connect(expr, &Cantor::Expression::needsAdditionalInformation, this, &CommandEntry::showAdditionalInformationPrompt);
0445     connect(expr, &Cantor::Expression::statusChanged, this,  [=]() { updatePrompt();} );
0446 
0447     updatePrompt();
0448 
0449     if(expr->result())
0450     {
0451         worksheet()->gotResult(expr);
0452         updateEntry();
0453     }
0454 
0455     expressionChangedStatus(expr->status());
0456 }
0457 
0458 Cantor::Expression* CommandEntry::expression()
0459 {
0460     return m_expression;
0461 }
0462 
0463 bool CommandEntry::acceptRichText()
0464 {
0465     return false;
0466 }
0467 
0468 void CommandEntry::setContent(const QString& content)
0469 {
0470     m_commandItem->setPlainText(content);
0471 }
0472 
0473 void CommandEntry::setContent(const QDomElement& content, const KZip& file)
0474 {
0475     m_commandItem->setPlainText(content.firstChildElement(QLatin1String("Command")).text());
0476 
0477     LoadedExpression* expr = new LoadedExpression( worksheet()->session() );
0478     expr->loadFromXml(content, file);
0479 
0480     //background color
0481     QDomElement backgroundElem = content.firstChildElement(QLatin1String("Background"));
0482     if (!backgroundElem.isNull())
0483     {
0484         QColor color;
0485         color.setRed(backgroundElem.attribute(QLatin1String("red")).toInt());
0486         color.setGreen(backgroundElem.attribute(QLatin1String("green")).toInt());
0487         color.setBlue(backgroundElem.attribute(QLatin1String("blue")).toInt());
0488         m_commandItem->setBackgroundColor(color);
0489         m_backgroundColorCustom = true;
0490     }
0491 
0492     //text properties
0493     QDomElement textElem = content.firstChildElement(QLatin1String("Text"));
0494     if (!textElem.isNull())
0495     {
0496         //text color
0497         QDomElement colorElem = textElem.firstChildElement(QLatin1String("Color"));
0498         if (!colorElem.isNull() && !colorElem.hasAttribute(QLatin1String("default")))
0499         {
0500             m_defaultDefaultTextColor = m_commandItem->defaultTextColor();
0501             QColor color;
0502             color.setRed(colorElem.attribute(QLatin1String("red")).toInt());
0503             color.setGreen(colorElem.attribute(QLatin1String("green")).toInt());
0504             color.setBlue(colorElem.attribute(QLatin1String("blue")).toInt());
0505             m_commandItem->setDefaultTextColor(color);
0506             m_textColorCustom = true;
0507         }
0508 
0509         //font properties
0510         QDomElement fontElem = textElem.firstChildElement(QLatin1String("Font"));
0511         if (!fontElem.isNull() && !fontElem.hasAttribute(QLatin1String("default")))
0512         {
0513             QFont font;
0514             font.setFamily(fontElem.attribute(QLatin1String("family")));
0515             font.setPointSize(fontElem.attribute(QLatin1String("pointSize")).toInt());
0516             font.setWeight(fontElem.attribute(QLatin1String("weight")).toInt());
0517             font.setItalic(fontElem.attribute(QLatin1String("italic")).toInt());
0518             m_commandItem->setFont(font);
0519         }
0520     }
0521 
0522     m_isExecutionEnabled = !(bool)(content.attribute(QLatin1String("ExecutionDisabled"), QLatin1String("0")).toInt());
0523     if (m_isExecutionEnabled == false)
0524         excludeFromExecution();
0525 
0526     setExpression(expr);
0527 }
0528 
0529 void CommandEntry::setContentFromJupyter(const QJsonObject& cell)
0530 {
0531     m_commandItem->setPlainText(Cantor::JupyterUtils::getSource(cell));
0532 
0533     LoadedExpression* expr=new LoadedExpression( worksheet()->session() );
0534     expr->loadFromJupyter(cell);
0535     setExpression(expr);
0536 
0537     // https://nbformat.readthedocs.io/en/latest/format_description.html#cell-metadata
0538     // 'collapsed': +
0539     // 'scrolled', 'deletable', 'name', 'tags' don't supported Cantor, so ignore them
0540     // 'source_hidden' don't supported
0541     // 'format' for raw entry, so ignore
0542     // I haven't found 'outputs_hidden' inside Jupyter notebooks, and difference from 'collapsed'
0543     // not clear, so also ignore
0544     const QJsonObject& metadata = Cantor::JupyterUtils::getMetadata(cell);
0545     const QJsonValue& collapsed = metadata.value(QLatin1String("collapsed"));
0546     if (collapsed.isBool() && collapsed.toBool() == true && !m_resultItems.isEmpty())
0547     {
0548         // Disable animation for hiding results, we don't need animation on document load stage
0549         bool animationValue = worksheet()->animationsEnabled();
0550         worksheet()->enableAnimations(false);
0551         collapseResults();
0552         worksheet()->enableAnimations(animationValue);
0553     }
0554 
0555     setJupyterMetadata(metadata);
0556 }
0557 
0558 QJsonValue CommandEntry::toJupyterJson()
0559 {
0560     QJsonObject entry;
0561 
0562     entry.insert(QLatin1String("cell_type"), QLatin1String("code"));
0563 
0564     QJsonValue executionCountValue;
0565     if (expression() && expression()->id() != -1)
0566         executionCountValue = QJsonValue(expression()->id());
0567     entry.insert(QLatin1String("execution_count"), executionCountValue);
0568 
0569     QJsonObject metadata(jupyterMetadata());
0570     if (m_resultsCollapsed)
0571         metadata.insert(QLatin1String("collapsed"), true);
0572 
0573     entry.insert(QLatin1String("metadata"), metadata);
0574 
0575     Cantor::JupyterUtils::setSource(entry, command());
0576 
0577     QJsonArray outputs;
0578     if (expression())
0579     {
0580         Cantor::Expression::Status status = expression()->status();
0581         if (status == Cantor::Expression::Error || status == Cantor::Expression::Interrupted)
0582         {
0583             QJsonObject errorOutput;
0584             errorOutput.insert(Cantor::JupyterUtils::outputTypeKey, QLatin1String("error"));
0585             errorOutput.insert(QLatin1String("ename"), QLatin1String("Unknown"));
0586             errorOutput.insert(QLatin1String("evalue"), QLatin1String("Unknown"));
0587 
0588             QJsonArray traceback;
0589             if (status == Cantor::Expression::Error)
0590             {
0591                 const QStringList& error = expression()->errorMessage().split(QLatin1Char('\n'));
0592                 for (const QString& line: error)
0593                     traceback.append(line);
0594             }
0595             else
0596             {
0597                 traceback.append(i18n("Interrupted"));
0598             }
0599             errorOutput.insert(QLatin1String("traceback"), traceback);
0600 
0601             outputs.append(errorOutput);
0602         }
0603 
0604         for (auto* result : expression()->results())
0605         {
0606             const QJsonValue& resultJson = result->toJupyterJson();
0607 
0608             if (!resultJson.isNull())
0609                 outputs.append(resultJson);
0610         }
0611     }
0612     entry.insert(QLatin1String("outputs"), outputs);
0613 
0614     return entry;
0615 }
0616 
0617 void CommandEntry::handleTabPress()
0618 {
0619     const QString& line = currentLine();
0620 
0621     // When the auto completion is disabled, we insert 'Tab' and exit immediately
0622     // When the auto completion is enabled, the logic is more complicated
0623     if(!worksheet()->completionEnabled())
0624     {
0625         if (m_commandItem->hasFocus())
0626             m_commandItem->insertTab();
0627         return;
0628     }
0629 
0630     if (isShowingCompletionPopup())
0631         handleExistedCompletionBox();
0632     else
0633     {
0634         QTextCursor cursor = m_commandItem->textCursor();
0635         int p = m_commandItem->textCursor().positionInBlock();
0636 
0637         if (cursor.hasSelection())
0638         {
0639             int count = 1 + cursor.selectedText().count(QChar(0x2029));
0640             cursor.setPosition(cursor.selectionEnd());
0641             cursor.beginEditBlock();
0642             for (int i = 0; i < count; i++)
0643             {
0644                 cursor.movePosition(QTextCursor::StartOfLine);
0645                 cursor.insertText(QLatin1String("    "));
0646                 cursor.movePosition(QTextCursor::StartOfLine);
0647                 cursor.movePosition(QTextCursor::PreviousCharacter);
0648             }
0649             cursor.endEditBlock();
0650         }
0651         else if(line.left(p).trimmed().isEmpty())
0652         {
0653             if (m_commandItem->hasFocus())
0654                 m_commandItem->insertTab();
0655         }
0656         else
0657             makeCompletion(line, p);
0658     }
0659 }
0660 
0661 void CommandEntry::showCompletion()
0662 {
0663     if(!worksheet()->completionEnabled())
0664         return;
0665 
0666     if (isShowingCompletionPopup())
0667         handleExistedCompletionBox();
0668     else
0669     {
0670         int p = m_commandItem->textCursor().positionInBlock();
0671         makeCompletion(currentLine(), p);
0672     }
0673 }
0674 
0675 void CommandEntry::handleExistedCompletionBox()
0676 {
0677     QString comp = m_completionObject->completion();
0678     if (comp != m_completionObject->command() || !m_completionObject->hasMultipleMatches())
0679     {
0680         if (m_completionObject->hasMultipleMatches())
0681             completeCommandTo(comp, PreliminaryCompletion);
0682         else
0683         {
0684             completeCommandTo(comp, FinalCompletion);
0685             m_completionBox->hide();
0686         }
0687     }
0688     else
0689     {
0690         m_completionBox->down();
0691     }
0692 }
0693 
0694 void CommandEntry::makeCompletion(const QString& line, int position)
0695 {
0696     Cantor::CompletionObject* tco=worksheet()->session()->completionFor(line, position);
0697     if(tco)
0698         setCompletion(tco);
0699 }
0700 
0701 void CommandEntry::handleBacktabPress()
0702 {
0703     QTextCursor cursor = m_commandItem->textCursor();
0704 
0705     if (isShowingCompletionPopup())
0706         m_completionBox->up();
0707     else if (cursor.hasSelection())
0708     {
0709         int count = 1 + cursor.selectedText().count(QChar(0x2029));
0710         cursor.setPosition(cursor.selectionEnd());
0711         cursor.beginEditBlock();
0712         for (int i = 0; i < count; i++)
0713         {
0714             const QString& line = cursor.block().text();
0715             cursor.movePosition(QTextCursor::StartOfLine);
0716             int counter = 0;
0717             // 4 spaces is Cantor tabulation size, so we remove also 4 characters or less
0718             while (cursor.positionInBlock() < line.length() && line[cursor.positionInBlock()] == QLatin1Char(' ') && counter < 4)
0719             {
0720                 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
0721                 counter++;
0722             }
0723             cursor.removeSelectedText();
0724             cursor.movePosition(QTextCursor::PreviousCharacter);
0725         }
0726         cursor.endEditBlock();
0727     }
0728     else
0729     {
0730         const QString& line = currentLine();
0731         if (line.length() >= 4)
0732         {
0733             cursor.movePosition(QTextCursor::StartOfLine);
0734             int counter = 0;
0735             // 4 spaces is Cantor tabulation size, so we remove also 4 characters or less
0736             while (cursor.positionInBlock() < line.length() && line[cursor.positionInBlock()] == QLatin1Char(' ') && counter < 4)
0737             {
0738                 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
0739                 counter++;
0740             }
0741             cursor.removeSelectedText();
0742         }
0743     }
0744 }
0745 
0746 QString CommandEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq)
0747 {
0748     Q_UNUSED(commentStartingSeq);
0749     Q_UNUSED(commentEndingSeq);
0750 
0751     if (command().isEmpty())
0752         return QString();
0753     return command() + commandSep;
0754 }
0755 
0756 QDomElement CommandEntry::toXml(QDomDocument& doc, KZip* archive)
0757 {
0758     QDomElement exprElem = doc.createElement( QLatin1String("Expression") );
0759     QDomElement cmdElem = doc.createElement( QLatin1String("Command") );
0760     cmdElem.appendChild(doc.createTextNode( command() ));
0761     exprElem.appendChild(cmdElem);
0762 
0763     if (!m_isExecutionEnabled)
0764         exprElem.setAttribute(QLatin1String("ExecutionDisabled"), true);
0765 
0766     // save results of the expression if they exist
0767     if (expression())
0768     {
0769         const QString& errorMessage = expression()->errorMessage();
0770         if (!errorMessage.isEmpty())
0771         {
0772             QDomElement errorElem = doc.createElement( QLatin1String("Error") );
0773             errorElem.appendChild(doc.createTextNode(errorMessage));
0774             exprElem.appendChild(errorElem);
0775         }
0776         for (auto* result : expression()->results())
0777         {
0778             const QDomElement& resultElem = result->toXml(doc);
0779             exprElem.appendChild(resultElem);
0780 
0781             if (archive)
0782                 result->saveAdditionalData(archive);
0783         }
0784     }
0785 
0786     bool isBackgroundColorNotDefault = false;
0787     // If user can change value from menu (menus have been inited) - check via menu
0788     // If use don't have menu, check if loaded color was custom color
0789     if (m_backgroundColorActionGroup)
0790         isBackgroundColorNotDefault = m_backgroundColorActionGroup->actions().indexOf(m_backgroundColorActionGroup->checkedAction()) != 0;
0791     else
0792         isBackgroundColorNotDefault = m_backgroundColorCustom;
0793     if (isBackgroundColorNotDefault)
0794     {
0795         QColor backgroundColor = (m_isExecutionEnabled ? m_commandItem->backgroundColor() : m_activeExecutionBackgroundColor);
0796         QDomElement colorElem = doc.createElement( QLatin1String("Background") );
0797         colorElem.setAttribute(QLatin1String("red"), QString::number(backgroundColor.red()));
0798         colorElem.setAttribute(QLatin1String("green"), QString::number(backgroundColor.green()));
0799         colorElem.setAttribute(QLatin1String("blue"), QString::number(backgroundColor.blue()));
0800         exprElem.appendChild(colorElem);
0801     }
0802 
0803     //save the text properties if they differ from default values
0804     const QFont& font = m_commandItem->font();
0805     const QColor& textColor = (m_isExecutionEnabled ? m_commandItem->defaultTextColor() : m_activeExecutionTextColor);
0806     bool isFontNotDefault = font != QFontDatabase::systemFont(QFontDatabase::FixedFont);
0807 
0808     bool isTextColorNotDefault = false;
0809     if (m_textColorActionGroup)
0810         isTextColorNotDefault = m_textColorActionGroup->actions().indexOf(m_textColorActionGroup->checkedAction()) != 0;
0811     else
0812         isTextColorNotDefault = m_textColorCustom;
0813 
0814     // Setting both values is necessary for previous Cantor versions compability
0815     // Value, added only for compability reason, marks with attribute
0816     if (isFontNotDefault || isTextColorNotDefault)
0817     {
0818         QDomElement textElem = doc.createElement(QLatin1String("Text"));
0819 
0820         //font properties
0821         QDomElement fontElem = doc.createElement(QLatin1String("Font"));
0822         if (!isFontNotDefault)
0823             fontElem.setAttribute(QLatin1String("default"), true);
0824         fontElem.setAttribute(QLatin1String("family"), font.family());
0825         fontElem.setAttribute(QLatin1String("pointSize"), QString::number(font.pointSize()));
0826         fontElem.setAttribute(QLatin1String("weight"), QString::number(font.weight()));
0827         fontElem.setAttribute(QLatin1String("italic"), QString::number(font.italic()));
0828         textElem.appendChild(fontElem);
0829 
0830         //text color
0831         QDomElement colorElem = doc.createElement( QLatin1String("Color") );
0832         if (!isTextColorNotDefault)
0833             colorElem.setAttribute(QLatin1String("default"), true);
0834         colorElem.setAttribute(QLatin1String("red"), QString::number(textColor.red()));
0835         colorElem.setAttribute(QLatin1String("green"), QString::number(textColor.green()));
0836         colorElem.setAttribute(QLatin1String("blue"), QString::number(textColor.blue()));
0837         textElem.appendChild(colorElem);
0838 
0839         exprElem.appendChild(textElem);
0840     }
0841 
0842     return exprElem;
0843 }
0844 
0845 QString CommandEntry::currentLine()
0846 {
0847     if (!m_commandItem->hasFocus())
0848         return QString();
0849 
0850     return m_commandItem->textCursor().block().text();
0851 }
0852 
0853 bool CommandEntry::evaluateCurrentItem()
0854 {
0855     // we can't use m_commandItem->hasFocus() here, because
0856     // that doesn't work when the scene doesn't have the focus,
0857     // e.g. when an assistant is used.
0858     if (m_commandItem == worksheet()->focusItem()) {
0859         return evaluate();
0860     } else if (informationItemHasFocus()) {
0861         addInformation();
0862         return true;
0863     }
0864 
0865     return false;
0866 }
0867 
0868 bool CommandEntry::evaluate(EvaluationOption evalOp)
0869 {
0870     if (m_isExecutionEnabled)
0871     {
0872         if (worksheet()->session()->status() == Cantor::Session::Disable)
0873             worksheet()->loginToSession();
0874 
0875         removeContextHelp();
0876         QToolTip::hideText();
0877 
0878         QString cmd = command();
0879         m_evaluationOption = evalOp;
0880 
0881         if(cmd.isEmpty()) {
0882             removeResults();
0883             for (auto* item : m_informationItems) {
0884                 item->deleteLater();
0885             }
0886             m_informationItems.clear();
0887             recalculateSize();
0888 
0889             evaluateNext(m_evaluationOption);
0890             return false;
0891         }
0892 
0893         auto* expr = worksheet()->session()->evaluateExpression(cmd);
0894         connect(expr, &Cantor::Expression::gotResult, this, [=]() { worksheet()->gotResult(expr); });
0895 
0896         setExpression(expr);
0897 
0898         return true;
0899     }
0900     else
0901     {
0902         evaluateNext(m_evaluationOption);
0903         return true;
0904     }
0905 }
0906 
0907 void CommandEntry::interruptEvaluation()
0908 {
0909     auto* expr = expression();
0910     if(expr)
0911         expr->interrupt();
0912 }
0913 
0914 void CommandEntry::updateEntry()
0915 {
0916     qDebug() << "update Entry";
0917     auto* expr = expression();
0918     if (expr == nullptr || expr->results().isEmpty())
0919         return;
0920 
0921     if (expr->results().last()->type() == Cantor::HelpResult::Type)
0922         return; // Help is handled elsewhere
0923 
0924     //CommandEntry::updateEntry() is only called if the worksheet view is resized
0925     //or when we got a new result(s).
0926     //In the second case the number of results and the number of result graphic objects is different
0927     //and we add a new graphic objects for the new result(s) (new result(s) are located in the end).
0928     // NOTE: LatexResult could request update (change from rendered to code, for example)
0929     // So, just update results, if we haven't new results or something similar
0930     if (m_resultItems.size() < expr->results().size())
0931     {
0932         if (m_resultsCollapsed)
0933             expandResults();
0934 
0935         for (int i = m_resultItems.size(); i < expr->results().size(); i++)
0936             m_resultItems << ResultItem::create(this, expr->results()[i]);
0937     }
0938     else
0939     {
0940         for (ResultItem* item: m_resultItems)
0941             item->update();
0942     }
0943 
0944     m_controlElement.isCollapsable = m_errorItem != nullptr
0945                                     || m_informationItems.size() > 0
0946                                     || m_resultItems.size() > 0;
0947 
0948     animateSizeChange();
0949 }
0950 
0951 void CommandEntry::expressionChangedStatus(Cantor::Expression::Status status)
0952 {
0953     switch (status)
0954     {
0955     case Cantor::Expression::Computing:
0956     {
0957         //change the background of the promt item and start animating it (fade in/out).
0958         //don't start the animation immediately in order to avoid unwanted flickering for "short" commands,
0959         //start the animation after 1 second passed.
0960         if (worksheet()->animationsEnabled())
0961         {
0962             const int id = m_expression->id();
0963             QTimer::singleShot(1000, this, [this, id] () {
0964                 if(m_expression->status() == Cantor::Expression::Computing && m_expression->id() == id)
0965                     m_promptItemAnimation->start();
0966             });
0967         }
0968         break;
0969     }
0970     case Cantor::Expression::Error:
0971     case Cantor::Expression::Interrupted:
0972         m_promptItemAnimation->stop();
0973         m_promptItem->setOpacity(1.);
0974 
0975         m_commandItem->setFocusAt(WorksheetTextItem::BottomRight, 0);
0976 
0977         if(!m_errorItem)
0978         {
0979             m_errorItem = new WorksheetTextItem(this, Qt::TextSelectableByMouse);
0980         }
0981 
0982         if (status == Cantor::Expression::Error)
0983         {
0984             QString error = m_expression->errorMessage().toHtmlEscaped();
0985             while (error.endsWith(QLatin1Char('\n')))
0986                 error.chop(1);
0987             error.replace(QLatin1String("\n"), QLatin1String("<br>"));
0988             error.replace(QLatin1String(" "), QLatin1String("&nbsp;"));
0989             m_errorItem->setHtml(error);
0990         }
0991         else
0992             m_errorItem->setHtml(i18n("Interrupted"));
0993 
0994         recalculateSize();
0995         // Mostly we handle setting of modification in WorksheetEntry inside ::evaluateNext.
0996         // But command entry wouldn't triger ::evaluateNext for Error and Interrupted states
0997         // So, we set it here
0998         worksheet()->setModified();
0999         break;
1000     case Cantor::Expression::Done:
1001         m_promptItemAnimation->stop();
1002         m_promptItem->setOpacity(1.);
1003         evaluateNext(m_evaluationOption);
1004         m_evaluationOption = DoNothing;
1005         break;
1006     default:
1007         break;
1008     }
1009 }
1010 
1011 void CommandEntry::animatePromptItem() {
1012     if(m_expression->status() == Cantor::Expression::Computing)
1013         m_promptItemAnimation->start();
1014 }
1015 
1016 bool CommandEntry::isEmpty()
1017 {
1018     if (m_commandItem->toPlainText().trimmed().isEmpty()) {
1019         if (!m_resultItems.isEmpty())
1020             return false;
1021         return true;
1022     }
1023     return false;
1024 }
1025 
1026 bool CommandEntry::focusEntry(int pos, qreal xCoord)
1027 {
1028     if (aboutToBeRemoved())
1029         return false;
1030     WorksheetTextItem* item;
1031     if (pos == WorksheetTextItem::TopLeft || pos == WorksheetTextItem::TopCoord)
1032         item = m_commandItem;
1033     else if (m_informationItems.size() && currentInformationItem()->isEditable())
1034         item = currentInformationItem();
1035     else
1036         item = m_commandItem;
1037 
1038     item->setFocusAt(pos, xCoord);
1039     return true;
1040 }
1041 
1042 void CommandEntry::setCompletion(Cantor::CompletionObject* tc)
1043 {
1044     if (m_completionObject)
1045         delete m_completionObject;
1046 
1047     m_completionObject = tc;
1048     connect(m_completionObject, &Cantor::CompletionObject::done, this, &CommandEntry::showCompletions);
1049     connect(m_completionObject, &Cantor::CompletionObject::lineDone, this, &CommandEntry::completeLineTo);
1050 }
1051 
1052 void CommandEntry::showCompletions()
1053 {
1054     disconnect(m_completionObject, &Cantor::CompletionObject::done, this, &CommandEntry::showCompletions);
1055     QString completion = m_completionObject->completion();
1056     qDebug()<<"completion: "<<completion;
1057     qDebug()<<"showing "<<m_completionObject->allMatches();
1058 
1059     if(m_completionObject->hasMultipleMatches())
1060     {
1061         completeCommandTo(completion);
1062 
1063         QToolTip::showText(QPoint(), QString(), worksheetView());
1064         if (!m_completionBox)
1065                m_completionBox = new KCompletionBox(worksheetView());
1066 
1067         m_completionBox->clear();
1068         m_completionBox->setItems(m_completionObject->allMatches());
1069         QList<QListWidgetItem*> items = m_completionBox->findItems(m_completionObject->command(), Qt::MatchFixedString|Qt::MatchCaseSensitive);
1070         if (!items.empty())
1071             m_completionBox->setCurrentItem(items.first());
1072         m_completionBox->setTabHandling(false);
1073         m_completionBox->setActivateOnSelect(true);
1074 
1075         connect(m_completionBox.data(), &KCompletionBox::textActivated, this, &CommandEntry::applySelectedCompletion);
1076         connect(m_commandItem->document(), &QTextDocument::contentsChanged, this, &CommandEntry::completedLineChanged);
1077         connect(m_completionObject, &Cantor::CompletionObject::done, this, &CommandEntry::updateCompletions);
1078 
1079         m_commandItem->activateCompletion(true);
1080         m_completionBox->popup();
1081         m_completionBox->move(getPopupPosition());
1082     } else
1083     {
1084         completeCommandTo(completion, FinalCompletion);
1085     }
1086 }
1087 
1088 bool CommandEntry::isShowingCompletionPopup()
1089 {
1090     return m_completionBox && m_completionBox->isVisible();
1091 }
1092 
1093 void CommandEntry::applySelectedCompletion()
1094 {
1095     QListWidgetItem* item = m_completionBox->currentItem();
1096     if(item)
1097         completeCommandTo(item->text(), FinalCompletion);
1098     m_completionBox->hide();
1099 }
1100 
1101 void CommandEntry::completedLineChanged()
1102 {
1103     if (!isShowingCompletionPopup()) {
1104         // the completion popup is not visible anymore, so let's clean up
1105         removeContextHelp();
1106         return;
1107     }
1108     const QString line = currentLine();
1109     //FIXME: For some reason, this slot constantly triggeres, so I have added checking, is this update really needed
1110     if (line != m_completionObject->command())
1111         m_completionObject->updateLine(line, m_commandItem->textCursor().positionInBlock());
1112 }
1113 
1114 void CommandEntry::updateCompletions()
1115 {
1116     if (!m_completionObject)
1117         return;
1118     QString completion = m_completionObject->completion();
1119     qDebug()<<"completion: "<<completion;
1120     qDebug()<<"showing "<<m_completionObject->allMatches();
1121 
1122     if(m_completionObject->hasMultipleMatches() || !completion.isEmpty())
1123     {
1124         QToolTip::showText(QPoint(), QString(), worksheetView());
1125         m_completionBox->setItems(m_completionObject->allMatches());
1126         QList<QListWidgetItem*> items = m_completionBox->findItems(m_completionObject->command(), Qt::MatchFixedString|Qt::MatchCaseSensitive);
1127         if (!items.empty())
1128             m_completionBox->setCurrentItem(items.first());
1129         else if (m_completionBox->items().count() == 1)
1130             m_completionBox->setCurrentRow(0);
1131         else
1132             m_completionBox->clearSelection();
1133 
1134         m_completionBox->move(getPopupPosition());
1135     } else
1136     {
1137         removeContextHelp();
1138     }
1139 }
1140 
1141 void CommandEntry::completeCommandTo(const QString& completion, CompletionMode mode)
1142 {
1143     qDebug() << "completion: " << completion;
1144 
1145     Cantor::CompletionObject::LineCompletionMode cmode;
1146     if (mode == FinalCompletion) {
1147         cmode = Cantor::CompletionObject::FinalCompletion;
1148         Cantor::SyntaxHelpObject* obj = worksheet()->session()->syntaxHelpFor(completion);
1149         if(obj)
1150             setSyntaxHelp(obj);
1151     } else {
1152         cmode = Cantor::CompletionObject::PreliminaryCompletion;
1153         if(m_syntaxHelpObject)
1154             m_syntaxHelpObject->deleteLater();
1155         m_syntaxHelpObject=nullptr;
1156     }
1157 
1158     m_completionObject->completeLine(completion, cmode);
1159 }
1160 
1161 void CommandEntry::completeLineTo(const QString& line, int index)
1162 {
1163     qDebug() << "line completion: " << line;
1164     QTextCursor cursor = m_commandItem->textCursor();
1165     cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
1166     cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
1167     int startPosition = cursor.position();
1168     cursor.insertText(line);
1169     cursor.setPosition(startPosition + index);
1170     m_commandItem->setTextCursor(cursor);
1171 
1172     if (m_syntaxHelpObject) {
1173         m_syntaxHelpObject->fetchSyntaxHelp();
1174         // If we are about to show syntax help, then this was the final
1175         // completion, and we should clean up.
1176         removeContextHelp();
1177     }
1178 }
1179 
1180 void CommandEntry::setSyntaxHelp(Cantor::SyntaxHelpObject* sh)
1181 {
1182     if(m_syntaxHelpObject)
1183         m_syntaxHelpObject->deleteLater();
1184 
1185     m_syntaxHelpObject=sh;
1186     connect(sh, SIGNAL(done()), this, SLOT(showSyntaxHelp()));
1187 }
1188 
1189 void CommandEntry::showSyntaxHelp()
1190 {
1191     QString msg = m_syntaxHelpObject->toHtml();
1192     const QPointF cursorPos = m_commandItem->cursorPosition();
1193 
1194     // QToolTip doesn't support &nbsp;, but support multiple spaces
1195     msg.replace(QLatin1String("&nbsp;"), QLatin1String(" "));
1196     // Doesn't support &quot, neither;
1197     msg.replace(QLatin1String("&quot;"), QLatin1String("\""));
1198 
1199     QToolTip::showText(toGlobalPosition(cursorPos), msg, worksheetView());
1200 }
1201 
1202 void CommandEntry::resultDeleted()
1203 {
1204     qDebug()<<"result got removed...";
1205 }
1206 
1207 void CommandEntry::addInformation()
1208 {
1209     auto* answerItem = currentInformationItem();
1210     answerItem->setTextInteractionFlags(Qt::TextSelectableByMouse);
1211 
1212     QString inf = answerItem->toPlainText();
1213     inf.replace(QChar::ParagraphSeparator, QLatin1Char('\n'));
1214     inf.replace(QChar::LineSeparator, QLatin1Char('\n'));
1215 
1216     qDebug()<<"adding information: "<<inf;
1217     if(m_expression)
1218         m_expression->addInformation(inf);
1219 }
1220 
1221 void CommandEntry::showAdditionalInformationPrompt(const QString& question)
1222 {
1223     auto* questionItem = new WorksheetTextItem(this, Qt::TextSelectableByMouse);
1224     auto* answerItem = new WorksheetTextItem(this, Qt::TextEditorInteraction);
1225 
1226     //change the color and the font for when asking for additional information in order to
1227     //better discriminate from the usual input in the command entry
1228     KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View);
1229     QColor color = scheme.foreground(KColorScheme::PositiveText).color();
1230 
1231     QFont font;
1232     font.setItalic(true);
1233 
1234     questionItem->setFont(font);
1235     questionItem->setDefaultTextColor(color);
1236     answerItem->setFont(font);
1237     answerItem->setDefaultTextColor(color);
1238 
1239     questionItem->setPlainText(question);
1240 
1241     m_informationItems.append(questionItem);
1242     m_informationItems.append(answerItem);
1243     connect(answerItem, &WorksheetTextItem::moveToPrevious, this, &CommandEntry::moveToPreviousItem);
1244     connect(answerItem, &WorksheetTextItem::moveToNext, this, &CommandEntry::moveToNextItem);
1245     connect(answerItem, &WorksheetTextItem::execute, this, &CommandEntry::addInformation);
1246     answerItem->setFocus();
1247 
1248     recalculateSize();
1249 }
1250 
1251 void CommandEntry::removeResults()
1252 {
1253     //clear the Result objects
1254     if(m_expression)
1255         m_expression->clearResults();
1256 }
1257 
1258 void CommandEntry::removeResult(Cantor::Result* result)
1259 {
1260     if (m_expression)
1261         m_expression->removeResult(result);
1262 }
1263 
1264 void CommandEntry::removeResultItem(int index)
1265 {
1266     fadeOutItem(m_resultItems[index]->graphicsObject());
1267     m_resultItems.remove(index);
1268     recalculateSize();
1269 }
1270 
1271 void CommandEntry::clearResultItems()
1272 {
1273     //fade out all result graphic objects
1274     for(auto* item : m_resultItems)
1275         fadeOutItem(item->graphicsObject());
1276 
1277     m_resultItems.clear();
1278     recalculateSize();
1279 }
1280 
1281 void CommandEntry::replaceResultItem(int index)
1282 {
1283     auto* previousItem = m_resultItems[index];
1284     m_resultItems[index] = ResultItem::create(this, m_expression->results()[index]);
1285     previousItem->deleteLater();
1286     recalculateSize();
1287 }
1288 
1289 void CommandEntry::removeContextHelp()
1290 {
1291     disconnect(m_commandItem->document(), SIGNAL(contentsChanged()), this, SLOT(completedLineChanged()));
1292 
1293     m_commandItem->activateCompletion(false);
1294     if (m_completionBox)
1295         m_completionBox->hide();
1296 }
1297 
1298 void CommandEntry::updatePrompt(const QString& postfix)
1299 {
1300     KColorScheme color = KColorScheme(QPalette::Normal, KColorScheme::View);
1301 
1302     m_promptItem->setPlainText(QString());
1303     QTextCursor c = m_promptItem->textCursor();
1304     QTextCharFormat cformat = c.charFormat();
1305 
1306     cformat.clearForeground();
1307     c.setCharFormat(cformat);
1308     cformat.setFontWeight(QFont::Bold);
1309 
1310     //insert the session id if available
1311     if(m_expression && worksheet()->showExpressionIds()&&m_expression->id()!=-1)
1312         c.insertText(QString::number(m_expression->id()),cformat);
1313 
1314     //detect the correct color for the prompt, depending on the
1315     //Expression state
1316     if(m_expression)
1317     {
1318         if(m_expression ->status() == Cantor::Expression::Computing&&worksheet()->isRunning())
1319             cformat.setForeground(color.foreground(KColorScheme::PositiveText));
1320         else if(m_expression ->status() == Cantor::Expression::Queued)
1321             cformat.setForeground(color.foreground(KColorScheme::InactiveText));
1322         else if(m_expression ->status() == Cantor::Expression::Error)
1323             cformat.setForeground(color.foreground(KColorScheme::NegativeText));
1324         else if(m_expression ->status() == Cantor::Expression::Interrupted)
1325             cformat.setForeground(color.foreground(KColorScheme::NeutralText));
1326         else
1327             cformat.setFontWeight(QFont::Normal);
1328     }
1329 
1330     c.insertText(postfix, cformat);
1331     recalculateSize();
1332 }
1333 
1334 WorksheetTextItem* CommandEntry::currentInformationItem()
1335 {
1336     if (m_informationItems.isEmpty())
1337         return nullptr;
1338     return m_informationItems.last();
1339 }
1340 
1341 bool CommandEntry::informationItemHasFocus()
1342 {
1343     if (m_informationItems.isEmpty())
1344         return false;
1345     return m_informationItems.last()->hasFocus();
1346 }
1347 
1348 bool CommandEntry::focusWithinThisItem()
1349 {
1350     return focusItem() != nullptr;
1351 }
1352 
1353 QPoint CommandEntry::getPopupPosition()
1354 {
1355     const QPointF cursorPos = m_commandItem->cursorPosition();
1356     const QPoint globalPos = toGlobalPosition(cursorPos);
1357     const QScreen* desktop = QGuiApplication::primaryScreen();
1358     const QRect screenRect = desktop->geometry();
1359     if (globalPos.y() + m_completionBox->height() < screenRect.bottom()) {
1360         return (globalPos);
1361     } else {
1362         QTextBlock block = m_commandItem->textCursor().block();
1363         QTextLayout* layout = block.layout();
1364         int pos = m_commandItem->textCursor().position() - block.position();
1365         QTextLine line = layout->lineForTextPosition(pos);
1366         int dy = - m_completionBox->height() - line.height() - line.leading();
1367         return QPoint(globalPos.x(), globalPos.y() + dy);
1368     }
1369 }
1370 
1371 void CommandEntry::invalidate()
1372 {
1373     qDebug() << "ToDo: Invalidate here";
1374 }
1375 
1376 bool CommandEntry::wantToEvaluate()
1377 {
1378     return !isEmpty();
1379 }
1380 
1381 QPoint CommandEntry::toGlobalPosition(QPointF localPos)
1382 {
1383     const QPointF scenePos = mapToScene(localPos);
1384     const QPoint viewportPos = worksheetView()->mapFromScene(scenePos);
1385     return worksheetView()->viewport()->mapToGlobal(viewportPos);
1386 }
1387 
1388 WorksheetCursor CommandEntry::search(const QString& pattern, unsigned flags,
1389                                      QTextDocument::FindFlags qt_flags,
1390                                      const WorksheetCursor& pos)
1391 {
1392     if (pos.isValid() && pos.entry() != this)
1393         return WorksheetCursor();
1394 
1395     WorksheetCursor p = pos;
1396     QTextCursor cursor;
1397     if (flags & WorksheetEntry::SearchCommand) {
1398         cursor = m_commandItem->search(pattern, qt_flags, p);
1399         if (!cursor.isNull())
1400             return WorksheetCursor(this, m_commandItem, cursor);
1401     }
1402 
1403     if (p.textItem() == m_commandItem)
1404         p = WorksheetCursor();
1405 
1406     if (m_errorItem && flags & WorksheetEntry::SearchError) {
1407         cursor = m_errorItem->search(pattern, qt_flags, p);
1408         if (!cursor.isNull())
1409             return WorksheetCursor(this, m_errorItem, cursor);
1410     }
1411 
1412     if (p.textItem() == m_errorItem)
1413         p = WorksheetCursor();
1414 
1415     for (auto* resultItem : m_resultItems)
1416     {
1417         auto* textResult = dynamic_cast<WorksheetTextItem*>(resultItem);
1418         if (textResult && flags & WorksheetEntry::SearchResult) {
1419             cursor = textResult->search(pattern, qt_flags, p);
1420             if (!cursor.isNull())
1421                 return WorksheetCursor(this, textResult, cursor);
1422         }
1423     }
1424 
1425     return WorksheetCursor();
1426 }
1427 
1428 void CommandEntry::layOutForWidth(qreal entry_zone_x, qreal w, bool force)
1429 {
1430     if (w == size().width() && m_commandItem->pos().x() == entry_zone_x && !force)
1431         return;
1432 
1433     m_promptItem->setPos(0, 0);
1434     double x = std::max(0 + m_promptItem->width() + HorizontalSpacing, entry_zone_x);
1435     double y = 0;
1436     double width = 0;
1437 
1438     const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin;
1439 
1440     m_commandItem->setGeometry(x, y, w - x - margin);
1441     width = qMax(width, m_commandItem->width()+margin);
1442 
1443     y += qMax(m_commandItem->height(), m_promptItem->height());
1444 
1445     for (auto* item : m_informationItems) {
1446         y += VerticalSpacing;
1447         y += item->setGeometry(x, y, w - x - margin);
1448         width = qMax(width, item->width() + margin);
1449     }
1450 
1451     if (m_errorItem) {
1452         y += VerticalSpacing;
1453         y += m_errorItem->setGeometry(x,y,w - x - margin);
1454         width = qMax(width, m_errorItem->width() + margin);
1455     }
1456 
1457     for (auto* resultItem : m_resultItems)
1458     {
1459         if (!resultItem || !resultItem->graphicsObject()->isVisible())
1460             continue;
1461         y += VerticalSpacing;
1462         y += resultItem->setGeometry(x, y, w - x - margin);
1463         width = qMax(width, resultItem->width() + margin);
1464     }
1465     y += VerticalMargin;
1466 
1467     QSizeF s(x+ width, y);
1468     if (animationActive()) {
1469         updateSizeAnimation(s);
1470     } else {
1471         setSize(s);
1472     }
1473 }
1474 
1475 void CommandEntry::startRemoving()
1476 {
1477     m_promptItem->setItemDragable(false);
1478     WorksheetEntry::startRemoving();
1479 }
1480 
1481 WorksheetTextItem* CommandEntry::highlightItem()
1482 {
1483     return m_isExecutionEnabled ? m_commandItem : nullptr;
1484 }
1485 
1486 void CommandEntry::collapseResults()
1487 {
1488     if (m_resultsCollapsed)
1489         return;
1490 
1491     for(auto* item : m_informationItems) {
1492         fadeOutItem(item, nullptr);
1493         item->hide();
1494     }
1495 
1496     for(auto* item : m_resultItems) {
1497         fadeOutItem(item->graphicsObject(), nullptr);
1498         item->graphicsObject()->hide();
1499     }
1500 
1501     m_resultsCollapsed = true;
1502 
1503     if (worksheet()->animationsEnabled())
1504     {
1505         QTimer::singleShot(100, this, &CommandEntry::setMidPrompt);
1506         QTimer::singleShot(200, this, &CommandEntry::setHidePrompt);
1507     }
1508     else
1509         setHidePrompt();
1510 
1511     m_controlElement.isCollapsed = true;
1512     animateSizeChange();
1513 }
1514 
1515 void CommandEntry::expandResults()
1516 {
1517     if(!m_resultsCollapsed)
1518         return;
1519 
1520     for(auto* item : m_informationItems) {
1521         fadeInItem(item, nullptr);
1522         item->show();
1523     }
1524 
1525     for(auto* item : m_resultItems) {
1526         fadeInItem(item->graphicsObject(), nullptr);
1527         item->graphicsObject()->show();
1528     }
1529 
1530     m_resultsCollapsed = false;
1531 
1532     if (worksheet()->animationsEnabled())
1533     {
1534         QTimer::singleShot(100, this, &CommandEntry::setMidPrompt);
1535         QTimer::singleShot(200, this, SLOT(updatePrompt()));
1536     }
1537     else
1538         this->updatePrompt();
1539 
1540     m_controlElement.isCollapsed = false;
1541     animateSizeChange();
1542 }
1543 
1544 void CommandEntry::setHidePrompt()
1545 {
1546     updatePrompt(HidePrompt);
1547 }
1548 
1549 void CommandEntry::setMidPrompt()
1550 {
1551     updatePrompt(MidPrompt);
1552 }
1553 
1554 void CommandEntry::changeResultCollapsingAction()
1555 {
1556     if (m_resultItems.size() == 0)
1557         return;
1558 
1559     if (m_resultsCollapsed)
1560         expandResults();
1561     else
1562         collapseResults();
1563 }
1564 
1565 qreal CommandEntry::promptItemWidth()
1566 {
1567     return m_promptItem->width();
1568 }
1569 
1570 /*!
1571  * called when the "Get Help" action is triggered in the context menu.
1572  * requests the worksheet to show the current keyword in the documentation panel.
1573  * the current keyword is either the currenly selected text or the text under
1574  * the cursor if there is no selection.
1575  */
1576 void CommandEntry::showHelp()
1577 {
1578     QString keyword;
1579     const QTextCursor& cursor = m_commandItem->textCursor();
1580     if (cursor.hasSelection())
1581         keyword = cursor.selectedText();
1582     else
1583         keyword = cursor.block().text();
1584 
1585     if (!keyword.simplified().isEmpty())
1586         emit worksheet()->requestDocumentation(keyword);
1587 }
1588 
1589 void CommandEntry::toggleEnabled() {
1590     auto* action = static_cast<QAction*>(QObject::sender());
1591     if (action->isChecked())
1592         addToExecution();
1593     else
1594         excludeFromExecution();
1595 }
1596 
1597 void CommandEntry::excludeFromExecution()
1598 {
1599     m_isExecutionEnabled = false;
1600 
1601     KColorScheme scheme = KColorScheme(QPalette::Inactive, KColorScheme::View);
1602 
1603     m_activeExecutionBackgroundColor = m_commandItem->backgroundColor();
1604     m_activeExecutionTextColor = m_commandItem->defaultTextColor();
1605 
1606     disconnect(m_commandItem, &WorksheetTextItem::receivedFocus, worksheet(), &Worksheet::highlightItem);
1607 
1608     m_commandItem->setBackgroundColor(scheme.background(KColorScheme::AlternateBackground).color());
1609     m_commandItem->setDefaultTextColor(scheme.foreground(KColorScheme::InactiveText).color());
1610 }
1611 
1612 void CommandEntry::addToExecution()
1613 {
1614     m_isExecutionEnabled = true;
1615 
1616     m_commandItem->setBackgroundColor(m_activeExecutionBackgroundColor);
1617     m_commandItem->setDefaultTextColor(m_activeExecutionTextColor);
1618 
1619     connect(m_commandItem, &WorksheetTextItem::receivedFocus, worksheet(), &Worksheet::highlightItem);
1620     worksheet()->highlightItem(m_commandItem);
1621 }
1622 
1623 bool CommandEntry::isExcludedFromExecution()
1624 {
1625     return m_isExecutionEnabled == false;
1626 }
1627 
1628 bool CommandEntry::isResultCollapsed()
1629 {
1630     return m_resultsCollapsed;
1631 }