File indexing completed on 2022-12-06 18:50:27

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