File indexing completed on 2024-04-28 11:21:03
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 //TODO: what for? relevant for sage only? 1748 help.replace(QRegularExpression(QStringLiteral("\\\\code\\{([^\\}]*)\\}")), QStringLiteral("<b>\\1</b>")); 1749 help.replace(QRegularExpression(QStringLiteral("\\$([^\\$])\\$")), QStringLiteral("<i>\\1</i>")); 1750 1751 emit showHelp(help); 1752 1753 //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). 1754 break; 1755 } 1756 } 1757 } 1758 1759 void Worksheet::removeCurrentEntry() 1760 { 1761 qDebug()<<"removing current entry"; 1762 auto* entry = currentEntry(); 1763 if(!entry) 1764 return; 1765 1766 // In case we just removed this 1767 if (entry->isAncestorOf(m_lastFocusedTextItem)) 1768 m_lastFocusedTextItem = nullptr; 1769 1770 entry->startRemoving(); 1771 } 1772 1773 Cantor::Renderer* Worksheet::renderer() 1774 { 1775 return &m_epsRenderer; 1776 } 1777 1778 MathRenderer* Worksheet::mathRenderer() 1779 { 1780 return &m_mathRenderer; 1781 } 1782 1783 QMenu* Worksheet::createContextMenu() 1784 { 1785 auto* menu = new QMenu(worksheetView()); 1786 connect(menu, SIGNAL(aboutToHide()), menu, SLOT(deleteLater())); 1787 1788 return menu; 1789 } 1790 1791 void Worksheet::populateMenu(QMenu* menu, QPointF pos) 1792 { 1793 // Two menu: for particular entry and for selection (multiple entry) 1794 if (m_selectedEntries.isEmpty()) 1795 { 1796 auto* entry = entryAt(pos); 1797 if (entry && !entry->isAncestorOf(m_lastFocusedTextItem)) { 1798 auto* item = 1799 qgraphicsitem_cast<WorksheetTextItem*>(itemAt(pos, QTransform())); 1800 if (item && item->isEditable()) 1801 m_lastFocusedTextItem = item; 1802 } 1803 1804 if (entry) { 1805 //"Convert To" menu 1806 QMenu* convertTo = new QMenu(i18n("Convert To")); 1807 convertTo->setIcon(QIcon::fromTheme(QLatin1String("gtk-convert"))); 1808 menu->addMenu(convertTo); 1809 1810 if (entry->type() != CommandEntry::Type) 1811 convertTo->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), entry, &WorksheetEntry::convertToCommandEntry); 1812 1813 if (entry->type() != TextEntry::Type) 1814 convertTo->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), entry, &WorksheetEntry::convertToTextEntry); 1815 1816 #ifdef Discount_FOUND 1817 if (entry->type() != MarkdownEntry::Type) 1818 convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), entry, &WorksheetEntry::convertToMarkdownEntry); 1819 #endif 1820 #ifdef WITH_EPS 1821 if (entry->type() != LatexEntry::Type) 1822 convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), entry, &WorksheetEntry::convertToLatexEntry); 1823 #endif 1824 if (entry->type() != ImageEntry::Type) 1825 convertTo->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, &WorksheetEntry::convertToImageEntry); 1826 1827 if (entry->type() != PageBreakEntry::Type) 1828 convertTo->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), entry, &WorksheetEntry::converToPageBreakEntry); 1829 1830 if (entry->type() != HorizontalRuleEntry::Type) 1831 convertTo->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), entry, &WorksheetEntry::convertToHorizontalRuleEntry); 1832 1833 if (entry->type() != HierarchyEntry::Type) 1834 convertTo->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), entry, &WorksheetEntry::convertToHierarchyEntry); 1835 1836 //"Insert After" menu 1837 QMenu* insert = new QMenu(i18n("Insert After"), menu); 1838 insert->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-below"))); 1839 menu->addSeparator(); 1840 menu->addMenu(insert); 1841 1842 insert->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), entry, SLOT(insertCommandEntry())); 1843 insert->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), entry, SLOT(insertTextEntry())); 1844 #ifdef Discount_FOUND 1845 insert->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), entry, SLOT(insertMarkdownEntry())); 1846 #endif 1847 #ifdef WITH_EPS 1848 insert->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), entry, SLOT(insertLatexEntry())); 1849 #endif 1850 insert->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntry())); 1851 insert->addSeparator(); 1852 insert->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), entry, SLOT(insertHorizontalRuleEntry())); 1853 insert->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), entry, SLOT(insertPageBreakEntry())); 1854 insert->addSeparator(); 1855 insert->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), entry, SLOT(insertHierarchyEntry())); 1856 1857 //"Insert Before" menu 1858 QMenu* insertBefore = new QMenu(i18n("Insert Before"), menu); 1859 insertBefore->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-above"))); 1860 menu->addMenu(insertBefore); 1861 1862 insertBefore->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), entry, SLOT(insertCommandEntryBefore())); 1863 insertBefore->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), entry, SLOT(insertTextEntryBefore())); 1864 #ifdef Discount_FOUND 1865 insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), entry, SLOT(insertMarkdownEntryBefore())); 1866 #endif 1867 #ifdef WITH_EPS 1868 insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), entry, SLOT(insertLatexEntryBefore())); 1869 #endif 1870 insertBefore->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntryBefore())); 1871 insertBefore->addSeparator(); 1872 insertBefore->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), entry, SLOT(insertHorizontalRuleEntryBefore())); 1873 insertBefore->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), entry, SLOT(insertPageBreakEntryBefore())); 1874 insertBefore->addSeparator(); 1875 insertBefore->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), entry, SLOT(insertHierarchyEntryBefore())); 1876 } else { 1877 QMenu* insertMenu = new QMenu(i18n("Insert")); 1878 insertMenu->setIcon(QIcon::fromTheme(QLatin1String("insert-table-row"))); 1879 1880 insertMenu->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), this, SLOT(appendCommandEntry())); 1881 insertMenu->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), this, &Worksheet::appendTextEntry); 1882 #ifdef Discount_FOUND 1883 insertMenu->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), this, &Worksheet::appendMarkdownEntry); 1884 #endif 1885 #ifdef WITH_EPS 1886 insertMenu->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), this, &Worksheet::appendLatexEntry); 1887 #endif 1888 insertMenu->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), this, &Worksheet::appendImageEntry); 1889 insertMenu->addSeparator(); 1890 insertMenu->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), this, &Worksheet::appendHorizontalRuleEntry); 1891 insertMenu->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), this, &Worksheet::appendPageBreakEntry); 1892 insertMenu->addSeparator(); 1893 insertMenu->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), this, &Worksheet::appendHierarchyEntry); 1894 1895 menu->addMenu(insertMenu); 1896 1897 //"Show help" for backend's documentation 1898 menu->addSeparator(); 1899 menu->addAction(QIcon::fromTheme(QLatin1String("help-hint")), i18n("Show Help"), this, 1900 [=] () { requestDocumentation(QString()); }); 1901 } 1902 1903 //evaluate the whole worksheet or interrupt the current calculation 1904 menu->addSeparator(); 1905 if (!isRunning()) 1906 menu->addAction(QIcon::fromTheme(QLatin1String("system-run")), i18n("Evaluate Worksheet"), 1907 this, &Worksheet::evaluate); 1908 else 1909 menu->addAction(QIcon::fromTheme(QLatin1String("process-stop")), i18n("Interrupt"), this, 1910 &Worksheet::interrupt); 1911 1912 //zooming 1913 menu->addSeparator(); 1914 auto* zoomMenu = new QMenu(i18n("Zoom")); 1915 zoomMenu->setIcon(QIcon::fromTheme(QLatin1String("zoom-draw"))); 1916 auto* view = worksheetView(); 1917 1918 auto* action = zoomMenu->addAction(QIcon::fromTheme(QLatin1String("zoom-in")), i18n("Zoom In"), view, &WorksheetView::zoomIn); 1919 action->setShortcut(Qt::CTRL+Qt::Key_Plus); 1920 1921 action = zoomMenu->addAction(QIcon::fromTheme(QLatin1String("zoom-out")), i18n("Zoom Out"), view, &WorksheetView::zoomOut); 1922 action->setShortcut(Qt::CTRL+Qt::Key_Minus); 1923 zoomMenu->addSeparator(); 1924 1925 action = zoomMenu->addAction(QIcon::fromTheme(QLatin1String("zoom-original")), i18n("Original Size"), view, &WorksheetView::actualSize); 1926 action->setShortcut(Qt::CTRL+Qt::Key_1); 1927 1928 menu->addMenu(zoomMenu); 1929 } 1930 else 1931 { 1932 menu->clear(); 1933 menu->addAction(QIcon::fromTheme(QLatin1String("go-up")), i18n("Move Entries Up"), this, &Worksheet::selectionMoveUp); 1934 menu->addAction(QIcon::fromTheme(QLatin1String("go-down")), i18n("Move Entries Down"), this, &Worksheet::selectionMoveDown); 1935 menu->addAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate Entries"), this, &Worksheet::selectionEvaluate); 1936 menu->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove Entries"), this, &Worksheet::selectionRemove); 1937 1938 bool isAnyCommandEntryInSelection = false; 1939 for (WorksheetEntry* entry : m_selectedEntries) 1940 if (entry->type() == CommandEntry::Type) 1941 { 1942 isAnyCommandEntryInSelection = true; 1943 break; 1944 } 1945 1946 if (isAnyCommandEntryInSelection) 1947 { 1948 menu->addSeparator(); 1949 menu->addAction(QIcon(), i18n("Collapse Command Entry Results"), this, &Worksheet::collapseSelectionResults); 1950 menu->addAction(QIcon(), i18n("Expand Command Entry Results"), this, &Worksheet::uncollapseSelectionResults); 1951 menu->addAction(QIcon(), i18n("Remove Command Entry Results"), this, &Worksheet::removeSelectionResults); 1952 menu->addAction(QIcon(), i18n("Exclude Command Entry From Execution"), this, &Worksheet::excludeFromExecutionSelection); 1953 menu->addAction(QIcon(), i18n("Add Command Entry To Execution"), this, &Worksheet::addToExectuionSelection); 1954 } 1955 } 1956 } 1957 1958 void Worksheet::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) 1959 { 1960 if (m_readOnly) 1961 return; 1962 1963 // forward the event to the items 1964 QGraphicsScene::contextMenuEvent(event); 1965 1966 if (!event->isAccepted()) { 1967 event->accept(); 1968 QMenu* menu = createContextMenu(); 1969 populateMenu(menu, event->scenePos()); 1970 1971 menu->popup(event->screenPos()); 1972 } 1973 } 1974 1975 void Worksheet::mousePressEvent(QGraphicsSceneMouseEvent* event) 1976 { 1977 /* 1978 if (event->button() == Qt::LeftButton && !focusItem() && lastEntry() && 1979 event->scenePos().y() > lastEntry()->y() + lastEntry()->size().height()) 1980 lastEntry()->focusEntry(WorksheetTextItem::BottomRight); 1981 */ 1982 QGraphicsScene::mousePressEvent(event); 1983 1984 if (!m_readOnly && event->buttons() & Qt::LeftButton) 1985 { 1986 auto* selectedEntry = entryAt(event->scenePos()); 1987 if (event->modifiers() & Qt::ControlModifier) 1988 { 1989 clearFocus(); 1990 resetEntryCursor(); 1991 1992 if (selectedEntry) 1993 { 1994 selectedEntry->setCellSelected(!selectedEntry->isCellSelected()); 1995 selectedEntry->update(); 1996 1997 WorksheetEntry* lastSelectedEntry = m_circularFocusBuffer.size() > 0 ? m_circularFocusBuffer.last() : nullptr; 1998 if (lastSelectedEntry) 1999 { 2000 lastSelectedEntry->setCellSelected(!lastSelectedEntry->isCellSelected()); 2001 lastSelectedEntry->update(); 2002 m_circularFocusBuffer.clear(); 2003 } 2004 2005 for (WorksheetEntry* entry : {selectedEntry, lastSelectedEntry}) 2006 if (entry) 2007 { 2008 if (entry->isCellSelected()) 2009 m_selectedEntries.append(entry); 2010 else if (!entry->isCellSelected()) 2011 m_selectedEntries.removeOne(entry); 2012 } 2013 } 2014 } 2015 else 2016 { 2017 for (auto* entry : m_selectedEntries) 2018 { 2019 if(isValidEntry(entry)) 2020 { 2021 entry->setCellSelected(false); 2022 entry->update(); 2023 } 2024 } 2025 m_selectedEntries.clear(); 2026 2027 if (selectedEntry) 2028 notifyEntryFocus(selectedEntry); 2029 2030 updateEntryCursor(event); 2031 } 2032 } 2033 } 2034 2035 void Worksheet::keyPressEvent(QKeyEvent* event) 2036 { 2037 if (m_readOnly) 2038 return; 2039 2040 if ((event->modifiers() & Qt::ControlModifier) && (event->key() == Qt::Key_1)) 2041 worksheetView()->actualSize(); 2042 else if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && !event->text().isEmpty()) 2043 addEntryFromEntryCursor(); //add new enty when the entry cursor is activa the user starts typing the text 2044 2045 QGraphicsScene::keyPressEvent(event); 2046 } 2047 2048 void Worksheet::setActionCollection(KActionCollection* collection) 2049 { 2050 m_collection = collection; 2051 } 2052 2053 void Worksheet::initActions() 2054 { 2055 // Mostly copied from KRichTextWidget::createActions(KActionCollection*) 2056 // It would be great if this wasn't necessary. 2057 2058 // Text color 2059 /* This is "format-stroke-color" in KRichTextWidget */ 2060 auto* action = new QAction(QIcon::fromTheme(QLatin1String("format-text-color")), 2061 i18nc("@action", "Text &Color..."), m_collection); 2062 action->setIconText(i18nc("@label text color", "Color")); 2063 action->setPriority(QAction::LowPriority); 2064 m_richTextActionList.append(action); 2065 connect(action, &QAction::triggered, this, &Worksheet::setTextForegroundColor); 2066 2067 // Text color 2068 action = new QAction(QIcon::fromTheme(QLatin1String("format-fill-color")), 2069 i18nc("@action", "Text &Highlight..."), m_collection); 2070 action->setPriority(QAction::LowPriority); 2071 m_richTextActionList.append(action); 2072 connect(action, &QAction::triggered, this, &Worksheet::setTextBackgroundColor); 2073 2074 // Font Family 2075 m_fontAction = new KFontAction(i18nc("@action", "&Font"), m_collection); 2076 m_richTextActionList.append(m_fontAction); 2077 #if KCOREADDONS_VERSION >= QT_VERSION_CHECK(5, 78, 0) 2078 connect(m_fontAction, &KFontAction::textTriggered, this, &Worksheet::setFontFamily); 2079 #else 2080 connect(m_fontAction, QOverload<const QString&>::of(&KFontAction::triggered), this, &Worksheet::setFontFamily); 2081 #endif 2082 2083 // Font Size 2084 m_fontSizeAction = new KFontSizeAction(i18nc("@action", "Font &Size"), m_collection); 2085 m_richTextActionList.append(m_fontSizeAction); 2086 connect(m_fontSizeAction, &KFontSizeAction::fontSizeChanged, this, &Worksheet::setFontSize); 2087 2088 // Bold 2089 m_boldAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-bold")), 2090 i18nc("@action boldify selected text", "&Bold"), 2091 m_collection); 2092 m_boldAction->setPriority(QAction::LowPriority); 2093 QFont bold; 2094 bold.setBold(true); 2095 m_boldAction->setFont(bold); 2096 m_richTextActionList.append(m_boldAction); 2097 connect(m_boldAction, &QAction::triggered, this, &Worksheet::setTextBold); 2098 2099 // Italic 2100 m_italicAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-italic")), 2101 i18nc("@action italicize selected text", "&Italic"), 2102 m_collection); 2103 m_italicAction->setPriority(QAction::LowPriority); 2104 QFont italic; 2105 italic.setItalic(true); 2106 m_italicAction->setFont(italic); 2107 m_richTextActionList.append(m_italicAction); 2108 connect(m_italicAction, &QAction::triggered, this, &Worksheet::setTextItalic); 2109 2110 // Underline 2111 m_underlineAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-underline")), 2112 i18nc("@action underline selected text", 2113 "&Underline"), 2114 m_collection); 2115 m_underlineAction->setPriority(QAction::LowPriority); 2116 QFont underline; 2117 underline.setUnderline(true); 2118 m_underlineAction->setFont(underline); 2119 m_richTextActionList.append(m_underlineAction); 2120 connect(m_underlineAction, &QAction::triggered, this, &Worksheet::setTextUnderline); 2121 2122 // Strike 2123 m_strikeOutAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-strikethrough")), 2124 i18nc("@action", "&Strike Out"), 2125 m_collection); 2126 m_strikeOutAction->setPriority(QAction::LowPriority); 2127 m_richTextActionList.append(m_strikeOutAction); 2128 connect(m_strikeOutAction, &QAction::triggered, this, &Worksheet::setTextStrikeOut); 2129 2130 // Alignment 2131 auto* alignmentGroup = new QActionGroup(this); 2132 2133 // Align left 2134 m_alignLeftAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-left")), 2135 i18nc("@action", "Align &Left"), 2136 m_collection); 2137 m_alignLeftAction->setPriority(QAction::LowPriority); 2138 m_alignLeftAction->setIconText(i18nc("@label left justify", "Left")); 2139 m_richTextActionList.append(m_alignLeftAction); 2140 connect(m_alignLeftAction, &QAction::triggered, this, &Worksheet::setAlignLeft); 2141 alignmentGroup->addAction(m_alignLeftAction); 2142 2143 // Align center 2144 m_alignCenterAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-center")), 2145 i18nc("@action", "Align &Center"), 2146 m_collection); 2147 m_alignCenterAction->setPriority(QAction::LowPriority); 2148 m_alignCenterAction->setIconText(i18nc("@label center justify", "Center")); 2149 m_richTextActionList.append(m_alignCenterAction); 2150 connect(m_alignCenterAction, &QAction::triggered, this, &Worksheet::setAlignCenter); 2151 alignmentGroup->addAction(m_alignCenterAction); 2152 2153 // Align right 2154 m_alignRightAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-right")), 2155 i18nc("@action", "Align &Right"), 2156 m_collection); 2157 m_alignRightAction->setPriority(QAction::LowPriority); 2158 m_alignRightAction->setIconText(i18nc("@label right justify", "Right")); 2159 m_richTextActionList.append(m_alignRightAction); 2160 connect(m_alignRightAction, &QAction::triggered, this, &Worksheet::setAlignRight); 2161 alignmentGroup->addAction(m_alignRightAction); 2162 2163 // Align justify 2164 m_alignJustifyAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-fill")), 2165 i18nc("@action", "&Justify"), 2166 m_collection); 2167 m_alignJustifyAction->setPriority(QAction::LowPriority); 2168 m_alignJustifyAction->setIconText(i18nc("@label justify fill", "Justify")); 2169 m_richTextActionList.append(m_alignJustifyAction); 2170 connect(m_alignJustifyAction, &QAction::triggered, this, &Worksheet::setAlignJustify); 2171 alignmentGroup->addAction(m_alignJustifyAction); 2172 2173 if (m_collection) 2174 { 2175 m_collection->addAction(QLatin1String("format_text_foreground_color"), action); 2176 m_collection->addAction(QLatin1String("format_text_background_color"), action); 2177 m_collection->addAction(QLatin1String("format_font_family"), m_fontAction); 2178 m_collection->addAction(QLatin1String("format_font_size"), m_fontSizeAction); 2179 m_collection->addAction(QLatin1String("format_text_bold"), m_boldAction); 2180 m_collection->setDefaultShortcut(m_boldAction, Qt::CTRL + Qt::Key_B); 2181 m_collection->addAction(QLatin1String("format_text_italic"), m_italicAction); 2182 m_collection->setDefaultShortcut(m_italicAction, Qt::CTRL + Qt::Key_I); 2183 m_collection->addAction(QLatin1String("format_text_underline"), m_underlineAction); 2184 m_collection->setDefaultShortcut(m_underlineAction, Qt::CTRL + Qt::Key_U); 2185 m_collection->addAction(QLatin1String("format_text_strikeout"), m_strikeOutAction); 2186 m_collection->setDefaultShortcut(m_strikeOutAction, Qt::CTRL + Qt::Key_L); 2187 m_collection->addAction(QLatin1String("format_align_left"), m_alignLeftAction); 2188 m_collection->addAction(QLatin1String("format_align_center"), m_alignCenterAction); 2189 m_collection->addAction(QLatin1String("format_align_right"), m_alignRightAction); 2190 m_collection->addAction(QLatin1String("format_align_justify"), m_alignJustifyAction); 2191 } 2192 2193 /* 2194 // List style 2195 KSelectAction* selAction; 2196 selAction = new KSelectAction(QIcon::fromTheme("format-list-unordered"), 2197 i18nc("@title:menu", "List Style"), 2198 collection); 2199 QStringList listStyles; 2200 listStyles << i18nc("@item:inmenu no list style", "None") 2201 << i18nc("@item:inmenu disc list style", "Disc") 2202 << i18nc("@item:inmenu circle list style", "Circle") 2203 << i18nc("@item:inmenu square list style", "Square") 2204 << i18nc("@item:inmenu numbered lists", "123") 2205 << i18nc("@item:inmenu lowercase abc lists", "abc") 2206 << i18nc("@item:inmenu uppercase abc lists", "ABC"); 2207 selAction->setItems(listStyles); 2208 selAction->setCurrentItem(0); 2209 action = selAction; 2210 m_richTextActionList.append(action); 2211 collection->addAction("format_list_style", action); 2212 connect(action, SIGNAL(triggered(int)), 2213 this, &Worksheet::_k_setListStyle(int))); 2214 connect(action, &QAction::triggered, 2215 this, &Worksheet::_k_updateMiscActions())); 2216 2217 // Indent 2218 action = new QAction(QIcon::fromTheme("format-indent-more"), 2219 i18nc("@action", "Increase Indent"), collection); 2220 action->setPriority(QAction::LowPriority); 2221 m_richTextActionList.append(action); 2222 collection->addAction("format_list_indent_more", action); 2223 connect(action, &QAction::triggered, 2224 this, &Worksheet::indentListMore())); 2225 connect(action, &QAction::triggered, 2226 this, &Worksheet::_k_updateMiscActions())); 2227 2228 // Dedent 2229 action = new QAction(QIcon::fromTheme("format-indent-less"), 2230 i18nc("@action", "Decrease Indent"), collection); 2231 action->setPriority(QAction::LowPriority); 2232 m_richTextActionList.append(action); 2233 collection->addAction("format_list_indent_less", action); 2234 connect(action, &QAction::triggered, this, &Worksheet::indentListLess())); 2235 connect(action, &QAction::triggered, this, &Worksheet::_k_updateMiscActions())); 2236 */ 2237 } 2238 2239 WorksheetTextItem* Worksheet::lastFocusedTextItem() 2240 { 2241 return m_lastFocusedTextItem; 2242 } 2243 2244 void Worksheet::updateFocusedTextItem(WorksheetTextItem* newItem) 2245 { 2246 // No need update and emit signals about editing actions in readonly 2247 // So support only copy action and reset selection 2248 if (m_readOnly) 2249 { 2250 if (m_lastFocusedTextItem && m_lastFocusedTextItem != newItem) 2251 { 2252 disconnect(this, SIGNAL(copy()), m_lastFocusedTextItem, SLOT(copy())); 2253 m_lastFocusedTextItem->clearSelection(); 2254 } 2255 2256 if (newItem && m_lastFocusedTextItem != newItem) 2257 { 2258 connect(this, SIGNAL(copy()), newItem, SLOT(copy())); 2259 emit copyAvailable(newItem->isCopyAvailable()); 2260 } 2261 else if (!newItem) 2262 { 2263 emit copyAvailable(false); 2264 } 2265 2266 m_lastFocusedTextItem = newItem; 2267 return; 2268 } 2269 2270 if (m_lastFocusedTextItem && m_lastFocusedTextItem != newItem) { 2271 disconnect(m_lastFocusedTextItem, SIGNAL(undoAvailable(bool)), 2272 this, SIGNAL(undoAvailable(bool))); 2273 disconnect(m_lastFocusedTextItem, SIGNAL(redoAvailable(bool)), 2274 this, SIGNAL(redoAvailable(bool))); 2275 disconnect(this, SIGNAL(undo()), m_lastFocusedTextItem, SLOT(undo())); 2276 disconnect(this, SIGNAL(redo()), m_lastFocusedTextItem, SLOT(redo())); 2277 disconnect(m_lastFocusedTextItem, SIGNAL(cutAvailable(bool)), 2278 this, SIGNAL(cutAvailable(bool))); 2279 disconnect(m_lastFocusedTextItem, SIGNAL(copyAvailable(bool)), 2280 this, SIGNAL(copyAvailable(bool))); 2281 disconnect(m_lastFocusedTextItem, SIGNAL(pasteAvailable(bool)), 2282 this, SIGNAL(pasteAvailable(bool))); 2283 disconnect(this, SIGNAL(cut()), m_lastFocusedTextItem, SLOT(cut())); 2284 disconnect(this, SIGNAL(copy()), m_lastFocusedTextItem, SLOT(copy())); 2285 2286 m_lastFocusedTextItem->clearSelection(); 2287 } 2288 2289 if (newItem && m_lastFocusedTextItem != newItem) { 2290 setAcceptRichText(newItem->richTextEnabled()); 2291 emit undoAvailable(newItem->isUndoAvailable()); 2292 emit redoAvailable(newItem->isRedoAvailable()); 2293 connect(newItem, SIGNAL(undoAvailable(bool)), 2294 this, SIGNAL(undoAvailable(bool))); 2295 connect(newItem, SIGNAL(redoAvailable(bool)), 2296 this, SIGNAL(redoAvailable(bool))); 2297 connect(this, SIGNAL(undo()), newItem, SLOT(undo())); 2298 connect(this, SIGNAL(redo()), newItem, SLOT(redo())); 2299 emit cutAvailable(newItem->isCutAvailable()); 2300 emit copyAvailable(newItem->isCopyAvailable()); 2301 emit pasteAvailable(newItem->isPasteAvailable()); 2302 connect(newItem, SIGNAL(cutAvailable(bool)), 2303 this, SIGNAL(cutAvailable(bool))); 2304 connect(newItem, SIGNAL(copyAvailable(bool)), 2305 this, SIGNAL(copyAvailable(bool))); 2306 connect(newItem, SIGNAL(pasteAvailable(bool)), 2307 this, SIGNAL(pasteAvailable(bool))); 2308 connect(this, SIGNAL(cut()), newItem, SLOT(cut())); 2309 connect(this, SIGNAL(copy()), newItem, SLOT(copy())); 2310 } else if (!newItem) { 2311 emit undoAvailable(false); 2312 emit redoAvailable(false); 2313 emit cutAvailable(false); 2314 emit copyAvailable(false); 2315 emit pasteAvailable(false); 2316 } 2317 m_lastFocusedTextItem = newItem; 2318 } 2319 2320 /*! 2321 * handles the paste action triggered in cantor_part. 2322 * Pastes into the last focused text item. 2323 * In case the "new entry"-cursor is currently shown, 2324 * a new entry is created first which the content will be pasted into. 2325 */ 2326 void Worksheet::paste() { 2327 if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) 2328 addEntryFromEntryCursor(); 2329 2330 m_lastFocusedTextItem->paste(); 2331 } 2332 2333 void Worksheet::setRichTextInformation(const RichTextInfo& info) 2334 { 2335 if (!m_boldAction) 2336 initActions(); 2337 2338 m_boldAction->setChecked(info.bold); 2339 m_italicAction->setChecked(info.italic); 2340 m_underlineAction->setChecked(info.underline); 2341 m_strikeOutAction->setChecked(info.strikeOut); 2342 m_fontAction->setFont(info.font); 2343 if (info.fontSize > 0) 2344 m_fontSizeAction->setFontSize(info.fontSize); 2345 2346 if (info.align & Qt::AlignLeft) 2347 m_alignLeftAction->setChecked(true); 2348 else if (info.align & Qt::AlignCenter) 2349 m_alignCenterAction->setChecked(true); 2350 else if (info.align & Qt::AlignRight) 2351 m_alignRightAction->setChecked(true); 2352 else if (info.align & Qt::AlignJustify) 2353 m_alignJustifyAction->setChecked(true); 2354 } 2355 2356 void Worksheet::setAcceptRichText(bool b) 2357 { 2358 if (!m_readOnly) 2359 for(auto* action : m_richTextActionList) 2360 action->setVisible(b); 2361 } 2362 2363 WorksheetTextItem* Worksheet::currentTextItem() 2364 { 2365 auto* item = focusItem(); 2366 if (!item) 2367 item = m_lastFocusedTextItem; 2368 while (item && item->type() != WorksheetTextItem::Type) 2369 item = item->parentItem(); 2370 2371 return qgraphicsitem_cast<WorksheetTextItem*>(item); 2372 } 2373 2374 void Worksheet::setTextForegroundColor() 2375 { 2376 auto* item = currentTextItem(); 2377 if (item) 2378 item->setTextForegroundColor(); 2379 } 2380 2381 void Worksheet::setTextBackgroundColor() 2382 { 2383 auto* item = currentTextItem(); 2384 if (item) 2385 item->setTextBackgroundColor(); 2386 } 2387 2388 void Worksheet::setTextBold(bool b) 2389 { 2390 auto* item = currentTextItem(); 2391 if (item) 2392 item->setTextBold(b); 2393 } 2394 2395 void Worksheet::setTextItalic(bool b) 2396 { 2397 auto* item = currentTextItem(); 2398 if (item) 2399 item->setTextItalic(b); 2400 } 2401 2402 void Worksheet::setTextUnderline(bool b) 2403 { 2404 auto* item = currentTextItem(); 2405 if (item) 2406 item->setTextUnderline(b); 2407 } 2408 2409 void Worksheet::setTextStrikeOut(bool b) 2410 { 2411 auto* item = currentTextItem(); 2412 if (item) 2413 item->setTextStrikeOut(b); 2414 } 2415 2416 void Worksheet::setAlignLeft() 2417 { 2418 auto* item = currentTextItem(); 2419 if (item) 2420 item->setAlignment(Qt::AlignLeft); 2421 } 2422 2423 void Worksheet::setAlignRight() 2424 { 2425 auto* item = currentTextItem(); 2426 if (item) 2427 item->setAlignment(Qt::AlignRight); 2428 } 2429 2430 void Worksheet::setAlignCenter() 2431 { 2432 auto* item = currentTextItem(); 2433 if (item) 2434 item->setAlignment(Qt::AlignCenter); 2435 } 2436 2437 void Worksheet::setAlignJustify() 2438 { 2439 auto* item = currentTextItem(); 2440 if (item) 2441 item->setAlignment(Qt::AlignJustify); 2442 } 2443 2444 void Worksheet::setFontFamily(const QString& font) 2445 { 2446 auto* item = currentTextItem(); 2447 if (item) 2448 item->setFontFamily(font); 2449 } 2450 2451 void Worksheet::setFontSize(int size) 2452 { 2453 auto* item = currentTextItem(); 2454 if (item) 2455 item->setFontSize(size); 2456 } 2457 2458 bool Worksheet::isShortcut(const QKeySequence& sequence) 2459 { 2460 return m_shortcuts.contains(sequence); 2461 } 2462 2463 void Worksheet::registerShortcut(QAction* action) 2464 { 2465 for (auto& shortcut : action->shortcuts()) 2466 m_shortcuts.insert(shortcut, action); 2467 2468 connect(action, &QAction::changed, this, &Worksheet::updateShortcut); 2469 } 2470 2471 void Worksheet::updateShortcut() 2472 { 2473 QAction* action = qobject_cast<QAction*>(sender()); 2474 if (!action) 2475 return; 2476 2477 // delete the old shortcuts of this action 2478 QList<QKeySequence> shortcuts = m_shortcuts.keys(action); 2479 for (auto& shortcut : shortcuts) 2480 m_shortcuts.remove(shortcut); 2481 2482 // add the new shortcuts 2483 for (auto& shortcut : action->shortcuts()) 2484 m_shortcuts.insert(shortcut, action); 2485 } 2486 2487 void Worksheet::dragEnterEvent(QGraphicsSceneDragDropEvent* event) 2488 { 2489 if (m_dragEntry) 2490 event->accept(); 2491 else 2492 QGraphicsScene::dragEnterEvent(event); 2493 } 2494 2495 void Worksheet::dragLeaveEvent(QGraphicsSceneDragDropEvent* event) 2496 { 2497 if (!m_dragEntry) { 2498 QGraphicsScene::dragLeaveEvent(event); 2499 return; 2500 } 2501 2502 event->accept(); 2503 if (m_placeholderEntry) { 2504 m_placeholderEntry->startRemoving(); 2505 m_placeholderEntry = nullptr; 2506 } 2507 } 2508 2509 void Worksheet::dragMoveEvent(QGraphicsSceneDragDropEvent* event) 2510 { 2511 if (!m_dragEntry) { 2512 QGraphicsScene::dragMoveEvent(event); 2513 return; 2514 } 2515 2516 QPointF pos = event->scenePos(); 2517 auto* entry = entryAt(pos); 2518 WorksheetEntry* prev = nullptr; 2519 WorksheetEntry* next = nullptr; 2520 if (entry) { 2521 if (pos.y() < entry->y() + entry->size().height()/2) { 2522 prev = entry->previous(); 2523 next = entry; 2524 } else if (pos.y() >= entry->y() + entry->size().height()/2) { 2525 prev = entry; 2526 next = entry->next(); 2527 } 2528 } else { 2529 WorksheetEntry* last = lastEntry(); 2530 if (last && pos.y() > last->y() + last->size().height()) { 2531 prev = last; 2532 next = nullptr; 2533 } 2534 } 2535 2536 bool dragWithHierarchy = m_hierarchySubentriesDrag.size() != 0; 2537 2538 if (prev || next) { 2539 auto* oldPlaceHolder = m_placeholderEntry; 2540 if (prev && prev->type() == PlaceHolderEntry::Type && 2541 (!prev->aboutToBeRemoved() || prev->stopRemoving())) { 2542 m_placeholderEntry = qgraphicsitem_cast<PlaceHolderEntry*>(prev); 2543 if (dragWithHierarchy) 2544 m_placeholderEntry->changeSize(m_hierarchyDragSize); 2545 else 2546 m_placeholderEntry->changeSize(m_dragEntry->size()); 2547 } else if (next && next->type() == PlaceHolderEntry::Type && 2548 (!next->aboutToBeRemoved() || next->stopRemoving())) { 2549 m_placeholderEntry = qgraphicsitem_cast<PlaceHolderEntry*>(next); 2550 if (dragWithHierarchy) 2551 m_placeholderEntry->changeSize(m_hierarchyDragSize); 2552 else 2553 m_placeholderEntry->changeSize(m_dragEntry->size()); 2554 } else { 2555 m_placeholderEntry = new PlaceHolderEntry(this, QSizeF(0,0)); 2556 m_placeholderEntry->setPrevious(prev); 2557 m_placeholderEntry->setNext(next); 2558 if (prev) 2559 prev->setNext(m_placeholderEntry); 2560 else 2561 setFirstEntry(m_placeholderEntry); 2562 if (next) 2563 next->setPrevious(m_placeholderEntry); 2564 else 2565 setLastEntry(m_placeholderEntry); 2566 if (dragWithHierarchy) 2567 m_placeholderEntry->changeSize(m_hierarchyDragSize); 2568 else 2569 m_placeholderEntry->changeSize(m_dragEntry->size()); 2570 } 2571 if (oldPlaceHolder && oldPlaceHolder != m_placeholderEntry) 2572 oldPlaceHolder->startRemoving(); 2573 updateLayout(); 2574 } 2575 2576 const QPoint viewPos = worksheetView()->mapFromScene(pos); 2577 const int viewHeight = worksheetView()->viewport()->height(); 2578 if ((viewPos.y() < 10 || viewPos.y() > viewHeight - 10) && 2579 !m_dragScrollTimer) { 2580 m_dragScrollTimer = new QTimer(this); 2581 m_dragScrollTimer->setSingleShot(true); 2582 m_dragScrollTimer->setInterval(100); 2583 connect(m_dragScrollTimer, SIGNAL(timeout()), this, 2584 SLOT(updateDragScrollTimer())); 2585 m_dragScrollTimer->start(); 2586 } 2587 2588 event->accept(); 2589 } 2590 2591 void Worksheet::dropEvent(QGraphicsSceneDragDropEvent* event) 2592 { 2593 if (!m_dragEntry) 2594 QGraphicsScene::dropEvent(event); 2595 event->accept(); 2596 } 2597 2598 void Worksheet::updateDragScrollTimer() 2599 { 2600 if (!m_dragScrollTimer) 2601 return; 2602 2603 const QPoint viewPos = worksheetView()->viewCursorPos(); 2604 const QWidget* viewport = worksheetView()->viewport(); 2605 const int viewHeight = viewport->height(); 2606 if (!m_dragEntry || !(viewport->rect().contains(viewPos)) || 2607 (viewPos.y() >= 10 && viewPos.y() <= viewHeight - 10)) { 2608 delete m_dragScrollTimer; 2609 m_dragScrollTimer = nullptr; 2610 return; 2611 } 2612 2613 if (viewPos.y() < 10) 2614 worksheetView()->scrollBy(-10*(10 - viewPos.y())); 2615 else 2616 worksheetView()->scrollBy(10*(viewHeight - viewPos.y())); 2617 2618 m_dragScrollTimer->start(); 2619 } 2620 2621 void Worksheet::updateEntryCursor(QGraphicsSceneMouseEvent* event) 2622 { 2623 // determine the worksheet entry near which the entry cursor will be shown 2624 resetEntryCursor(); 2625 if (event->button() == Qt::LeftButton && !focusItem()) 2626 { 2627 const qreal y = event->scenePos().y(); 2628 for (auto* entry = firstEntry(); entry; entry = entry->next()) 2629 { 2630 if (entry == firstEntry() && y < entry->y() ) 2631 { 2632 m_choosenCursorEntry = firstEntry(); 2633 break; 2634 } 2635 else if (entry->y() < y && (entry->next() && y < entry->next()->y())) 2636 { 2637 m_choosenCursorEntry = entry->next(); 2638 break; 2639 } 2640 else if (entry->y() < y && entry == lastEntry()) 2641 { 2642 m_isCursorEntryAfterLastEntry = true; 2643 break; 2644 } 2645 } 2646 } 2647 2648 if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) 2649 drawEntryCursor(); 2650 } 2651 2652 void Worksheet::addEntryFromEntryCursor() 2653 { 2654 qDebug() << "Add new entry from entry cursor"; 2655 if (m_isCursorEntryAfterLastEntry) 2656 insertCommandEntry(lastEntry()); 2657 else 2658 insertCommandEntryBefore(m_choosenCursorEntry); 2659 resetEntryCursor(); 2660 } 2661 2662 void Worksheet::animateEntryCursor() 2663 { 2664 if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && m_entryCursorItem) 2665 m_entryCursorItem->setVisible(!m_entryCursorItem->isVisible()); 2666 } 2667 2668 void Worksheet::resetEntryCursor() 2669 { 2670 m_choosenCursorEntry = nullptr; 2671 m_isCursorEntryAfterLastEntry = false; 2672 m_entryCursorItem->hide(); 2673 } 2674 2675 void Worksheet::drawEntryCursor() 2676 { 2677 if (m_entryCursorItem && (m_choosenCursorEntry || (m_isCursorEntryAfterLastEntry && lastEntry()))) 2678 { 2679 qreal x; 2680 qreal y; 2681 if (m_isCursorEntryAfterLastEntry) 2682 { 2683 x = lastEntry()->x(); 2684 y = lastEntry()->y() + lastEntry()->size().height() - (EntryCursorWidth - 1); 2685 } 2686 else 2687 { 2688 x = m_choosenCursorEntry->x(); 2689 y = m_choosenCursorEntry->y(); 2690 } 2691 m_entryCursorItem->setLine(x,y,x+EntryCursorLength,y); 2692 m_entryCursorItem->show(); 2693 } 2694 } 2695 2696 void Worksheet::setType(Worksheet::Type type) 2697 { 2698 m_type = type; 2699 } 2700 2701 Worksheet::Type Worksheet::type() const 2702 { 2703 return m_type; 2704 } 2705 2706 void Worksheet::changeEntryType(WorksheetEntry* target, int newType) 2707 { 2708 if (target && target->type() != newType) 2709 { 2710 bool animation_state = m_animationsEnabled; 2711 m_animationsEnabled = false; 2712 2713 QString content; 2714 2715 int targetEntryType = target->type(); 2716 switch(targetEntryType) 2717 { 2718 case CommandEntry::Type: 2719 content = static_cast<CommandEntry*>(target)->command(); 2720 break; 2721 case MarkdownEntry::Type: 2722 content = static_cast<MarkdownEntry*>(target)->plainText(); 2723 break; 2724 case TextEntry::Type: 2725 content = static_cast<TextEntry*>(target)->text(); 2726 break; 2727 case LatexEntry::Type: 2728 content = static_cast<LatexEntry*>(target)->plain(); 2729 } 2730 2731 auto* newEntry = WorksheetEntry::create(newType, this); 2732 if (newEntry) 2733 { 2734 newEntry->setContent(content); 2735 auto* tmp = target; 2736 2737 newEntry->setPrevious(tmp->previous()); 2738 newEntry->setNext(tmp->next()); 2739 2740 tmp->setPrevious(nullptr); 2741 tmp->setNext(nullptr); 2742 tmp->clearFocus(); 2743 tmp->forceRemove(); 2744 2745 if (newEntry->previous()) 2746 newEntry->previous()->setNext(newEntry); 2747 else 2748 setFirstEntry(newEntry); 2749 2750 if (newEntry->next()) 2751 newEntry->next()->setPrevious(newEntry); 2752 else 2753 setLastEntry(newEntry); 2754 2755 if (newType == HierarchyEntry::Type || targetEntryType == HierarchyEntry::Type) 2756 updateHierarchyLayout(); 2757 updateLayout(); 2758 makeVisible(newEntry); 2759 focusEntry(newEntry); 2760 setModified(); 2761 newEntry->focusEntry(); 2762 } 2763 m_animationsEnabled = animation_state; 2764 } 2765 } 2766 2767 bool Worksheet::isValidEntry(WorksheetEntry* entry) 2768 { 2769 for (auto* iter = firstEntry(); iter; iter = iter->next()) 2770 if (entry == iter) 2771 return true; 2772 2773 return false; 2774 } 2775 2776 void Worksheet::selectionRemove() 2777 { 2778 for (auto* entry : m_selectedEntries) 2779 if (isValidEntry(entry)) 2780 entry->startRemoving(); 2781 2782 m_selectedEntries.clear(); 2783 } 2784 2785 void Worksheet::selectionEvaluate() 2786 { 2787 // run entries in worksheet order: from top to down 2788 for (auto* entry = firstEntry(); entry; entry = entry->next()) 2789 if (m_selectedEntries.indexOf(entry) != -1) 2790 entry->evaluate(); 2791 } 2792 2793 void Worksheet::selectionMoveUp() 2794 { 2795 bool moveHierarchyEntry = false; 2796 // movement up should have an order from top to down. 2797 for(auto* entry = firstEntry(); entry; entry = entry->next()) 2798 if(m_selectedEntries.indexOf(entry) != -1) 2799 if (entry->previous() && m_selectedEntries.indexOf(entry->previous()) == -1) 2800 { 2801 entry->moveToPrevious(false); 2802 if (entry->type() == HierarchyEntry::Type) 2803 moveHierarchyEntry = true; 2804 } 2805 if (moveHierarchyEntry) 2806 updateHierarchyLayout(); 2807 updateLayout(); 2808 } 2809 2810 void Worksheet::selectionMoveDown() 2811 { 2812 bool moveHierarchyEntry = false; 2813 // movement up should have an order from down to top. 2814 for(auto* entry = lastEntry(); entry; entry = entry->previous()) 2815 if(m_selectedEntries.indexOf(entry) != -1) 2816 if (entry->next() && m_selectedEntries.indexOf(entry->next()) == -1) 2817 { 2818 entry->moveToNext(false); 2819 if (entry->type() == HierarchyEntry::Type) 2820 moveHierarchyEntry = true; 2821 } 2822 if (moveHierarchyEntry) 2823 updateHierarchyLayout(); 2824 updateLayout(); 2825 } 2826 2827 void Worksheet::notifyEntryFocus(WorksheetEntry* entry) 2828 { 2829 if (entry) 2830 { 2831 m_circularFocusBuffer.enqueue(entry); 2832 2833 if (m_circularFocusBuffer.size() > 2) 2834 m_circularFocusBuffer.dequeue(); 2835 } 2836 else 2837 m_circularFocusBuffer.clear(); 2838 } 2839 2840 void Worksheet::collapseAllResults() 2841 { 2842 for (auto* entry = firstEntry(); entry; entry = entry->next()) 2843 if (entry->type() == CommandEntry::Type) 2844 static_cast<CommandEntry*>(entry)->collapseResults(); 2845 } 2846 2847 void Worksheet::uncollapseAllResults() 2848 { 2849 for (auto* entry = firstEntry(); entry; entry = entry->next()) 2850 if (entry->type() == CommandEntry::Type) 2851 static_cast<CommandEntry*>(entry)->expandResults(); 2852 } 2853 2854 void Worksheet::removeAllResults() 2855 { 2856 bool remove = false; 2857 2858 if (KMessageBox::shouldBeShownContinue(QLatin1String("WarnAboutAllResultsRemoving"))) 2859 { 2860 KMessageBox::ButtonCode btn = KMessageBox::warningContinueCancel( 2861 views().first(), 2862 i18n("This action will remove all results without the possibility of cancellation. Are you sure?"), 2863 i18n("Remove all results"), 2864 KStandardGuiItem::cont(), 2865 KStandardGuiItem::cancel(), 2866 QLatin1String("WarnAboutAllResultsRemoving") 2867 ); 2868 remove = (btn == KMessageBox::Continue); 2869 } 2870 else 2871 remove = true; 2872 2873 if (remove) 2874 { 2875 for (auto *entry = firstEntry(); entry; entry = entry->next()) 2876 if (entry->type() == CommandEntry::Type) 2877 static_cast<CommandEntry*>(entry)->removeResults(); 2878 } 2879 } 2880 2881 void Worksheet::addToExectuionSelection() 2882 { 2883 for (auto* entry : m_selectedEntries) 2884 if (entry->type() == CommandEntry::Type) 2885 static_cast<CommandEntry*>(entry)->addToExecution(); 2886 } 2887 2888 void Worksheet::excludeFromExecutionSelection() 2889 { 2890 for (auto* entry : m_selectedEntries) 2891 if (entry->type() == CommandEntry::Type) 2892 static_cast<CommandEntry*>(entry)->excludeFromExecution(); 2893 } 2894 2895 void Worksheet::collapseSelectionResults() 2896 { 2897 for (auto* entry : m_selectedEntries) 2898 if (entry->type() == CommandEntry::Type) 2899 static_cast<CommandEntry*>(entry)->collapseResults(); 2900 } 2901 2902 void Worksheet::uncollapseSelectionResults() 2903 { 2904 for (auto* entry : m_selectedEntries) 2905 if (entry->type() == CommandEntry::Type) 2906 static_cast<CommandEntry*>(entry)->expandResults(); 2907 } 2908 2909 void Worksheet::removeSelectionResults() 2910 { 2911 for (auto* entry : m_selectedEntries) 2912 if (entry->type() == CommandEntry::Type) 2913 static_cast<CommandEntry*>(entry)->removeResults(); 2914 } 2915 2916 void Worksheet::requestScrollToHierarchyEntry(QString hierarchyText) 2917 { 2918 for (auto* entry = firstEntry(); entry; entry = entry->next()) 2919 { 2920 if (entry->type() == HierarchyEntry::Type) 2921 { 2922 auto* hierarchEntry = static_cast<HierarchyEntry*>(entry); 2923 if (hierarchEntry->hierarchyText() == hierarchyText) 2924 worksheetView()->scrollTo(hierarchEntry->y()); 2925 } 2926 } 2927 } 2928 2929 WorksheetEntry * Worksheet::cutSubentriesForHierarchy(HierarchyEntry* hierarchyEntry) 2930 { 2931 Q_ASSERT(hierarchyEntry->next()); 2932 auto* cutBegin = hierarchyEntry->next(); 2933 auto* cutEnd = cutBegin; 2934 2935 bool isCutEnd = false; 2936 int level = (int)hierarchyEntry->level(); 2937 while (!isCutEnd && cutEnd && cutEnd->next()) 2938 { 2939 auto* next = cutEnd->next(); 2940 if (next->type() == HierarchyEntry::Type && (int)static_cast<HierarchyEntry*>(next)->level() <= level) 2941 isCutEnd = true; 2942 else 2943 cutEnd = next; 2944 } 2945 2946 //cutEnd not an end of all entries 2947 if (cutEnd->next()) 2948 { 2949 hierarchyEntry->setNext(cutEnd->next()); 2950 cutEnd->setNext(nullptr); 2951 } 2952 else 2953 { 2954 hierarchyEntry->setNext(nullptr); 2955 setLastEntry(hierarchyEntry); 2956 } 2957 2958 cutBegin->setPrevious(nullptr); 2959 2960 for(auto* entry = cutBegin; entry; entry = entry->next()) 2961 entry->hide(); 2962 2963 return cutBegin; 2964 } 2965 2966 void Worksheet::insertSubentriesForHierarchy(HierarchyEntry* hierarchyEntry, WorksheetEntry* storedSubentriesBegin) 2967 { 2968 auto* previousNext = hierarchyEntry->next(); 2969 hierarchyEntry->setNext(storedSubentriesBegin); 2970 storedSubentriesBegin->show(); 2971 2972 auto* storedEnd = storedSubentriesBegin; 2973 while(storedEnd->next()) 2974 { 2975 storedEnd = storedEnd->next(); 2976 storedEnd->show(); 2977 } 2978 storedEnd->setNext(previousNext); 2979 if (!previousNext) 2980 setLastEntry(storedEnd); 2981 } 2982 2983 void Worksheet::handleSettingsChanges() 2984 { 2985 for (auto* entry = firstEntry(); entry; entry = entry->next()) 2986 entry->updateAfterSettingsChanges(); 2987 }