Warning, file /education/cantor/src/worksheet.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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