Warning, file /education/cantor/src/worksheet.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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: 2018-2022 Alexander Semke <alexander.semke@web.de> 0006 */ 0007 0008 #include "worksheet.h" 0009 #include "commandentry.h" 0010 #include "hierarchyentry.h" 0011 #include "horizontalruleentry.h" 0012 #include "imageentry.h" 0013 #include "latexentry.h" 0014 #include "markdownentry.h" 0015 #include "pagebreakentry.h" 0016 #include "placeholderentry.h" 0017 #include "settings.h" 0018 #include "textentry.h" 0019 #include "worksheetview.h" 0020 #include "lib/jupyterutils.h" 0021 #include "lib/backend.h" 0022 #include "lib/extension.h" 0023 #include "lib/helpresult.h" 0024 #include "lib/session.h" 0025 #include "lib/defaulthighlighter.h" 0026 0027 #include <config-cantor.h> 0028 0029 #include <QApplication> 0030 #include <QBuffer> 0031 #include <QDrag> 0032 #include <QGraphicsSceneMouseEvent> 0033 #include <QJsonArray> 0034 #include <QJsonDocument> 0035 #include <QJsonObject> 0036 #include <QPrinter> 0037 #include <QRegularExpression> 0038 #include <QTimer> 0039 #include <QActionGroup> 0040 #include <QFile> 0041 #include <QXmlQuery> 0042 0043 #include <kcoreaddons_version.h> 0044 #include <KMessageBox> 0045 #include <KActionCollection> 0046 #include <KFontAction> 0047 #include <KFontSizeAction> 0048 #include <KToggleAction> 0049 #include <KLocalizedString> 0050 #include <KZip> 0051 0052 const double Worksheet::LeftMargin = 4; 0053 const double Worksheet::RightMargin = 4; 0054 const double Worksheet::TopMargin = 12; 0055 const double Worksheet::EntryCursorLength = 30; 0056 const double Worksheet::EntryCursorWidth = 2; 0057 0058 Worksheet::Worksheet(Cantor::Backend* backend, QWidget* parent, bool useDefaultWorksheetParameters) 0059 : QGraphicsScene(parent), 0060 m_cursorItemTimer(new QTimer(this)), 0061 m_useDefaultWorksheetParameters(useDefaultWorksheetParameters) 0062 { 0063 m_entryCursorItem = addLine(0,0,0,0); 0064 const QColor& color = (palette().color(QPalette::Base).lightness() < 128) ? Qt::white : Qt::black; 0065 QPen pen(color); 0066 pen.setWidth(EntryCursorWidth); 0067 m_entryCursorItem->setPen(pen); 0068 m_entryCursorItem->hide(); 0069 0070 connect(m_cursorItemTimer, &QTimer::timeout, this, &Worksheet::animateEntryCursor); 0071 m_cursorItemTimer->start(500); 0072 0073 if (backend) 0074 initSession(backend); 0075 } 0076 0077 void Worksheet::stopAnimations() 0078 { 0079 m_cursorItemTimer->stop(); 0080 m_entryCursorItem->hide(); 0081 } 0082 0083 void Worksheet::resumeAnimations() 0084 { 0085 delete m_cursorItemTimer; 0086 m_cursorItemTimer = new QTimer(this); 0087 connect(m_cursorItemTimer, &QTimer::timeout, this, &Worksheet::animateEntryCursor); 0088 m_cursorItemTimer->start(500); 0089 } 0090 0091 Worksheet::~Worksheet() 0092 { 0093 m_isClosing = true; 0094 0095 // This is necessary, because a SearchBar might access firstEntry() 0096 // while the scene is deleted. Maybe there is a better solution to 0097 // this problem, but I can't seem to find it. 0098 m_firstEntry = nullptr; 0099 0100 if (m_session) 0101 { 0102 disconnect(m_session, nullptr, nullptr, nullptr); 0103 if (m_session->status() != Cantor::Session::Disable) 0104 m_session->logout(); 0105 m_session->deleteLater(); 0106 } 0107 0108 if (m_jupyterMetadata) 0109 delete m_jupyterMetadata; 0110 } 0111 0112 void Worksheet::loginToSession() 0113 { 0114 m_session->login(); 0115 } 0116 0117 void Worksheet::print(QPrinter* printer) 0118 { 0119 m_epsRenderer.useHighResolution(true); 0120 m_mathRenderer.useHighResolution(true); 0121 m_isPrinting = true; 0122 0123 QRect pageRect = printer->pageRect(); 0124 qreal scale = 1; // todo: find good scale for page size 0125 // todo: use epsRenderer()->scale() for printing ? 0126 const qreal width = pageRect.width()/scale; 0127 const qreal height = pageRect.height()/scale; 0128 setViewSize(width, height, scale, true); 0129 0130 QPainter painter(printer); 0131 painter.scale(scale, scale); 0132 painter.setRenderHint(QPainter::Antialiasing); 0133 0134 WorksheetEntry* entry = firstEntry(); 0135 qreal y = TopMargin; 0136 0137 while (entry) { 0138 qreal h = 0; 0139 do { 0140 if (entry->type() == PageBreakEntry::Type) { 0141 entry = entry->next(); 0142 break; 0143 } 0144 h += entry->size().height(); 0145 entry = entry->next(); 0146 } while (entry && h + entry->size().height() <= height); 0147 0148 render(&painter, QRectF(0, 0, width, height), 0149 QRectF(0, y, width, h)); 0150 y += h; 0151 if (entry) 0152 printer->newPage(); 0153 } 0154 0155 //render(&painter); 0156 0157 painter.end(); 0158 m_isPrinting = false; 0159 m_epsRenderer.useHighResolution(false); 0160 m_mathRenderer.useHighResolution(false); 0161 m_epsRenderer.setScale(-1); // force update in next call to setViewSize, 0162 worksheetView()->updateSceneSize(); // ... which happens in here 0163 } 0164 0165 bool Worksheet::isPrinting() 0166 { 0167 return m_isPrinting; 0168 } 0169 0170 void Worksheet::setViewSize(qreal w, qreal h, qreal s, bool forceUpdate) 0171 { 0172 Q_UNUSED(h); 0173 0174 m_viewWidth = w; 0175 if (s != m_epsRenderer.scale() || forceUpdate) { 0176 m_epsRenderer.setScale(s); 0177 m_mathRenderer.setScale(s); 0178 for (WorksheetEntry *entry = firstEntry(); entry; entry = entry->next()) 0179 entry->updateEntry(); 0180 } 0181 updateLayout(); 0182 } 0183 0184 void Worksheet::updateLayout() 0185 { 0186 bool cursorRectVisible = false; 0187 bool atEnd = worksheetView()->isAtEnd(); 0188 if (currentTextItem()) { 0189 QRectF cursorRect = currentTextItem()->sceneCursorRect(); 0190 cursorRectVisible = worksheetView()->isVisible(cursorRect); 0191 } 0192 0193 m_maxPromptWidth = 0; 0194 if (Settings::useOldCantorEntriesIndent() == false) 0195 { 0196 for (WorksheetEntry *entry = firstEntry(); entry; entry = entry->next()) 0197 if (entry->type() == CommandEntry::Type) 0198 m_maxPromptWidth = std::max(static_cast<CommandEntry*>(entry)->promptItemWidth(), m_maxPromptWidth); 0199 else if (entry->type() == HierarchyEntry::Type) 0200 m_maxPromptWidth = std::max(static_cast<HierarchyEntry*>(entry)->hierarchyItemWidth(), m_maxPromptWidth); 0201 } 0202 0203 const qreal w = m_viewWidth - LeftMargin - RightMargin - (WorksheetEntry::ControlElementWidth + WorksheetEntry::ControlElementBorder) * m_hierarchyMaxDepth; 0204 qreal y = TopMargin; 0205 const qreal x = LeftMargin; 0206 for (WorksheetEntry *entry = firstEntry(); entry; entry = entry->next()) 0207 y += entry->setGeometry(x, x+m_maxPromptWidth, y, w); 0208 0209 updateHierarchyControlsLayout(); 0210 0211 setSceneRect(QRectF(0, 0, sceneRect().width(), y)); 0212 if (cursorRectVisible) 0213 makeVisible(worksheetCursor()); 0214 else if (atEnd) 0215 worksheetView()->scrollToEnd(); 0216 drawEntryCursor(); 0217 } 0218 0219 void Worksheet::updateHierarchyLayout() 0220 { 0221 QStringList names; 0222 QStringList searchStrings; 0223 QList<int> depths; 0224 0225 m_hierarchyMaxDepth = 0; 0226 std::vector<int> hierarchyNumbers; 0227 for (WorksheetEntry *entry = firstEntry(); entry; entry = entry->next()) 0228 { 0229 if (entry->type() == HierarchyEntry::Type) 0230 { 0231 auto* hierarchEntry = static_cast<HierarchyEntry*>(entry); 0232 hierarchEntry->updateHierarchyLevel(hierarchyNumbers); 0233 m_hierarchyMaxDepth = std::max(m_hierarchyMaxDepth, hierarchyNumbers.size()); 0234 0235 names.append(hierarchEntry->text()); 0236 searchStrings.append(hierarchEntry->hierarchyText()); 0237 depths.append(hierarchyNumbers.size()-1); 0238 } 0239 } 0240 0241 emit hierarchyChanged(names, searchStrings, depths); 0242 } 0243 0244 void Worksheet::updateHierarchyControlsLayout(WorksheetEntry* startEntry) 0245 { 0246 if (startEntry == nullptr) 0247 startEntry = firstEntry(); 0248 0249 // Update sizes of control elements for hierarchy entries 0250 std::vector<HierarchyEntry*> levelsEntries; 0251 int numerationBegin = (int)HierarchyEntry::HierarchyLevel::Chapter; 0252 for (int i = numerationBegin; i < (int)HierarchyEntry::HierarchyLevel::EndValue; i++) 0253 levelsEntries.push_back(nullptr); 0254 0255 for (WorksheetEntry *entry = startEntry; entry; entry = entry->next()) 0256 { 0257 if (entry->type() == HierarchyEntry::Type) 0258 { 0259 HierarchyEntry* hierarchyEntry = static_cast<HierarchyEntry*>(entry); 0260 int idx = (int)hierarchyEntry->level() - numerationBegin; 0261 if (levelsEntries[idx] == nullptr) 0262 { 0263 levelsEntries[idx] = hierarchyEntry; 0264 } 0265 else 0266 { 0267 for (int i = idx; i < (int)levelsEntries.size(); i++) 0268 if (levelsEntries[i] != nullptr) 0269 { 0270 bool haveSubelements = levelsEntries[i]->next() ? levelsEntries[i]->next() != entry : false; 0271 levelsEntries[i]->updateControlElementForHierarchy(hierarchyEntry->y() - WorksheetEntry::VerticalMargin, m_hierarchyMaxDepth, haveSubelements); 0272 levelsEntries[i] = nullptr; 0273 } 0274 levelsEntries[idx] = hierarchyEntry; 0275 } 0276 } 0277 } 0278 0279 if (lastEntry()) 0280 for (int i = 0; i < (int)levelsEntries.size(); i++) 0281 if (levelsEntries[i] != nullptr) 0282 { 0283 bool haveSubelements = levelsEntries[i] != lastEntry(); 0284 levelsEntries[i]->updateControlElementForHierarchy(lastEntry()->y() + lastEntry()->size().height() - WorksheetEntry::VerticalMargin, m_hierarchyMaxDepth, haveSubelements); 0285 levelsEntries[i] = nullptr; 0286 } 0287 } 0288 0289 std::vector<WorksheetEntry*> Worksheet::hierarchySubelements(HierarchyEntry* hierarchyEntry) const 0290 { 0291 std::vector<WorksheetEntry*> subentries; 0292 0293 Q_ASSERT(hierarchyEntry); 0294 0295 bool subentriesEnd = false; 0296 int level = (int)hierarchyEntry->level(); 0297 for (WorksheetEntry *entry = hierarchyEntry->next(); entry && !subentriesEnd; entry = entry->next()) 0298 { 0299 if (entry->type() == HierarchyEntry::Type) 0300 { 0301 if ((int)(static_cast<HierarchyEntry*>(entry)->level()) <= level) 0302 subentriesEnd = true; 0303 else 0304 subentries.push_back(entry); 0305 } 0306 else 0307 subentries.push_back(entry); 0308 } 0309 return subentries; 0310 } 0311 0312 void Worksheet::updateEntrySize(WorksheetEntry* entry) 0313 { 0314 bool cursorRectVisible = false; 0315 bool atEnd = worksheetView()->isAtEnd(); 0316 if (currentTextItem()) { 0317 QRectF cursorRect = currentTextItem()->sceneCursorRect(); 0318 cursorRectVisible = worksheetView()->isVisible(cursorRect); 0319 } 0320 0321 if (Settings::useOldCantorEntriesIndent() == false) 0322 { 0323 qreal newMaxPromptWidth = m_maxPromptWidth; 0324 if (entry->type() == CommandEntry::Type) 0325 newMaxPromptWidth = static_cast<CommandEntry*>(entry)->promptItemWidth(); 0326 else if (entry->type() == HierarchyEntry::Type) 0327 newMaxPromptWidth = static_cast<HierarchyEntry*>(entry)->hierarchyItemWidth(); 0328 0329 // If width of prompt (if precense) of the entry more, that currect maximum, 0330 // then we need full layout update 0331 if (newMaxPromptWidth > m_maxPromptWidth) 0332 { 0333 updateLayout(); 0334 return; 0335 } 0336 } 0337 0338 qreal y = entry->y() + entry->size().height(); 0339 for (entry = entry->next(); entry; entry = entry->next()) { 0340 entry->setY(y); 0341 y += entry->size().height(); 0342 } 0343 0344 if (!m_isLoadingFromFile) 0345 updateHierarchyControlsLayout(entry); 0346 0347 setSceneRect(QRectF(0, 0, sceneRect().width(), y)); 0348 if (cursorRectVisible) 0349 makeVisible(worksheetCursor()); 0350 else if (atEnd) 0351 worksheetView()->scrollToEnd(); 0352 drawEntryCursor(); 0353 } 0354 0355 void Worksheet::setRequestedWidth(QGraphicsObject* object, qreal width) 0356 { 0357 qreal oldWidth = m_itemWidths[object]; 0358 m_itemWidths[object] = width; 0359 0360 if (width > m_maxWidth || oldWidth == m_maxWidth) 0361 { 0362 m_maxWidth = width; 0363 qreal y = lastEntry() ? lastEntry()->size().height() + lastEntry()->y() : 0; 0364 setSceneRect(QRectF(0, 0, m_maxWidth + LeftMargin + RightMargin, y)); 0365 } 0366 } 0367 0368 void Worksheet::removeRequestedWidth(QGraphicsObject* object) 0369 { 0370 if (!m_itemWidths.contains(object)) 0371 return; 0372 0373 qreal width = m_itemWidths[object]; 0374 m_itemWidths.remove(object); 0375 0376 if (width == m_maxWidth) 0377 { 0378 m_maxWidth = 0; 0379 for (qreal width : m_itemWidths.values()) 0380 if (width > m_maxWidth) 0381 m_maxWidth = width; 0382 qreal y = lastEntry() ? lastEntry()->size().height() + lastEntry()->y() : 0; 0383 setSceneRect(QRectF(0, 0, m_maxWidth + LeftMargin + RightMargin, y)); 0384 } 0385 } 0386 0387 bool Worksheet::isEmpty() 0388 { 0389 return !m_firstEntry; 0390 } 0391 0392 bool Worksheet::isLoadingFromFile() 0393 { 0394 return m_isLoadingFromFile; 0395 } 0396 0397 void Worksheet::makeVisible(WorksheetEntry* entry) 0398 { 0399 QRectF r = entry->boundingRect(); 0400 r = entry->mapRectToScene(r); 0401 r.adjust(0, -10, 0, 10); 0402 worksheetView()->makeVisible(r); 0403 } 0404 0405 void Worksheet::makeVisible(const WorksheetCursor& cursor) 0406 { 0407 if (cursor.textCursor().isNull()) { 0408 if (cursor.entry()) 0409 makeVisible(cursor.entry()); 0410 return; 0411 } 0412 QRectF r = cursor.textItem()->sceneCursorRect(cursor.textCursor()); 0413 QRectF er = cursor.entry()->boundingRect(); 0414 er = cursor.entry()->mapRectToScene(er); 0415 er.adjust(0, -10, 0, 10); 0416 r.adjust(0, qMax(qreal(-100.0), er.top() - r.top()), 0417 0, qMin(qreal(100.0), er.bottom() - r.bottom())); 0418 worksheetView()->makeVisible(r); 0419 } 0420 0421 WorksheetView* Worksheet::worksheetView() 0422 { 0423 return static_cast<WorksheetView*>(views().first()); 0424 } 0425 0426 void Worksheet::setModified() 0427 { 0428 if (!m_isClosing && !m_isLoadingFromFile) 0429 emit modified(); 0430 } 0431 0432 WorksheetCursor Worksheet::worksheetCursor() 0433 { 0434 WorksheetEntry* entry = currentEntry(); 0435 WorksheetTextItem* item = currentTextItem(); 0436 0437 if (!entry || !item) 0438 return WorksheetCursor(); 0439 return WorksheetCursor(entry, item, item->textCursor()); 0440 } 0441 0442 void Worksheet::setWorksheetCursor(const WorksheetCursor& cursor) 0443 { 0444 if (!cursor.isValid()) 0445 return; 0446 0447 if (m_lastFocusedTextItem) 0448 m_lastFocusedTextItem->clearSelection(); 0449 0450 m_lastFocusedTextItem = cursor.textItem(); 0451 0452 cursor.textItem()->setTextCursor(cursor.textCursor()); 0453 } 0454 0455 WorksheetEntry* Worksheet::currentEntry() 0456 { 0457 // Entry cursor activate 0458 if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) 0459 return nullptr; 0460 0461 QGraphicsItem* item = focusItem(); 0462 if (!item /*&& !hasFocus()*/) 0463 item = m_lastFocusedTextItem; 0464 /*else 0465 m_focusItem = item;*/ 0466 while (item && (item->type() < QGraphicsItem::UserType || 0467 item->type() >= QGraphicsItem::UserType + 100)) 0468 item = item->parentItem(); 0469 if (item) { 0470 WorksheetEntry* entry = qobject_cast<WorksheetEntry*>(item->toGraphicsObject()); 0471 if (entry && entry->aboutToBeRemoved()) { 0472 if (entry->isAncestorOf(m_lastFocusedTextItem)) 0473 m_lastFocusedTextItem = nullptr; 0474 return nullptr; 0475 } 0476 return entry; 0477 } 0478 return nullptr; 0479 } 0480 0481 WorksheetEntry* Worksheet::firstEntry() 0482 { 0483 return m_firstEntry; 0484 } 0485 0486 WorksheetEntry* Worksheet::lastEntry() 0487 { 0488 return m_lastEntry; 0489 } 0490 0491 void Worksheet::setFirstEntry(WorksheetEntry* entry) 0492 { 0493 if (m_firstEntry) 0494 disconnect(m_firstEntry, &WorksheetEntry::aboutToBeDeleted, 0495 this, &Worksheet::invalidateFirstEntry); 0496 m_firstEntry = entry; 0497 if (m_firstEntry) 0498 connect(m_firstEntry, &WorksheetEntry::aboutToBeDeleted, 0499 this, &Worksheet::invalidateFirstEntry, Qt::DirectConnection); 0500 } 0501 0502 void Worksheet::setLastEntry(WorksheetEntry* entry) 0503 { 0504 if (m_lastEntry) 0505 disconnect(m_lastEntry, &WorksheetEntry::aboutToBeDeleted, 0506 this, &Worksheet::invalidateLastEntry); 0507 m_lastEntry = entry; 0508 if (m_lastEntry) 0509 connect(m_lastEntry, &WorksheetEntry::aboutToBeDeleted, 0510 this, &Worksheet::invalidateLastEntry, Qt::DirectConnection); 0511 } 0512 0513 void Worksheet::invalidateFirstEntry() 0514 { 0515 if (m_firstEntry) 0516 setFirstEntry(m_firstEntry->next()); 0517 } 0518 0519 void Worksheet::invalidateLastEntry() 0520 { 0521 if (m_lastEntry) 0522 setLastEntry(m_lastEntry->previous()); 0523 } 0524 0525 WorksheetEntry* Worksheet::entryAt(qreal x, qreal y) 0526 { 0527 QGraphicsItem* item = itemAt(x, y, QTransform()); 0528 while (item && (item->type() <= QGraphicsItem::UserType || 0529 item->type() >= QGraphicsItem::UserType + 100)) 0530 item = item->parentItem(); 0531 if (item) 0532 return qobject_cast<WorksheetEntry*>(item->toGraphicsObject()); 0533 return nullptr; 0534 } 0535 0536 WorksheetEntry* Worksheet::entryAt(QPointF p) 0537 { 0538 return entryAt(p.x(), p.y()); 0539 } 0540 0541 void Worksheet::focusEntry(WorksheetEntry *entry) 0542 { 0543 if (!entry) 0544 return; 0545 entry->focusEntry(); 0546 resetEntryCursor(); 0547 //bool rt = entry->acceptRichText(); 0548 //setActionsEnabled(rt); 0549 //setAcceptRichText(rt); 0550 //ensureCursorVisible(); 0551 } 0552 0553 void Worksheet::startDrag(WorksheetEntry* entry, QDrag* drag) 0554 { 0555 if (m_readOnly) 0556 return; 0557 0558 resetEntryCursor(); 0559 m_dragEntry = entry; 0560 auto* prev = entry->previous(); 0561 auto* next = entry->next(); 0562 m_placeholderEntry = new PlaceHolderEntry(this, entry->size()); 0563 m_placeholderEntry->setPrevious(prev); 0564 m_placeholderEntry->setNext(next); 0565 if (prev) 0566 prev->setNext(m_placeholderEntry); 0567 else 0568 setFirstEntry(m_placeholderEntry); 0569 if (next) 0570 next->setPrevious(m_placeholderEntry); 0571 else 0572 setLastEntry(m_placeholderEntry); 0573 m_dragEntry->hide(); 0574 Qt::DropAction action = drag->exec(); 0575 0576 qDebug() << action; 0577 if (action == Qt::MoveAction && m_placeholderEntry) { 0578 qDebug() << "insert in new position"; 0579 prev = m_placeholderEntry->previous(); 0580 next = m_placeholderEntry->next(); 0581 } 0582 m_dragEntry->setPrevious(prev); 0583 m_dragEntry->setNext(next); 0584 if (prev) 0585 prev->setNext(m_dragEntry); 0586 else 0587 setFirstEntry(m_dragEntry); 0588 if (next) 0589 next->setPrevious(m_dragEntry); 0590 else 0591 setLastEntry(m_dragEntry); 0592 m_dragEntry->show(); 0593 if (m_dragEntry->type() == HierarchyEntry::Type) 0594 updateHierarchyLayout(); 0595 m_dragEntry->focusEntry(); 0596 const QPointF scenePos = worksheetView()->sceneCursorPos(); 0597 if (entryAt(scenePos) != m_dragEntry) 0598 m_dragEntry->hideActionBar(); 0599 updateLayout(); 0600 if (m_placeholderEntry) { 0601 m_placeholderEntry->setPrevious(nullptr); 0602 m_placeholderEntry->setNext(nullptr); 0603 m_placeholderEntry->hide(); 0604 m_placeholderEntry->deleteLater(); 0605 m_placeholderEntry = nullptr; 0606 } 0607 m_dragEntry = nullptr; 0608 } 0609 0610 void Worksheet::startDragWithHierarchy(HierarchyEntry* entry, QDrag* drag, QSizeF responsibleZoneSize) 0611 { 0612 if (m_readOnly) 0613 return; 0614 0615 resetEntryCursor(); 0616 m_dragEntry = entry; 0617 WorksheetEntry* prev = entry->previous(); 0618 m_hierarchySubentriesDrag = hierarchySubelements(entry); 0619 0620 WorksheetEntry* next; 0621 if (m_hierarchySubentriesDrag.size() != 0) 0622 next = m_hierarchySubentriesDrag.back()->next(); 0623 else 0624 next = entry->next(); 0625 0626 m_placeholderEntry = new PlaceHolderEntry(this, responsibleZoneSize); 0627 m_hierarchyDragSize = responsibleZoneSize; 0628 m_placeholderEntry->setPrevious(prev); 0629 m_placeholderEntry->setNext(next); 0630 if (prev) 0631 prev->setNext(m_placeholderEntry); 0632 else 0633 setFirstEntry(m_placeholderEntry); 0634 if (next) 0635 next->setPrevious(m_placeholderEntry); 0636 else 0637 setLastEntry(m_placeholderEntry); 0638 0639 m_dragEntry->hide(); 0640 for(WorksheetEntry* subEntry : m_hierarchySubentriesDrag) 0641 subEntry->hide(); 0642 0643 Qt::DropAction action = drag->exec(); 0644 0645 qDebug() << action; 0646 if (action == Qt::MoveAction && m_placeholderEntry) { 0647 qDebug() << "insert in new position"; 0648 prev = m_placeholderEntry->previous(); 0649 next = m_placeholderEntry->next(); 0650 } 0651 m_dragEntry->setPrevious(prev); 0652 0653 WorksheetEntry* lastDraggingEntry; 0654 if (m_hierarchySubentriesDrag.size() != 0) 0655 lastDraggingEntry = m_hierarchySubentriesDrag.back(); 0656 else 0657 lastDraggingEntry = entry; 0658 0659 lastDraggingEntry->setNext(next); 0660 0661 if (prev) 0662 prev->setNext(m_dragEntry); 0663 else 0664 setFirstEntry(m_dragEntry); 0665 0666 if (next) 0667 next->setPrevious(lastDraggingEntry); 0668 else 0669 setLastEntry(lastDraggingEntry); 0670 0671 m_dragEntry->show(); 0672 for(WorksheetEntry* subEntry : m_hierarchySubentriesDrag) 0673 subEntry->show(); 0674 0675 updateHierarchyLayout(); 0676 m_dragEntry->focusEntry(); 0677 const QPointF scenePos = worksheetView()->sceneCursorPos(); 0678 if (entryAt(scenePos) != m_dragEntry) 0679 m_dragEntry->hideActionBar(); 0680 updateLayout(); 0681 0682 if (m_placeholderEntry) { 0683 m_placeholderEntry->setPrevious(nullptr); 0684 m_placeholderEntry->setNext(nullptr); 0685 m_placeholderEntry->hide(); 0686 m_placeholderEntry->deleteLater(); 0687 m_placeholderEntry = nullptr; 0688 } 0689 m_dragEntry = nullptr; 0690 m_hierarchySubentriesDrag.clear(); 0691 } 0692 0693 void Worksheet::evaluate() 0694 { 0695 qDebug()<<"evaluate worksheet"; 0696 if (!m_readOnly && m_session && m_session->status() == Cantor::Session::Disable) 0697 loginToSession(); 0698 0699 firstEntry()->evaluate(WorksheetEntry::EvaluateNext); 0700 0701 setModified(); 0702 } 0703 0704 void Worksheet::evaluateCurrentEntry() 0705 { 0706 if (!m_readOnly && m_session && m_session->status() == Cantor::Session::Disable) 0707 loginToSession(); 0708 0709 WorksheetEntry* entry = currentEntry(); 0710 if(!entry) 0711 return; 0712 entry->evaluateCurrentItem(); 0713 } 0714 0715 bool Worksheet::completionEnabled() 0716 { 0717 return m_completionEnabled; 0718 } 0719 0720 void Worksheet::showCompletion() 0721 { 0722 WorksheetEntry* current = currentEntry(); 0723 if (current) 0724 current->showCompletion(); 0725 } 0726 0727 WorksheetEntry* Worksheet::appendEntry(const int type, bool focus) 0728 { 0729 WorksheetEntry* entry = WorksheetEntry::create(type, this); 0730 0731 if (entry) 0732 { 0733 qDebug() << "Entry Appended"; 0734 entry->setPrevious(lastEntry()); 0735 if (lastEntry()) 0736 lastEntry()->setNext(entry); 0737 if (!firstEntry()) 0738 setFirstEntry(entry); 0739 setLastEntry(entry); 0740 if (!m_isLoadingFromFile) 0741 { 0742 if (type == HierarchyEntry::Type) 0743 updateHierarchyLayout(); 0744 updateLayout(); 0745 if (focus) 0746 { 0747 makeVisible(entry); 0748 focusEntry(entry); 0749 } 0750 setModified(); 0751 } 0752 } 0753 return entry; 0754 } 0755 0756 WorksheetEntry* Worksheet::appendCommandEntry() 0757 { 0758 return appendEntry(CommandEntry::Type); 0759 } 0760 0761 WorksheetEntry* Worksheet::appendTextEntry() 0762 { 0763 return appendEntry(TextEntry::Type); 0764 } 0765 0766 WorksheetEntry* Worksheet::appendMarkdownEntry() 0767 { 0768 return appendEntry(MarkdownEntry::Type); 0769 } 0770 0771 WorksheetEntry* Worksheet::appendPageBreakEntry() 0772 { 0773 return appendEntry(PageBreakEntry::Type); 0774 } 0775 0776 WorksheetEntry* Worksheet::appendImageEntry() 0777 { 0778 return appendEntry(ImageEntry::Type); 0779 } 0780 0781 WorksheetEntry* Worksheet::appendLatexEntry() 0782 { 0783 return appendEntry(LatexEntry::Type); 0784 } 0785 0786 void Worksheet::appendCommandEntry(const QString& text) 0787 { 0788 auto* entry = lastEntry(); 0789 if(!entry->isEmpty()) 0790 entry = appendCommandEntry(); 0791 0792 if (entry) 0793 { 0794 focusEntry(entry); 0795 entry->setContent(text); 0796 evaluateCurrentEntry(); 0797 } 0798 } 0799 0800 WorksheetEntry* Worksheet::appendHorizontalRuleEntry() 0801 { 0802 return appendEntry(HorizontalRuleEntry::Type); 0803 } 0804 0805 WorksheetEntry* Worksheet::appendHierarchyEntry() 0806 { 0807 return appendEntry(HierarchyEntry::Type); 0808 } 0809 0810 WorksheetEntry* Worksheet::insertEntry(const int type, WorksheetEntry* current) 0811 { 0812 if (!current) 0813 current = currentEntry(); 0814 0815 if (!current) 0816 return appendEntry(type); 0817 0818 auto* next = current->next(); 0819 WorksheetEntry* entry = nullptr; 0820 0821 if (!next || next->type() != type || !next->isEmpty()) 0822 { 0823 entry = WorksheetEntry::create(type, this); 0824 entry->setPrevious(current); 0825 entry->setNext(next); 0826 current->setNext(entry); 0827 if (next) 0828 next->setPrevious(entry); 0829 else 0830 setLastEntry(entry); 0831 if (type == HierarchyEntry::Type) 0832 updateHierarchyLayout(); 0833 updateLayout(); 0834 setModified(); 0835 } else { 0836 entry = next; 0837 } 0838 0839 focusEntry(entry); 0840 makeVisible(entry); 0841 0842 return entry; 0843 } 0844 0845 WorksheetEntry* Worksheet::insertTextEntry(WorksheetEntry* current) 0846 { 0847 return insertEntry(TextEntry::Type, current); 0848 } 0849 0850 WorksheetEntry* Worksheet::insertMarkdownEntry(WorksheetEntry* current) 0851 { 0852 return insertEntry(MarkdownEntry::Type, current); 0853 } 0854 0855 WorksheetEntry* Worksheet::insertCommandEntry(WorksheetEntry* current) 0856 { 0857 return insertEntry(CommandEntry::Type, current); 0858 } 0859 0860 WorksheetEntry* Worksheet::insertImageEntry(WorksheetEntry* current) 0861 { 0862 auto* entry = insertEntry(ImageEntry::Type, current); 0863 auto* imageEntry = static_cast<ImageEntry*>(entry); 0864 QTimer::singleShot(0, this, [=] () {imageEntry->startConfigDialog();}); 0865 return entry; 0866 } 0867 0868 WorksheetEntry* Worksheet::insertPageBreakEntry(WorksheetEntry* current) 0869 { 0870 return insertEntry(PageBreakEntry::Type, current); 0871 } 0872 0873 WorksheetEntry* Worksheet::insertLatexEntry(WorksheetEntry* current) 0874 { 0875 return insertEntry(LatexEntry::Type, current); 0876 } 0877 0878 WorksheetEntry * Worksheet::insertHorizontalRuleEntry(WorksheetEntry* current) 0879 { 0880 return insertEntry(HorizontalRuleEntry::Type, current); 0881 } 0882 0883 WorksheetEntry * Worksheet::insertHierarchyEntry(WorksheetEntry* current) 0884 { 0885 return insertEntry(HierarchyEntry::Type, current); 0886 } 0887 0888 WorksheetEntry* Worksheet::insertEntryBefore(int type, WorksheetEntry* current) 0889 { 0890 if (!current) 0891 current = currentEntry(); 0892 0893 if (!current) 0894 return nullptr; 0895 0896 auto* prev = current->previous(); 0897 WorksheetEntry *entry = nullptr; 0898 0899 if(!prev || prev->type() != type || !prev->isEmpty()) 0900 { 0901 entry = WorksheetEntry::create(type, this); 0902 entry->setNext(current); 0903 entry->setPrevious(prev); 0904 current->setPrevious(entry); 0905 if (prev) 0906 prev->setNext(entry); 0907 else 0908 setFirstEntry(entry); 0909 if (type == HierarchyEntry::Type) 0910 updateHierarchyLayout(); 0911 updateLayout(); 0912 setModified(); 0913 } 0914 else 0915 entry = prev; 0916 0917 focusEntry(entry); 0918 return entry; 0919 } 0920 0921 WorksheetEntry* Worksheet::insertTextEntryBefore(WorksheetEntry* current) 0922 { 0923 return insertEntryBefore(TextEntry::Type, current); 0924 } 0925 0926 WorksheetEntry* Worksheet::insertMarkdownEntryBefore(WorksheetEntry* current) 0927 { 0928 return insertEntryBefore(MarkdownEntry::Type, current); 0929 } 0930 0931 WorksheetEntry* Worksheet::insertCommandEntryBefore(WorksheetEntry* current) 0932 { 0933 return insertEntryBefore(CommandEntry::Type, current); 0934 } 0935 0936 WorksheetEntry* Worksheet::insertPageBreakEntryBefore(WorksheetEntry* current) 0937 { 0938 return insertEntryBefore(PageBreakEntry::Type, current); 0939 } 0940 0941 WorksheetEntry* Worksheet::insertImageEntryBefore(WorksheetEntry* current) 0942 { 0943 auto* entry = insertEntryBefore(ImageEntry::Type, current); 0944 auto* imageEntry = static_cast<ImageEntry*>(entry); 0945 QTimer::singleShot(0, this, [=] () {imageEntry->startConfigDialog();}); 0946 return entry; 0947 } 0948 0949 WorksheetEntry* Worksheet::insertLatexEntryBefore(WorksheetEntry* current) 0950 { 0951 return insertEntryBefore(LatexEntry::Type, current); 0952 } 0953 0954 WorksheetEntry* Worksheet::insertHorizontalRuleEntryBefore(WorksheetEntry* current) 0955 { 0956 return insertEntryBefore(HorizontalRuleEntry::Type, current); 0957 } 0958 0959 WorksheetEntry* Worksheet::insertHierarchyEntryBefore(WorksheetEntry* current) 0960 { 0961 return insertEntryBefore(HierarchyEntry::Type, current); 0962 } 0963 0964 void Worksheet::interrupt() 0965 { 0966 if (m_session->status() == Cantor::Session::Running) 0967 { 0968 m_session->interrupt(); 0969 emit updatePrompt(); 0970 } 0971 } 0972 0973 void Worksheet::interruptCurrentEntryEvaluation() 0974 { 0975 currentEntry()->interruptEvaluation(); 0976 } 0977 0978 void Worksheet::highlightItem(WorksheetTextItem* item) 0979 { 0980 if (!m_highlighter) 0981 return; 0982 0983 auto* oldDocument = m_highlighter->document(); 0984 QList<QVector<QTextLayout::FormatRange> > formats; 0985 0986 if (oldDocument) 0987 { 0988 for (QTextBlock b = oldDocument->firstBlock(); 0989 b.isValid(); b = b.next()) 0990 { 0991 formats.append(b.layout()->formats()); 0992 } 0993 } 0994 0995 // Not every highlighter is a Cantor::DefaultHighligther (e.g. the 0996 // highlighter for KAlgebra) 0997 auto* hl = qobject_cast<Cantor::DefaultHighlighter*>(m_highlighter); 0998 if (hl) 0999 hl->setTextItem(item); 1000 else 1001 m_highlighter->setDocument(item->document()); 1002 1003 if (oldDocument) 1004 { 1005 QTextCursor cursor(oldDocument); 1006 cursor.beginEditBlock(); 1007 for (QTextBlock b = oldDocument->firstBlock(); 1008 b.isValid(); b = b.next()) 1009 { 1010 b.layout()->setFormats(formats.first()); 1011 formats.pop_front(); 1012 } 1013 cursor.endEditBlock(); 1014 } 1015 } 1016 1017 void Worksheet::rehighlight() 1018 { 1019 if(m_highlighter) 1020 { 1021 // highlight every entry 1022 WorksheetEntry* entry; 1023 for (entry = firstEntry(); entry; entry = entry->next()) { 1024 auto* item = entry->highlightItem(); 1025 if (!item) 1026 continue; 1027 1028 highlightItem(item); 1029 m_highlighter->rehighlight(); 1030 } 1031 1032 entry = currentEntry(); 1033 auto* textitem = entry ? entry->highlightItem() : nullptr; 1034 if (textitem && textitem->hasFocus()) 1035 highlightItem(textitem); 1036 } 1037 else 1038 { 1039 // remove highlighting from entries 1040 WorksheetEntry* entry; 1041 for (entry = firstEntry(); entry; entry = entry->next()) { 1042 auto* item = entry->highlightItem(); 1043 if (!item) 1044 continue; 1045 1046 QTextCursor cursor(item->document()); 1047 cursor.beginEditBlock(); 1048 for (auto b = item->document()->firstBlock(); b.isValid(); b = b.next()) 1049 b.layout()->clearFormats(); 1050 1051 cursor.endEditBlock(); 1052 } 1053 update(); 1054 } 1055 } 1056 1057 void Worksheet::enableHighlighting(bool highlight) 1058 { 1059 if(highlight) 1060 { 1061 if(m_highlighter) 1062 m_highlighter->deleteLater(); 1063 1064 if (!m_readOnly) 1065 m_highlighter=session()->syntaxHighlighter(this); 1066 else 1067 m_highlighter=nullptr; 1068 1069 if(!m_highlighter) 1070 m_highlighter=new Cantor::DefaultHighlighter(this); 1071 1072 //TODO: new syntax 1073 connect(m_highlighter, SIGNAL(rulesChanged()), this, SLOT(rehighlight())); 1074 }else 1075 { 1076 if(m_highlighter) 1077 m_highlighter->deleteLater(); 1078 m_highlighter=nullptr; 1079 } 1080 1081 rehighlight(); 1082 } 1083 1084 void Worksheet::enableCompletion(bool enable) 1085 { 1086 m_completionEnabled=enable; 1087 } 1088 1089 Cantor::Session* Worksheet::session() 1090 { 1091 return m_session; 1092 } 1093 1094 bool Worksheet::isRunning() 1095 { 1096 return m_session && m_session->status()==Cantor::Session::Running; 1097 } 1098 1099 bool Worksheet::isReadOnly() 1100 { 1101 return m_readOnly; 1102 } 1103 1104 bool Worksheet::showExpressionIds() 1105 { 1106 return m_showExpressionIds; 1107 } 1108 1109 bool Worksheet::animationsEnabled() 1110 { 1111 return m_animationsEnabled; 1112 } 1113 1114 void Worksheet::enableAnimations(bool enable) 1115 { 1116 m_animationsEnabled = enable; 1117 } 1118 1119 bool Worksheet::embeddedMathEnabled() 1120 { 1121 return m_embeddedMathEnabled && m_mathRenderer.mathRenderAvailable(); 1122 } 1123 1124 void Worksheet::enableEmbeddedMath(bool enable) 1125 { 1126 m_embeddedMathEnabled = enable; 1127 } 1128 1129 void Worksheet::enableExpressionNumbering(bool enable) 1130 { 1131 m_showExpressionIds=enable; 1132 emit updatePrompt(); 1133 if (views().size() != 0) 1134 updateLayout(); 1135 } 1136 1137 QDomDocument Worksheet::toXML(KZip* archive) 1138 { 1139 QDomDocument doc( QLatin1String("CantorWorksheet") ); 1140 QDomElement root = doc.createElement( QLatin1String("Worksheet") ); 1141 root.setAttribute(QLatin1String("backend"), (m_session ? m_session->backend()->name(): m_backendName)); 1142 doc.appendChild(root); 1143 1144 for( auto* entry = firstEntry(); entry; entry = entry->next()) 1145 { 1146 QDomElement el = entry->toXml(doc, archive); 1147 root.appendChild( el ); 1148 } 1149 return doc; 1150 } 1151 1152 QJsonDocument Worksheet::toJupyterJson() 1153 { 1154 QJsonDocument doc; 1155 QJsonObject root; 1156 1157 QJsonObject metadata(m_jupyterMetadata ? *m_jupyterMetadata : QJsonObject()); 1158 1159 QJsonObject kernalInfo; 1160 if (m_session && m_session->backend()) 1161 kernalInfo = Cantor::JupyterUtils::getKernelspec(m_session->backend()); 1162 else 1163 kernalInfo.insert(QLatin1String("name"), m_backendName); 1164 metadata.insert(QLatin1String("kernelspec"), kernalInfo); 1165 1166 root.insert(QLatin1String("metadata"), metadata); 1167 1168 // Not sure, but it looks like we support nbformat version 4.5 1169 root.insert(QLatin1String("nbformat"), 4); 1170 root.insert(QLatin1String("nbformat_minor"), 5); 1171 1172 QJsonArray cells; 1173 for( WorksheetEntry* entry = firstEntry(); entry; entry = entry->next()) 1174 { 1175 const QJsonValue entryJson = entry->toJupyterJson(); 1176 1177 if (!entryJson.isNull()) 1178 cells.append(entryJson); 1179 } 1180 root.insert(QLatin1String("cells"), cells); 1181 1182 doc.setObject(root); 1183 return doc; 1184 } 1185 1186 void Worksheet::save( const QString& filename ) 1187 { 1188 QFile file(filename); 1189 if ( !file.open(QIODevice::WriteOnly) ) 1190 { 1191 KMessageBox::error( worksheetView(), 1192 i18n( "Cannot write file %1." , filename ), 1193 i18n( "Error - Cantor" )); 1194 return; 1195 } 1196 1197 save(&file); 1198 } 1199 1200 QByteArray Worksheet::saveToByteArray() 1201 { 1202 QBuffer buffer; 1203 save(&buffer); 1204 1205 return buffer.buffer(); 1206 } 1207 1208 void Worksheet::save( QIODevice* device) 1209 { 1210 qDebug()<<"saving to filename"; 1211 switch (m_type) 1212 { 1213 case CantorWorksheet: 1214 { 1215 KZip zipFile( device ); 1216 1217 if ( !zipFile.open(QIODevice::WriteOnly) ) 1218 { 1219 KMessageBox::error( worksheetView(), 1220 i18n( "Cannot write file." ), 1221 i18n( "Error - Cantor" )); 1222 return; 1223 } 1224 1225 QByteArray content = toXML(&zipFile).toByteArray(); 1226 zipFile.writeFile( QLatin1String("content.xml"), content.data()); 1227 break; 1228 } 1229 1230 case JupyterNotebook: 1231 { 1232 if (!device->isWritable()) 1233 { 1234 KMessageBox::error( worksheetView(), 1235 i18n( "Cannot write file." ), 1236 i18n( "Error - Cantor" )); 1237 return; 1238 } 1239 1240 const QJsonDocument& doc = toJupyterJson(); 1241 device->write(doc.toJson(QJsonDocument::Indented)); 1242 break; 1243 } 1244 } 1245 } 1246 1247 void Worksheet::savePlain(const QString& filename) 1248 { 1249 QFile file(filename); 1250 if(!file.open(QIODevice::WriteOnly)) 1251 { 1252 KMessageBox::error(worksheetView(), i18n("Error saving file %1", filename), i18n("Error - Cantor")); 1253 return; 1254 } 1255 1256 QString cmdSep=QLatin1String(";\n"); 1257 QString commentStartingSeq = QLatin1String(""); 1258 QString commentEndingSeq = QLatin1String(""); 1259 1260 if (!m_readOnly) 1261 { 1262 Cantor::Backend * const backend=session()->backend(); 1263 if (backend->extensions().contains(QLatin1String("ScriptExtension"))) 1264 { 1265 Cantor::ScriptExtension* e=dynamic_cast<Cantor::ScriptExtension*>(backend->extension(QLatin1String(("ScriptExtension")))); 1266 if (e) 1267 { 1268 cmdSep=e->commandSeparator(); 1269 commentStartingSeq = e->commentStartingSequence(); 1270 commentEndingSeq = e->commentEndingSequence(); 1271 } 1272 } 1273 } 1274 else 1275 KMessageBox::information(worksheetView(), i18n("In read-only mode Cantor couldn't guarantee, that the export will be valid for %1", m_backendName), i18n("Cantor")); 1276 1277 QTextStream stream(&file); 1278 1279 for(WorksheetEntry * entry = firstEntry(); entry; entry = entry->next()) 1280 { 1281 const QString& str=entry->toPlain(cmdSep, commentStartingSeq, commentEndingSeq); 1282 if(!str.isEmpty()) 1283 stream << str + QLatin1Char('\n'); 1284 } 1285 1286 file.close(); 1287 } 1288 1289 void Worksheet::saveLatex(const QString& filename) 1290 { 1291 qDebug()<<"exporting to Latex: " <<filename; 1292 1293 QFile file(filename); 1294 if(!file.open(QIODevice::WriteOnly)) 1295 { 1296 KMessageBox::error(worksheetView(), i18n("Error saving file %1", filename), i18n("Error - Cantor")); 1297 return; 1298 } 1299 1300 QString xml = toXML().toString(); 1301 QTextStream stream(&file); 1302 QXmlQuery query(QXmlQuery::XSLT20); 1303 query.setFocus(xml); 1304 1305 QString stylesheet = QStandardPaths::locate(QStandardPaths::DataLocation, QLatin1String("xslt/latex.xsl")); 1306 if (stylesheet.isEmpty()) 1307 { 1308 KMessageBox::error(worksheetView(), i18n("Error loading latex.xsl stylesheet"), i18n("Error - Cantor")); 1309 return; 1310 } 1311 1312 query.setQuery(QUrl(stylesheet)); 1313 QString out; 1314 if (query.evaluateTo(&out)) 1315 // Transform HTML escaped special characters to valid LaTeX characters (&, <, >) 1316 stream << out.replace(QLatin1String("&"), QLatin1String("&")) 1317 .replace(QLatin1String(">"), QLatin1String(">")) 1318 .replace(QLatin1String("<"), QLatin1String("<")); 1319 file.close(); 1320 } 1321 1322 bool Worksheet::load(const QString& filename ) 1323 { 1324 qDebug() << "loading worksheet" << filename; 1325 QFile file(filename); 1326 if (!file.open(QIODevice::ReadOnly)) { 1327 KMessageBox::error(worksheetView(), i18n("Couldn't open the file %1.", filename), i18n("Open File")); 1328 return false; 1329 } 1330 1331 bool rc = load(&file); 1332 if (rc && !m_readOnly) 1333 m_session->setWorksheetPath(filename); 1334 1335 return rc; 1336 } 1337 1338 void Worksheet::load(QByteArray* data) 1339 { 1340 QBuffer buf(data); 1341 buf.open(QIODevice::ReadOnly); 1342 load(&buf); 1343 } 1344 1345 bool Worksheet::load(QIODevice* device) 1346 { 1347 if (!device->isReadable()) 1348 { 1349 QApplication::restoreOverrideCursor(); 1350 KMessageBox::error(worksheetView(), i18n("Couldn't open the selected file for reading."), i18n("Open File")); 1351 return false; 1352 } 1353 1354 KZip archive(device); 1355 1356 if (archive.open(QIODevice::ReadOnly)) 1357 return loadCantorWorksheet(archive); 1358 else 1359 { 1360 qDebug() <<"not a zip file"; 1361 // Go to begin of data, we need read all data in second time 1362 device->seek(0); 1363 1364 QJsonParseError error; 1365 const QJsonDocument& doc = QJsonDocument::fromJson(device->readAll(), &error); 1366 if (error.error != QJsonParseError::NoError) 1367 { 1368 qDebug()<<"not a json file, parsing failed with error: " << error.errorString(); 1369 QApplication::restoreOverrideCursor(); 1370 KMessageBox::error(worksheetView(), i18n("The selected file is not a valid Cantor or Jupyter project file."), i18n("Open File")); 1371 return false; 1372 } 1373 else 1374 return loadJupyterNotebook(doc); 1375 } 1376 } 1377 1378 bool Worksheet::loadCantorWorksheet(const KZip& archive) 1379 { 1380 m_type = Type::CantorWorksheet; 1381 1382 const KArchiveEntry* contentEntry=archive.directory()->entry(QLatin1String("content.xml")); 1383 if (!contentEntry->isFile()) 1384 { 1385 qDebug()<<"content.xml file not found in the zip archive"; 1386 QApplication::restoreOverrideCursor(); 1387 KMessageBox::error(worksheetView(), i18n("The selected file is not a valid Cantor project file."), i18n("Open File")); 1388 return false; 1389 } 1390 1391 const KArchiveFile* content = static_cast<const KArchiveFile*>(contentEntry); 1392 QByteArray data = content->data(); 1393 1394 QDomDocument doc; 1395 doc.setContent(data); 1396 QDomElement root = doc.documentElement(); 1397 1398 m_backendName = root.attribute(QLatin1String("backend")); 1399 1400 //There is "Python" only now, replace "Python 3" by "Python" 1401 if (m_backendName == QLatin1String("Python 3")) 1402 m_backendName = QLatin1String("Python"); 1403 1404 //"Python 2" in older projects not supported anymore, switch to Python (=Python3) 1405 if (m_backendName == QLatin1String("Python 2")) 1406 { 1407 QApplication::restoreOverrideCursor(); 1408 KMessageBox::information(worksheetView(), 1409 i18n("This worksheet was created using Python2 which is not supported anymore. Python3 will be used."), 1410 i18n("Python2 not supported anymore")); 1411 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); 1412 m_backendName = QLatin1String("Python"); 1413 } 1414 1415 auto* b = Cantor::Backend::getBackend(m_backendName); 1416 if (!b) 1417 { 1418 QApplication::restoreOverrideCursor(); 1419 KMessageBox::information(worksheetView(), i18n("%1 backend was not found. Editing and executing entries is not possible.", m_backendName), i18n("Open File")); 1420 m_readOnly = true; 1421 } 1422 else 1423 m_readOnly = false; 1424 1425 if(!m_readOnly && !b->isEnabled()) 1426 { 1427 QApplication::restoreOverrideCursor(); 1428 KMessageBox::information(worksheetView(), i18n("There are some problems with the %1 backend,\n"\ 1429 "please check your configuration or install the needed packages.\n" 1430 "You will only be able to view this worksheet.", m_backendName), i18n("Open File")); 1431 m_readOnly = true; 1432 } 1433 1434 m_isLoadingFromFile = true; 1435 1436 //cleanup the worksheet and all it contains 1437 delete m_session; 1438 m_session = nullptr; 1439 1440 //file can only be loaded in a worksheet that was not edited/modified yet (s.a. CantorShell::load()) 1441 //in this case on the default "first entry" is available -> delete it. 1442 if (m_firstEntry) { 1443 delete m_firstEntry; 1444 m_firstEntry = nullptr; 1445 } 1446 1447 resetEntryCursor(); 1448 m_itemWidths.clear(); 1449 m_maxWidth = 0; 1450 1451 if (!m_readOnly) 1452 initSession(b); 1453 1454 qDebug()<<"loading entries"; 1455 QDomElement expressionChild = root.firstChildElement(); 1456 while (!expressionChild.isNull()) { 1457 QString tag = expressionChild.tagName(); 1458 // Don't add focus on load 1459 auto* entry = appendEntry(typeForTagName(tag), false); 1460 if (entry) 1461 { 1462 entry->setContent(expressionChild, archive); 1463 if (m_readOnly) 1464 entry->setAcceptHoverEvents(false); 1465 } 1466 1467 expressionChild = expressionChild.nextSiblingElement(); 1468 } 1469 1470 if (m_readOnly) 1471 clearFocus(); 1472 1473 m_isLoadingFromFile = false; 1474 updateHierarchyLayout(); 1475 updateLayout(); 1476 1477 //Set the Highlighting, depending on the current state 1478 //If the session isn't logged in, use the default 1479 enableHighlighting( m_highlighter!=nullptr || Settings::highlightDefault() ); 1480 1481 emit loaded(); 1482 return true; 1483 } 1484 1485 int Worksheet::typeForTagName(const QString& tag) 1486 { 1487 if (tag == QLatin1String("Expression")) 1488 return CommandEntry::Type; 1489 else if (tag == QLatin1String("Text")) 1490 return TextEntry::Type; 1491 else if (tag == QLatin1String("Markdown")) 1492 return MarkdownEntry::Type; 1493 else if (tag == QLatin1String("Latex")) 1494 return LatexEntry::Type; 1495 else if (tag == QLatin1String("PageBreak")) 1496 return PageBreakEntry::Type; 1497 else if (tag == QLatin1String("Image")) 1498 return ImageEntry::Type; 1499 else if (tag == QLatin1String("HorizontalRule")) 1500 return HorizontalRuleEntry::Type; 1501 else if (tag == QLatin1String("Hierarchy")) 1502 return HierarchyEntry::Type; 1503 1504 return 0; 1505 } 1506 1507 1508 void Worksheet::initSession(Cantor::Backend* backend) 1509 { 1510 m_session = backend->createSession(); 1511 if (m_useDefaultWorksheetParameters) 1512 { 1513 enableHighlighting(Settings::self()->highlightDefault()); 1514 enableCompletion(Settings::self()->completionDefault()); 1515 enableExpressionNumbering(Settings::self()->expressionNumberingDefault()); 1516 enableAnimations(Settings::self()->animationDefault()); 1517 enableEmbeddedMath(Settings::self()->embeddedMathDefault()); 1518 } 1519 } 1520 1521 bool Worksheet::loadJupyterNotebook(const QJsonDocument& doc) 1522 { 1523 m_type = Type::JupyterNotebook; 1524 1525 int nbformatMajor, nbformatMinor; 1526 if (!Cantor::JupyterUtils::isJupyterNotebook(doc)) 1527 { 1528 // Two possibilities: old jupyter notebook (version <= 4.0.0 and a another scheme) or just not a notebook at all 1529 std::tie(nbformatMajor, nbformatMinor) = Cantor::JupyterUtils::getNbformatVersion(doc.object()); 1530 if (nbformatMajor == 0 && nbformatMinor == 0) 1531 { 1532 QApplication::restoreOverrideCursor(); 1533 showInvalidNotebookSchemeError(); 1534 } 1535 else 1536 { 1537 KMessageBox::error(worksheetView(), 1538 i18n("Jupyter notebooks with versions lower than 4.5 (detected version %1.%2) are not supported.", nbformatMajor, nbformatMinor ), 1539 i18n("Open File")); 1540 } 1541 1542 return false; 1543 } 1544 1545 QJsonObject notebookObject = doc.object(); 1546 std::tie(nbformatMajor, nbformatMinor) = Cantor::JupyterUtils::getNbformatVersion(notebookObject); 1547 1548 if (QT_VERSION_CHECK(nbformatMajor, nbformatMinor, 0) > QT_VERSION_CHECK(4,5,0)) 1549 { 1550 QApplication::restoreOverrideCursor(); 1551 KMessageBox::error( 1552 worksheetView(), 1553 i18n("Jupyter notebooks with versions higher than 4.5 (detected version %1.%2) are not supported.", nbformatMajor, nbformatMinor), 1554 i18n("Open File") 1555 ); 1556 return false; 1557 } 1558 1559 const QJsonArray& cells = Cantor::JupyterUtils::getCells(notebookObject); 1560 const QJsonObject& metadata = Cantor::JupyterUtils::getMetadata(notebookObject); 1561 if (m_jupyterMetadata) 1562 delete m_jupyterMetadata; 1563 m_jupyterMetadata = new QJsonObject(metadata); 1564 1565 const QJsonObject& kernalspec = metadata.value(QLatin1String("kernelspec")).toObject(); 1566 m_backendName = Cantor::JupyterUtils::getKernelName(kernalspec); 1567 1568 //There is "Python" only now, replace "python3" by "Python" 1569 if (m_backendName == QLatin1String("python3")) 1570 m_backendName = QLatin1String("Python"); 1571 1572 //"python 2" in older projects not supported anymore, switch to Python (=Python3) 1573 if (m_backendName == QLatin1String("python2")) 1574 { 1575 QApplication::restoreOverrideCursor(); 1576 KMessageBox::information(worksheetView(), 1577 i18n("This notebook was created using Python2 which is not supported anymore. Python3 will be used."), 1578 i18n("Python2 not supported anymore")); 1579 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); 1580 m_backendName = QLatin1String("Python"); 1581 } 1582 1583 if (kernalspec.isEmpty() || m_backendName.isEmpty()) 1584 { 1585 QApplication::restoreOverrideCursor(); 1586 showInvalidNotebookSchemeError(); 1587 return false; 1588 } 1589 1590 Cantor::Backend* backend = Cantor::Backend::getBackend(m_backendName); 1591 if (!backend) 1592 { 1593 QApplication::restoreOverrideCursor(); 1594 KMessageBox::information(worksheetView(), 1595 i18n("%1 backend was not found. Editing and executing entries is not possible.", m_backendName), 1596 i18n("Open File")); 1597 m_readOnly = true; 1598 } 1599 else 1600 m_readOnly = false; 1601 1602 if(!m_readOnly && !backend->isEnabled()) 1603 { 1604 QApplication::restoreOverrideCursor(); 1605 KMessageBox::information(worksheetView(), i18n("There are some problems with the %1 backend,\n"\ 1606 "please check your configuration or install the needed packages.\n" 1607 "You will only be able to view this worksheet.", m_backendName), i18n("Open File")); 1608 m_readOnly = true; 1609 } 1610 1611 if (m_readOnly) 1612 { 1613 for (QAction* action : m_richTextActionList) 1614 action->setEnabled(false); 1615 } 1616 1617 1618 m_isLoadingFromFile = true; 1619 1620 if (m_session) 1621 delete m_session; 1622 m_session = nullptr; 1623 1624 if (m_firstEntry) { 1625 delete m_firstEntry; 1626 m_firstEntry = nullptr; 1627 } 1628 1629 resetEntryCursor(); 1630 m_itemWidths.clear(); 1631 m_maxWidth = 0; 1632 1633 if (!m_readOnly) 1634 initSession(backend); 1635 1636 qDebug() << "loading jupyter entries"; 1637 1638 WorksheetEntry* entry = nullptr; 1639 for (QJsonArray::const_iterator iter = cells.begin(); iter != cells.end(); ++iter) { 1640 if (!Cantor::JupyterUtils::isJupyterCell(*iter)) 1641 { 1642 QApplication::restoreOverrideCursor(); 1643 QString explanation; 1644 if (iter->isObject()) 1645 explanation = i18n("an object with keys: %1", iter->toObject().keys().join(QLatin1String(", "))); 1646 else 1647 explanation = i18n("non object JSON value"); 1648 1649 m_isLoadingFromFile = false; 1650 showInvalidNotebookSchemeError(i18n("found incorrect data (%1) that is not Jupyter cell", explanation)); 1651 return false; 1652 } 1653 1654 const QJsonObject& cell = iter->toObject(); 1655 QString cellType = Cantor::JupyterUtils::getCellType(cell); 1656 1657 if (cellType == QLatin1String("code")) 1658 { 1659 if (LatexEntry::isConvertableToLatexEntry(cell)) 1660 { 1661 entry = appendEntry(LatexEntry::Type, false); 1662 entry->setContentFromJupyter(cell); 1663 entry->evaluate(WorksheetEntry::InternalEvaluation); 1664 } 1665 else 1666 { 1667 entry = appendEntry(CommandEntry::Type, false); 1668 entry->setContentFromJupyter(cell); 1669 } 1670 } 1671 else if (cellType == QLatin1String("markdown")) 1672 { 1673 if (TextEntry::isConvertableToTextEntry(cell)) 1674 { 1675 entry = appendEntry(TextEntry::Type, false); 1676 entry->setContentFromJupyter(cell); 1677 } 1678 else if (HorizontalRuleEntry::isConvertableToHorizontalRuleEntry(cell)) 1679 { 1680 entry = appendEntry(HorizontalRuleEntry::Type, false); 1681 entry->setContentFromJupyter(cell); 1682 } 1683 else if (HierarchyEntry::isConvertableToHierarchyEntry(cell)) 1684 { 1685 entry = appendEntry(HierarchyEntry::Type, false); 1686 entry->setContentFromJupyter(cell); 1687 } 1688 else 1689 { 1690 entry = appendEntry(MarkdownEntry::Type, false); 1691 entry->setContentFromJupyter(cell); 1692 entry->evaluate(WorksheetEntry::InternalEvaluation); 1693 } 1694 } 1695 else if (cellType == QLatin1String("raw")) 1696 { 1697 if (PageBreakEntry::isConvertableToPageBreakEntry(cell)) 1698 entry = appendEntry(PageBreakEntry::Type, false); 1699 else 1700 entry = appendEntry(TextEntry::Type, false); 1701 entry->setContentFromJupyter(cell); 1702 } 1703 1704 if (m_readOnly && entry) 1705 { 1706 entry->setAcceptHoverEvents(false); 1707 entry = nullptr; 1708 } 1709 } 1710 1711 if (m_readOnly) 1712 clearFocus(); 1713 1714 m_isLoadingFromFile = false; 1715 updateHierarchyLayout(); 1716 updateLayout(); 1717 1718 enableHighlighting( m_highlighter!=nullptr || Settings::highlightDefault() ); 1719 1720 emit loaded(); 1721 return true; 1722 } 1723 1724 void Worksheet::showInvalidNotebookSchemeError(QString additionalInfo) 1725 { 1726 if (additionalInfo.isEmpty()) 1727 KMessageBox::error(worksheetView(), i18n("The file is not valid Jupyter notebook"), i18n("Open File")); 1728 else 1729 KMessageBox::error(worksheetView(), i18n("Invalid Jupyter notebook scheme: %1", additionalInfo), i18n("Open File")); 1730 } 1731 1732 void Worksheet::gotResult(Cantor::Expression* expr) 1733 { 1734 if(expr==nullptr) 1735 expr=qobject_cast<Cantor::Expression*>(sender()); 1736 1737 if(expr==nullptr) 1738 return; 1739 1740 //We're only interested in help results, others are handled by the WorksheetEntry 1741 for (auto* result : expr->results()) 1742 { 1743 if(result && result->type()==Cantor::HelpResult::Type) 1744 { 1745 QString help = result->toHtml(); 1746 //Do some basic LaTeX replacing 1747 help.replace(QRegularExpression(QStringLiteral("\\\\code\\{([^\\}]*)\\}")), QStringLiteral("<b>\\1</b>")); 1748 help.replace(QRegularExpression(QStringLiteral("\\$([^\\$])\\$")), QStringLiteral("<i>\\1</i>")); 1749 1750 emit showHelp(help); 1751 1752 //TODO: break after the first help result found, not clear yet how to handle multiple requests for help within one single command (e.g. ??ev;??int). 1753 break; 1754 } 1755 } 1756 } 1757 1758 void Worksheet::removeCurrentEntry() 1759 { 1760 qDebug()<<"removing current entry"; 1761 auto* entry = currentEntry(); 1762 if(!entry) 1763 return; 1764 1765 // In case we just removed this 1766 if (entry->isAncestorOf(m_lastFocusedTextItem)) 1767 m_lastFocusedTextItem = nullptr; 1768 1769 entry->startRemoving(); 1770 } 1771 1772 Cantor::Renderer* Worksheet::renderer() 1773 { 1774 return &m_epsRenderer; 1775 } 1776 1777 MathRenderer* Worksheet::mathRenderer() 1778 { 1779 return &m_mathRenderer; 1780 } 1781 1782 QMenu* Worksheet::createContextMenu() 1783 { 1784 auto* menu = new QMenu(worksheetView()); 1785 connect(menu, SIGNAL(aboutToHide()), menu, SLOT(deleteLater())); 1786 1787 return menu; 1788 } 1789 1790 void Worksheet::populateMenu(QMenu* menu, QPointF pos) 1791 { 1792 // Two menu: for particular entry and for selection (multiple entry) 1793 if (m_selectedEntries.isEmpty()) 1794 { 1795 auto* entry = entryAt(pos); 1796 if (entry && !entry->isAncestorOf(m_lastFocusedTextItem)) { 1797 auto* item = 1798 qgraphicsitem_cast<WorksheetTextItem*>(itemAt(pos, QTransform())); 1799 if (item && item->isEditable()) 1800 m_lastFocusedTextItem = item; 1801 } 1802 1803 if (entry) { 1804 //"Convert To" menu 1805 QMenu* convertTo = new QMenu(i18n("Convert To")); 1806 convertTo->setIcon(QIcon::fromTheme(QLatin1String("gtk-convert"))); 1807 menu->addMenu(convertTo); 1808 1809 if (entry->type() != CommandEntry::Type) 1810 convertTo->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), entry, &WorksheetEntry::convertToCommandEntry); 1811 1812 if (entry->type() != TextEntry::Type) 1813 convertTo->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), entry, &WorksheetEntry::convertToTextEntry); 1814 1815 #ifdef Discount_FOUND 1816 if (entry->type() != MarkdownEntry::Type) 1817 convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), entry, &WorksheetEntry::convertToMarkdownEntry); 1818 #endif 1819 #ifdef WITH_EPS 1820 if (entry->type() != LatexEntry::Type) 1821 convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), entry, &WorksheetEntry::convertToLatexEntry); 1822 #endif 1823 if (entry->type() != ImageEntry::Type) 1824 convertTo->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, &WorksheetEntry::convertToImageEntry); 1825 1826 if (entry->type() != PageBreakEntry::Type) 1827 convertTo->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), entry, &WorksheetEntry::converToPageBreakEntry); 1828 1829 if (entry->type() != HorizontalRuleEntry::Type) 1830 convertTo->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), entry, &WorksheetEntry::convertToHorizontalRuleEntry); 1831 1832 if (entry->type() != HierarchyEntry::Type) 1833 convertTo->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), entry, &WorksheetEntry::convertToHierarchyEntry); 1834 1835 //"Insert After" menu 1836 QMenu* insert = new QMenu(i18n("Insert After"), menu); 1837 insert->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-below"))); 1838 menu->addSeparator(); 1839 menu->addMenu(insert); 1840 1841 insert->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), entry, SLOT(insertCommandEntry())); 1842 insert->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), entry, SLOT(insertTextEntry())); 1843 #ifdef Discount_FOUND 1844 insert->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), entry, SLOT(insertMarkdownEntry())); 1845 #endif 1846 #ifdef WITH_EPS 1847 insert->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), entry, SLOT(insertLatexEntry())); 1848 #endif 1849 insert->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntry())); 1850 insert->addSeparator(); 1851 insert->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), entry, SLOT(insertHorizontalRuleEntry())); 1852 insert->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), entry, SLOT(insertPageBreakEntry())); 1853 insert->addSeparator(); 1854 insert->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), entry, SLOT(insertHierarchyEntry())); 1855 1856 //"Insert Before" menu 1857 QMenu* insertBefore = new QMenu(i18n("Insert Before"), menu); 1858 insertBefore->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-above"))); 1859 menu->addMenu(insertBefore); 1860 1861 insertBefore->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), entry, SLOT(insertCommandEntryBefore())); 1862 insertBefore->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), entry, SLOT(insertTextEntryBefore())); 1863 #ifdef Discount_FOUND 1864 insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), entry, SLOT(insertMarkdownEntryBefore())); 1865 #endif 1866 #ifdef WITH_EPS 1867 insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), entry, SLOT(insertLatexEntryBefore())); 1868 #endif 1869 insertBefore->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntryBefore())); 1870 insertBefore->addSeparator(); 1871 insertBefore->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), entry, SLOT(insertHorizontalRuleEntryBefore())); 1872 insertBefore->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), entry, SLOT(insertPageBreakEntryBefore())); 1873 insertBefore->addSeparator(); 1874 insertBefore->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), entry, SLOT(insertHierarchyEntryBefore())); 1875 } else { 1876 QMenu* insertMenu = new QMenu(i18n("Insert")); 1877 insertMenu->setIcon(QIcon::fromTheme(QLatin1String("insert-table-row"))); 1878 1879 insertMenu->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), this, SLOT(appendCommandEntry())); 1880 insertMenu->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), this, &Worksheet::appendTextEntry); 1881 #ifdef Discount_FOUND 1882 insertMenu->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), this, &Worksheet::appendMarkdownEntry); 1883 #endif 1884 #ifdef WITH_EPS 1885 insertMenu->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), this, &Worksheet::appendLatexEntry); 1886 #endif 1887 insertMenu->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), this, &Worksheet::appendImageEntry); 1888 insertMenu->addSeparator(); 1889 insertMenu->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), this, &Worksheet::appendHorizontalRuleEntry); 1890 insertMenu->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), this, &Worksheet::appendPageBreakEntry); 1891 insertMenu->addSeparator(); 1892 insertMenu->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), this, &Worksheet::appendHierarchyEntry); 1893 1894 menu->addMenu(insertMenu); 1895 1896 //"Show help" for backend's documentation 1897 menu->addSeparator(); 1898 menu->addAction(QIcon::fromTheme(QLatin1String("help-hint")), i18n("Show Help"), this, 1899 [=] () { requestDocumentation(QString()); }); 1900 } 1901 1902 //evaluate the whole worksheet or interrupt the current calculation 1903 menu->addSeparator(); 1904 if (!isRunning()) 1905 menu->addAction(QIcon::fromTheme(QLatin1String("system-run")), i18n("Evaluate Worksheet"), 1906 this, &Worksheet::evaluate); 1907 else 1908 menu->addAction(QIcon::fromTheme(QLatin1String("process-stop")), i18n("Interrupt"), this, 1909 &Worksheet::interrupt); 1910 1911 //zooming 1912 menu->addSeparator(); 1913 auto* zoomMenu = new QMenu(i18n("Zoom")); 1914 zoomMenu->setIcon(QIcon::fromTheme(QLatin1String("zoom-draw"))); 1915 auto* view = worksheetView(); 1916 1917 auto* action = zoomMenu->addAction(QIcon::fromTheme(QLatin1String("zoom-in")), i18n("Zoom In"), view, &WorksheetView::zoomIn); 1918 action->setShortcut(Qt::CTRL+Qt::Key_Plus); 1919 1920 action = zoomMenu->addAction(QIcon::fromTheme(QLatin1String("zoom-out")), i18n("Zoom Out"), view, &WorksheetView::zoomOut); 1921 action->setShortcut(Qt::CTRL+Qt::Key_Minus); 1922 zoomMenu->addSeparator(); 1923 1924 action = zoomMenu->addAction(QIcon::fromTheme(QLatin1String("zoom-original")), i18n("Original Size"), view, &WorksheetView::actualSize); 1925 action->setShortcut(Qt::CTRL+Qt::Key_1); 1926 1927 menu->addMenu(zoomMenu); 1928 } 1929 else 1930 { 1931 menu->clear(); 1932 menu->addAction(QIcon::fromTheme(QLatin1String("go-up")), i18n("Move Entries Up"), this, &Worksheet::selectionMoveUp); 1933 menu->addAction(QIcon::fromTheme(QLatin1String("go-down")), i18n("Move Entries Down"), this, &Worksheet::selectionMoveDown); 1934 menu->addAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate Entries"), this, &Worksheet::selectionEvaluate); 1935 menu->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove Entries"), this, &Worksheet::selectionRemove); 1936 1937 bool isAnyCommandEntryInSelection = false; 1938 for (WorksheetEntry* entry : m_selectedEntries) 1939 if (entry->type() == CommandEntry::Type) 1940 { 1941 isAnyCommandEntryInSelection = true; 1942 break; 1943 } 1944 1945 if (isAnyCommandEntryInSelection) 1946 { 1947 menu->addSeparator(); 1948 menu->addAction(QIcon(), i18n("Collapse Command Entry Results"), this, &Worksheet::collapseSelectionResults); 1949 menu->addAction(QIcon(), i18n("Expand Command Entry Results"), this, &Worksheet::uncollapseSelectionResults); 1950 menu->addAction(QIcon(), i18n("Remove Command Entry Results"), this, &Worksheet::removeSelectionResults); 1951 menu->addAction(QIcon(), i18n("Exclude Command Entry From Execution"), this, &Worksheet::excludeFromExecutionSelection); 1952 menu->addAction(QIcon(), i18n("Add Command Entry To Execution"), this, &Worksheet::addToExectuionSelection); 1953 } 1954 } 1955 } 1956 1957 void Worksheet::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) 1958 { 1959 if (m_readOnly) 1960 return; 1961 1962 // forward the event to the items 1963 QGraphicsScene::contextMenuEvent(event); 1964 1965 if (!event->isAccepted()) { 1966 event->accept(); 1967 QMenu* menu = createContextMenu(); 1968 populateMenu(menu, event->scenePos()); 1969 1970 menu->popup(event->screenPos()); 1971 } 1972 } 1973 1974 void Worksheet::mousePressEvent(QGraphicsSceneMouseEvent* event) 1975 { 1976 /* 1977 if (event->button() == Qt::LeftButton && !focusItem() && lastEntry() && 1978 event->scenePos().y() > lastEntry()->y() + lastEntry()->size().height()) 1979 lastEntry()->focusEntry(WorksheetTextItem::BottomRight); 1980 */ 1981 QGraphicsScene::mousePressEvent(event); 1982 1983 if (!m_readOnly && event->buttons() & Qt::LeftButton) 1984 { 1985 auto* selectedEntry = entryAt(event->scenePos()); 1986 if (event->modifiers() & Qt::ControlModifier) 1987 { 1988 clearFocus(); 1989 resetEntryCursor(); 1990 1991 if (selectedEntry) 1992 { 1993 selectedEntry->setCellSelected(!selectedEntry->isCellSelected()); 1994 selectedEntry->update(); 1995 1996 WorksheetEntry* lastSelectedEntry = m_circularFocusBuffer.size() > 0 ? m_circularFocusBuffer.last() : nullptr; 1997 if (lastSelectedEntry) 1998 { 1999 lastSelectedEntry->setCellSelected(!lastSelectedEntry->isCellSelected()); 2000 lastSelectedEntry->update(); 2001 m_circularFocusBuffer.clear(); 2002 } 2003 2004 for (WorksheetEntry* entry : {selectedEntry, lastSelectedEntry}) 2005 if (entry) 2006 { 2007 if (entry->isCellSelected()) 2008 m_selectedEntries.append(entry); 2009 else if (!entry->isCellSelected()) 2010 m_selectedEntries.removeOne(entry); 2011 } 2012 } 2013 } 2014 else 2015 { 2016 for (auto* entry : m_selectedEntries) 2017 { 2018 if(isValidEntry(entry)) 2019 { 2020 entry->setCellSelected(false); 2021 entry->update(); 2022 } 2023 } 2024 m_selectedEntries.clear(); 2025 2026 if (selectedEntry) 2027 notifyEntryFocus(selectedEntry); 2028 2029 updateEntryCursor(event); 2030 } 2031 } 2032 } 2033 2034 void Worksheet::keyPressEvent(QKeyEvent* event) 2035 { 2036 if (m_readOnly) 2037 return; 2038 2039 if ((event->modifiers() & Qt::ControlModifier) && (event->key() == Qt::Key_1)) 2040 worksheetView()->actualSize(); 2041 else if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && !event->text().isEmpty()) 2042 addEntryFromEntryCursor(); //add new enty when the entry cursor is activa the user starts typing the text 2043 2044 QGraphicsScene::keyPressEvent(event); 2045 } 2046 2047 void Worksheet::setActionCollection(KActionCollection* collection) 2048 { 2049 m_collection = collection; 2050 } 2051 2052 void Worksheet::initActions() 2053 { 2054 // Mostly copied from KRichTextWidget::createActions(KActionCollection*) 2055 // It would be great if this wasn't necessary. 2056 2057 // Text color 2058 /* This is "format-stroke-color" in KRichTextWidget */ 2059 auto* action = new QAction(QIcon::fromTheme(QLatin1String("format-text-color")), 2060 i18nc("@action", "Text &Color..."), m_collection); 2061 action->setIconText(i18nc("@label text color", "Color")); 2062 action->setPriority(QAction::LowPriority); 2063 m_richTextActionList.append(action); 2064 connect(action, &QAction::triggered, this, &Worksheet::setTextForegroundColor); 2065 2066 // Text color 2067 action = new QAction(QIcon::fromTheme(QLatin1String("format-fill-color")), 2068 i18nc("@action", "Text &Highlight..."), m_collection); 2069 action->setPriority(QAction::LowPriority); 2070 m_richTextActionList.append(action); 2071 connect(action, &QAction::triggered, this, &Worksheet::setTextBackgroundColor); 2072 2073 // Font Family 2074 m_fontAction = new KFontAction(i18nc("@action", "&Font"), m_collection); 2075 m_richTextActionList.append(m_fontAction); 2076 #if KCOREADDONS_VERSION >= QT_VERSION_CHECK(5, 78, 0) 2077 connect(m_fontAction, &KFontAction::textTriggered, this, &Worksheet::setFontFamily); 2078 #else 2079 connect(m_fontAction, QOverload<const QString&>::of(&KFontAction::triggered), this, &Worksheet::setFontFamily); 2080 #endif 2081 2082 // Font Size 2083 m_fontSizeAction = new KFontSizeAction(i18nc("@action", "Font &Size"), m_collection); 2084 m_richTextActionList.append(m_fontSizeAction); 2085 connect(m_fontSizeAction, &KFontSizeAction::fontSizeChanged, this, &Worksheet::setFontSize); 2086 2087 // Bold 2088 m_boldAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-bold")), 2089 i18nc("@action boldify selected text", "&Bold"), 2090 m_collection); 2091 m_boldAction->setPriority(QAction::LowPriority); 2092 QFont bold; 2093 bold.setBold(true); 2094 m_boldAction->setFont(bold); 2095 m_richTextActionList.append(m_boldAction); 2096 connect(m_boldAction, &QAction::triggered, this, &Worksheet::setTextBold); 2097 2098 // Italic 2099 m_italicAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-italic")), 2100 i18nc("@action italicize selected text", "&Italic"), 2101 m_collection); 2102 m_italicAction->setPriority(QAction::LowPriority); 2103 QFont italic; 2104 italic.setItalic(true); 2105 m_italicAction->setFont(italic); 2106 m_richTextActionList.append(m_italicAction); 2107 connect(m_italicAction, &QAction::triggered, this, &Worksheet::setTextItalic); 2108 2109 // Underline 2110 m_underlineAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-underline")), 2111 i18nc("@action underline selected text", 2112 "&Underline"), 2113 m_collection); 2114 m_underlineAction->setPriority(QAction::LowPriority); 2115 QFont underline; 2116 underline.setUnderline(true); 2117 m_underlineAction->setFont(underline); 2118 m_richTextActionList.append(m_underlineAction); 2119 connect(m_underlineAction, &QAction::triggered, this, &Worksheet::setTextUnderline); 2120 2121 // Strike 2122 m_strikeOutAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-strikethrough")), 2123 i18nc("@action", "&Strike Out"), 2124 m_collection); 2125 m_strikeOutAction->setPriority(QAction::LowPriority); 2126 m_richTextActionList.append(m_strikeOutAction); 2127 connect(m_strikeOutAction, &QAction::triggered, this, &Worksheet::setTextStrikeOut); 2128 2129 // Alignment 2130 auto* alignmentGroup = new QActionGroup(this); 2131 2132 // Align left 2133 m_alignLeftAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-left")), 2134 i18nc("@action", "Align &Left"), 2135 m_collection); 2136 m_alignLeftAction->setPriority(QAction::LowPriority); 2137 m_alignLeftAction->setIconText(i18nc("@label left justify", "Left")); 2138 m_richTextActionList.append(m_alignLeftAction); 2139 connect(m_alignLeftAction, &QAction::triggered, this, &Worksheet::setAlignLeft); 2140 alignmentGroup->addAction(m_alignLeftAction); 2141 2142 // Align center 2143 m_alignCenterAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-center")), 2144 i18nc("@action", "Align &Center"), 2145 m_collection); 2146 m_alignCenterAction->setPriority(QAction::LowPriority); 2147 m_alignCenterAction->setIconText(i18nc("@label center justify", "Center")); 2148 m_richTextActionList.append(m_alignCenterAction); 2149 connect(m_alignCenterAction, &QAction::triggered, this, &Worksheet::setAlignCenter); 2150 alignmentGroup->addAction(m_alignCenterAction); 2151 2152 // Align right 2153 m_alignRightAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-right")), 2154 i18nc("@action", "Align &Right"), 2155 m_collection); 2156 m_alignRightAction->setPriority(QAction::LowPriority); 2157 m_alignRightAction->setIconText(i18nc("@label right justify", "Right")); 2158 m_richTextActionList.append(m_alignRightAction); 2159 connect(m_alignRightAction, &QAction::triggered, this, &Worksheet::setAlignRight); 2160 alignmentGroup->addAction(m_alignRightAction); 2161 2162 // Align justify 2163 m_alignJustifyAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-fill")), 2164 i18nc("@action", "&Justify"), 2165 m_collection); 2166 m_alignJustifyAction->setPriority(QAction::LowPriority); 2167 m_alignJustifyAction->setIconText(i18nc("@label justify fill", "Justify")); 2168 m_richTextActionList.append(m_alignJustifyAction); 2169 connect(m_alignJustifyAction, &QAction::triggered, this, &Worksheet::setAlignJustify); 2170 alignmentGroup->addAction(m_alignJustifyAction); 2171 2172 if (m_collection) 2173 { 2174 m_collection->addAction(QLatin1String("format_text_foreground_color"), action); 2175 m_collection->addAction(QLatin1String("format_text_background_color"), action); 2176 m_collection->addAction(QLatin1String("format_font_family"), m_fontAction); 2177 m_collection->addAction(QLatin1String("format_font_size"), m_fontSizeAction); 2178 m_collection->addAction(QLatin1String("format_text_bold"), m_boldAction); 2179 m_collection->setDefaultShortcut(m_boldAction, Qt::CTRL + Qt::Key_B); 2180 m_collection->addAction(QLatin1String("format_text_italic"), m_italicAction); 2181 m_collection->setDefaultShortcut(m_italicAction, Qt::CTRL + Qt::Key_I); 2182 m_collection->addAction(QLatin1String("format_text_underline"), m_underlineAction); 2183 m_collection->setDefaultShortcut(m_underlineAction, Qt::CTRL + Qt::Key_U); 2184 m_collection->addAction(QLatin1String("format_text_strikeout"), m_strikeOutAction); 2185 m_collection->setDefaultShortcut(m_strikeOutAction, Qt::CTRL + Qt::Key_L); 2186 m_collection->addAction(QLatin1String("format_align_left"), m_alignLeftAction); 2187 m_collection->addAction(QLatin1String("format_align_center"), m_alignCenterAction); 2188 m_collection->addAction(QLatin1String("format_align_right"), m_alignRightAction); 2189 m_collection->addAction(QLatin1String("format_align_justify"), m_alignJustifyAction); 2190 } 2191 2192 /* 2193 // List style 2194 KSelectAction* selAction; 2195 selAction = new KSelectAction(QIcon::fromTheme("format-list-unordered"), 2196 i18nc("@title:menu", "List Style"), 2197 collection); 2198 QStringList listStyles; 2199 listStyles << i18nc("@item:inmenu no list style", "None") 2200 << i18nc("@item:inmenu disc list style", "Disc") 2201 << i18nc("@item:inmenu circle list style", "Circle") 2202 << i18nc("@item:inmenu square list style", "Square") 2203 << i18nc("@item:inmenu numbered lists", "123") 2204 << i18nc("@item:inmenu lowercase abc lists", "abc") 2205 << i18nc("@item:inmenu uppercase abc lists", "ABC"); 2206 selAction->setItems(listStyles); 2207 selAction->setCurrentItem(0); 2208 action = selAction; 2209 m_richTextActionList.append(action); 2210 collection->addAction("format_list_style", action); 2211 connect(action, SIGNAL(triggered(int)), 2212 this, &Worksheet::_k_setListStyle(int))); 2213 connect(action, &QAction::triggered, 2214 this, &Worksheet::_k_updateMiscActions())); 2215 2216 // Indent 2217 action = new QAction(QIcon::fromTheme("format-indent-more"), 2218 i18nc("@action", "Increase Indent"), collection); 2219 action->setPriority(QAction::LowPriority); 2220 m_richTextActionList.append(action); 2221 collection->addAction("format_list_indent_more", action); 2222 connect(action, &QAction::triggered, 2223 this, &Worksheet::indentListMore())); 2224 connect(action, &QAction::triggered, 2225 this, &Worksheet::_k_updateMiscActions())); 2226 2227 // Dedent 2228 action = new QAction(QIcon::fromTheme("format-indent-less"), 2229 i18nc("@action", "Decrease Indent"), collection); 2230 action->setPriority(QAction::LowPriority); 2231 m_richTextActionList.append(action); 2232 collection->addAction("format_list_indent_less", action); 2233 connect(action, &QAction::triggered, this, &Worksheet::indentListLess())); 2234 connect(action, &QAction::triggered, this, &Worksheet::_k_updateMiscActions())); 2235 */ 2236 } 2237 2238 WorksheetTextItem* Worksheet::lastFocusedTextItem() 2239 { 2240 return m_lastFocusedTextItem; 2241 } 2242 2243 void Worksheet::updateFocusedTextItem(WorksheetTextItem* newItem) 2244 { 2245 // No need update and emit signals about editing actions in readonly 2246 // So support only copy action and reset selection 2247 if (m_readOnly) 2248 { 2249 if (m_lastFocusedTextItem && m_lastFocusedTextItem != newItem) 2250 { 2251 disconnect(this, SIGNAL(copy()), m_lastFocusedTextItem, SLOT(copy())); 2252 m_lastFocusedTextItem->clearSelection(); 2253 } 2254 2255 if (newItem && m_lastFocusedTextItem != newItem) 2256 { 2257 connect(this, SIGNAL(copy()), newItem, SLOT(copy())); 2258 emit copyAvailable(newItem->isCopyAvailable()); 2259 } 2260 else if (!newItem) 2261 { 2262 emit copyAvailable(false); 2263 } 2264 2265 m_lastFocusedTextItem = newItem; 2266 return; 2267 } 2268 2269 if (m_lastFocusedTextItem && m_lastFocusedTextItem != newItem) { 2270 disconnect(m_lastFocusedTextItem, SIGNAL(undoAvailable(bool)), 2271 this, SIGNAL(undoAvailable(bool))); 2272 disconnect(m_lastFocusedTextItem, SIGNAL(redoAvailable(bool)), 2273 this, SIGNAL(redoAvailable(bool))); 2274 disconnect(this, SIGNAL(undo()), m_lastFocusedTextItem, SLOT(undo())); 2275 disconnect(this, SIGNAL(redo()), m_lastFocusedTextItem, SLOT(redo())); 2276 disconnect(m_lastFocusedTextItem, SIGNAL(cutAvailable(bool)), 2277 this, SIGNAL(cutAvailable(bool))); 2278 disconnect(m_lastFocusedTextItem, SIGNAL(copyAvailable(bool)), 2279 this, SIGNAL(copyAvailable(bool))); 2280 disconnect(m_lastFocusedTextItem, SIGNAL(pasteAvailable(bool)), 2281 this, SIGNAL(pasteAvailable(bool))); 2282 disconnect(this, SIGNAL(cut()), m_lastFocusedTextItem, SLOT(cut())); 2283 disconnect(this, SIGNAL(copy()), m_lastFocusedTextItem, SLOT(copy())); 2284 2285 m_lastFocusedTextItem->clearSelection(); 2286 } 2287 2288 if (newItem && m_lastFocusedTextItem != newItem) { 2289 setAcceptRichText(newItem->richTextEnabled()); 2290 emit undoAvailable(newItem->isUndoAvailable()); 2291 emit redoAvailable(newItem->isRedoAvailable()); 2292 connect(newItem, SIGNAL(undoAvailable(bool)), 2293 this, SIGNAL(undoAvailable(bool))); 2294 connect(newItem, SIGNAL(redoAvailable(bool)), 2295 this, SIGNAL(redoAvailable(bool))); 2296 connect(this, SIGNAL(undo()), newItem, SLOT(undo())); 2297 connect(this, SIGNAL(redo()), newItem, SLOT(redo())); 2298 emit cutAvailable(newItem->isCutAvailable()); 2299 emit copyAvailable(newItem->isCopyAvailable()); 2300 emit pasteAvailable(newItem->isPasteAvailable()); 2301 connect(newItem, SIGNAL(cutAvailable(bool)), 2302 this, SIGNAL(cutAvailable(bool))); 2303 connect(newItem, SIGNAL(copyAvailable(bool)), 2304 this, SIGNAL(copyAvailable(bool))); 2305 connect(newItem, SIGNAL(pasteAvailable(bool)), 2306 this, SIGNAL(pasteAvailable(bool))); 2307 connect(this, SIGNAL(cut()), newItem, SLOT(cut())); 2308 connect(this, SIGNAL(copy()), newItem, SLOT(copy())); 2309 } else if (!newItem) { 2310 emit undoAvailable(false); 2311 emit redoAvailable(false); 2312 emit cutAvailable(false); 2313 emit copyAvailable(false); 2314 emit pasteAvailable(false); 2315 } 2316 m_lastFocusedTextItem = newItem; 2317 } 2318 2319 /*! 2320 * handles the paste action triggered in cantor_part. 2321 * Pastes into the last focused text item. 2322 * In case the "new entry"-cursor is currently shown, 2323 * a new entry is created first which the content will be pasted into. 2324 */ 2325 void Worksheet::paste() { 2326 if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) 2327 addEntryFromEntryCursor(); 2328 2329 m_lastFocusedTextItem->paste(); 2330 } 2331 2332 void Worksheet::setRichTextInformation(const RichTextInfo& info) 2333 { 2334 if (!m_boldAction) 2335 initActions(); 2336 2337 m_boldAction->setChecked(info.bold); 2338 m_italicAction->setChecked(info.italic); 2339 m_underlineAction->setChecked(info.underline); 2340 m_strikeOutAction->setChecked(info.strikeOut); 2341 m_fontAction->setFont(info.font); 2342 if (info.fontSize > 0) 2343 m_fontSizeAction->setFontSize(info.fontSize); 2344 2345 if (info.align & Qt::AlignLeft) 2346 m_alignLeftAction->setChecked(true); 2347 else if (info.align & Qt::AlignCenter) 2348 m_alignCenterAction->setChecked(true); 2349 else if (info.align & Qt::AlignRight) 2350 m_alignRightAction->setChecked(true); 2351 else if (info.align & Qt::AlignJustify) 2352 m_alignJustifyAction->setChecked(true); 2353 } 2354 2355 void Worksheet::setAcceptRichText(bool b) 2356 { 2357 if (!m_readOnly) 2358 for(auto* action : m_richTextActionList) 2359 action->setVisible(b); 2360 } 2361 2362 WorksheetTextItem* Worksheet::currentTextItem() 2363 { 2364 auto* item = focusItem(); 2365 if (!item) 2366 item = m_lastFocusedTextItem; 2367 while (item && item->type() != WorksheetTextItem::Type) 2368 item = item->parentItem(); 2369 2370 return qgraphicsitem_cast<WorksheetTextItem*>(item); 2371 } 2372 2373 void Worksheet::setTextForegroundColor() 2374 { 2375 auto* item = currentTextItem(); 2376 if (item) 2377 item->setTextForegroundColor(); 2378 } 2379 2380 void Worksheet::setTextBackgroundColor() 2381 { 2382 auto* item = currentTextItem(); 2383 if (item) 2384 item->setTextBackgroundColor(); 2385 } 2386 2387 void Worksheet::setTextBold(bool b) 2388 { 2389 auto* item = currentTextItem(); 2390 if (item) 2391 item->setTextBold(b); 2392 } 2393 2394 void Worksheet::setTextItalic(bool b) 2395 { 2396 auto* item = currentTextItem(); 2397 if (item) 2398 item->setTextItalic(b); 2399 } 2400 2401 void Worksheet::setTextUnderline(bool b) 2402 { 2403 auto* item = currentTextItem(); 2404 if (item) 2405 item->setTextUnderline(b); 2406 } 2407 2408 void Worksheet::setTextStrikeOut(bool b) 2409 { 2410 auto* item = currentTextItem(); 2411 if (item) 2412 item->setTextStrikeOut(b); 2413 } 2414 2415 void Worksheet::setAlignLeft() 2416 { 2417 auto* item = currentTextItem(); 2418 if (item) 2419 item->setAlignment(Qt::AlignLeft); 2420 } 2421 2422 void Worksheet::setAlignRight() 2423 { 2424 auto* item = currentTextItem(); 2425 if (item) 2426 item->setAlignment(Qt::AlignRight); 2427 } 2428 2429 void Worksheet::setAlignCenter() 2430 { 2431 auto* item = currentTextItem(); 2432 if (item) 2433 item->setAlignment(Qt::AlignCenter); 2434 } 2435 2436 void Worksheet::setAlignJustify() 2437 { 2438 auto* item = currentTextItem(); 2439 if (item) 2440 item->setAlignment(Qt::AlignJustify); 2441 } 2442 2443 void Worksheet::setFontFamily(const QString& font) 2444 { 2445 auto* item = currentTextItem(); 2446 if (item) 2447 item->setFontFamily(font); 2448 } 2449 2450 void Worksheet::setFontSize(int size) 2451 { 2452 auto* item = currentTextItem(); 2453 if (item) 2454 item->setFontSize(size); 2455 } 2456 2457 bool Worksheet::isShortcut(const QKeySequence& sequence) 2458 { 2459 return m_shortcuts.contains(sequence); 2460 } 2461 2462 void Worksheet::registerShortcut(QAction* action) 2463 { 2464 for (auto& shortcut : action->shortcuts()) 2465 m_shortcuts.insert(shortcut, action); 2466 2467 connect(action, &QAction::changed, this, &Worksheet::updateShortcut); 2468 } 2469 2470 void Worksheet::updateShortcut() 2471 { 2472 QAction* action = qobject_cast<QAction*>(sender()); 2473 if (!action) 2474 return; 2475 2476 // delete the old shortcuts of this action 2477 QList<QKeySequence> shortcuts = m_shortcuts.keys(action); 2478 for (auto& shortcut : shortcuts) 2479 m_shortcuts.remove(shortcut); 2480 2481 // add the new shortcuts 2482 for (auto& shortcut : action->shortcuts()) 2483 m_shortcuts.insert(shortcut, action); 2484 } 2485 2486 void Worksheet::dragEnterEvent(QGraphicsSceneDragDropEvent* event) 2487 { 2488 if (m_dragEntry) 2489 event->accept(); 2490 else 2491 QGraphicsScene::dragEnterEvent(event); 2492 } 2493 2494 void Worksheet::dragLeaveEvent(QGraphicsSceneDragDropEvent* event) 2495 { 2496 if (!m_dragEntry) { 2497 QGraphicsScene::dragLeaveEvent(event); 2498 return; 2499 } 2500 2501 event->accept(); 2502 if (m_placeholderEntry) { 2503 m_placeholderEntry->startRemoving(); 2504 m_placeholderEntry = nullptr; 2505 } 2506 } 2507 2508 void Worksheet::dragMoveEvent(QGraphicsSceneDragDropEvent* event) 2509 { 2510 if (!m_dragEntry) { 2511 QGraphicsScene::dragMoveEvent(event); 2512 return; 2513 } 2514 2515 QPointF pos = event->scenePos(); 2516 auto* entry = entryAt(pos); 2517 WorksheetEntry* prev = nullptr; 2518 WorksheetEntry* next = nullptr; 2519 if (entry) { 2520 if (pos.y() < entry->y() + entry->size().height()/2) { 2521 prev = entry->previous(); 2522 next = entry; 2523 } else if (pos.y() >= entry->y() + entry->size().height()/2) { 2524 prev = entry; 2525 next = entry->next(); 2526 } 2527 } else { 2528 WorksheetEntry* last = lastEntry(); 2529 if (last && pos.y() > last->y() + last->size().height()) { 2530 prev = last; 2531 next = nullptr; 2532 } 2533 } 2534 2535 bool dragWithHierarchy = m_hierarchySubentriesDrag.size() != 0; 2536 2537 if (prev || next) { 2538 auto* oldPlaceHolder = m_placeholderEntry; 2539 if (prev && prev->type() == PlaceHolderEntry::Type && 2540 (!prev->aboutToBeRemoved() || prev->stopRemoving())) { 2541 m_placeholderEntry = qgraphicsitem_cast<PlaceHolderEntry*>(prev); 2542 if (dragWithHierarchy) 2543 m_placeholderEntry->changeSize(m_hierarchyDragSize); 2544 else 2545 m_placeholderEntry->changeSize(m_dragEntry->size()); 2546 } else if (next && next->type() == PlaceHolderEntry::Type && 2547 (!next->aboutToBeRemoved() || next->stopRemoving())) { 2548 m_placeholderEntry = qgraphicsitem_cast<PlaceHolderEntry*>(next); 2549 if (dragWithHierarchy) 2550 m_placeholderEntry->changeSize(m_hierarchyDragSize); 2551 else 2552 m_placeholderEntry->changeSize(m_dragEntry->size()); 2553 } else { 2554 m_placeholderEntry = new PlaceHolderEntry(this, QSizeF(0,0)); 2555 m_placeholderEntry->setPrevious(prev); 2556 m_placeholderEntry->setNext(next); 2557 if (prev) 2558 prev->setNext(m_placeholderEntry); 2559 else 2560 setFirstEntry(m_placeholderEntry); 2561 if (next) 2562 next->setPrevious(m_placeholderEntry); 2563 else 2564 setLastEntry(m_placeholderEntry); 2565 if (dragWithHierarchy) 2566 m_placeholderEntry->changeSize(m_hierarchyDragSize); 2567 else 2568 m_placeholderEntry->changeSize(m_dragEntry->size()); 2569 } 2570 if (oldPlaceHolder && oldPlaceHolder != m_placeholderEntry) 2571 oldPlaceHolder->startRemoving(); 2572 updateLayout(); 2573 } 2574 2575 const QPoint viewPos = worksheetView()->mapFromScene(pos); 2576 const int viewHeight = worksheetView()->viewport()->height(); 2577 if ((viewPos.y() < 10 || viewPos.y() > viewHeight - 10) && 2578 !m_dragScrollTimer) { 2579 m_dragScrollTimer = new QTimer(this); 2580 m_dragScrollTimer->setSingleShot(true); 2581 m_dragScrollTimer->setInterval(100); 2582 connect(m_dragScrollTimer, SIGNAL(timeout()), this, 2583 SLOT(updateDragScrollTimer())); 2584 m_dragScrollTimer->start(); 2585 } 2586 2587 event->accept(); 2588 } 2589 2590 void Worksheet::dropEvent(QGraphicsSceneDragDropEvent* event) 2591 { 2592 if (!m_dragEntry) 2593 QGraphicsScene::dropEvent(event); 2594 event->accept(); 2595 } 2596 2597 void Worksheet::updateDragScrollTimer() 2598 { 2599 if (!m_dragScrollTimer) 2600 return; 2601 2602 const QPoint viewPos = worksheetView()->viewCursorPos(); 2603 const QWidget* viewport = worksheetView()->viewport(); 2604 const int viewHeight = viewport->height(); 2605 if (!m_dragEntry || !(viewport->rect().contains(viewPos)) || 2606 (viewPos.y() >= 10 && viewPos.y() <= viewHeight - 10)) { 2607 delete m_dragScrollTimer; 2608 m_dragScrollTimer = nullptr; 2609 return; 2610 } 2611 2612 if (viewPos.y() < 10) 2613 worksheetView()->scrollBy(-10*(10 - viewPos.y())); 2614 else 2615 worksheetView()->scrollBy(10*(viewHeight - viewPos.y())); 2616 2617 m_dragScrollTimer->start(); 2618 } 2619 2620 void Worksheet::updateEntryCursor(QGraphicsSceneMouseEvent* event) 2621 { 2622 // determine the worksheet entry near which the entry cursor will be shown 2623 resetEntryCursor(); 2624 if (event->button() == Qt::LeftButton && !focusItem()) 2625 { 2626 const qreal y = event->scenePos().y(); 2627 for (auto* entry = firstEntry(); entry; entry = entry->next()) 2628 { 2629 if (entry == firstEntry() && y < entry->y() ) 2630 { 2631 m_choosenCursorEntry = firstEntry(); 2632 break; 2633 } 2634 else if (entry->y() < y && (entry->next() && y < entry->next()->y())) 2635 { 2636 m_choosenCursorEntry = entry->next(); 2637 break; 2638 } 2639 else if (entry->y() < y && entry == lastEntry()) 2640 { 2641 m_isCursorEntryAfterLastEntry = true; 2642 break; 2643 } 2644 } 2645 } 2646 2647 if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) 2648 drawEntryCursor(); 2649 } 2650 2651 void Worksheet::addEntryFromEntryCursor() 2652 { 2653 qDebug() << "Add new entry from entry cursor"; 2654 if (m_isCursorEntryAfterLastEntry) 2655 insertCommandEntry(lastEntry()); 2656 else 2657 insertCommandEntryBefore(m_choosenCursorEntry); 2658 resetEntryCursor(); 2659 } 2660 2661 void Worksheet::animateEntryCursor() 2662 { 2663 if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && m_entryCursorItem) 2664 m_entryCursorItem->setVisible(!m_entryCursorItem->isVisible()); 2665 } 2666 2667 void Worksheet::resetEntryCursor() 2668 { 2669 m_choosenCursorEntry = nullptr; 2670 m_isCursorEntryAfterLastEntry = false; 2671 m_entryCursorItem->hide(); 2672 } 2673 2674 void Worksheet::drawEntryCursor() 2675 { 2676 if (m_entryCursorItem && (m_choosenCursorEntry || (m_isCursorEntryAfterLastEntry && lastEntry()))) 2677 { 2678 qreal x; 2679 qreal y; 2680 if (m_isCursorEntryAfterLastEntry) 2681 { 2682 x = lastEntry()->x(); 2683 y = lastEntry()->y() + lastEntry()->size().height() - (EntryCursorWidth - 1); 2684 } 2685 else 2686 { 2687 x = m_choosenCursorEntry->x(); 2688 y = m_choosenCursorEntry->y(); 2689 } 2690 m_entryCursorItem->setLine(x,y,x+EntryCursorLength,y); 2691 m_entryCursorItem->show(); 2692 } 2693 } 2694 2695 void Worksheet::setType(Worksheet::Type type) 2696 { 2697 m_type = type; 2698 } 2699 2700 Worksheet::Type Worksheet::type() const 2701 { 2702 return m_type; 2703 } 2704 2705 void Worksheet::changeEntryType(WorksheetEntry* target, int newType) 2706 { 2707 if (target && target->type() != newType) 2708 { 2709 bool animation_state = m_animationsEnabled; 2710 m_animationsEnabled = false; 2711 2712 QString content; 2713 2714 int targetEntryType = target->type(); 2715 switch(targetEntryType) 2716 { 2717 case CommandEntry::Type: 2718 content = static_cast<CommandEntry*>(target)->command(); 2719 break; 2720 case MarkdownEntry::Type: 2721 content = static_cast<MarkdownEntry*>(target)->plainText(); 2722 break; 2723 case TextEntry::Type: 2724 content = static_cast<TextEntry*>(target)->text(); 2725 break; 2726 case LatexEntry::Type: 2727 content = static_cast<LatexEntry*>(target)->plain(); 2728 } 2729 2730 auto* newEntry = WorksheetEntry::create(newType, this); 2731 if (newEntry) 2732 { 2733 newEntry->setContent(content); 2734 auto* tmp = target; 2735 2736 newEntry->setPrevious(tmp->previous()); 2737 newEntry->setNext(tmp->next()); 2738 2739 tmp->setPrevious(nullptr); 2740 tmp->setNext(nullptr); 2741 tmp->clearFocus(); 2742 tmp->forceRemove(); 2743 2744 if (newEntry->previous()) 2745 newEntry->previous()->setNext(newEntry); 2746 else 2747 setFirstEntry(newEntry); 2748 2749 if (newEntry->next()) 2750 newEntry->next()->setPrevious(newEntry); 2751 else 2752 setLastEntry(newEntry); 2753 2754 if (newType == HierarchyEntry::Type || targetEntryType == HierarchyEntry::Type) 2755 updateHierarchyLayout(); 2756 updateLayout(); 2757 makeVisible(newEntry); 2758 focusEntry(newEntry); 2759 setModified(); 2760 newEntry->focusEntry(); 2761 } 2762 m_animationsEnabled = animation_state; 2763 } 2764 } 2765 2766 bool Worksheet::isValidEntry(WorksheetEntry* entry) 2767 { 2768 for (auto* iter = firstEntry(); iter; iter = iter->next()) 2769 if (entry == iter) 2770 return true; 2771 2772 return false; 2773 } 2774 2775 void Worksheet::selectionRemove() 2776 { 2777 for (auto* entry : m_selectedEntries) 2778 if (isValidEntry(entry)) 2779 entry->startRemoving(); 2780 2781 m_selectedEntries.clear(); 2782 } 2783 2784 void Worksheet::selectionEvaluate() 2785 { 2786 // run entries in worksheet order: from top to down 2787 for (auto* entry = firstEntry(); entry; entry = entry->next()) 2788 if (m_selectedEntries.indexOf(entry) != -1) 2789 entry->evaluate(); 2790 } 2791 2792 void Worksheet::selectionMoveUp() 2793 { 2794 bool moveHierarchyEntry = false; 2795 // movement up should have an order from top to down. 2796 for(auto* entry = firstEntry(); entry; entry = entry->next()) 2797 if(m_selectedEntries.indexOf(entry) != -1) 2798 if (entry->previous() && m_selectedEntries.indexOf(entry->previous()) == -1) 2799 { 2800 entry->moveToPrevious(false); 2801 if (entry->type() == HierarchyEntry::Type) 2802 moveHierarchyEntry = true; 2803 } 2804 if (moveHierarchyEntry) 2805 updateHierarchyLayout(); 2806 updateLayout(); 2807 } 2808 2809 void Worksheet::selectionMoveDown() 2810 { 2811 bool moveHierarchyEntry = false; 2812 // movement up should have an order from down to top. 2813 for(auto* entry = lastEntry(); entry; entry = entry->previous()) 2814 if(m_selectedEntries.indexOf(entry) != -1) 2815 if (entry->next() && m_selectedEntries.indexOf(entry->next()) == -1) 2816 { 2817 entry->moveToNext(false); 2818 if (entry->type() == HierarchyEntry::Type) 2819 moveHierarchyEntry = true; 2820 } 2821 if (moveHierarchyEntry) 2822 updateHierarchyLayout(); 2823 updateLayout(); 2824 } 2825 2826 void Worksheet::notifyEntryFocus(WorksheetEntry* entry) 2827 { 2828 if (entry) 2829 { 2830 m_circularFocusBuffer.enqueue(entry); 2831 2832 if (m_circularFocusBuffer.size() > 2) 2833 m_circularFocusBuffer.dequeue(); 2834 } 2835 else 2836 m_circularFocusBuffer.clear(); 2837 } 2838 2839 void Worksheet::collapseAllResults() 2840 { 2841 for (auto* entry = firstEntry(); entry; entry = entry->next()) 2842 if (entry->type() == CommandEntry::Type) 2843 static_cast<CommandEntry*>(entry)->collapseResults(); 2844 } 2845 2846 void Worksheet::uncollapseAllResults() 2847 { 2848 for (auto* entry = firstEntry(); entry; entry = entry->next()) 2849 if (entry->type() == CommandEntry::Type) 2850 static_cast<CommandEntry*>(entry)->expandResults(); 2851 } 2852 2853 void Worksheet::removeAllResults() 2854 { 2855 bool remove = false; 2856 2857 if (KMessageBox::shouldBeShownContinue(QLatin1String("WarnAboutAllResultsRemoving"))) 2858 { 2859 KMessageBox::ButtonCode btn = KMessageBox::warningContinueCancel( 2860 views().first(), 2861 i18n("This action will remove all results without the possibility of cancellation. Are you sure?"), 2862 i18n("Remove all results"), 2863 KStandardGuiItem::cont(), 2864 KStandardGuiItem::cancel(), 2865 QLatin1String("WarnAboutAllResultsRemoving") 2866 ); 2867 remove = (btn == KMessageBox::Continue); 2868 } 2869 else 2870 remove = true; 2871 2872 if (remove) 2873 { 2874 for (auto *entry = firstEntry(); entry; entry = entry->next()) 2875 if (entry->type() == CommandEntry::Type) 2876 static_cast<CommandEntry*>(entry)->removeResults(); 2877 } 2878 } 2879 2880 void Worksheet::addToExectuionSelection() 2881 { 2882 for (auto* entry : m_selectedEntries) 2883 if (entry->type() == CommandEntry::Type) 2884 static_cast<CommandEntry*>(entry)->addToExecution(); 2885 } 2886 2887 void Worksheet::excludeFromExecutionSelection() 2888 { 2889 for (auto* entry : m_selectedEntries) 2890 if (entry->type() == CommandEntry::Type) 2891 static_cast<CommandEntry*>(entry)->excludeFromExecution(); 2892 } 2893 2894 void Worksheet::collapseSelectionResults() 2895 { 2896 for (auto* entry : m_selectedEntries) 2897 if (entry->type() == CommandEntry::Type) 2898 static_cast<CommandEntry*>(entry)->collapseResults(); 2899 } 2900 2901 void Worksheet::uncollapseSelectionResults() 2902 { 2903 for (auto* entry : m_selectedEntries) 2904 if (entry->type() == CommandEntry::Type) 2905 static_cast<CommandEntry*>(entry)->expandResults(); 2906 } 2907 2908 void Worksheet::removeSelectionResults() 2909 { 2910 for (auto* entry : m_selectedEntries) 2911 if (entry->type() == CommandEntry::Type) 2912 static_cast<CommandEntry*>(entry)->removeResults(); 2913 } 2914 2915 void Worksheet::requestScrollToHierarchyEntry(QString hierarchyText) 2916 { 2917 for (auto* entry = firstEntry(); entry; entry = entry->next()) 2918 { 2919 if (entry->type() == HierarchyEntry::Type) 2920 { 2921 auto* hierarchEntry = static_cast<HierarchyEntry*>(entry); 2922 if (hierarchEntry->hierarchyText() == hierarchyText) 2923 worksheetView()->scrollTo(hierarchEntry->y()); 2924 } 2925 } 2926 } 2927 2928 WorksheetEntry * Worksheet::cutSubentriesForHierarchy(HierarchyEntry* hierarchyEntry) 2929 { 2930 Q_ASSERT(hierarchyEntry->next()); 2931 auto* cutBegin = hierarchyEntry->next(); 2932 auto* cutEnd = cutBegin; 2933 2934 bool isCutEnd = false; 2935 int level = (int)hierarchyEntry->level(); 2936 while (!isCutEnd && cutEnd && cutEnd->next()) 2937 { 2938 auto* next = cutEnd->next(); 2939 if (next->type() == HierarchyEntry::Type && (int)static_cast<HierarchyEntry*>(next)->level() <= level) 2940 isCutEnd = true; 2941 else 2942 cutEnd = next; 2943 } 2944 2945 //cutEnd not an end of all entries 2946 if (cutEnd->next()) 2947 { 2948 hierarchyEntry->setNext(cutEnd->next()); 2949 cutEnd->setNext(nullptr); 2950 } 2951 else 2952 { 2953 hierarchyEntry->setNext(nullptr); 2954 setLastEntry(hierarchyEntry); 2955 } 2956 2957 cutBegin->setPrevious(nullptr); 2958 2959 for(auto* entry = cutBegin; entry; entry = entry->next()) 2960 entry->hide(); 2961 2962 return cutBegin; 2963 } 2964 2965 void Worksheet::insertSubentriesForHierarchy(HierarchyEntry* hierarchyEntry, WorksheetEntry* storedSubentriesBegin) 2966 { 2967 auto* previousNext = hierarchyEntry->next(); 2968 hierarchyEntry->setNext(storedSubentriesBegin); 2969 storedSubentriesBegin->show(); 2970 2971 auto* storedEnd = storedSubentriesBegin; 2972 while(storedEnd->next()) 2973 { 2974 storedEnd = storedEnd->next(); 2975 storedEnd->show(); 2976 } 2977 storedEnd->setNext(previousNext); 2978 if (!previousNext) 2979 setLastEntry(storedEnd); 2980 } 2981 2982 void Worksheet::handleSettingsChanges() 2983 { 2984 for (auto* entry = firstEntry(); entry; entry = entry->next()) 2985 entry->updateAfterSettingsChanges(); 2986 }