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(" ")); 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 , but support multiple spaces 1195 msg.replace(QLatin1String(" "), QLatin1String(" ")); 1196 // Doesn't support ", neither; 1197 msg.replace(QLatin1String("""), 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 }