File indexing completed on 2024-04-28 11:21:03

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