[education/cantor] src: Move worksheet hierarchy handling into a manager

Alexander Semke <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 59ae70e88806c14b7c69fb4ac174d009f1bd9fad by Alexander Semke, on behalf of Nanhao Lv.
Committed on 26/07/2026 at 19:50.
Pushed by asemke into branch 'master'.

Move worksheet hierarchy handling into a manager

M  +1    -0    src/CMakeLists.txt
M  +1    -0    src/test/CMakeLists.txt
M  +2169 -3335 src/worksheet.cpp
M  +4    -46   src/worksheet.h
A  +1308 -0    src/worksheethierarchymanager.cpp  *
A  +110  -0    src/worksheethierarchymanager.h  *

The files marked with a * at the end have a non valid license. Please read: https://community.kde.org/Policies/Licensing_Policy and use the headers which are listed at that page.


https://invent.kde.org/education/cantor/-/commit/59ae70e88806c14b7c69fb4ac174d009f1bd9fad

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e5914920..86ea33a0 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -63,6 +63,7 @@ set(cantor_PART_SRCS
    cantor_part.cpp
    cantorcompletionmodel.cpp
    worksheet.cpp
+   worksheethierarchymanager.cpp
    worksheetview.cpp
    worksheetentry.cpp
    worksheettexteditoritem.cpp
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
index d27d31e4..1b3e7f70 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -1,5 +1,6 @@
 set(worksheettest_SRCS
     ../worksheet.cpp
+    ../worksheethierarchymanager.cpp
     ../worksheetview.cpp
     ../worksheetentry.cpp
     ../worksheettexteditoritem.cpp
diff --git a/src/worksheet.cpp b/src/worksheet.cpp
index 4aa7a198..d8f566a3 100644
--- a/src/worksheet.cpp
+++ b/src/worksheet.cpp
@@ -14,17 +14,14 @@
 #include "markdownentry.h"
 #include "pagebreakentry.h"
 #include "placeholderentry.h"
-#include "resultitem.h"
 #include "settings.h"
 #include "textentry.h"
+#include "worksheethierarchymanager.h"
 #include "worksheetview.h"
-#include "lib/animationresult.h"
 #include "lib/backend.h"
 #include "lib/extension.h"
 #include "lib/helpresult.h"
-#include "lib/imageresult.h"
 #include "lib/jupyterutils.h"
-#include "lib/pdfresult.h"
 #include "lib/result.h"
 #include "lib/session.h"
 
@@ -44,7 +41,6 @@
 #include <QActionGroup>
 #include <QFile>
 #include <QScopedValueRollback>
-#include <QSet>
 
 #include <KMessageBox>
 #include <KActionCollection>
@@ -60,14 +56,6 @@
 #include <libxslt/xsltutils.h>
 #include <libxslt/transform.h>
 
-namespace
-{
-    const QLatin1String TocNodeTypeChapter("chapter");
-    const QLatin1String TocNodeTypeSection("section");
-    const QLatin1String TocNodeTypeCommand("command");
-    const QLatin1String TocNodeTypePlot("plot");
-}
-
 const double Worksheet::LeftMargin = 4;
 const double Worksheet::RightMargin = 4;
 const double Worksheet::TopMargin = 12;
@@ -79,6 +67,8 @@ Worksheet::Worksheet(Cantor::Backend* backend, QWidget* parent, bool useDefaultW
     m_cursorItemTimer(new QTimer(this)),
     m_useDefaultWorksheetParameters(useDefaultWorksheetParameters)
 {
+    m_hierarchyManager = new WorksheetHierarchyManager(this);
+
     m_entryCursorItem = addLine(0,0,0,0);
     const QColor& color = (palette().color(QPalette::Base).lightness() < 128) ? Qt::white : Qt::black;
     QPen pen(color);
@@ -291,7 +281,7 @@ void Worksheet::updateLayout()
     }
 
     // Hierarchy controls are hidden while printing.
-    const qreal hierarchyControlsWidth = m_isPrinting ? 0.0 : static_cast<qreal>(m_hierarchyMaxDepth) * (WorksheetEntry::ControlElementWidth + WorksheetEntry::ControlElementBorder);
+    const qreal hierarchyControlsWidth = m_isPrinting ? 0.0 : static_cast<qreal>(m_hierarchyManager->hierarchyMaxDepth()) * (WorksheetEntry::ControlElementWidth + WorksheetEntry::ControlElementBorder);
 
     const qreal w = m_viewWidth - LeftMargin - RightMargin - hierarchyControlsWidth;
 
@@ -315,3821 +305,2675 @@ void Worksheet::updateLayout()
 
 void Worksheet::refreshTocStructure()
 {
-    if (m_isClosing || m_isLoadingFromFile)
-        return;
-
-    m_tocRefreshScheduled = false;
-    m_tocNodeSnapshot = collectTocNodes();
-
-    if (!m_currentTocNodeId.isEmpty())
-    {
-        bool currentNodeStillExists = false;
-        for (const QVariant& nodeValue : m_tocNodeSnapshot)
-        {
-            if (nodeValue.toMap().value(QStringLiteral("id")).toString() == m_currentTocNodeId)
-            {
-                currentNodeStillExists = true;
-                break;
-            }
-        }
-
-        if (!currentNodeStillExists)
-            setCurrentTocNode(QString());
-    }
-
-    Q_EMIT tocNodesChanged(m_tocNodeSnapshot);
+    m_hierarchyManager->refreshTocStructure();
 }
 
 void Worksheet::scheduleTocStructureRefresh()
 {
-    if (m_isClosing || m_isLoadingFromFile || m_tocRefreshScheduled)
-        return;
-
-    m_tocRefreshScheduled = true;
-    QTimer::singleShot(0, this, [this]()
-    {
-        if (!m_tocRefreshScheduled)
-            return;
-
-        refreshTocStructure();
-    });
+    m_hierarchyManager->scheduleTocStructureRefresh();
 }
 
 void Worksheet::emitTocNodeSnapshot()
 {
-    if (m_isClosing)
-        return;
-
-    if (m_tocNodeSnapshot.isEmpty() && firstEntry())
-        m_tocNodeSnapshot = collectTocNodes();
-
-    Q_EMIT tocNodesChanged(m_tocNodeSnapshot);
+    m_hierarchyManager->emitTocNodeSnapshot();
 }
 
-QVariantList Worksheet::collectTocNodes()
+void Worksheet::updateHierarchyLayout()
 {
-    QVariantList nodes;
-    QVector<QString> hierarchyNodeIds;
-    QVector<int> hierarchyDepths;
-
-    visitLogicalEntries([&](WorksheetEntry* entry)
-    {
-        if (entry->type() == HierarchyEntry::Type)
-        {
-            auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
-            const int depth = static_cast<int>(hierarchyEntry->level()) - static_cast<int>(HierarchyEntry::HierarchyLevel::Chapter);
-
-            while (!hierarchyDepths.isEmpty() && hierarchyDepths.last() >= depth)
-            {
-                hierarchyDepths.removeLast();
-                hierarchyNodeIds.removeLast();
-            }
-
-            const QString parentNodeId = hierarchyNodeIds.isEmpty() ? QString() : hierarchyNodeIds.last();
-            const QString hierarchyId = hierarchyEntry->hierarchyId();
-            const QString title = hierarchyEntry->text();
-            const QString displayText = hierarchyEntry->hierarchyText().isEmpty()
-                ? hierarchyEntry->text()
-                : hierarchyEntry->hierarchyText() + QLatin1Char(' ') + hierarchyEntry->text();
-
-            QVariantMap node;
-            node.insert(QStringLiteral("id"), hierarchyId);
-            node.insert(QStringLiteral("parentId"), parentNodeId);
-            node.insert(QStringLiteral("type"), depth == 0 ? QString(TocNodeTypeChapter) : QString(TocNodeTypeSection));
-            node.insert(QStringLiteral("title"), title);
-            node.insert(QStringLiteral("displayText"), displayText);
-            node.insert(QStringLiteral("hierarchyText"), hierarchyEntry->hierarchyText());
-            node.insert(QStringLiteral("depth"), depth);
-            node.insert(QStringLiteral("editable"), true);
-            node.insert(QStringLiteral("navigable"), true);
-            node.insert(QStringLiteral("hierarchyId"), hierarchyId);
-            node.insert(QStringLiteral("resultIndex"), -1);
-            node.insert(QStringLiteral("entryId"), hierarchyId);
-            nodes.append(node);
-
-            hierarchyNodeIds.append(hierarchyId);
-            hierarchyDepths.append(depth);
-        }
-        else if (entry->type() == CommandEntry::Type)
-        {
-            auto* commandEntry = static_cast<CommandEntry*>(entry);
-            const QString parentNodeId = hierarchyNodeIds.isEmpty() ? QString() : hierarchyNodeIds.last();
-            const QString commandNodeId = buildCommandNodeId(commandEntry);
-
-            QVariantMap commandNode;
-            commandNode.insert(QStringLiteral("id"), commandNodeId);
-            commandNode.insert(QStringLiteral("parentId"), parentNodeId);
-            commandNode.insert(QStringLiteral("type"), QString(TocNodeTypeCommand));
-            commandNode.insert(QStringLiteral("title"), commandTocTitle());
-            commandNode.insert(QStringLiteral("displayText"), commandTocDisplayText(commandEntry));
-            commandNode.insert(QStringLiteral("hierarchyText"), QString());
-            commandNode.insert(QStringLiteral("depth"), hierarchyDepths.isEmpty() ? 0 : hierarchyDepths.last() + 1);
-            commandNode.insert(QStringLiteral("editable"), false);
-            commandNode.insert(QStringLiteral("navigable"), true);
-            commandNode.insert(QStringLiteral("hierarchyId"), QString());
-            commandNode.insert(QStringLiteral("resultIndex"), -1);
-            commandNode.insert(QStringLiteral("entryId"), commandEntry->commandId());
-            nodes.append(commandNode);
-
-            if (auto* expression = commandEntry->expression())
-            {
-                const auto& results = expression->results();
-                int plotCount = 0;
-                for (auto* result : results)
-                {
-                    if (isPlotResult(result))
-                        ++plotCount;
-                }
-
-                int plotOrdinal = 0;
-                for (int index = 0; index < results.size(); ++index)
-                {
-                    auto* result = results.at(index);
-                    if (!isPlotResult(result))
-                        continue;
-
-                    ++plotOrdinal;
-
-                    QVariantMap plotNode;
-                    const QString customTitle = result->displayName();
-                    plotNode.insert(QStringLiteral("id"), buildPlotNodeId(commandEntry->commandId(), result->resultId()));
-                    plotNode.insert(QStringLiteral("parentId"), commandNodeId);
-                    plotNode.insert(QStringLiteral("type"), QString(TocNodeTypePlot));
-                    plotNode.insert(QStringLiteral("title"), plotTocTitle(result));
-                    plotNode.insert(QStringLiteral("customTitle"), customTitle);
-                    plotNode.insert(QStringLiteral("displayText"), plotTocDisplayText(commandEntry, result, plotOrdinal, plotCount));
-                    plotNode.insert(QStringLiteral("hierarchyText"), QString());
-                    plotNode.insert(QStringLiteral("depth"), hierarchyDepths.isEmpty() ? 1 : hierarchyDepths.last() + 2);
-                    plotNode.insert(QStringLiteral("editable"), true);
-                    plotNode.insert(QStringLiteral("navigable"), true);
-                    plotNode.insert(QStringLiteral("hierarchyId"), QString());
-                    plotNode.insert(QStringLiteral("resultIndex"), index);
-                    plotNode.insert(QStringLiteral("resultId"), result->resultId());
-                    plotNode.insert(QStringLiteral("entryId"), commandEntry->commandId());
-                    nodes.append(plotNode);
-                }
-            }
-        }
-        return true;
-    });
-
-    return nodes;
+    m_hierarchyManager->updateHierarchyLayout();
 }
 
-QString Worksheet::buildCommandNodeId(CommandEntry* entry)
+void Worksheet::updateHierarchyControlsLayout(WorksheetEntry* startEntry)
 {
-    if (!entry)
-        return QString();
-
-    const QString& commandId = entry->commandId();
-    if (commandId.isEmpty())
-        return QString();
-
-    return QStringLiteral("command:") + commandId;
+    m_hierarchyManager->updateHierarchyControlsLayout(startEntry);
 }
 
-QString Worksheet::buildPlotNodeId(const QString& commandId, const QString& resultId) const
+std::vector<WorksheetEntry*> Worksheet::hierarchySubelements(HierarchyEntry* hierarchyEntry) const
 {
-    if (commandId.isEmpty() || resultId.isEmpty())
-        return QString();
-
-    return QStringLiteral("plot:%1:%2").arg(commandId, resultId);
+    return m_hierarchyManager->hierarchySubelements(hierarchyEntry);
 }
 
-bool Worksheet::parseCommandNodeId(const QString& nodeId, QString* commandId) const
+void Worksheet::updateCurrentHierarchy(WorksheetEntry* entry)
 {
-    const QString prefix = QStringLiteral("command:");
-    if (!nodeId.startsWith(prefix))
-        return false;
-
-    const QString parsedCommandId = nodeId.mid(prefix.size());
-    if (parsedCommandId.isEmpty() || parsedCommandId.contains(QLatin1Char(':')))
-        return false;
-
-    if (commandId)
-        *commandId = parsedCommandId;
-
-    return true;
+    m_hierarchyManager->updateCurrentHierarchy(entry);
 }
 
-bool Worksheet::parsePlotNodeId(const QString& nodeId, QString* commandId, QString* resultId) const
+void Worksheet::normalizeDraggedHierarchyLevels(HierarchyEntry* rootEntry, WorksheetEntry* previousEntry, const std::vector<WorksheetEntry*>& subentries)
 {
-    const QString prefix = QStringLiteral("plot:");
-    if (!nodeId.startsWith(prefix))
-        return false;
-
-    const QStringList parts = nodeId.mid(prefix.size()).split(QLatin1Char(':'));
-    if (parts.size() != 2 || parts.at(0).isEmpty() || parts.at(1).isEmpty())
-        return false;
-
-    if (commandId)
-        *commandId = parts.at(0);
-    if (resultId)
-        *resultId = parts.at(1);
-
-    return true;
+    m_hierarchyManager->normalizeDraggedHierarchyLevels(rootEntry, previousEntry, subentries);
 }
 
-QString Worksheet::commandTocTitle() const
+void Worksheet::updateCurrentHierarchyFromView(const QRectF& viewRect)
 {
-    return i18n("Command");
+    m_hierarchyManager->updateCurrentHierarchyFromView(viewRect);
 }
 
-QString Worksheet::commandTocDisplayText(CommandEntry* entry) const
+void Worksheet::updateCurrentHierarchyFromEntry(WorksheetEntry* entry)
 {
-    auto* expression = entry ? entry->expression() : nullptr;
-    if (m_showExpressionIds && expression && expression->id() != -1)
-        return i18n("Command %1", expression->id());
-
-    return commandTocTitle();
+    m_hierarchyManager->updateCurrentHierarchyFromEntry(entry);
 }
 
-QString Worksheet::plotTocTitle(Cantor::Result* result) const
+void Worksheet::followHierarchyFromView()
 {
-    if (result && !result->displayName().isEmpty())
-        return result->displayName();
-
-    return i18n("Plot");
+    m_hierarchyManager->followHierarchyFromView();
 }
 
-QString Worksheet::plotTocDisplayText(CommandEntry* entry, Cantor::Result* result, int plotOrdinal, int plotCount) const
+void Worksheet::updateEntrySize(WorksheetEntry* entry)
 {
-    const QString title = plotTocTitle(result);
-    auto* expression = entry ? entry->expression() : nullptr;
-    if (m_showExpressionIds && expression && expression->id() != -1)
+    QScopedValueRollback<bool> layoutGuard(m_layoutUpdateInProgress, true);
+    bool cursorRectVisible = false;
+    bool atEnd = worksheetView()->isAtEnd();
+    if (currentTextItem()) {
+        QRectF cursorRect = currentTextItem()->sceneCursorRect();
+        cursorRectVisible = worksheetView()->isVisible(cursorRect);
+    }
+
+    if (Settings::useOldCantorEntriesIndent() == false)
     {
-        if (plotCount > 1)
-            return i18n("%1 %2.%3", title, expression->id(), plotOrdinal);
+        qreal newMaxPromptWidth = m_maxPromptWidth;
+        if (entry->type() == CommandEntry::Type)
+            newMaxPromptWidth = static_cast<CommandEntry*>(entry)->promptItemWidth();
+        else if (entry->type() == HierarchyEntry::Type)
+            newMaxPromptWidth = static_cast<HierarchyEntry*>(entry)->hierarchyItemWidth();
+
+        // If width of prompt (if presence) of the entry more, is less than that of current
+        // maximum, then we need full layout update
+        if (newMaxPromptWidth > m_maxPromptWidth)
+        {
+            updateLayout();
+            return;
+        }
+    }
 
-        return i18n("%1 %2", title, expression->id());
+    qreal y = entry->y() + entry->size().height();
+    for (entry = entry->next(); entry; entry = entry->next()) {
+        entry->setY(y);
+        y += entry->size().height();
     }
 
-    if (plotCount > 1)
-        return i18n("%1 %2", title, plotOrdinal);
+    if (!m_isLoadingFromFile)
+        updateHierarchyControlsLayout(entry);
 
-    return title;
+    setSceneRect(QRectF(0, 0, sceneRect().width(), y));
+    if (cursorRectVisible)
+        makeVisible(worksheetCursor());
+    else if (atEnd)
+        worksheetView()->scrollToEnd();
+    drawEntryCursor();
 }
 
-bool Worksheet::isPlotResult(Cantor::Result* result)
+void Worksheet::setRequestedWidth(QGraphicsObject* object, qreal width)
 {
-    if (!result || result->role() != Cantor::Result::Role::Plot)
-        return false;
-
-    if (result->type() == Cantor::ImageResult::Type)
-        return true;
-
-    if (result->type() == Cantor::AnimationResult::Type)
-        return true;
+    qreal oldWidth = m_itemWidths[object];
+    m_itemWidths[object] = width;
 
-    return result->type() == Cantor::PdfResult::Type && dynamic_cast<Cantor::PdfResult*>(result);
+    if (width > m_maxWidth || oldWidth == m_maxWidth)
+    {
+        m_maxWidth = width;
+        qreal y = lastEntry() ? lastEntry()->size().height() + lastEntry()->y() : 0;
+        setSceneRect(QRectF(0, 0, m_maxWidth + LeftMargin + RightMargin, y));
+    }
 }
 
-bool Worksheet::visitLogicalEntries(WorksheetEntry* first, const std::function<bool(WorksheetEntry*)>& visitor)
+void Worksheet::removeRequestedWidth(QGraphicsObject* object)
 {
-    for (auto* entry = first; entry; entry = entry->next())
-    {
-        if (!visitor(entry))
-            return false;
+    if (!m_itemWidths.contains(object))
+        return;
 
-        if (entry->type() != HierarchyEntry::Type)
-            continue;
+    qreal width = m_itemWidths[object];
+    m_itemWidths.remove(object);
 
-        auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
-        if (auto* hiddenEntry = hierarchyEntry->hiddenSubentries())
-        {
-            if (!visitLogicalEntries(hiddenEntry, visitor))
-                return false;
-        }
+    if (width == m_maxWidth)
+    {
+        m_maxWidth = 0;
+        for (qreal width : m_itemWidths.values())
+            if (width > m_maxWidth)
+                m_maxWidth = width;
+        qreal y = lastEntry() ? lastEntry()->size().height() + lastEntry()->y() : 0;
+        setSceneRect(QRectF(0, 0, m_maxWidth + LeftMargin + RightMargin, y));
     }
-
-    return true;
 }
 
-bool Worksheet::visitLogicalEntries(const std::function<bool(WorksheetEntry*)>& visitor)
+bool Worksheet::isEmpty()
 {
-    return visitLogicalEntries(firstEntry(), visitor);
+    return !m_firstEntry;
 }
 
-bool Worksheet::findHierarchyEntryById(WorksheetEntry* first, const QString& hierarchyId, QVector<HierarchyEntry*> collapsedAncestors, HierarchySearchResult& result)
+bool Worksheet::isLoadingFromFile()
 {
-    for (auto* entry = first; entry; entry = entry->next())
-    {
-        if (entry->type() != HierarchyEntry::Type)
-            continue;
-
-        auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
-
-        if (hierarchyEntry->hierarchyId() == hierarchyId)
-        {
-            result.entry = hierarchyEntry;
-            result.collapsedAncestors = collapsedAncestors;
-            return true;
-        }
-
-        if (auto* hiddenEntry = hierarchyEntry->hiddenSubentries())
-        {
-            auto childAncestors = collapsedAncestors;
-            childAncestors.append(hierarchyEntry);
-
-            if (findHierarchyEntryById(hiddenEntry, hierarchyId, childAncestors, result))
-                return true;
-        }
-    }
-
-    return false;
+    return m_isLoadingFromFile;
 }
 
-Worksheet::HierarchySearchResult Worksheet::findHierarchyEntryById(const QString& hierarchyId)
+void Worksheet::makeVisible(WorksheetEntry* entry)
 {
-    HierarchySearchResult result;
-
-    if (hierarchyId.isEmpty())
-        return result;
-
-    findHierarchyEntryById(firstEntry(), hierarchyId, {}, result);
-    return result;
+    QRectF r = entry->boundingRect();
+    r = entry->mapRectToScene(r);
+    r.adjust(0, -10, 0, 10);
+    worksheetView()->makeVisible(r);
 }
 
-bool Worksheet::findCommandEntryById(WorksheetEntry* first, const QString& commandId, QVector<HierarchyEntry*> collapsedAncestors, CommandSearchResult& result)
+void Worksheet::makeVisible(const KWorksheetCursor& cursor)
 {
-    for (auto* entry = first; entry; entry = entry->next())
+    if(!cursor.cursor().isValid())
     {
-        if (entry->type() == CommandEntry::Type)
-        {
-            auto* commandEntry = static_cast<CommandEntry*>(entry);
-            if (commandEntry->commandId() == commandId)
-            {
-                result.entry = commandEntry;
-                result.collapsedAncestors = collapsedAncestors;
-                return true;
-            }
-        }
-
-        if (entry->type() != HierarchyEntry::Type)
-            continue;
-
-        auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
-        if (auto* hiddenEntry = hierarchyEntry->hiddenSubentries())
-        {
-            auto childAncestors = collapsedAncestors;
-            childAncestors.append(hierarchyEntry);
-
-            if (findCommandEntryById(hiddenEntry, commandId, childAncestors, result))
-                return true;
-        }
+        if(cursor.entry())
+            makeVisible(cursor.entry());
+        return;
     }
-
-    return false;
+    QRectF r = cursor.textItem()->sceneCursorRect();
+    QRectF er = cursor.entry()->boundingRect();
+    er = cursor.entry()->mapRectToScene(er);
+    er.adjust(0, -10, 0, 10);
+    r.adjust(0, qMax(qreal(-100.0), er.top() - r.top()),
+             0, qMin(qreal(100.0), er.bottom() - r.bottom()));
+    worksheetView()->makeVisible(r);
 }
 
-Worksheet::CommandSearchResult Worksheet::findCommandEntryById(const QString& commandId)
+void Worksheet::makeVisible(const WorksheetCursor& cursor)
 {
-    CommandSearchResult result;
-
-    if (commandId.isEmpty())
-        return result;
-
-    findCommandEntryById(firstEntry(), commandId, {}, result);
-    return result;
+    if (cursor.textCursor().isNull()) {
+        if (cursor.entry())
+            makeVisible(cursor.entry());
+        return;
+    }
+    QRectF r = cursor.textItem()->sceneCursorRect(cursor.textCursor());
+    QRectF er = cursor.entry()->boundingRect();
+    er = cursor.entry()->mapRectToScene(er);
+    er.adjust(0, -10, 0, 10);
+    r.adjust(0, qMax(qreal(-100.0), er.top() - r.top()),
+             0, qMin(qreal(100.0), er.bottom() - r.bottom()));
+    worksheetView()->makeVisible(r);
 }
 
-bool Worksheet::expandHierarchyAncestors(const QVector<HierarchyEntry*>& ancestors)
+WorksheetView* Worksheet::worksheetView()
 {
-    bool expanded = false;
-
-    for (auto* ancestor : ancestors)
-    {
-        if (!ancestor || !ancestor->hasHiddenSubentries())
-            continue;
-
-        WorksheetEntry* hiddenSubentries = ancestor->takeHiddenSubentries();
+    return static_cast<WorksheetView*>(views().first());
+}
 
-        if (!hiddenSubentries)
-            continue;
+void Worksheet::setModified()
+{
+    if (!m_isClosing && !m_isLoadingFromFile)
+        Q_EMIT modified();
+}
 
-        insertSubentriesForHierarchy(ancestor, hiddenSubentries);
-        expanded = true;
-    }
+KWorksheetCursor Worksheet::worksheetCursor()
+{
+    auto* entry = currentEntry();
+    auto* item = currentTextItem();
 
-    return expanded;
+    if (!entry || !item)
+        return KWorksheetCursor();
+    return KWorksheetCursor(entry, item, item->view()->cursorPosition());
 }
 
-void Worksheet::updateHierarchyLayout()
+void Worksheet::setWorksheetCursor(const WorksheetCursor& cursor)
 {
-    QSet<QString> usedHierarchyIds;
-    QSet<QString> usedCommandIds;
-    QSet<QString> usedResultIds;
+    if (!cursor.isValid() || !cursor.textItem())
+        return;
 
-    m_hierarchyMaxDepth = 0;
-    std::vector<int> hierarchyNumbers;
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->clearSelection();
+    if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->clearSelection();
 
-    visitLogicalEntries([&](WorksheetEntry* entry)
-    {
-        if (entry->type() == HierarchyEntry::Type)
-        {
-            auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
+    m_legacylastFocusedTextItem = cursor.textItem();
+    m_lastFocusedTextItem = nullptr;
 
-            hierarchyEntry->updateHierarchyLevel(hierarchyNumbers);
+    cursor.textItem()->setTextCursor(cursor.textCursor());
+}
 
-            m_hierarchyMaxDepth = std::max(m_hierarchyMaxDepth, hierarchyNumbers.size());
+void Worksheet::setWorksheetCursor(const KWorksheetCursor& cursor)
+{
+    if(!cursor.isValid())
+        return;
 
-            // Keep IDs valid for old files and duplicated entries.
-            QString hierarchyId = hierarchyEntry->hierarchyId();
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->clearSelection();
+    if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->clearSelection();
 
-            if (hierarchyId.isEmpty() || usedHierarchyIds.contains(hierarchyId))
-            {
-                hierarchyEntry->regenerateHierarchyId();
-                hierarchyId = hierarchyEntry->hierarchyId();
-            }
+    m_lastFocusedTextItem = cursor.textItem();
+    m_legacylastFocusedTextItem = nullptr;
 
-            usedHierarchyIds.insert(hierarchyId);
-        }
-        else if (entry->type() == CommandEntry::Type)
-        {
-            auto* commandEntry = static_cast<CommandEntry*>(entry);
-            QString commandId = commandEntry->commandId();
+    cursor.textItem()->view()->setSelection(cursor.foundRange());
+}
 
-            if (commandId.isEmpty() || usedCommandIds.contains(commandId))
-            {
-                commandEntry->regenerateCommandId();
-                commandId = commandEntry->commandId();
-            }
 
-            usedCommandIds.insert(commandId);
+WorksheetEntry* Worksheet::currentEntry()
+{
+    // Entry cursor activate
+    if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry)
+        return nullptr;
 
-            if (auto* expression = commandEntry->expression())
-            {
-                const auto& results = expression->results();
-                for (auto* result : results)
-                {
-                    if (!result)
-                        continue;
-
-                    QString resultId = result->resultId();
-                    if (resultId.isEmpty() || usedResultIds.contains(resultId))
-                    {
-                        result->regenerateResultId();
-                        resultId = result->resultId();
-                    }
-
-                    usedResultIds.insert(resultId);
-                }
-            }
+    auto* item = focusItem();
+    if (!item /*&& !hasFocus()*/)
+        item = m_lastFocusedTextItem;
+    /*else
+      m_focusItem = item;*/
+    while (item && (item->type() < QGraphicsItem::UserType ||
+                    item->type() >= QGraphicsItem::UserType + 100))
+        item = item->parentItem();
+    if (item) {
+        auto* entry = qobject_cast<WorksheetEntry*>(item->toGraphicsObject());
+        if (entry && entry->aboutToBeRemoved()) {
+            if (entry->isAncestorOf(m_lastFocusedTextItem))
+                m_lastFocusedTextItem = nullptr;
+            return nullptr;
         }
-
-        return true;
-    });
-
-    refreshTocStructure();
+        return entry;
+    }
+    return nullptr;
 }
 
-void Worksheet::updateHierarchyControlsLayout(WorksheetEntry* startEntry)
+WorksheetEntry* Worksheet::firstEntry()
 {
-    Q_UNUSED(startEntry);
+    return m_firstEntry;
+}
 
-    std::vector<HierarchyEntry*> levelEntries;
-    const int numerationBegin = static_cast<int>(HierarchyEntry::HierarchyLevel::Chapter);
-    const int numerationEnd = static_cast<int>(HierarchyEntry::HierarchyLevel::EndValue);
+WorksheetEntry* Worksheet::lastEntry()
+{
+    return m_lastEntry;
+}
 
-    for (int i = numerationBegin; i < numerationEnd; ++i)
-        levelEntries.push_back(nullptr);
+void Worksheet::setFirstEntry(WorksheetEntry* entry)
+{
+    if (m_firstEntry)
+        disconnect(m_firstEntry, &WorksheetEntry::aboutToBeDeleted,
+                   this, &Worksheet::invalidateFirstEntry);
+    m_firstEntry = entry;
+    if (m_firstEntry)
+        connect(m_firstEntry, &WorksheetEntry::aboutToBeDeleted,
+                this, &Worksheet::invalidateFirstEntry, Qt::DirectConnection);
+}
 
-    WorksheetEntry* lastRealEntry = nullptr;
+void Worksheet::setLastEntry(WorksheetEntry* entry)
+{
+    if (m_lastEntry)
+        disconnect(m_lastEntry, &WorksheetEntry::aboutToBeDeleted,
+                   this, &Worksheet::invalidateLastEntry);
+    m_lastEntry = entry;
+    if (m_lastEntry)
+        connect(m_lastEntry, &WorksheetEntry::aboutToBeDeleted,
+                this, &Worksheet::invalidateLastEntry, Qt::DirectConnection);
+}
 
-    for (auto* entry = firstEntry(); entry; entry = entry->next())
-    {
-        if (entry->type() == PlaceHolderEntry::Type || entry->aboutToBeRemoved())
-            continue;
+void Worksheet::invalidateFirstEntry()
+{
+    if (m_firstEntry)
+        setFirstEntry(m_firstEntry->next());
+}
 
-        lastRealEntry = entry;
+void Worksheet::invalidateLastEntry()
+{
+    if (m_lastEntry)
+        setLastEntry(m_lastEntry->previous());
+}
 
-        if (entry->type() != HierarchyEntry::Type)
-            continue;
+WorksheetEntry* Worksheet::entryAt(qreal x, qreal y)
+{
+    auto* item = itemAt(x, y, QTransform());
+    while (item && (item->type() <= QGraphicsItem::UserType ||
+                    item->type() >= QGraphicsItem::UserType + 100))
+        item = item->parentItem();
+    if (item)
+        return qobject_cast<WorksheetEntry*>(item->toGraphicsObject());
+    return nullptr;
+}
 
-        auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
+WorksheetEntry* Worksheet::entryAt(QPointF p)
+{
+    return entryAt(p.x(), p.y());
+}
 
-        const int index = static_cast<int>(hierarchyEntry->level()) - numerationBegin;
+void Worksheet::focusEntry(WorksheetEntry* entry)
+{
+    if (!entry)
+        return;
+    entry->focusEntry();
+    resetEntryCursor();
+    //bool rt = entry->acceptRichText();
+    //setActionsEnabled(rt);
+    //setAcceptRichText(rt);
+    //ensureCursorVisible();
+}
 
-        if (index < 0 || index >= static_cast<int>(levelEntries.size()))
-            continue;
+void Worksheet::startDrag(WorksheetEntry* entry, QDrag* drag)
+{
+    if (m_readOnly || !entry || !drag)
+        return;
 
-        if (!levelEntries[index])
-        {
-            levelEntries[index] = hierarchyEntry;
-            continue;
-        }
+    resetEntryCursor();
 
-        // Close previous controls at this level and below.
-        for (int i = index; i < static_cast<int>(levelEntries.size()); ++i)
-        {
-            auto* openEntry = levelEntries[i];
+    m_dragEntry = entry;
 
-            if (!openEntry)
-                continue;
+    WorksheetEntry* originalPrevious = entry->previous();
+    WorksheetEntry* originalNext = entry->next();
+    WorksheetEntry* previous = originalPrevious;
+    WorksheetEntry* next = originalNext;
 
-            const qreal ownBottom = openEntry->y() + openEntry->size().height() - WorksheetEntry::VerticalMargin;
-            const qreal controlEnd = hierarchyEntry->y() - WorksheetEntry::VerticalMargin;
-            const bool hasSubelements = controlEnd > ownBottom;
+    m_placeholderEntry = new PlaceHolderEntry(this, entry->size());
+    m_placeholderEntry->setPrevious(previous);
+    m_placeholderEntry->setNext(next);
 
-            openEntry->updateControlElementForHierarchy(qMax(controlEnd, ownBottom), m_hierarchyMaxDepth, hasSubelements);
+    if (previous)
+        previous->setNext(m_placeholderEntry);
+    else
+        setFirstEntry(m_placeholderEntry);
 
-            levelEntries[i] = nullptr;
-        }
+    if (next)
+        next->setPrevious(m_placeholderEntry);
+    else
+        setLastEntry(m_placeholderEntry);
 
-        levelEntries[index] = hierarchyEntry;
-    }
+    m_dragEntry->hide();
 
-    if (!lastRealEntry)
-        return;
+    const Qt::DropAction action = drag->exec();
 
-    const qreal documentEnd = lastRealEntry->y() + lastRealEntry->size().height() - WorksheetEntry::VerticalMargin;
+    bool positionChanged = false;
 
-    for (auto* openEntry : levelEntries)
+    if (action == Qt::MoveAction && m_placeholderEntry)
     {
-        if (!openEntry)
-            continue;
-
-        const qreal ownBottom = openEntry->y() + openEntry->size().height() - WorksheetEntry::VerticalMargin;
-        const qreal controlEnd = qMax(documentEnd, ownBottom);
-        const bool hasSubelements = controlEnd > ownBottom;
-
-        openEntry->updateControlElementForHierarchy(controlEnd, m_hierarchyMaxDepth, hasSubelements);
+        previous = m_placeholderEntry->previous();
+        next = m_placeholderEntry->next();
+        positionChanged = previous != originalPrevious || next != originalNext;
     }
-}
 
-std::vector<WorksheetEntry*> Worksheet::hierarchySubelements(HierarchyEntry* hierarchyEntry) const
-{
-    std::vector<WorksheetEntry*> subentries;
-
-    Q_ASSERT(hierarchyEntry);
+    removeDragPlaceholder();
 
-    bool subentriesEnd = false;
-    const int level = (int)hierarchyEntry->level();
-    for (auto* entry = hierarchyEntry->next(); entry && !subentriesEnd; entry = entry->next())
-    {
-        if (entry->type() == HierarchyEntry::Type)
-        {
-            if ((int)(static_cast<HierarchyEntry*>(entry)->level()) <= level)
-                subentriesEnd = true;
-            else
-                subentries.push_back(entry);
-        }
-        else
-            subentries.push_back(entry);
-    }
-    return subentries;
-}
+    m_dragEntry->setPrevious(previous);
+    m_dragEntry->setNext(next);
 
-void Worksheet::updateCurrentHierarchy(WorksheetEntry* entry)
-{
-    QString nodeId;
+    if (previous)
+        previous->setNext(m_dragEntry);
+    else
+        setFirstEntry(m_dragEntry);
 
-    if (entry && entry->type() == CommandEntry::Type)
-        nodeId = buildCommandNodeId(static_cast<CommandEntry*>(entry));
+    if (next)
+        next->setPrevious(m_dragEntry);
     else
-        nodeId = hierarchyIdForEntry(entry);
+        setLastEntry(m_dragEntry);
 
-    setCurrentTocNode(nodeId);
-}
+    m_dragEntry->show();
+    const bool hierarchyMoved = m_dragEntry->type() == HierarchyEntry::Type;
 
-QString Worksheet::hierarchyIdForEntry(WorksheetEntry* entry) const
-{
-    for (auto* current = entry; current; current = current->previous())
-    {
-        if (current->type() == HierarchyEntry::Type)
-            return static_cast<HierarchyEntry*>(current)->hierarchyId();
-    }
+    m_dragEntry->focusEntry();
+    const QPointF scenePosition = worksheetView()->sceneCursorPos();
 
-    return QString();
-}
+    if (entryAt(scenePosition) != m_dragEntry)
+        m_dragEntry->hideActionBar();
 
-void Worksheet::setCurrentTocNode(const QString& nodeId)
-{
-    if (m_currentTocNodeId == nodeId)
-        return;
+    m_dragEntry = nullptr;
 
-    m_currentTocNodeId = nodeId;
+    if (hierarchyMoved && positionChanged)
+        updateHierarchyLayout();
+
+    updateLayout();
 
-    Q_EMIT currentTocNodeChanged(nodeId);
+    if (positionChanged)
+        setModified();
 }
 
-void Worksheet::normalizeDraggedHierarchyLevels(HierarchyEntry* rootEntry, WorksheetEntry* previousEntry, const std::vector<WorksheetEntry*>& subentries)
+void Worksheet::startDragWithHierarchy(HierarchyEntry* entry, QDrag* drag, QSizeF responsibleZoneSize)
 {
-    if (!rootEntry)
+    if (m_readOnly || !entry || !drag)
         return;
 
-    HierarchyEntry* previousHierarchyEntry = nullptr;
+    resetEntryCursor();
 
-    for (auto* entry = previousEntry; entry; entry = entry->previous())
-    {
-        if (entry->type() != HierarchyEntry::Type)
-            continue;
+    m_dragEntry = entry;
+    m_hierarchySubentriesDrag = hierarchySubelements(entry);
+    m_hierarchyDragSize = responsibleZoneSize;
 
-        previousHierarchyEntry = static_cast<HierarchyEntry*>(entry);
-        break;
-    }
+    WorksheetEntry* originalPrevious = entry->previous();
+    WorksheetEntry* originalNext = nullptr;
 
-    const int minimumLevel = static_cast<int>(HierarchyEntry::HierarchyLevel::Chapter);
-    const int maximumLevel = static_cast<int>(HierarchyEntry::HierarchyLevel::Subparagraph);
+    if (!m_hierarchySubentriesDrag.empty())
+        originalNext = m_hierarchySubentriesDrag.back()->next();
+    else
+        originalNext = entry->next();
 
-    int maximumAllowedRootLevel = minimumLevel;
+    WorksheetEntry* previous = originalPrevious;
+    WorksheetEntry* next = originalNext;
 
-    if (previousHierarchyEntry)
-        maximumAllowedRootLevel = qMin(maximumLevel, static_cast<int>(previousHierarchyEntry->level()) + 1);
+    m_placeholderEntry = new PlaceHolderEntry(this, responsibleZoneSize);
+    m_placeholderEntry->setPrevious(previous);
+    m_placeholderEntry->setNext(next);
 
-    const int oldRootLevel = static_cast<int>(rootEntry->level());
-    if (oldRootLevel <= maximumAllowedRootLevel)
-        return;
+    if (previous)
+        previous->setNext(m_placeholderEntry);
+    else
+        setFirstEntry(m_placeholderEntry);
 
-    const int levelOffset = maximumAllowedRootLevel - oldRootLevel;
+    if (next)
+        next->setPrevious(m_placeholderEntry);
+    else
+        setLastEntry(m_placeholderEntry);
 
-    const auto shiftLevel = [levelOffset, minimumLevel, maximumLevel](HierarchyEntry* hierarchyEntry)
-    {
-        if (!hierarchyEntry)
-            return;
+    m_dragEntry->hide();
 
-        const int newLevel = qBound(minimumLevel, static_cast<int>(hierarchyEntry->level()) + levelOffset, maximumLevel);
+    for (auto* subentry : m_hierarchySubentriesDrag)
+        subentry->hide();
 
-        hierarchyEntry->setLevel(static_cast<HierarchyEntry::HierarchyLevel>(newLevel));
-    };
+    const Qt::DropAction action = drag->exec();
 
-    shiftLevel(rootEntry);
+    bool positionChanged = false;
 
-    for (auto* entry : subentries)
+    if (action == Qt::MoveAction && m_placeholderEntry)
     {
-        if (entry->type() != HierarchyEntry::Type)
-            continue;
-
-        shiftLevel(static_cast<HierarchyEntry*>(entry));
+        previous = m_placeholderEntry->previous();
+        next = m_placeholderEntry->next();
+        positionChanged = previous != originalPrevious || next != originalNext;
     }
-}
 
-void Worksheet::updateCurrentHierarchyFromView(const QRectF& viewRect)
-{
-    if (m_hierarchyTrackingSource != HierarchyTrackingSource::Viewport || m_layoutUpdateInProgress || m_isLoadingFromFile || viewRect.isEmpty())
-        return;
+    removeDragPlaceholder();
 
-    const qreal activationOffset = qMin<qreal>(48.0, viewRect.height() * 0.15);
+    m_dragEntry->setPrevious(previous);
 
-    const qreal activationY = viewRect.top() + activationOffset;
+    if (previous)
+        previous->setNext(m_dragEntry);
+    else
+        setFirstEntry(m_dragEntry);
 
-    WorksheetEntry* activeEntry = nullptr;
+    WorksheetEntry* lastDraggingEntry = m_hierarchySubentriesDrag.empty() ? static_cast<WorksheetEntry*>(entry) : m_hierarchySubentriesDrag.back();
 
-    for (auto* entry = firstEntry(); entry; entry = entry->next())
-    {
-        if (!entry->isVisible())
-            continue;
+    lastDraggingEntry->setNext(next);
 
-        if (entry->scenePos().y() > activationY)
-            break;
+    if (next)
+        next->setPrevious(lastDraggingEntry);
+    else
+        setLastEntry(lastDraggingEntry);
 
-        activeEntry = entry;
-    }
+    if (positionChanged)
+        normalizeDraggedHierarchyLevels(entry, previous, m_hierarchySubentriesDrag);
 
-    updateCurrentHierarchy(activeEntry);
-}
+    m_dragEntry->show();
 
-void Worksheet::updateCurrentHierarchyFromEntry(WorksheetEntry* entry)
-{
-    m_hierarchyTrackingSource = HierarchyTrackingSource::FocusedEntry;
+    for (auto* subentry : m_hierarchySubentriesDrag)
+        subentry->show();
 
-    updateCurrentHierarchy(entry);
-}
+    m_dragEntry->focusEntry();
 
-void Worksheet::followHierarchyFromView()
-{
-    m_hierarchyTrackingSource = HierarchyTrackingSource::Viewport;
+    const QPointF scenePosition = worksheetView()->sceneCursorPos();
+    if (entryAt(scenePosition) != m_dragEntry)
+        m_dragEntry->hideActionBar();
 
-    // Defer until viewRect() reflects the final scroll position.
-    QTimer::singleShot(0, this, [this]() {
-        if (m_hierarchyTrackingSource != HierarchyTrackingSource::Viewport)
-            return;
+#ifndef NDEBUG
+    for (auto* current = firstEntry(); current; current = current->next())
+        Q_ASSERT(current->type() != PlaceHolderEntry::Type);
+#endif
 
-        updateCurrentHierarchyFromView(worksheetView()->viewRect());
-    });
-}
+    m_hierarchySubentriesDrag.clear();
+    m_dragEntry = nullptr;
 
-void Worksheet::updateEntrySize(WorksheetEntry* entry)
-{
-    QScopedValueRollback<bool> layoutGuard(m_layoutUpdateInProgress, true);
-    bool cursorRectVisible = false;
-    bool atEnd = worksheetView()->isAtEnd();
-    if (currentTextItem()) {
-        QRectF cursorRect = currentTextItem()->sceneCursorRect();
-        cursorRectVisible = worksheetView()->isVisible(cursorRect);
-    }
-
-    if (Settings::useOldCantorEntriesIndent() == false)
-    {
-        qreal newMaxPromptWidth = m_maxPromptWidth;
-        if (entry->type() == CommandEntry::Type)
-            newMaxPromptWidth = static_cast<CommandEntry*>(entry)->promptItemWidth();
-        else if (entry->type() == HierarchyEntry::Type)
-            newMaxPromptWidth = static_cast<HierarchyEntry*>(entry)->hierarchyItemWidth();
-
-        // If width of prompt (if presence) of the entry more, is less than that of current
-        // maximum, then we need full layout update
-        if (newMaxPromptWidth > m_maxPromptWidth)
-        {
-            updateLayout();
-            return;
-        }
-    }
-
-    qreal y = entry->y() + entry->size().height();
-    for (entry = entry->next(); entry; entry = entry->next()) {
-        entry->setY(y);
-        y += entry->size().height();
-    }
-
-    if (!m_isLoadingFromFile)
-        updateHierarchyControlsLayout(entry);
+    updateHierarchyLayout();
+    updateLayout();
 
-    setSceneRect(QRectF(0, 0, sceneRect().width(), y));
-    if (cursorRectVisible)
-        makeVisible(worksheetCursor());
-    else if (atEnd)
-        worksheetView()->scrollToEnd();
-    drawEntryCursor();
+    if (positionChanged)
+        setModified();
 }
 
-void Worksheet::setRequestedWidth(QGraphicsObject* object, qreal width)
+void Worksheet::evaluate()
 {
-    qreal oldWidth = m_itemWidths[object];
-    m_itemWidths[object] = width;
+    qDebug()<<"evaluate worksheet";
+    // login if not done yet
+    if (!m_readOnly && m_session && m_session->status() == Cantor::Session::Disable)
+        loginToSession();
 
-    if (width > m_maxWidth || oldWidth == m_maxWidth)
-    {
-        m_maxWidth = width;
-        qreal y = lastEntry() ? lastEntry()->size().height() + lastEntry()->y() : 0;
-        setSceneRect(QRectF(0, 0, m_maxWidth + LeftMargin + RightMargin, y));
+    // evaluate the worksheet if the login was successful
+    if (m_session && m_session->status() == Cantor::Session::Done) {
+        firstEntry()->evaluate(WorksheetEntry::EvaluateNext);
+        setModified();
     }
 }
 
-void Worksheet::removeRequestedWidth(QGraphicsObject* object)
+void Worksheet::evaluateCurrentEntry()
 {
-    if (!m_itemWidths.contains(object))
-        return;
+    // login if not done yet
+    if (!m_readOnly && m_session && m_session->status() == Cantor::Session::Disable)
+        loginToSession();
 
-    qreal width = m_itemWidths[object];
-    m_itemWidths.remove(object);
+    // evaluate the current entry if the login was successful
+    if (!m_session)
+        return;
 
-    if (width == m_maxWidth)
+    // the current status of the session should be Done or Running - the later is the case when we're
+    // waiting for the additional input like Maxima's help requests or assumptions for integrate(), etc.
+    if (m_session->status() == Cantor::Session::Done || m_session->status() == Cantor::Session::Running)
     {
-        m_maxWidth = 0;
-        for (qreal width : m_itemWidths.values())
-            if (width > m_maxWidth)
-                m_maxWidth = width;
-        qreal y = lastEntry() ? lastEntry()->size().height() + lastEntry()->y() : 0;
-        setSceneRect(QRectF(0, 0, m_maxWidth + LeftMargin + RightMargin, y));
+        if(auto* entry = currentEntry())
+            entry->evaluateCurrentItem();
     }
 }
 
-bool Worksheet::isEmpty()
+bool Worksheet::completionEnabled()
 {
-    return !m_firstEntry;
+    return m_completionEnabled;
 }
 
-bool Worksheet::isLoadingFromFile()
+void Worksheet::showCompletion()
 {
-    return m_isLoadingFromFile;
+    auto* current = currentEntry();
+    if (current)
+        current->showCompletion();
 }
 
-void Worksheet::makeVisible(WorksheetEntry* entry)
+WorksheetEntry* Worksheet::appendEntry(const int type, bool focus)
 {
-    QRectF r = entry->boundingRect();
-    r = entry->mapRectToScene(r);
-    r.adjust(0, -10, 0, 10);
-    worksheetView()->makeVisible(r);
-}
+    auto* entry = WorksheetEntry::create(type, this);
 
-void Worksheet::makeVisible(const KWorksheetCursor& cursor)
-{
-    if(!cursor.cursor().isValid())
+    if (entry)
     {
-        if(cursor.entry())
-            makeVisible(cursor.entry());
-        return;
+        qDebug() << "Entry Appended";
+        entry->setPrevious(lastEntry());
+        if (lastEntry())
+            lastEntry()->setNext(entry);
+        if (!firstEntry())
+            setFirstEntry(entry);
+        setLastEntry(entry);
+        if (!m_isLoadingFromFile)
+        {
+            updateHierarchyLayout();
+            updateLayout();
+            if (focus)
+            {
+                makeVisible(entry);
+                focusEntry(entry);
+            }
+            setModified();
+        }
     }
-    QRectF r = cursor.textItem()->sceneCursorRect();
-    QRectF er = cursor.entry()->boundingRect();
-    er = cursor.entry()->mapRectToScene(er);
-    er.adjust(0, -10, 0, 10);
-    r.adjust(0, qMax(qreal(-100.0), er.top() - r.top()),
-             0, qMin(qreal(100.0), er.bottom() - r.bottom()));
-    worksheetView()->makeVisible(r);
+    return entry;
 }
 
-void Worksheet::makeVisible(const WorksheetCursor& cursor)
+WorksheetEntry* Worksheet::appendCommandEntry()
 {
-    if (cursor.textCursor().isNull()) {
-        if (cursor.entry())
-            makeVisible(cursor.entry());
-        return;
-    }
-    QRectF r = cursor.textItem()->sceneCursorRect(cursor.textCursor());
-    QRectF er = cursor.entry()->boundingRect();
-    er = cursor.entry()->mapRectToScene(er);
-    er.adjust(0, -10, 0, 10);
-    r.adjust(0, qMax(qreal(-100.0), er.top() - r.top()),
-             0, qMin(qreal(100.0), er.bottom() - r.bottom()));
-    worksheetView()->makeVisible(r);
+   return appendEntry(CommandEntry::Type);
 }
 
-WorksheetView* Worksheet::worksheetView()
+WorksheetEntry* Worksheet::appendTextEntry()
 {
-    return static_cast<WorksheetView*>(views().first());
+   return appendEntry(TextEntry::Type);
 }
 
-void Worksheet::setModified()
+WorksheetEntry* Worksheet::appendMarkdownEntry()
 {
-    if (!m_isClosing && !m_isLoadingFromFile)
-        Q_EMIT modified();
+   return appendEntry(MarkdownEntry::Type);
 }
 
-KWorksheetCursor Worksheet::worksheetCursor()
+WorksheetEntry* Worksheet::appendPageBreakEntry()
 {
-    auto* entry = currentEntry();
-    auto* item = currentTextItem();
-
-    if (!entry || !item)
-        return KWorksheetCursor();
-    return KWorksheetCursor(entry, item, item->view()->cursorPosition());
+    return appendEntry(PageBreakEntry::Type);
 }
 
-void Worksheet::setWorksheetCursor(const WorksheetCursor& cursor)
+WorksheetEntry* Worksheet::appendImageEntry()
 {
-    if (!cursor.isValid() || !cursor.textItem())
-        return;
-
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->clearSelection();
-    if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->clearSelection();
-
-    m_legacylastFocusedTextItem = cursor.textItem();
-    m_lastFocusedTextItem = nullptr;
-
-    cursor.textItem()->setTextCursor(cursor.textCursor());
+   return appendEntry(ImageEntry::Type);
 }
 
-void Worksheet::setWorksheetCursor(const KWorksheetCursor& cursor)
+WorksheetEntry* Worksheet::appendLatexEntry()
 {
-    if(!cursor.isValid())
-        return;
-
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->clearSelection();
-    if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->clearSelection();
-
-    m_lastFocusedTextItem = cursor.textItem();
-    m_legacylastFocusedTextItem = nullptr;
-
-    cursor.textItem()->view()->setSelection(cursor.foundRange());
+    return appendEntry(LatexEntry::Type);
 }
 
-
-WorksheetEntry* Worksheet::currentEntry()
+void Worksheet::appendCommandEntry(const QString& text)
 {
-    // Entry cursor activate
-    if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry)
-        return nullptr;
+    auto* entry = lastEntry();
+    if(!entry->isEmpty())
+        entry = appendCommandEntry();
 
-    auto* item = focusItem();
-    if (!item /*&& !hasFocus()*/)
-        item = m_lastFocusedTextItem;
-    /*else
-      m_focusItem = item;*/
-    while (item && (item->type() < QGraphicsItem::UserType ||
-                    item->type() >= QGraphicsItem::UserType + 100))
-        item = item->parentItem();
-    if (item) {
-        auto* entry = qobject_cast<WorksheetEntry*>(item->toGraphicsObject());
-        if (entry && entry->aboutToBeRemoved()) {
-            if (entry->isAncestorOf(m_lastFocusedTextItem))
-                m_lastFocusedTextItem = nullptr;
-            return nullptr;
-        }
-        return entry;
+    if (entry)
+    {
+        focusEntry(entry);
+        entry->setContent(text);
+        evaluateCurrentEntry();
     }
-    return nullptr;
 }
 
-WorksheetEntry* Worksheet::firstEntry()
+WorksheetEntry* Worksheet::appendHorizontalRuleEntry()
 {
-    return m_firstEntry;
+    return appendEntry(HorizontalRuleEntry::Type);
 }
 
-WorksheetEntry* Worksheet::lastEntry()
+WorksheetEntry* Worksheet::appendHierarchyEntry()
 {
-    return m_lastEntry;
+    return appendEntry(HierarchyEntry::Type);
 }
 
-void Worksheet::setFirstEntry(WorksheetEntry* entry)
+WorksheetEntry* Worksheet::insertEntry(const int type, WorksheetEntry* current)
 {
-    if (m_firstEntry)
-        disconnect(m_firstEntry, &WorksheetEntry::aboutToBeDeleted,
-                   this, &Worksheet::invalidateFirstEntry);
-    m_firstEntry = entry;
-    if (m_firstEntry)
-        connect(m_firstEntry, &WorksheetEntry::aboutToBeDeleted,
-                this, &Worksheet::invalidateFirstEntry, Qt::DirectConnection);
-}
+    if (!current)
+        current = currentEntry();
 
-void Worksheet::setLastEntry(WorksheetEntry* entry)
-{
-    if (m_lastEntry)
-        disconnect(m_lastEntry, &WorksheetEntry::aboutToBeDeleted,
-                   this, &Worksheet::invalidateLastEntry);
-    m_lastEntry = entry;
-    if (m_lastEntry)
-        connect(m_lastEntry, &WorksheetEntry::aboutToBeDeleted,
-                this, &Worksheet::invalidateLastEntry, Qt::DirectConnection);
+    if (!current)
+        return appendEntry(type);
+
+    auto* next = current->next();
+    WorksheetEntry* entry = nullptr;
+
+    if (!next || next->type() != type || !next->isEmpty())
+    {
+        entry = WorksheetEntry::create(type, this);
+        entry->setPrevious(current);
+        entry->setNext(next);
+        current->setNext(entry);
+        if (next)
+            next->setPrevious(entry);
+        else
+            setLastEntry(entry);
+        updateHierarchyLayout();
+        updateLayout();
+        setModified();
+    } else {
+        entry = next;
+    }
+
+    focusEntry(entry);
+    makeVisible(entry);
+
+    return entry;
 }
 
-void Worksheet::invalidateFirstEntry()
+WorksheetEntry* Worksheet::insertTextEntry(WorksheetEntry* current)
 {
-    if (m_firstEntry)
-        setFirstEntry(m_firstEntry->next());
+    return insertEntry(TextEntry::Type, current);
 }
 
-void Worksheet::invalidateLastEntry()
+WorksheetEntry* Worksheet::insertMarkdownEntry(WorksheetEntry* current)
 {
-    if (m_lastEntry)
-        setLastEntry(m_lastEntry->previous());
+    return insertEntry(MarkdownEntry::Type, current);
 }
 
-WorksheetEntry* Worksheet::entryAt(qreal x, qreal y)
+WorksheetEntry* Worksheet::insertCommandEntry(WorksheetEntry* current)
 {
-    auto* item = itemAt(x, y, QTransform());
-    while (item && (item->type() <= QGraphicsItem::UserType ||
-                    item->type() >= QGraphicsItem::UserType + 100))
-        item = item->parentItem();
-    if (item)
-        return qobject_cast<WorksheetEntry*>(item->toGraphicsObject());
-    return nullptr;
+    return insertEntry(CommandEntry::Type, current);
 }
 
-WorksheetEntry* Worksheet::entryAt(QPointF p)
+WorksheetEntry* Worksheet::insertImageEntry(WorksheetEntry* current)
 {
-    return entryAt(p.x(), p.y());
+    auto* entry = insertEntry(ImageEntry::Type, current);
+    auto* imageEntry = static_cast<ImageEntry*>(entry);
+    QTimer::singleShot(0, this, [=] () {imageEntry->startConfigDialog();});
+    return entry;
 }
 
-void Worksheet::focusEntry(WorksheetEntry* entry)
+WorksheetEntry* Worksheet::insertPageBreakEntry(WorksheetEntry* current)
 {
-    if (!entry)
-        return;
-    entry->focusEntry();
-    resetEntryCursor();
-    //bool rt = entry->acceptRichText();
-    //setActionsEnabled(rt);
-    //setAcceptRichText(rt);
-    //ensureCursorVisible();
+    return insertEntry(PageBreakEntry::Type, current);
 }
 
-void Worksheet::startDrag(WorksheetEntry* entry, QDrag* drag)
+WorksheetEntry* Worksheet::insertLatexEntry(WorksheetEntry* current)
 {
-    if (m_readOnly || !entry || !drag)
-        return;
+    return insertEntry(LatexEntry::Type, current);
+}
 
-    resetEntryCursor();
+WorksheetEntry* Worksheet::insertHorizontalRuleEntry(WorksheetEntry* current)
+{
+    return insertEntry(HorizontalRuleEntry::Type, current);
+}
 
-    m_dragEntry = entry;
+WorksheetEntry* Worksheet::insertHierarchyEntry(WorksheetEntry* current)
+{
+    return insertEntry(HierarchyEntry::Type, current);
+}
 
-    WorksheetEntry* originalPrevious = entry->previous();
-    WorksheetEntry* originalNext = entry->next();
-    WorksheetEntry* previous = originalPrevious;
-    WorksheetEntry* next = originalNext;
+WorksheetEntry* Worksheet::insertEntryBefore(int type, WorksheetEntry* current)
+{
+    if (!current)
+        current = currentEntry();
 
-    m_placeholderEntry = new PlaceHolderEntry(this, entry->size());
-    m_placeholderEntry->setPrevious(previous);
-    m_placeholderEntry->setNext(next);
+    if (!current)
+        return nullptr;
 
-    if (previous)
-        previous->setNext(m_placeholderEntry);
-    else
-        setFirstEntry(m_placeholderEntry);
+    auto* prev = current->previous();
+    WorksheetEntry* entry = nullptr;
 
-    if (next)
-        next->setPrevious(m_placeholderEntry);
+    if(!prev || prev->type() != type || !prev->isEmpty())
+    {
+        entry = WorksheetEntry::create(type, this);
+        entry->setNext(current);
+        entry->setPrevious(prev);
+        current->setPrevious(entry);
+        if (prev)
+            prev->setNext(entry);
+        else
+            setFirstEntry(entry);
+        updateHierarchyLayout();
+        updateLayout();
+        setModified();
+    }
     else
-        setLastEntry(m_placeholderEntry);
-
-    m_dragEntry->hide();
-
-    const Qt::DropAction action = drag->exec();
+        entry = prev;
 
-    bool positionChanged = false;
+    focusEntry(entry);
+    return entry;
+}
 
-    if (action == Qt::MoveAction && m_placeholderEntry)
-    {
-        previous = m_placeholderEntry->previous();
-        next = m_placeholderEntry->next();
-        positionChanged = previous != originalPrevious || next != originalNext;
-    }
+WorksheetEntry* Worksheet::insertTextEntryBefore(WorksheetEntry* current)
+{
+    return insertEntryBefore(TextEntry::Type, current);
+}
 
-    removeDragPlaceholder();
+WorksheetEntry* Worksheet::insertMarkdownEntryBefore(WorksheetEntry* current)
+{
+    return insertEntryBefore(MarkdownEntry::Type, current);
+}
 
-    m_dragEntry->setPrevious(previous);
-    m_dragEntry->setNext(next);
+WorksheetEntry* Worksheet::insertCommandEntryBefore(WorksheetEntry* current)
+{
+    return insertEntryBefore(CommandEntry::Type, current);
+}
 
-    if (previous)
-        previous->setNext(m_dragEntry);
-    else
-        setFirstEntry(m_dragEntry);
+WorksheetEntry* Worksheet::insertPageBreakEntryBefore(WorksheetEntry* current)
+{
+    return insertEntryBefore(PageBreakEntry::Type, current);
+}
 
-    if (next)
-        next->setPrevious(m_dragEntry);
-    else
-        setLastEntry(m_dragEntry);
+WorksheetEntry* Worksheet::insertImageEntryBefore(WorksheetEntry* current)
+{
+    auto* entry = insertEntryBefore(ImageEntry::Type, current);
+    auto* imageEntry = static_cast<ImageEntry*>(entry);
+    QTimer::singleShot(0, this, [=] () {imageEntry->startConfigDialog();});
+    return entry;
+}
 
-    m_dragEntry->show();
-    const bool hierarchyMoved = m_dragEntry->type() == HierarchyEntry::Type;
+WorksheetEntry* Worksheet::insertLatexEntryBefore(WorksheetEntry* current)
+{
+    return insertEntryBefore(LatexEntry::Type, current);
+}
 
-    m_dragEntry->focusEntry();
-    const QPointF scenePosition = worksheetView()->sceneCursorPos();
+WorksheetEntry* Worksheet::insertHorizontalRuleEntryBefore(WorksheetEntry* current)
+{
+    return insertEntryBefore(HorizontalRuleEntry::Type, current);
+}
 
-    if (entryAt(scenePosition) != m_dragEntry)
-        m_dragEntry->hideActionBar();
+WorksheetEntry* Worksheet::insertHierarchyEntryBefore(WorksheetEntry* current)
+{
+    return insertEntryBefore(HierarchyEntry::Type, current);
+}
 
-    m_dragEntry = nullptr;
+void Worksheet::interrupt()
+{
+    if (m_session->status() == Cantor::Session::Running)
+    {
+        m_session->interrupt();
+        Q_EMIT updatePrompt();
+    }
+}
 
-    if (hierarchyMoved && positionChanged)
-        updateHierarchyLayout();
+void Worksheet::interruptCurrentEntryEvaluation()
+{
+    currentEntry()->interruptEvaluation();
+}
 
-    updateLayout();
 
-    if (positionChanged)
-        setModified();
+bool Worksheet::variableHighlightingEnabled() const
+{
+    return m_variableHighlightingEnabled;
 }
 
-void Worksheet::startDragWithHierarchy(HierarchyEntry* entry, QDrag* drag, QSizeF responsibleZoneSize)
+void Worksheet::setVariableHighlightingEnabled(bool enabled)
 {
-    if (m_readOnly || !entry || !drag)
+    if (m_variableHighlightingEnabled == enabled)
+    {
         return;
+    }
+    m_variableHighlightingEnabled = enabled;
 
-    resetEntryCursor();
+    for (auto* entry = firstEntry(); entry; entry = entry->next())
+    {
+        if (entry->type() == CommandEntry::Type)
+            static_cast<CommandEntry*>(entry)->setVariableHighlightingEnabled(enabled);
+    }
+}
 
-    m_dragEntry = entry;
-    m_hierarchySubentriesDrag = hierarchySubelements(entry);
-    m_hierarchyDragSize = responsibleZoneSize;
+void Worksheet::enableCompletion(bool enable)
+{
+    m_completionEnabled=enable;
+}
 
-    WorksheetEntry* originalPrevious = entry->previous();
-    WorksheetEntry* originalNext = nullptr;
+Cantor::Session* Worksheet::session() const
+{
+    return m_session;
+}
 
-    if (!m_hierarchySubentriesDrag.empty())
-        originalNext = m_hierarchySubentriesDrag.back()->next();
-    else
-        originalNext = entry->next();
+bool Worksheet::isRunning()
+{
+    return m_session && m_session->status()==Cantor::Session::Running;
+}
 
-    WorksheetEntry* previous = originalPrevious;
-    WorksheetEntry* next = originalNext;
+bool Worksheet::isReadOnly()
+{
+    return m_readOnly;
+}
 
-    m_placeholderEntry = new PlaceHolderEntry(this, responsibleZoneSize);
-    m_placeholderEntry->setPrevious(previous);
-    m_placeholderEntry->setNext(next);
+bool Worksheet::showExpressionIds()
+{
+    return m_showExpressionIds;
+}
 
-    if (previous)
-        previous->setNext(m_placeholderEntry);
-    else
-        setFirstEntry(m_placeholderEntry);
+bool Worksheet::animationsEnabled()
+{
+    return m_animationsEnabled;
+}
 
-    if (next)
-        next->setPrevious(m_placeholderEntry);
-    else
-        setLastEntry(m_placeholderEntry);
+void Worksheet::enableAnimations(bool enable)
+{
+    m_animationsEnabled = enable;
+}
 
-    m_dragEntry->hide();
+bool Worksheet::embeddedMathEnabled()
+{
+    return m_embeddedMathEnabled && m_mathRenderer.mathRenderAvailable();
+}
 
-    for (auto* subentry : m_hierarchySubentriesDrag)
-        subentry->hide();
+void Worksheet::enableEmbeddedMath(bool enable)
+{
+    m_embeddedMathEnabled = enable;
+}
 
-    const Qt::DropAction action = drag->exec();
+void Worksheet::enableExpressionNumbering(bool enable)
+{
+    m_showExpressionIds=enable;
+    Q_EMIT updatePrompt();
+    refreshTocStructure();
+    if (views().size() != 0)
+        updateLayout();
+}
 
-    bool positionChanged = false;
+QDomDocument Worksheet::toXML(KZip* archive)
+{
+    QDomDocument doc( QLatin1String("CantorWorksheet") );
+    QDomElement root = doc.createElement( QLatin1String("Worksheet") );
+    root.setAttribute(QLatin1String("backend"), (m_session ? m_session->backend()->name(): m_backendName));
+    doc.appendChild(root);
 
-    if (action == Qt::MoveAction && m_placeholderEntry)
+    for( auto* entry = firstEntry(); entry; entry = entry->next())
     {
-        previous = m_placeholderEntry->previous();
-        next = m_placeholderEntry->next();
-        positionChanged = previous != originalPrevious || next != originalNext;
+        QDomElement el = entry->toXml(doc, archive);
+        root.appendChild( el );
     }
+    return doc;
+}
 
-    removeDragPlaceholder();
-
-    m_dragEntry->setPrevious(previous);
-
-    if (previous)
-        previous->setNext(m_dragEntry);
-    else
-        setFirstEntry(m_dragEntry);
-
-    WorksheetEntry* lastDraggingEntry = m_hierarchySubentriesDrag.empty() ? static_cast<WorksheetEntry*>(entry) : m_hierarchySubentriesDrag.back();
+QJsonDocument Worksheet::toJupyterJson()
+{
+    QJsonDocument doc;
+    QJsonObject root;
 
-    lastDraggingEntry->setNext(next);
+    QJsonObject metadata(m_jupyterMetadata ? *m_jupyterMetadata : QJsonObject());
 
-    if (next)
-        next->setPrevious(lastDraggingEntry);
+    QJsonObject kernalInfo;
+    if (m_session && m_session->backend())
+        kernalInfo = Cantor::JupyterUtils::getKernelspec(m_session->backend());
     else
-        setLastEntry(lastDraggingEntry);
-
-    if (positionChanged)
-        normalizeDraggedHierarchyLevels(entry, previous, m_hierarchySubentriesDrag);
-
-    m_dragEntry->show();
+        kernalInfo.insert(QLatin1String("name"), m_backendName);
+    metadata.insert(QLatin1String("kernelspec"), kernalInfo);
 
-    for (auto* subentry : m_hierarchySubentriesDrag)
-        subentry->show();
+    root.insert(QLatin1String("metadata"), metadata);
 
-    m_dragEntry->focusEntry();
+    // Not sure, but it looks like we support nbformat version 4.5
+    root.insert(QLatin1String("nbformat"), 4);
+    root.insert(QLatin1String("nbformat_minor"), 5);
 
-    const QPointF scenePosition = worksheetView()->sceneCursorPos();
-    if (entryAt(scenePosition) != m_dragEntry)
-        m_dragEntry->hideActionBar();
+    QJsonArray cells;
+    for( auto* entry = firstEntry(); entry; entry = entry->next())
+    {
+        const QJsonValue entryJson = entry->toJupyterJson();
 
-#ifndef NDEBUG
-    for (auto* current = firstEntry(); current; current = current->next())
-        Q_ASSERT(current->type() != PlaceHolderEntry::Type);
-#endif
+        if (!entryJson.isNull())
+            cells.append(entryJson);
+    }
+    root.insert(QLatin1String("cells"), cells);
 
-    m_hierarchySubentriesDrag.clear();
-    m_dragEntry = nullptr;
+    doc.setObject(root);
+    return doc;
+}
 
-    updateHierarchyLayout();
-    updateLayout();
+void Worksheet::save( const QString& filename )
+{
+    QFile file(filename);
+    if ( !file.open(QIODevice::WriteOnly) )
+    {
+        KMessageBox::error( worksheetView(),
+                            i18n( "Cannot write file %1." , filename ),
+                            i18n( "Error - Cantor" ));
+        return;
+    }
 
-    if (positionChanged)
-        setModified();
+    save(&file);
 }
 
-void Worksheet::evaluate()
+QByteArray Worksheet::saveToByteArray()
 {
-    qDebug()<<"evaluate worksheet";
-    // login if not done yet
-    if (!m_readOnly && m_session && m_session->status() == Cantor::Session::Disable)
-        loginToSession();
+    QBuffer buffer;
+    save(&buffer);
 
-    // evaluate the worksheet if the login was successful
-    if (m_session && m_session->status() == Cantor::Session::Done) {
-        firstEntry()->evaluate(WorksheetEntry::EvaluateNext);
-        setModified();
-    }
+    return buffer.buffer();
 }
 
-void Worksheet::evaluateCurrentEntry()
+void Worksheet::save( QIODevice* device)
 {
-    // login if not done yet
-    if (!m_readOnly && m_session && m_session->status() == Cantor::Session::Disable)
-        loginToSession();
+    qDebug()<<"saving to filename";
+    switch (m_type)
+    {
+        case CantorWorksheet:
+        {
+            KZip zipFile( device );
 
-    // evaluate the current entry if the login was successful
-    if (!m_session)
-        return;
+            if ( !zipFile.open(QIODevice::WriteOnly) )
+            {
+                KMessageBox::error( worksheetView(),
+                                    i18n( "Cannot write file." ),
+                                    i18n( "Error - Cantor" ));
+                return;
+            }
 
-    // the current status of the session should be Done or Running - the later is the case when we're
-    // waiting for the additional input like Maxima's help requests or assumptions for integrate(), etc.
-    if (m_session->status() == Cantor::Session::Done || m_session->status() == Cantor::Session::Running)
-    {
-        if(auto* entry = currentEntry())
-            entry->evaluateCurrentItem();
-    }
-}
+            QByteArray content = toXML(&zipFile).toByteArray();
+            zipFile.writeFile( QLatin1String("content.xml"), content.data());
+            break;
+        }
 
-bool Worksheet::completionEnabled()
-{
-    return m_completionEnabled;
-}
+        case JupyterNotebook:
+        {
+            if (!device->isWritable())
+            {
+                KMessageBox::error( worksheetView(),
+                                    i18n( "Cannot write file." ),
+                                    i18n( "Error - Cantor" ));
+                return;
+            }
 
-void Worksheet::showCompletion()
-{
-    auto* current = currentEntry();
-    if (current)
-        current->showCompletion();
+            const QJsonDocument& doc = toJupyterJson();
+            device->write(doc.toJson(QJsonDocument::Indented));
+            break;
+        }
+    }
 }
 
-WorksheetEntry* Worksheet::appendEntry(const int type, bool focus)
+void Worksheet::savePlain(const QString& filename)
 {
-    auto* entry = WorksheetEntry::create(type, this);
+    QFile file(filename);
+    if(!file.open(QIODevice::WriteOnly))
+    {
+        KMessageBox::error(worksheetView(), i18n("Error saving file %1", filename), i18n("Error - Cantor"));
+        return;
+    }
 
-    if (entry)
+    QString cmdSep=QLatin1String(";\n");
+    QString commentStartingSeq = QLatin1String("");
+    QString commentEndingSeq = QLatin1String("");
+
+    if (!m_readOnly)
     {
-        qDebug() << "Entry Appended";
-        entry->setPrevious(lastEntry());
-        if (lastEntry())
-            lastEntry()->setNext(entry);
-        if (!firstEntry())
-            setFirstEntry(entry);
-        setLastEntry(entry);
-        if (!m_isLoadingFromFile)
+        Cantor::Backend * const backend=session()->backend();
+        if (backend->extensions().contains(QLatin1String("ScriptExtension")))
         {
-            updateHierarchyLayout();
-            updateLayout();
-            if (focus)
+            Cantor::ScriptExtension* e=dynamic_cast<Cantor::ScriptExtension*>(backend->extension(QLatin1String(("ScriptExtension"))));
+            if (e)
             {
-                makeVisible(entry);
-                focusEntry(entry);
+                cmdSep=e->commandSeparator();
+                commentStartingSeq = e->commentStartingSequence();
+                commentEndingSeq = e->commentEndingSequence();
             }
-            setModified();
         }
     }
-    return entry;
-}
+    else
+        KMessageBox::information(worksheetView(), i18n("In read-only mode Cantor couldn't guarantee, that the export will be valid for %1", m_backendName), i18n("Cantor"));
 
-WorksheetEntry* Worksheet::appendCommandEntry()
-{
-   return appendEntry(CommandEntry::Type);
-}
+    QTextStream stream(&file);
 
-WorksheetEntry* Worksheet::appendTextEntry()
-{
-   return appendEntry(TextEntry::Type);
-}
+    for(auto* entry = firstEntry(); entry; entry = entry->next())
+    {
+        const QString& str=entry->toPlain(cmdSep, commentStartingSeq, commentEndingSeq);
+        if(!str.isEmpty())
+            stream << str + QLatin1Char('\n');
+    }
 
-WorksheetEntry* Worksheet::appendMarkdownEntry()
-{
-   return appendEntry(MarkdownEntry::Type);
+    file.close();
 }
 
-WorksheetEntry* Worksheet::appendPageBreakEntry()
+void Worksheet::saveLatex(const QString& filename)
 {
-    return appendEntry(PageBreakEntry::Type);
-}
+    qDebug()<<"exporting to Latex: " <<filename;
 
-WorksheetEntry* Worksheet::appendImageEntry()
-{
-   return appendEntry(ImageEntry::Type);
-}
-
-WorksheetEntry* Worksheet::appendLatexEntry()
-{
-    return appendEntry(LatexEntry::Type);
-}
-
-void Worksheet::appendCommandEntry(const QString& text)
-{
-    auto* entry = lastEntry();
-    if(!entry->isEmpty())
-        entry = appendCommandEntry();
-
-    if (entry)
+    QFile file(filename);
+    if(!file.open(QIODevice::WriteOnly))
     {
-        focusEntry(entry);
-        entry->setContent(text);
-        evaluateCurrentEntry();
+        KMessageBox::error(worksheetView(), i18n("Error saving file %1", filename), i18n("Export to LaTeX"));
+        return;
     }
-}
-
-WorksheetEntry* Worksheet::appendHorizontalRuleEntry()
-{
-    return appendEntry(HorizontalRuleEntry::Type);
-}
 
-WorksheetEntry* Worksheet::appendHierarchyEntry()
-{
-    return appendEntry(HierarchyEntry::Type);
-}
+    QString stylesheet = QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("xslt/latex.xsl"));
+    if (stylesheet.isEmpty()) {
+        KMessageBox::error(worksheetView(), i18n("Error loading latex.xsl stylesheet"), i18n("Export to LaTeX"));
+        return;
+    }
 
-WorksheetEntry* Worksheet::insertEntry(const int type, WorksheetEntry* current)
-{
-    if (!current)
-        current = currentEntry();
+    xsltStylesheetPtr xsltStyleSheet = xsltParseStylesheetFile((const xmlChar *)stylesheet.toLocal8Bit().constData());
+    xmlDocPtr output = xmlReadDoc((const xmlChar *)toXML().toString().toStdString().c_str(), nullptr, "utf-8", XML_PARSE_RECOVER | XML_PARSE_NOENT | XML_PARSE_DTDLOAD);
 
-    if (!current)
-        return appendEntry(type);
+    const char *params[16+1];
+    params[0] = nullptr;
+    auto res = xsltApplyStylesheet(xsltStyleSheet, output, params);
+    if (res) {
+        xmlChar *xmlResultBuffer = nullptr;
+        int xmlResultLength = 0;
+        int res = xsltSaveResultToString(&xmlResultBuffer, &xmlResultLength, output, xsltStyleSheet);
+        if (res != -1) {
+            QString outString = QString::fromUtf8((char *)xmlResultBuffer);
 
-    auto* next = current->next();
-    WorksheetEntry* entry = nullptr;
+            // Transform HTML escaped special characters to valid LaTeX characters (&, <, >)
+            QTextStream stream(&file);
+            stream << outString.replace(QLatin1String("&amp;"), QLatin1String("&"))
+                         .replace(QLatin1String("&gt;"), QLatin1String(">"))
+                         .replace(QLatin1String("&lt;"), QLatin1String("<"));
+            file.close();
+        }
 
-    if (!next || next->type() != type || !next->isEmpty())
-    {
-        entry = WorksheetEntry::create(type, this);
-        entry->setPrevious(current);
-        entry->setNext(next);
-        current->setNext(entry);
-        if (next)
-            next->setPrevious(entry);
-        else
-            setLastEntry(entry);
-        updateHierarchyLayout();
-        updateLayout();
-        setModified();
-    } else {
-        entry = next;
+        xmlFree(xmlResultBuffer);
     }
 
-    focusEntry(entry);
-    makeVisible(entry);
+    xsltFreeStylesheet(xsltStyleSheet);
+    xmlFreeDoc(res);
+    xmlFreeDoc(output);
 
-    return entry;
+    xsltCleanupGlobals();
+    xmlCleanupParser();
 }
 
-WorksheetEntry* Worksheet::insertTextEntry(WorksheetEntry* current)
+bool Worksheet::load(const QString& filename )
 {
-    return insertEntry(TextEntry::Type, current);
-}
+    qDebug() << "loading worksheet" << filename;
+    QFile file(filename);
+    if (!file.open(QIODevice::ReadOnly)) {
+        KMessageBox::error(worksheetView(), i18n("Couldn't open the file %1.", filename), i18n("Open File"));
+        return false;
+    }
 
-WorksheetEntry* Worksheet::insertMarkdownEntry(WorksheetEntry* current)
-{
-    return insertEntry(MarkdownEntry::Type, current);
-}
+    bool rc = load(&file);
+    if (rc && !m_readOnly)
+        m_session->setWorksheetPath(filename);
 
-WorksheetEntry* Worksheet::insertCommandEntry(WorksheetEntry* current)
-{
-    return insertEntry(CommandEntry::Type, current);
+    return rc;
 }
 
-WorksheetEntry* Worksheet::insertImageEntry(WorksheetEntry* current)
+void Worksheet::load(QByteArray* data)
 {
-    auto* entry = insertEntry(ImageEntry::Type, current);
-    auto* imageEntry = static_cast<ImageEntry*>(entry);
-    QTimer::singleShot(0, this, [=] () {imageEntry->startConfigDialog();});
-    return entry;
+    QBuffer buf(data);
+    buf.open(QIODevice::ReadOnly);
+    load(&buf);
 }
 
-WorksheetEntry* Worksheet::insertPageBreakEntry(WorksheetEntry* current)
+bool Worksheet::load(QIODevice* device)
 {
-    return insertEntry(PageBreakEntry::Type, current);
-}
+    if (!device->isReadable())
+    {
+        QApplication::restoreOverrideCursor();
+        KMessageBox::error(worksheetView(), i18n("Couldn't open the selected file for reading."), i18n("Open File"));
+        return false;
+    }
 
-WorksheetEntry* Worksheet::insertLatexEntry(WorksheetEntry* current)
-{
-    return insertEntry(LatexEntry::Type, current);
-}
+    KZip archive(device);
 
-WorksheetEntry* Worksheet::insertHorizontalRuleEntry(WorksheetEntry* current)
-{
-    return insertEntry(HorizontalRuleEntry::Type, current);
-}
+    if (archive.open(QIODevice::ReadOnly))
+        return loadCantorWorksheet(archive);
+    else
+    {
+        qDebug() <<"not a zip file";
+        // Go to begin of data, we need read all data in second time
+        device->seek(0);
 
-WorksheetEntry* Worksheet::insertHierarchyEntry(WorksheetEntry* current)
-{
-    return insertEntry(HierarchyEntry::Type, current);
+        QJsonParseError error;
+        const QJsonDocument& doc = QJsonDocument::fromJson(device->readAll(), &error);
+        if (error.error != QJsonParseError::NoError)
+        {
+            qDebug()<<"not a json file, parsing failed with error: " << error.errorString();
+            QApplication::restoreOverrideCursor();
+            KMessageBox::error(worksheetView(), i18n("The selected file is not a valid Cantor or Jupyter project file."), i18n("Open File"));
+            return false;
+        }
+        else
+            return loadJupyterNotebook(doc);
+    }
 }
 
-WorksheetEntry* Worksheet::insertEntryBefore(int type, WorksheetEntry* current)
+bool Worksheet::loadCantorWorksheet(const KZip& archive)
 {
-    if (!current)
-        current = currentEntry();
+    m_type = Type::CantorWorksheet;
 
-    if (!current)
-        return nullptr;
+    const KArchiveEntry* contentEntry=archive.directory()->entry(QLatin1String("content.xml"));
+    if (!contentEntry->isFile())
+    {
+        qDebug()<<"content.xml file not found in the zip archive";
+        QApplication::restoreOverrideCursor();
+        KMessageBox::error(worksheetView(), i18n("The selected file is not a valid Cantor project file."), i18n("Open File"));
+        return false;
+    }
 
-    auto* prev = current->previous();
-    WorksheetEntry* entry = nullptr;
+    const KArchiveFile* content = static_cast<const KArchiveFile*>(contentEntry);
+    QByteArray data = content->data();
 
-    if(!prev || prev->type() != type || !prev->isEmpty())
+    QDomDocument doc;
+    doc.setContent(data);
+    QDomElement root = doc.documentElement();
+
+    m_backendName = root.attribute(QLatin1String("backend"));
+
+    //There is "Python" only now, replace "Python 3" by "Python"
+    if (m_backendName == QLatin1String("Python 3"))
+        m_backendName = QLatin1String("Python");
+
+    //"Python 2" in older projects not supported anymore, switch to Python (=Python3)
+    if (m_backendName == QLatin1String("Python 2"))
     {
-        entry = WorksheetEntry::create(type, this);
-        entry->setNext(current);
-        entry->setPrevious(prev);
-        current->setPrevious(entry);
-        if (prev)
-            prev->setNext(entry);
-        else
-            setFirstEntry(entry);
-        updateHierarchyLayout();
-        updateLayout();
-        setModified();
+        QApplication::restoreOverrideCursor();
+        KMessageBox::information(worksheetView(),
+                                 i18n("This worksheet was created using Python2 which is not supported anymore. Python3 will be used."),
+                                 i18n("Python2 not supported anymore"));
+        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
+        m_backendName = QLatin1String("Python");
+    }
+
+    auto* b = Cantor::Backend::getBackend(m_backendName);
+    if (!b)
+    {
+        QApplication::restoreOverrideCursor();
+        KMessageBox::information(worksheetView(), i18n("%1 backend was not found. Editing and executing entries is not possible.", m_backendName), i18n("Open File"));
+        m_readOnly = true;
     }
     else
-        entry = prev;
+        m_readOnly = false;
 
-    focusEntry(entry);
-    return entry;
-}
+    if(!m_readOnly && !b->isEnabled())
+    {
+        QApplication::restoreOverrideCursor();
+        KMessageBox::information(worksheetView(), i18n("There are some problems with the %1 backend,\n"\
+                                            "please check your configuration or install the needed packages.\n"
+                                            "You will only be able to view this worksheet.", m_backendName), i18n("Open File"));
+        m_readOnly = true;
+    }
 
-WorksheetEntry* Worksheet::insertTextEntryBefore(WorksheetEntry* current)
-{
-    return insertEntryBefore(TextEntry::Type, current);
-}
+    m_isLoadingFromFile = true;
 
-WorksheetEntry* Worksheet::insertMarkdownEntryBefore(WorksheetEntry* current)
-{
-    return insertEntryBefore(MarkdownEntry::Type, current);
-}
+    //cleanup the worksheet and all it contains
+    delete m_session;
+    m_session = nullptr;
 
-WorksheetEntry* Worksheet::insertCommandEntryBefore(WorksheetEntry* current)
-{
-    return insertEntryBefore(CommandEntry::Type, current);
-}
+    //file can only be loaded in a worksheet that was not edited/modified yet (s.a. CantorShell::load())
+    //in this case on the default "first entry" is available -> delete it.
+    if (m_firstEntry) {
+        delete m_firstEntry;
+        m_firstEntry = nullptr;
+    }
 
-WorksheetEntry* Worksheet::insertPageBreakEntryBefore(WorksheetEntry* current)
-{
-    return insertEntryBefore(PageBreakEntry::Type, current);
-}
-
-WorksheetEntry* Worksheet::insertImageEntryBefore(WorksheetEntry* current)
-{
-    auto* entry = insertEntryBefore(ImageEntry::Type, current);
-    auto* imageEntry = static_cast<ImageEntry*>(entry);
-    QTimer::singleShot(0, this, [=] () {imageEntry->startConfigDialog();});
-    return entry;
-}
-
-WorksheetEntry* Worksheet::insertLatexEntryBefore(WorksheetEntry* current)
-{
-    return insertEntryBefore(LatexEntry::Type, current);
-}
-
-WorksheetEntry* Worksheet::insertHorizontalRuleEntryBefore(WorksheetEntry* current)
-{
-    return insertEntryBefore(HorizontalRuleEntry::Type, current);
-}
-
-WorksheetEntry* Worksheet::insertHierarchyEntryBefore(WorksheetEntry* current)
-{
-    return insertEntryBefore(HierarchyEntry::Type, current);
-}
-
-void Worksheet::interrupt()
-{
-    if (m_session->status() == Cantor::Session::Running)
-    {
-        m_session->interrupt();
-        Q_EMIT updatePrompt();
-    }
-}
-
-void Worksheet::interruptCurrentEntryEvaluation()
-{
-    currentEntry()->interruptEvaluation();
-}
+    resetEntryCursor();
+    m_itemWidths.clear();
+    m_maxWidth = 0;
 
+    if (!m_readOnly)
+        initSession(b);
 
-bool Worksheet::variableHighlightingEnabled() const
-{
-    return m_variableHighlightingEnabled;
-}
+    qDebug()<<"loading entries";
+    QDomElement expressionChild = root.firstChildElement();
+    while (!expressionChild.isNull()) {
+        QString tag = expressionChild.tagName();
+        // Don't add focus on load
+        auto* entry = appendEntry(typeForTagName(tag), false);
+        if (entry)
+        {
+            entry->setContent(expressionChild, archive);
+            if (m_readOnly)
+                entry->setAcceptHoverEvents(false);
+        }
 
-void Worksheet::setVariableHighlightingEnabled(bool enabled)
-{
-    if (m_variableHighlightingEnabled == enabled)
-    {
-        return;
+        expressionChild = expressionChild.nextSiblingElement();
     }
-    m_variableHighlightingEnabled = enabled;
 
-    for (auto* entry = firstEntry(); entry; entry = entry->next())
-    {
-        if (entry->type() == CommandEntry::Type)
-            static_cast<CommandEntry*>(entry)->setVariableHighlightingEnabled(enabled);
-    }
-}
+    if (m_readOnly)
+        clearFocus();
 
-void Worksheet::enableCompletion(bool enable)
-{
-    m_completionEnabled=enable;
-}
+    m_isLoadingFromFile = false;
+    updateHierarchyLayout();
+    updateLayout();
 
-Cantor::Session* Worksheet::session() const
-{
-    return m_session;
-}
+    //Set the Highlighting, depending on the current state
+    //If the session isn't logged in, use the default
 
-bool Worksheet::isRunning()
-{
-    return m_session && m_session->status()==Cantor::Session::Running;
+    Q_EMIT loaded();
+    return true;
 }
 
-bool Worksheet::isReadOnly()
+int Worksheet::typeForTagName(const QString& tag)
 {
-    return m_readOnly;
-}
+    if (tag == QLatin1String("Expression"))
+        return CommandEntry::Type;
+    else if (tag == QLatin1String("Text"))
+        return TextEntry::Type;
+    else if (tag == QLatin1String("Markdown"))
+        return MarkdownEntry::Type;
+    else if (tag == QLatin1String("Latex"))
+        return LatexEntry::Type;
+    else if (tag == QLatin1String("PageBreak"))
+        return PageBreakEntry::Type;
+    else if (tag == QLatin1String("Image"))
+        return ImageEntry::Type;
+    else if (tag == QLatin1String("HorizontalRule"))
+        return HorizontalRuleEntry::Type;
+    else if (tag == QLatin1String("Hierarchy"))
+        return HierarchyEntry::Type;
 
-bool Worksheet::showExpressionIds()
-{
-    return m_showExpressionIds;
+    return 0;
 }
 
-bool Worksheet::animationsEnabled()
-{
-    return m_animationsEnabled;
-}
 
-void Worksheet::enableAnimations(bool enable)
+void Worksheet::initSession(Cantor::Backend* backend)
 {
-    m_animationsEnabled = enable;
+    m_session = backend->createSession();
+    if (m_useDefaultWorksheetParameters)
+    {
+        if (Cantor::LatexRenderer::isLatexAvailable())
+            m_session->setTypesettingEnabled(Settings::self()->typesetDefault());
+        enableCompletion(Settings::self()->completionDefault());
+        enableExpressionNumbering(Settings::self()->expressionNumberingDefault());
+        enableAnimations(Settings::self()->animationDefault());
+        enableEmbeddedMath(Settings::self()->embeddedMathDefault());
+    }
 }
 
-bool Worksheet::embeddedMathEnabled()
+bool Worksheet::loadJupyterNotebook(const QJsonDocument& doc)
 {
-    return m_embeddedMathEnabled && m_mathRenderer.mathRenderAvailable();
-}
+    m_type = Type::JupyterNotebook;
 
-void Worksheet::enableEmbeddedMath(bool enable)
-{
-    m_embeddedMathEnabled = enable;
-}
+    int nbformatMajor, nbformatMinor;
+    if (!Cantor::JupyterUtils::isJupyterNotebook(doc))
+    {
+        // Two possibilities: old jupyter notebook (version <= 4.0.0 and a another scheme) or just not a notebook at all
+        std::tie(nbformatMajor, nbformatMinor) = Cantor::JupyterUtils::getNbformatVersion(doc.object());
+        if (nbformatMajor == 0 && nbformatMinor == 0)
+        {
+            QApplication::restoreOverrideCursor();
+            showInvalidNotebookSchemeError();
+        }
+        else
+        {
+            KMessageBox::error(worksheetView(),
+                i18n("Jupyter notebooks with versions lower than 4.5 (detected version %1.%2) are not supported.", nbformatMajor, nbformatMinor ),
+                i18n("Open File"));
+        }
 
-void Worksheet::enableExpressionNumbering(bool enable)
-{
-    m_showExpressionIds=enable;
-    Q_EMIT updatePrompt();
-    refreshTocStructure();
-    if (views().size() != 0)
-        updateLayout();
-}
+        return false;
+    }
 
-QDomDocument Worksheet::toXML(KZip* archive)
-{
-    QDomDocument doc( QLatin1String("CantorWorksheet") );
-    QDomElement root = doc.createElement( QLatin1String("Worksheet") );
-    root.setAttribute(QLatin1String("backend"), (m_session ? m_session->backend()->name(): m_backendName));
-    doc.appendChild(root);
+    QJsonObject notebookObject = doc.object();
+    std::tie(nbformatMajor, nbformatMinor) = Cantor::JupyterUtils::getNbformatVersion(notebookObject);
 
-    for( auto* entry = firstEntry(); entry; entry = entry->next())
+    if (QT_VERSION_CHECK(nbformatMajor, nbformatMinor, 0) > QT_VERSION_CHECK(4,5,0))
     {
-        QDomElement el = entry->toXml(doc, archive);
-        root.appendChild( el );
+        QApplication::restoreOverrideCursor();
+        KMessageBox::error(
+            worksheetView(),
+            i18n("Jupyter notebooks with versions higher than 4.5 (detected version %1.%2) are not supported.", nbformatMajor, nbformatMinor),
+            i18n("Open File")
+        );
+        return false;
     }
-    return doc;
-}
-
-QJsonDocument Worksheet::toJupyterJson()
-{
-    QJsonDocument doc;
-    QJsonObject root;
-
-    QJsonObject metadata(m_jupyterMetadata ? *m_jupyterMetadata : QJsonObject());
 
-    QJsonObject kernalInfo;
-    if (m_session && m_session->backend())
-        kernalInfo = Cantor::JupyterUtils::getKernelspec(m_session->backend());
-    else
-        kernalInfo.insert(QLatin1String("name"), m_backendName);
-    metadata.insert(QLatin1String("kernelspec"), kernalInfo);
+    const QJsonArray& cells = Cantor::JupyterUtils::getCells(notebookObject);
+    const QJsonObject& metadata = Cantor::JupyterUtils::getMetadata(notebookObject);
+    if (m_jupyterMetadata)
+        delete m_jupyterMetadata;
+    m_jupyterMetadata = new QJsonObject(metadata);
 
-    root.insert(QLatin1String("metadata"), metadata);
+    const QJsonObject& kernalspec = metadata.value(QLatin1String("kernelspec")).toObject();
+    m_backendName = Cantor::JupyterUtils::getKernelName(kernalspec);
 
-    // Not sure, but it looks like we support nbformat version 4.5
-    root.insert(QLatin1String("nbformat"), 4);
-    root.insert(QLatin1String("nbformat_minor"), 5);
+    //There is "Python" only now, replace "python3" by "Python"
+    if (m_backendName == QLatin1String("python3"))
+        m_backendName = QLatin1String("Python");
 
-    QJsonArray cells;
-    for( auto* entry = firstEntry(); entry; entry = entry->next())
+    //"python 2" in older projects not supported anymore, switch to Python (=Python3)
+    if (m_backendName == QLatin1String("python2"))
     {
-        const QJsonValue entryJson = entry->toJupyterJson();
-
-        if (!entryJson.isNull())
-            cells.append(entryJson);
+        QApplication::restoreOverrideCursor();
+        KMessageBox::information(worksheetView(),
+                                 i18n("This notebook was created using Python2 which is not supported anymore. Python3 will be used."),
+                                 i18n("Python2 not supported anymore"));
+        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
+        m_backendName = QLatin1String("Python");
     }
-    root.insert(QLatin1String("cells"), cells);
 
-    doc.setObject(root);
-    return doc;
-}
+    if (kernalspec.isEmpty() || m_backendName.isEmpty())
+    {
+        QApplication::restoreOverrideCursor();
+        showInvalidNotebookSchemeError();
+        return false;
+    }
 
-void Worksheet::save( const QString& filename )
-{
-    QFile file(filename);
-    if ( !file.open(QIODevice::WriteOnly) )
+    Cantor::Backend* backend = Cantor::Backend::getBackend(m_backendName);
+    if (!backend)
     {
-        KMessageBox::error( worksheetView(),
-                            i18n( "Cannot write file %1." , filename ),
-                            i18n( "Error - Cantor" ));
-        return;
+        QApplication::restoreOverrideCursor();
+        KMessageBox::information(worksheetView(),
+            i18n("%1 backend was not found. Editing and executing entries is not possible.", m_backendName),
+            i18n("Open File"));
+        m_readOnly = true;
     }
+    else
+        m_readOnly = false;
 
-    save(&file);
-}
+    if(!m_readOnly && !backend->isEnabled())
+    {
+        QApplication::restoreOverrideCursor();
+        KMessageBox::information(worksheetView(), i18n("There are some problems with the %1 backend,\n"\
+                                            "please check your configuration or install the needed packages.\n"
+                                            "You will only be able to view this worksheet.", m_backendName), i18n("Open File"));
+        m_readOnly = true;
+    }
 
-QByteArray Worksheet::saveToByteArray()
-{
-    QBuffer buffer;
-    save(&buffer);
+    if (m_readOnly)
+    {
+        for (QAction* action : m_richTextActionList)
+            action->setEnabled(false);
+    }
 
-    return buffer.buffer();
-}
 
-void Worksheet::save( QIODevice* device)
-{
-    qDebug()<<"saving to filename";
-    switch (m_type)
-    {
-        case CantorWorksheet:
-        {
-            KZip zipFile( device );
+    m_isLoadingFromFile = true;
 
-            if ( !zipFile.open(QIODevice::WriteOnly) )
-            {
-                KMessageBox::error( worksheetView(),
-                                    i18n( "Cannot write file." ),
-                                    i18n( "Error - Cantor" ));
-                return;
-            }
+    if (m_session)
+        delete m_session;
+    m_session = nullptr;
 
-            QByteArray content = toXML(&zipFile).toByteArray();
-            zipFile.writeFile( QLatin1String("content.xml"), content.data());
-            break;
-        }
+    if (m_firstEntry) {
+        delete m_firstEntry;
+        m_firstEntry = nullptr;
+    }
 
-        case JupyterNotebook:
+    resetEntryCursor();
+    m_itemWidths.clear();
+    m_maxWidth = 0;
+
+    if (!m_readOnly)
+        initSession(backend);
+
+    qDebug() << "loading jupyter entries";
+
+    WorksheetEntry* entry = nullptr;
+    for (QJsonArray::const_iterator iter = cells.begin(); iter != cells.end(); ++iter) {
+        if (!Cantor::JupyterUtils::isJupyterCell(*iter))
         {
-            if (!device->isWritable())
-            {
-                KMessageBox::error( worksheetView(),
-                                    i18n( "Cannot write file." ),
-                                    i18n( "Error - Cantor" ));
-                return;
-            }
+            QApplication::restoreOverrideCursor();
+            QString explanation;
+            if (iter->isObject())
+                explanation = i18n("an object with keys: %1", iter->toObject().keys().join(QLatin1String(", ")));
+            else
+                explanation = i18n("non object JSON value");
 
-            const QJsonDocument& doc = toJupyterJson();
-            device->write(doc.toJson(QJsonDocument::Indented));
-            break;
+            m_isLoadingFromFile = false;
+            showInvalidNotebookSchemeError(i18n("found incorrect data (%1) that is not Jupyter cell", explanation));
+            return false;
         }
-    }
-}
-
-void Worksheet::savePlain(const QString& filename)
-{
-    QFile file(filename);
-    if(!file.open(QIODevice::WriteOnly))
-    {
-        KMessageBox::error(worksheetView(), i18n("Error saving file %1", filename), i18n("Error - Cantor"));
-        return;
-    }
 
-    QString cmdSep=QLatin1String(";\n");
-    QString commentStartingSeq = QLatin1String("");
-    QString commentEndingSeq = QLatin1String("");
+        const QJsonObject& cell = iter->toObject();
+        QString cellType = Cantor::JupyterUtils::getCellType(cell);
 
-    if (!m_readOnly)
-    {
-        Cantor::Backend * const backend=session()->backend();
-        if (backend->extensions().contains(QLatin1String("ScriptExtension")))
+        if (cellType == QLatin1String("code"))
         {
-            Cantor::ScriptExtension* e=dynamic_cast<Cantor::ScriptExtension*>(backend->extension(QLatin1String(("ScriptExtension"))));
-            if (e)
+            if (LatexEntry::isConvertableToLatexEntry(cell))
             {
-                cmdSep=e->commandSeparator();
-                commentStartingSeq = e->commentStartingSequence();
-                commentEndingSeq = e->commentEndingSequence();
+                entry = appendEntry(LatexEntry::Type, false);
+                entry->setContentFromJupyter(cell);
+                entry->evaluate(WorksheetEntry::InternalEvaluation);
+            }
+            else
+            {
+                entry = appendEntry(CommandEntry::Type, false);
+                entry->setContentFromJupyter(cell);
+            }
+        }
+        else if (cellType == QLatin1String("markdown"))
+        {
+            if (TextEntry::isConvertableToTextEntry(cell))
+            {
+                entry = appendEntry(TextEntry::Type, false);
+                entry->setContentFromJupyter(cell);
+            }
+            else if (HorizontalRuleEntry::isConvertableToHorizontalRuleEntry(cell))
+            {
+                entry = appendEntry(HorizontalRuleEntry::Type, false);
+                entry->setContentFromJupyter(cell);
             }
+            else if (HierarchyEntry::isConvertableToHierarchyEntry(cell))
+            {
+                entry = appendEntry(HierarchyEntry::Type, false);
+                entry->setContentFromJupyter(cell);
+            }
+            else
+            {
+                entry = appendEntry(MarkdownEntry::Type, false);
+                entry->setContentFromJupyter(cell);
+                entry->evaluate(WorksheetEntry::InternalEvaluation);
+            }
+        }
+        else if (cellType == QLatin1String("raw"))
+        {
+            if (PageBreakEntry::isConvertableToPageBreakEntry(cell))
+                entry = appendEntry(PageBreakEntry::Type, false);
+            else
+                entry = appendEntry(TextEntry::Type, false);
+            entry->setContentFromJupyter(cell);
+        }
+
+        if (m_readOnly && entry)
+        {
+            entry->setAcceptHoverEvents(false);
+            entry = nullptr;
         }
     }
-    else
-        KMessageBox::information(worksheetView(), i18n("In read-only mode Cantor couldn't guarantee, that the export will be valid for %1", m_backendName), i18n("Cantor"));
 
-    QTextStream stream(&file);
+    if (m_readOnly)
+        clearFocus();
 
-    for(auto* entry = firstEntry(); entry; entry = entry->next())
-    {
-        const QString& str=entry->toPlain(cmdSep, commentStartingSeq, commentEndingSeq);
-        if(!str.isEmpty())
-            stream << str + QLatin1Char('\n');
-    }
+    m_isLoadingFromFile = false;
+    updateHierarchyLayout();
+    updateLayout();
 
-    file.close();
+
+    Q_EMIT loaded();
+    return true;
 }
 
-void Worksheet::saveLatex(const QString& filename)
+void Worksheet::showInvalidNotebookSchemeError(QString additionalInfo)
 {
-    qDebug()<<"exporting to Latex: " <<filename;
+    if (additionalInfo.isEmpty())
+        KMessageBox::error(worksheetView(), i18n("The file is not valid Jupyter notebook"), i18n("Open File"));
+    else
+        KMessageBox::error(worksheetView(), i18n("Invalid Jupyter notebook scheme: %1", additionalInfo), i18n("Open File"));
+}
 
-    QFile file(filename);
-    if(!file.open(QIODevice::WriteOnly))
-    {
-        KMessageBox::error(worksheetView(), i18n("Error saving file %1", filename), i18n("Export to LaTeX"));
-        return;
-    }
+void Worksheet::gotResult(Cantor::Expression* expr)
+{
+    if(expr==nullptr)
+        expr=qobject_cast<Cantor::Expression*>(sender());
 
-    QString stylesheet = QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("xslt/latex.xsl"));
-    if (stylesheet.isEmpty()) {
-        KMessageBox::error(worksheetView(), i18n("Error loading latex.xsl stylesheet"), i18n("Export to LaTeX"));
+    if(expr==nullptr)
         return;
-    }
 
-    xsltStylesheetPtr xsltStyleSheet = xsltParseStylesheetFile((const xmlChar *)stylesheet.toLocal8Bit().constData());
-    xmlDocPtr output = xmlReadDoc((const xmlChar *)toXML().toString().toStdString().c_str(), nullptr, "utf-8", XML_PARSE_RECOVER | XML_PARSE_NOENT | XML_PARSE_DTDLOAD);
+    //We're only interested in help results, others are handled by the WorksheetEntry
+    for (auto* result : expr->results())
+    {
+        if(result && result->type()==Cantor::HelpResult::Type)
+        {
+            QString help = result->toHtml();
+            //Do some basic LaTeX replacing
+            //TODO: what for? relevant for sage only?
+            help.replace(QRegularExpression(QStringLiteral("\\\\code\\{([^\\}]*)\\}")), QStringLiteral("<b>\\1</b>"));
+            help.replace(QRegularExpression(QStringLiteral("\\$([^\\$])\\$")), QStringLiteral("<i>\\1</i>"));
 
-    const char *params[16+1];
-    params[0] = nullptr;
-    auto res = xsltApplyStylesheet(xsltStyleSheet, output, params);
-    if (res) {
-        xmlChar *xmlResultBuffer = nullptr;
-        int xmlResultLength = 0;
-        int res = xsltSaveResultToString(&xmlResultBuffer, &xmlResultLength, output, xsltStyleSheet);
-        if (res != -1) {
-            QString outString = QString::fromUtf8((char *)xmlResultBuffer);
+            Q_EMIT showHelp(help);
 
-            // Transform HTML escaped special characters to valid LaTeX characters (&, <, >)
-            QTextStream stream(&file);
-            stream << outString.replace(QLatin1String("&amp;"), QLatin1String("&"))
-                         .replace(QLatin1String("&gt;"), QLatin1String(">"))
-                         .replace(QLatin1String("&lt;"), QLatin1String("<"));
-            file.close();
+            //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).
+            break;
         }
-
-        xmlFree(xmlResultBuffer);
     }
 
-    xsltFreeStylesheet(xsltStyleSheet);
-    xmlFreeDoc(res);
-    xmlFreeDoc(output);
-
-    xsltCleanupGlobals();
-    xmlCleanupParser();
 }
 
-bool Worksheet::load(const QString& filename )
+void Worksheet::removeCurrentEntry()
 {
-    qDebug() << "loading worksheet" << filename;
-    QFile file(filename);
-    if (!file.open(QIODevice::ReadOnly)) {
-        KMessageBox::error(worksheetView(), i18n("Couldn't open the file %1.", filename), i18n("Open File"));
-        return false;
-    }
+    auto* entry = currentEntry();
+    if(!entry)
+        return;
 
-    bool rc = load(&file);
-    if (rc && !m_readOnly)
-        m_session->setWorksheetPath(filename);
+    // In case we just removed this
+    if (entry->isAncestorOf(m_lastFocusedTextItem))
+        m_lastFocusedTextItem = nullptr;
 
-    return rc;
+    entry->startRemoving();
 }
 
-void Worksheet::load(QByteArray* data)
+Cantor::Renderer* Worksheet::renderer()
 {
-    QBuffer buf(data);
-    buf.open(QIODevice::ReadOnly);
-    load(&buf);
+    return &m_renderer;
 }
 
-bool Worksheet::load(QIODevice* device)
+MathRenderer* Worksheet::mathRenderer()
 {
-    if (!device->isReadable())
-    {
-        QApplication::restoreOverrideCursor();
-        KMessageBox::error(worksheetView(), i18n("Couldn't open the selected file for reading."), i18n("Open File"));
-        return false;
-    }
-
-    KZip archive(device);
+    return &m_mathRenderer;
+}
 
-    if (archive.open(QIODevice::ReadOnly))
-        return loadCantorWorksheet(archive);
-    else
-    {
-        qDebug() <<"not a zip file";
-        // Go to begin of data, we need read all data in second time
-        device->seek(0);
+QMenu* Worksheet::createContextMenu()
+{
+    auto* menu = new QMenu(worksheetView());
+    connect(menu, SIGNAL(aboutToHide()), menu, SLOT(deleteLater()));
 
-        QJsonParseError error;
-        const QJsonDocument& doc = QJsonDocument::fromJson(device->readAll(), &error);
-        if (error.error != QJsonParseError::NoError)
-        {
-            qDebug()<<"not a json file, parsing failed with error: " << error.errorString();
-            QApplication::restoreOverrideCursor();
-            KMessageBox::error(worksheetView(), i18n("The selected file is not a valid Cantor or Jupyter project file."), i18n("Open File"));
-            return false;
-        }
-        else
-            return loadJupyterNotebook(doc);
-    }
+    return menu;
 }
 
-bool Worksheet::loadCantorWorksheet(const KZip& archive)
+void Worksheet::populateMenu(QMenu* menu, QPointF pos)
 {
-    m_type = Type::CantorWorksheet;
-
-    const KArchiveEntry* contentEntry=archive.directory()->entry(QLatin1String("content.xml"));
-    if (!contentEntry->isFile())
+    // Two different context menus - 1. for the current entry, 2. for multiple selected entries
+    if (m_selectedEntries.isEmpty())
     {
-        qDebug()<<"content.xml file not found in the zip archive";
-        QApplication::restoreOverrideCursor();
-        KMessageBox::error(worksheetView(), i18n("The selected file is not a valid Cantor project file."), i18n("Open File"));
-        return false;
-    }
-
-    const KArchiveFile* content = static_cast<const KArchiveFile*>(contentEntry);
-    QByteArray data = content->data();
+        auto* entry = entryAt(pos);
+        if (entry && !entry->isAncestorOf(m_lastFocusedTextItem)) {
+            auto* item =
+                qgraphicsitem_cast<WorksheetTextEditorItem*>(itemAt(pos, QTransform()));
+            if (item && item->isEditable())
+                m_lastFocusedTextItem = item;
+        }
 
-    QDomDocument doc;
-    doc.setContent(data);
-    QDomElement root = doc.documentElement();
+        if (entry) {
+            //"Convert To" menu
+            QMenu* convertTo = new QMenu(i18n("Convert To"));
+            convertTo->setIcon(QIcon::fromTheme(QLatin1String("gtk-convert")));
+            menu->addMenu(convertTo);
 
-    m_backendName = root.attribute(QLatin1String("backend"));
+            if (entry->type() != CommandEntry::Type)
+                convertTo->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), entry, &WorksheetEntry::convertToCommandEntry);
 
-    //There is "Python" only now, replace "Python 3" by "Python"
-    if (m_backendName == QLatin1String("Python 3"))
-        m_backendName = QLatin1String("Python");
+            if (entry->type() != TextEntry::Type)
+                convertTo->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), entry, &WorksheetEntry::convertToTextEntry);
 
-    //"Python 2" in older projects not supported anymore, switch to Python (=Python3)
-    if (m_backendName == QLatin1String("Python 2"))
-    {
-        QApplication::restoreOverrideCursor();
-        KMessageBox::information(worksheetView(),
-                                 i18n("This worksheet was created using Python2 which is not supported anymore. Python3 will be used."),
-                                 i18n("Python2 not supported anymore"));
-        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
-        m_backendName = QLatin1String("Python");
-    }
+    #ifdef Discount_FOUND
+            if (entry->type() != MarkdownEntry::Type)
+                convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), entry, &WorksheetEntry::convertToMarkdownEntry);
+    #endif
+            if (entry->type() != LatexEntry::Type)
+                convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), entry, &WorksheetEntry::convertToLatexEntry);
+            if (entry->type() != ImageEntry::Type)
+                convertTo->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, &WorksheetEntry::convertToImageEntry);
 
-    auto* b = Cantor::Backend::getBackend(m_backendName);
-    if (!b)
-    {
-        QApplication::restoreOverrideCursor();
-        KMessageBox::information(worksheetView(), i18n("%1 backend was not found. Editing and executing entries is not possible.", m_backendName), i18n("Open File"));
-        m_readOnly = true;
-    }
-    else
-        m_readOnly = false;
+            if (entry->type() != PageBreakEntry::Type)
+                convertTo->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), entry, &WorksheetEntry::converToPageBreakEntry);
 
-    if(!m_readOnly && !b->isEnabled())
-    {
-        QApplication::restoreOverrideCursor();
-        KMessageBox::information(worksheetView(), i18n("There are some problems with the %1 backend,\n"\
-                                            "please check your configuration or install the needed packages.\n"
-                                            "You will only be able to view this worksheet.", m_backendName), i18n("Open File"));
-        m_readOnly = true;
-    }
+            if (entry->type() != HorizontalRuleEntry::Type)
+                convertTo->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), entry, &WorksheetEntry::convertToHorizontalRuleEntry);
 
-    m_isLoadingFromFile = true;
+            if (entry->type() != HierarchyEntry::Type)
+                convertTo->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), entry, &WorksheetEntry::convertToHierarchyEntry);
 
-    //cleanup the worksheet and all it contains
-    delete m_session;
-    m_session = nullptr;
+            //"Insert After" menu
+            QMenu* insert = new QMenu(i18n("Insert After"), menu);
+            insert->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-below")));
+            menu->addSeparator();
+            menu->addMenu(insert);
 
-    //file can only be loaded in a worksheet that was not edited/modified yet (s.a. CantorShell::load())
-    //in this case on the default "first entry" is available -> delete it.
-    if (m_firstEntry) {
-        delete m_firstEntry;
-        m_firstEntry = nullptr;
-    }
+            insert->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), entry, SLOT(insertCommandEntry()));
+            insert->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), entry, SLOT(insertTextEntry()));
+    #ifdef Discount_FOUND
+            insert->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), entry, SLOT(insertMarkdownEntry()));
+    #endif
+            insert->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), entry, SLOT(insertLatexEntry()));
+            insert->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntry()));
+            insert->addSeparator();
+            insert->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), entry, SLOT(insertHorizontalRuleEntry()));
+            insert->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), entry, SLOT(insertPageBreakEntry()));
+            insert->addSeparator();
+            insert->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), entry, SLOT(insertHierarchyEntry()));
 
-    resetEntryCursor();
-    m_itemWidths.clear();
-    m_maxWidth = 0;
+            //"Insert Before" menu
+            QMenu* insertBefore = new QMenu(i18n("Insert Before"), menu);
+            insertBefore->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-above")));
+            menu->addMenu(insertBefore);
 
-    if (!m_readOnly)
-        initSession(b);
+            insertBefore->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), entry, SLOT(insertCommandEntryBefore()));
+            insertBefore->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), entry, SLOT(insertTextEntryBefore()));
+    #ifdef Discount_FOUND
+            insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), entry, SLOT(insertMarkdownEntryBefore()));
+    #endif
+            insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), entry, SLOT(insertLatexEntryBefore()));
+            insertBefore->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntryBefore()));
+            insertBefore->addSeparator();
+            insertBefore->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), entry, SLOT(insertHorizontalRuleEntryBefore()));
+            insertBefore->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), entry, SLOT(insertPageBreakEntryBefore()));
+            insertBefore->addSeparator();
+            insertBefore->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), entry, SLOT(insertHierarchyEntryBefore()));
+        } else {
+            QMenu* insertMenu = new QMenu(i18n("Insert"));
+            insertMenu->setIcon(QIcon::fromTheme(QLatin1String("insert-table-row")));
 
-    qDebug()<<"loading entries";
-    QDomElement expressionChild = root.firstChildElement();
-    while (!expressionChild.isNull()) {
-        QString tag = expressionChild.tagName();
-        // Don't add focus on load
-        auto* entry = appendEntry(typeForTagName(tag), false);
-        if (entry)
-        {
-            entry->setContent(expressionChild, archive);
-            if (m_readOnly)
-                entry->setAcceptHoverEvents(false);
-        }
+            insertMenu->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), this, SLOT(appendCommandEntry()));
+            insertMenu->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), this, &Worksheet::appendTextEntry);
+    #ifdef Discount_FOUND
+            insertMenu->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), this, &Worksheet::appendMarkdownEntry);
+    #endif
 
-        expressionChild = expressionChild.nextSiblingElement();
-    }
+            insertMenu->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), this, &Worksheet::appendLatexEntry);
+            insertMenu->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), this, &Worksheet::appendImageEntry);
+            insertMenu->addSeparator();
+            insertMenu->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), this, &Worksheet::appendHorizontalRuleEntry);
+            insertMenu->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), this, &Worksheet::appendPageBreakEntry);
+            insertMenu->addSeparator();
+            insertMenu->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), this, &Worksheet::appendHierarchyEntry);
 
-    if (m_readOnly)
-        clearFocus();
+            menu->addMenu(insertMenu);
 
-    m_isLoadingFromFile = false;
-    updateHierarchyLayout();
-    updateLayout();
+            //"Show help" for backend's documentation
+#ifdef HAVE_EMBEDDED_DOCUMENTATION
+            menu->addSeparator();
+            menu->addAction(QIcon::fromTheme(QLatin1String("help-hint")), i18n("Show Help"), this,
+                                        [=] () { requestDocumentation(QString()); });
+#endif
+        }
 
-    //Set the Highlighting, depending on the current state
-    //If the session isn't logged in, use the default
+        //evaluate the whole worksheet or interrupt the current calculation
+        menu->addSeparator();
+        if (!isRunning())
+            menu->addAction(QIcon::fromTheme(QLatin1String("system-run")), i18n("Evaluate Worksheet"),
+                            this, &Worksheet::evaluate);
+        else
+            menu->addAction(QIcon::fromTheme(QLatin1String("process-stop")), i18n("Interrupt"), this,
+                            &Worksheet::interrupt);
 
-    Q_EMIT loaded();
-    return true;
-}
+        //zooming
+        menu->addSeparator();
+        auto* zoomMenu = new QMenu(i18n("Zoom"));
+        zoomMenu->setIcon(QIcon::fromTheme(QLatin1String("zoom-draw")));
+        auto* view = worksheetView();
 
-int Worksheet::typeForTagName(const QString& tag)
-{
-    if (tag == QLatin1String("Expression"))
-        return CommandEntry::Type;
-    else if (tag == QLatin1String("Text"))
-        return TextEntry::Type;
-    else if (tag == QLatin1String("Markdown"))
-        return MarkdownEntry::Type;
-    else if (tag == QLatin1String("Latex"))
-        return LatexEntry::Type;
-    else if (tag == QLatin1String("PageBreak"))
-        return PageBreakEntry::Type;
-    else if (tag == QLatin1String("Image"))
-        return ImageEntry::Type;
-    else if (tag == QLatin1String("HorizontalRule"))
-        return HorizontalRuleEntry::Type;
-    else if (tag == QLatin1String("Hierarchy"))
-        return HierarchyEntry::Type;
+        auto* action = zoomMenu->addAction(QIcon::fromTheme(QLatin1String("zoom-in")), i18n("Zoom In"), view, &WorksheetView::zoomIn);
+        action->setShortcut(Qt::CTRL | Qt::Key_Plus);
 
-    return 0;
-}
+        action = zoomMenu->addAction(QIcon::fromTheme(QLatin1String("zoom-out")), i18n("Zoom Out"), view, &WorksheetView::zoomOut);
+        action->setShortcut(Qt::CTRL | Qt::Key_Minus);
+        zoomMenu->addSeparator();
 
+        action = zoomMenu->addAction(QIcon::fromTheme(QLatin1String("zoom-original")), i18n("Original Size"), view, &WorksheetView::actualSize);
+        action->setShortcut(Qt::CTRL | Qt::Key_1);
 
-void Worksheet::initSession(Cantor::Backend* backend)
-{
-    m_session = backend->createSession();
-    if (m_useDefaultWorksheetParameters)
-    {
-        if (Cantor::LatexRenderer::isLatexAvailable())
-            m_session->setTypesettingEnabled(Settings::self()->typesetDefault());
-        enableCompletion(Settings::self()->completionDefault());
-        enableExpressionNumbering(Settings::self()->expressionNumberingDefault());
-        enableAnimations(Settings::self()->animationDefault());
-        enableEmbeddedMath(Settings::self()->embeddedMathDefault());
+        menu->addMenu(zoomMenu);
     }
-}
+    else
+    {
+        menu->clear();
+        menu->addAction(QIcon::fromTheme(QLatin1String("go-up")), i18n("Move Entries Up"), this, &Worksheet::selectionMoveUp);
+        menu->addAction(QIcon::fromTheme(QLatin1String("go-down")), i18n("Move Entries Down"), this, &Worksheet::selectionMoveDown);
+        menu->addAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate Entries"), this, &Worksheet::selectionEvaluate);
+        menu->addSeparator();
+        menu->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete Entries"), this, &Worksheet::selectionRemove);
 
-bool Worksheet::loadJupyterNotebook(const QJsonDocument& doc)
-{
-    m_type = Type::JupyterNotebook;
+        bool isAnyCommandEntryInSelection = false;
+        for (auto* entry : m_selectedEntries)
+            if (entry->type() == CommandEntry::Type)
+            {
+                isAnyCommandEntryInSelection = true;
+                break;
+            }
 
-    int nbformatMajor, nbformatMinor;
-    if (!Cantor::JupyterUtils::isJupyterNotebook(doc))
-    {
-        // Two possibilities: old jupyter notebook (version <= 4.0.0 and a another scheme) or just not a notebook at all
-        std::tie(nbformatMajor, nbformatMinor) = Cantor::JupyterUtils::getNbformatVersion(doc.object());
-        if (nbformatMajor == 0 && nbformatMinor == 0)
-        {
-            QApplication::restoreOverrideCursor();
-            showInvalidNotebookSchemeError();
-        }
-        else
+        if (isAnyCommandEntryInSelection)
         {
-            KMessageBox::error(worksheetView(),
-                i18n("Jupyter notebooks with versions lower than 4.5 (detected version %1.%2) are not supported.", nbformatMajor, nbformatMinor ),
-                i18n("Open File"));
+            menu->addSeparator();
+            menu->addAction(QIcon(), i18n("Collapse Command Entry Results"), this, &Worksheet::collapseSelectionResults);
+            menu->addAction(QIcon(), i18n("Expand Command Entry Results"), this, &Worksheet::uncollapseSelectionResults);
+            menu->addSeparator();
+            menu->addAction(QIcon(), i18n("Delete Command Entry Results"), this, &Worksheet::removeSelectionResults);
+            menu->addSeparator();
+            menu->addAction(QIcon(), i18n("Exclude Command Entry From Execution"), this, &Worksheet::excludeFromExecutionSelection);
+            menu->addAction(QIcon(), i18n("Add Command Entry To Execution"), this, &Worksheet::addToExectuionSelection);
         }
-
-        return false;
     }
+}
 
-    QJsonObject notebookObject = doc.object();
-    std::tie(nbformatMajor, nbformatMinor) = Cantor::JupyterUtils::getNbformatVersion(notebookObject);
+void Worksheet::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
+{
+    if (m_readOnly)
+        return;
 
-    if (QT_VERSION_CHECK(nbformatMajor, nbformatMinor, 0) > QT_VERSION_CHECK(4,5,0))
-    {
-        QApplication::restoreOverrideCursor();
-        KMessageBox::error(
-            worksheetView(),
-            i18n("Jupyter notebooks with versions higher than 4.5 (detected version %1.%2) are not supported.", nbformatMajor, nbformatMinor),
-            i18n("Open File")
-        );
-        return false;
-    }
+    // forward the event to the items
+    QGraphicsScene::contextMenuEvent(event);
 
-    const QJsonArray& cells = Cantor::JupyterUtils::getCells(notebookObject);
-    const QJsonObject& metadata = Cantor::JupyterUtils::getMetadata(notebookObject);
-    if (m_jupyterMetadata)
-        delete m_jupyterMetadata;
-    m_jupyterMetadata = new QJsonObject(metadata);
+    if (!event->isAccepted()) {
+        event->accept();
+        QMenu* menu = createContextMenu();
+        populateMenu(menu, event->scenePos());
 
-    const QJsonObject& kernalspec = metadata.value(QLatin1String("kernelspec")).toObject();
-    m_backendName = Cantor::JupyterUtils::getKernelName(kernalspec);
+        menu->popup(event->screenPos());
+    }
+}
 
-    //There is "Python" only now, replace "python3" by "Python"
-    if (m_backendName == QLatin1String("python3"))
-        m_backendName = QLatin1String("Python");
+void Worksheet::mousePressEvent(QGraphicsSceneMouseEvent* event)
+{
+    /*
+    if (event->button() == Qt::LeftButton && !focusItem() && lastEntry() &&
+        event->scenePos().y() > lastEntry()->y() + lastEntry()->size().height())
+        lastEntry()->focusEntry(WorksheetTextItem::BottomRight);
+    */
+    QGraphicsScene::mousePressEvent(event);
 
-    //"python 2" in older projects not supported anymore, switch to Python (=Python3)
-    if (m_backendName == QLatin1String("python2"))
+    if (!m_readOnly && event->buttons() & Qt::LeftButton)
     {
-        QApplication::restoreOverrideCursor();
-        KMessageBox::information(worksheetView(),
-                                 i18n("This notebook was created using Python2 which is not supported anymore. Python3 will be used."),
-                                 i18n("Python2 not supported anymore"));
-        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
-        m_backendName = QLatin1String("Python");
-    }
+        auto* selectedEntry = entryAt(event->scenePos());
+        if (event->modifiers() & Qt::ControlModifier)
+        {
+            clearFocus();
+            resetEntryCursor();
 
-    if (kernalspec.isEmpty() || m_backendName.isEmpty())
-    {
-        QApplication::restoreOverrideCursor();
-        showInvalidNotebookSchemeError();
-        return false;
-    }
+            if (selectedEntry)
+            {
+                selectedEntry->setCellSelected(!selectedEntry->isCellSelected());
+                selectedEntry->update();
 
-    Cantor::Backend* backend = Cantor::Backend::getBackend(m_backendName);
-    if (!backend)
-    {
-        QApplication::restoreOverrideCursor();
-        KMessageBox::information(worksheetView(),
-            i18n("%1 backend was not found. Editing and executing entries is not possible.", m_backendName),
-            i18n("Open File"));
-        m_readOnly = true;
-    }
-    else
-        m_readOnly = false;
+                auto* lastSelectedEntry = m_circularFocusBuffer.size() > 0 ? m_circularFocusBuffer.last() : nullptr;
+                if (lastSelectedEntry)
+                {
+                    lastSelectedEntry->setCellSelected(!lastSelectedEntry->isCellSelected());
+                    lastSelectedEntry->update();
+                    m_circularFocusBuffer.clear();
+                }
 
-    if(!m_readOnly && !backend->isEnabled())
-    {
-        QApplication::restoreOverrideCursor();
-        KMessageBox::information(worksheetView(), i18n("There are some problems with the %1 backend,\n"\
-                                            "please check your configuration or install the needed packages.\n"
-                                            "You will only be able to view this worksheet.", m_backendName), i18n("Open File"));
-        m_readOnly = true;
+                for (auto* entry : {selectedEntry, lastSelectedEntry})
+                    if (entry)
+                    {
+                        if (entry->isCellSelected())
+                            m_selectedEntries.append(entry);
+                        else if (!entry->isCellSelected())
+                            m_selectedEntries.removeOne(entry);
+                    }
+            }
+        }
+        else
+        {
+            for (auto* entry : m_selectedEntries)
+            {
+                if(isValidEntry(entry))
+                {
+                    entry->setCellSelected(false);
+                    entry->update();
+                }
+            }
+            m_selectedEntries.clear();
+
+            if (selectedEntry)
+                notifyEntryFocus(selectedEntry);
+
+            updateEntryCursor(event);
+        }
     }
+}
 
+void Worksheet::keyPressEvent(QKeyEvent* event)
+{
     if (m_readOnly)
-    {
-        for (QAction* action : m_richTextActionList)
-            action->setEnabled(false);
-    }
+        return;
 
+    if ((event->modifiers() & Qt::ControlModifier) && (event->key() == Qt::Key_1))
+        worksheetView()->actualSize();
+    else if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && !event->text().isEmpty())
+        addEntryFromEntryCursor(); //add new entry when entry cursor is actived when user starts typing text
 
-    m_isLoadingFromFile = true;
+    QGraphicsScene::keyPressEvent(event);
+}
 
-    if (m_session)
-        delete m_session;
-    m_session = nullptr;
+void Worksheet::setActionCollection(KActionCollection* collection)
+{
+    m_collection = collection;
+}
 
-    if (m_firstEntry) {
-        delete m_firstEntry;
-        m_firstEntry = nullptr;
-    }
+void Worksheet::initActions()
+{
+    // Mostly copied from KRichTextWidget::createActions(KActionCollection*)
+    // It would be great if this wasn't necessary.
 
-    resetEntryCursor();
-    m_itemWidths.clear();
-    m_maxWidth = 0;
+    // Text color
+    /* This is "format-stroke-color" in KRichTextWidget */
+    auto* action = new QAction(QIcon::fromTheme(QLatin1String("format-text-color")),
+                         i18nc("@action", "Text &Color..."), m_collection);
+    action->setIconText(i18nc("@label text color", "Color"));
+    action->setPriority(QAction::LowPriority);
+    m_richTextActionList.append(action);
+    connect(action, &QAction::triggered, this, &Worksheet::setTextForegroundColor);
 
-    if (!m_readOnly)
-        initSession(backend);
+    // Text color
+    action = new QAction(QIcon::fromTheme(QLatin1String("format-fill-color")),
+                         i18nc("@action", "Text &Highlight..."), m_collection);
+    action->setPriority(QAction::LowPriority);
+    m_richTextActionList.append(action);
+    connect(action, &QAction::triggered, this, &Worksheet::setTextBackgroundColor);
 
-    qDebug() << "loading jupyter entries";
+    // Font Family
+    m_fontAction = new KFontAction(i18nc("@action", "&Font"), m_collection);
+    m_richTextActionList.append(m_fontAction);
+    connect(m_fontAction, &KFontAction::textTriggered, this, &Worksheet::setFontFamily);
 
-    WorksheetEntry* entry = nullptr;
-    for (QJsonArray::const_iterator iter = cells.begin(); iter != cells.end(); ++iter) {
-        if (!Cantor::JupyterUtils::isJupyterCell(*iter))
-        {
-            QApplication::restoreOverrideCursor();
-            QString explanation;
-            if (iter->isObject())
-                explanation = i18n("an object with keys: %1", iter->toObject().keys().join(QLatin1String(", ")));
-            else
-                explanation = i18n("non object JSON value");
+    // Font Size
+    m_fontSizeAction = new KFontSizeAction(i18nc("@action", "Font &Size"), m_collection);
+    m_richTextActionList.append(m_fontSizeAction);
+    connect(m_fontSizeAction, &KFontSizeAction::fontSizeChanged, this, &Worksheet::setFontSize);
 
-            m_isLoadingFromFile = false;
-            showInvalidNotebookSchemeError(i18n("found incorrect data (%1) that is not Jupyter cell", explanation));
-            return false;
-        }
+    // Bold
+    m_boldAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-bold")),
+                                     i18nc("@action boldify selected text", "&Bold"),
+                                     m_collection);
+    m_boldAction->setPriority(QAction::LowPriority);
+    QFont bold;
+    bold.setBold(true);
+    m_boldAction->setFont(bold);
+    m_richTextActionList.append(m_boldAction);
+    connect(m_boldAction, &QAction::triggered, this, &Worksheet::setTextBold);
 
-        const QJsonObject& cell = iter->toObject();
-        QString cellType = Cantor::JupyterUtils::getCellType(cell);
-
-        if (cellType == QLatin1String("code"))
-        {
-            if (LatexEntry::isConvertableToLatexEntry(cell))
-            {
-                entry = appendEntry(LatexEntry::Type, false);
-                entry->setContentFromJupyter(cell);
-                entry->evaluate(WorksheetEntry::InternalEvaluation);
-            }
-            else
-            {
-                entry = appendEntry(CommandEntry::Type, false);
-                entry->setContentFromJupyter(cell);
-            }
-        }
-        else if (cellType == QLatin1String("markdown"))
-        {
-            if (TextEntry::isConvertableToTextEntry(cell))
-            {
-                entry = appendEntry(TextEntry::Type, false);
-                entry->setContentFromJupyter(cell);
-            }
-            else if (HorizontalRuleEntry::isConvertableToHorizontalRuleEntry(cell))
-            {
-                entry = appendEntry(HorizontalRuleEntry::Type, false);
-                entry->setContentFromJupyter(cell);
-            }
-            else if (HierarchyEntry::isConvertableToHierarchyEntry(cell))
-            {
-                entry = appendEntry(HierarchyEntry::Type, false);
-                entry->setContentFromJupyter(cell);
-            }
-            else
-            {
-                entry = appendEntry(MarkdownEntry::Type, false);
-                entry->setContentFromJupyter(cell);
-                entry->evaluate(WorksheetEntry::InternalEvaluation);
-            }
-        }
-        else if (cellType == QLatin1String("raw"))
-        {
-            if (PageBreakEntry::isConvertableToPageBreakEntry(cell))
-                entry = appendEntry(PageBreakEntry::Type, false);
-            else
-                entry = appendEntry(TextEntry::Type, false);
-            entry->setContentFromJupyter(cell);
-        }
-
-        if (m_readOnly && entry)
-        {
-            entry->setAcceptHoverEvents(false);
-            entry = nullptr;
-        }
-    }
-
-    if (m_readOnly)
-        clearFocus();
-
-    m_isLoadingFromFile = false;
-    updateHierarchyLayout();
-    updateLayout();
-
-
-    Q_EMIT loaded();
-    return true;
-}
-
-void Worksheet::showInvalidNotebookSchemeError(QString additionalInfo)
-{
-    if (additionalInfo.isEmpty())
-        KMessageBox::error(worksheetView(), i18n("The file is not valid Jupyter notebook"), i18n("Open File"));
-    else
-        KMessageBox::error(worksheetView(), i18n("Invalid Jupyter notebook scheme: %1", additionalInfo), i18n("Open File"));
-}
-
-void Worksheet::gotResult(Cantor::Expression* expr)
-{
-    if(expr==nullptr)
-        expr=qobject_cast<Cantor::Expression*>(sender());
-
-    if(expr==nullptr)
-        return;
-
-    //We're only interested in help results, others are handled by the WorksheetEntry
-    for (auto* result : expr->results())
-    {
-        if(result && result->type()==Cantor::HelpResult::Type)
-        {
-            QString help = result->toHtml();
-            //Do some basic LaTeX replacing
-            //TODO: what for? relevant for sage only?
-            help.replace(QRegularExpression(QStringLiteral("\\\\code\\{([^\\}]*)\\}")), QStringLiteral("<b>\\1</b>"));
-            help.replace(QRegularExpression(QStringLiteral("\\$([^\\$])\\$")), QStringLiteral("<i>\\1</i>"));
-
-            Q_EMIT showHelp(help);
-
-            //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).
-            break;
-        }
-    }
-
-}
-
-void Worksheet::removeCurrentEntry()
-{
-    auto* entry = currentEntry();
-    if(!entry)
-        return;
-
-    // In case we just removed this
-    if (entry->isAncestorOf(m_lastFocusedTextItem))
-        m_lastFocusedTextItem = nullptr;
-
-    entry->startRemoving();
-}
-
-Cantor::Renderer* Worksheet::renderer()
-{
-    return &m_renderer;
-}
-
-MathRenderer* Worksheet::mathRenderer()
-{
-    return &m_mathRenderer;
-}
-
-QMenu* Worksheet::createContextMenu()
-{
-    auto* menu = new QMenu(worksheetView());
-    connect(menu, SIGNAL(aboutToHide()), menu, SLOT(deleteLater()));
-
-    return menu;
-}
-
-void Worksheet::populateMenu(QMenu* menu, QPointF pos)
-{
-    // Two different context menus - 1. for the current entry, 2. for multiple selected entries
-    if (m_selectedEntries.isEmpty())
-    {
-        auto* entry = entryAt(pos);
-        if (entry && !entry->isAncestorOf(m_lastFocusedTextItem)) {
-            auto* item =
-                qgraphicsitem_cast<WorksheetTextEditorItem*>(itemAt(pos, QTransform()));
-            if (item && item->isEditable())
-                m_lastFocusedTextItem = item;
-        }
-
-        if (entry) {
-            //"Convert To" menu
-            QMenu* convertTo = new QMenu(i18n("Convert To"));
-            convertTo->setIcon(QIcon::fromTheme(QLatin1String("gtk-convert")));
-            menu->addMenu(convertTo);
-
-            if (entry->type() != CommandEntry::Type)
-                convertTo->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), entry, &WorksheetEntry::convertToCommandEntry);
-
-            if (entry->type() != TextEntry::Type)
-                convertTo->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), entry, &WorksheetEntry::convertToTextEntry);
-
-    #ifdef Discount_FOUND
-            if (entry->type() != MarkdownEntry::Type)
-                convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), entry, &WorksheetEntry::convertToMarkdownEntry);
-    #endif
-            if (entry->type() != LatexEntry::Type)
-                convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), entry, &WorksheetEntry::convertToLatexEntry);
-            if (entry->type() != ImageEntry::Type)
-                convertTo->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, &WorksheetEntry::convertToImageEntry);
-
-            if (entry->type() != PageBreakEntry::Type)
-                convertTo->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), entry, &WorksheetEntry::converToPageBreakEntry);
-
-            if (entry->type() != HorizontalRuleEntry::Type)
-                convertTo->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), entry, &WorksheetEntry::convertToHorizontalRuleEntry);
-
-            if (entry->type() != HierarchyEntry::Type)
-                convertTo->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), entry, &WorksheetEntry::convertToHierarchyEntry);
-
-            //"Insert After" menu
-            QMenu* insert = new QMenu(i18n("Insert After"), menu);
-            insert->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-below")));
-            menu->addSeparator();
-            menu->addMenu(insert);
-
-            insert->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), entry, SLOT(insertCommandEntry()));
-            insert->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), entry, SLOT(insertTextEntry()));
-    #ifdef Discount_FOUND
-            insert->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), entry, SLOT(insertMarkdownEntry()));
-    #endif
-            insert->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), entry, SLOT(insertLatexEntry()));
-            insert->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntry()));
-            insert->addSeparator();
-            insert->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), entry, SLOT(insertHorizontalRuleEntry()));
-            insert->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), entry, SLOT(insertPageBreakEntry()));
-            insert->addSeparator();
-            insert->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), entry, SLOT(insertHierarchyEntry()));
-
-            //"Insert Before" menu
-            QMenu* insertBefore = new QMenu(i18n("Insert Before"), menu);
-            insertBefore->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-above")));
-            menu->addMenu(insertBefore);
-
-            insertBefore->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), entry, SLOT(insertCommandEntryBefore()));
-            insertBefore->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), entry, SLOT(insertTextEntryBefore()));
-    #ifdef Discount_FOUND
-            insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), entry, SLOT(insertMarkdownEntryBefore()));
-    #endif
-            insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), entry, SLOT(insertLatexEntryBefore()));
-            insertBefore->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntryBefore()));
-            insertBefore->addSeparator();
-            insertBefore->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), entry, SLOT(insertHorizontalRuleEntryBefore()));
-            insertBefore->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), entry, SLOT(insertPageBreakEntryBefore()));
-            insertBefore->addSeparator();
-            insertBefore->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), entry, SLOT(insertHierarchyEntryBefore()));
-        } else {
-            QMenu* insertMenu = new QMenu(i18n("Insert"));
-            insertMenu->setIcon(QIcon::fromTheme(QLatin1String("insert-table-row")));
-
-            insertMenu->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command"), this, SLOT(appendCommandEntry()));
-            insertMenu->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text"), this, &Worksheet::appendTextEntry);
-    #ifdef Discount_FOUND
-            insertMenu->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown"), this, &Worksheet::appendMarkdownEntry);
-    #endif
-
-            insertMenu->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX"), this, &Worksheet::appendLatexEntry);
-            insertMenu->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), this, &Worksheet::appendImageEntry);
-            insertMenu->addSeparator();
-            insertMenu->addAction(QIcon::fromTheme(QLatin1String("newline")), i18n("Horizontal Line"), this, &Worksheet::appendHorizontalRuleEntry);
-            insertMenu->addAction(QIcon::fromTheme(QLatin1String("insert-page-break")), i18n("Page Break"), this, &Worksheet::appendPageBreakEntry);
-            insertMenu->addSeparator();
-            insertMenu->addAction(QIcon::fromTheme(QLatin1String("view-list-tree")), i18n("Hierarchy Entry"), this, &Worksheet::appendHierarchyEntry);
-
-            menu->addMenu(insertMenu);
-
-            //"Show help" for backend's documentation
-#ifdef HAVE_EMBEDDED_DOCUMENTATION
-            menu->addSeparator();
-            menu->addAction(QIcon::fromTheme(QLatin1String("help-hint")), i18n("Show Help"), this,
-                                        [=] () { requestDocumentation(QString()); });
-#endif
-        }
-
-        //evaluate the whole worksheet or interrupt the current calculation
-        menu->addSeparator();
-        if (!isRunning())
-            menu->addAction(QIcon::fromTheme(QLatin1String("system-run")), i18n("Evaluate Worksheet"),
-                            this, &Worksheet::evaluate);
-        else
-            menu->addAction(QIcon::fromTheme(QLatin1String("process-stop")), i18n("Interrupt"), this,
-                            &Worksheet::interrupt);
-
-        //zooming
-        menu->addSeparator();
-        auto* zoomMenu = new QMenu(i18n("Zoom"));
-        zoomMenu->setIcon(QIcon::fromTheme(QLatin1String("zoom-draw")));
-        auto* view = worksheetView();
-
-        auto* action = zoomMenu->addAction(QIcon::fromTheme(QLatin1String("zoom-in")), i18n("Zoom In"), view, &WorksheetView::zoomIn);
-        action->setShortcut(Qt::CTRL | Qt::Key_Plus);
-
-        action = zoomMenu->addAction(QIcon::fromTheme(QLatin1String("zoom-out")), i18n("Zoom Out"), view, &WorksheetView::zoomOut);
-        action->setShortcut(Qt::CTRL | Qt::Key_Minus);
-        zoomMenu->addSeparator();
-
-        action = zoomMenu->addAction(QIcon::fromTheme(QLatin1String("zoom-original")), i18n("Original Size"), view, &WorksheetView::actualSize);
-        action->setShortcut(Qt::CTRL | Qt::Key_1);
-
-        menu->addMenu(zoomMenu);
-    }
-    else
-    {
-        menu->clear();
-        menu->addAction(QIcon::fromTheme(QLatin1String("go-up")), i18n("Move Entries Up"), this, &Worksheet::selectionMoveUp);
-        menu->addAction(QIcon::fromTheme(QLatin1String("go-down")), i18n("Move Entries Down"), this, &Worksheet::selectionMoveDown);
-        menu->addAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate Entries"), this, &Worksheet::selectionEvaluate);
-        menu->addSeparator();
-        menu->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete Entries"), this, &Worksheet::selectionRemove);
-
-        bool isAnyCommandEntryInSelection = false;
-        for (auto* entry : m_selectedEntries)
-            if (entry->type() == CommandEntry::Type)
-            {
-                isAnyCommandEntryInSelection = true;
-                break;
-            }
-
-        if (isAnyCommandEntryInSelection)
-        {
-            menu->addSeparator();
-            menu->addAction(QIcon(), i18n("Collapse Command Entry Results"), this, &Worksheet::collapseSelectionResults);
-            menu->addAction(QIcon(), i18n("Expand Command Entry Results"), this, &Worksheet::uncollapseSelectionResults);
-            menu->addSeparator();
-            menu->addAction(QIcon(), i18n("Delete Command Entry Results"), this, &Worksheet::removeSelectionResults);
-            menu->addSeparator();
-            menu->addAction(QIcon(), i18n("Exclude Command Entry From Execution"), this, &Worksheet::excludeFromExecutionSelection);
-            menu->addAction(QIcon(), i18n("Add Command Entry To Execution"), this, &Worksheet::addToExectuionSelection);
-        }
-    }
-}
-
-void Worksheet::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
-{
-    if (m_readOnly)
-        return;
-
-    // forward the event to the items
-    QGraphicsScene::contextMenuEvent(event);
-
-    if (!event->isAccepted()) {
-        event->accept();
-        QMenu* menu = createContextMenu();
-        populateMenu(menu, event->scenePos());
-
-        menu->popup(event->screenPos());
-    }
-}
-
-void Worksheet::mousePressEvent(QGraphicsSceneMouseEvent* event)
-{
-    /*
-    if (event->button() == Qt::LeftButton && !focusItem() && lastEntry() &&
-        event->scenePos().y() > lastEntry()->y() + lastEntry()->size().height())
-        lastEntry()->focusEntry(WorksheetTextItem::BottomRight);
-    */
-    QGraphicsScene::mousePressEvent(event);
-
-    if (!m_readOnly && event->buttons() & Qt::LeftButton)
-    {
-        auto* selectedEntry = entryAt(event->scenePos());
-        if (event->modifiers() & Qt::ControlModifier)
-        {
-            clearFocus();
-            resetEntryCursor();
-
-            if (selectedEntry)
-            {
-                selectedEntry->setCellSelected(!selectedEntry->isCellSelected());
-                selectedEntry->update();
-
-                auto* lastSelectedEntry = m_circularFocusBuffer.size() > 0 ? m_circularFocusBuffer.last() : nullptr;
-                if (lastSelectedEntry)
-                {
-                    lastSelectedEntry->setCellSelected(!lastSelectedEntry->isCellSelected());
-                    lastSelectedEntry->update();
-                    m_circularFocusBuffer.clear();
-                }
-
-                for (auto* entry : {selectedEntry, lastSelectedEntry})
-                    if (entry)
-                    {
-                        if (entry->isCellSelected())
-                            m_selectedEntries.append(entry);
-                        else if (!entry->isCellSelected())
-                            m_selectedEntries.removeOne(entry);
-                    }
-            }
-        }
-        else
-        {
-            for (auto* entry : m_selectedEntries)
-            {
-                if(isValidEntry(entry))
-                {
-                    entry->setCellSelected(false);
-                    entry->update();
-                }
-            }
-            m_selectedEntries.clear();
-
-            if (selectedEntry)
-                notifyEntryFocus(selectedEntry);
-
-            updateEntryCursor(event);
-        }
-    }
-}
-
-void Worksheet::keyPressEvent(QKeyEvent* event)
-{
-    if (m_readOnly)
-        return;
-
-    if ((event->modifiers() & Qt::ControlModifier) && (event->key() == Qt::Key_1))
-        worksheetView()->actualSize();
-    else if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && !event->text().isEmpty())
-        addEntryFromEntryCursor(); //add new entry when entry cursor is actived when user starts typing text
-
-    QGraphicsScene::keyPressEvent(event);
-}
-
-void Worksheet::setActionCollection(KActionCollection* collection)
-{
-    m_collection = collection;
-}
-
-void Worksheet::initActions()
-{
-    // Mostly copied from KRichTextWidget::createActions(KActionCollection*)
-    // It would be great if this wasn't necessary.
-
-    // Text color
-    /* This is "format-stroke-color" in KRichTextWidget */
-    auto* action = new QAction(QIcon::fromTheme(QLatin1String("format-text-color")),
-                         i18nc("@action", "Text &Color..."), m_collection);
-    action->setIconText(i18nc("@label text color", "Color"));
-    action->setPriority(QAction::LowPriority);
-    m_richTextActionList.append(action);
-    connect(action, &QAction::triggered, this, &Worksheet::setTextForegroundColor);
-
-    // Text color
-    action = new QAction(QIcon::fromTheme(QLatin1String("format-fill-color")),
-                         i18nc("@action", "Text &Highlight..."), m_collection);
-    action->setPriority(QAction::LowPriority);
-    m_richTextActionList.append(action);
-    connect(action, &QAction::triggered, this, &Worksheet::setTextBackgroundColor);
-
-    // Font Family
-    m_fontAction = new KFontAction(i18nc("@action", "&Font"), m_collection);
-    m_richTextActionList.append(m_fontAction);
-    connect(m_fontAction, &KFontAction::textTriggered, this, &Worksheet::setFontFamily);
-
-    // Font Size
-    m_fontSizeAction = new KFontSizeAction(i18nc("@action", "Font &Size"), m_collection);
-    m_richTextActionList.append(m_fontSizeAction);
-    connect(m_fontSizeAction, &KFontSizeAction::fontSizeChanged, this, &Worksheet::setFontSize);
-
-    // Bold
-    m_boldAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-bold")),
-                                     i18nc("@action boldify selected text", "&Bold"),
-                                     m_collection);
-    m_boldAction->setPriority(QAction::LowPriority);
-    QFont bold;
-    bold.setBold(true);
-    m_boldAction->setFont(bold);
-    m_richTextActionList.append(m_boldAction);
-    connect(m_boldAction, &QAction::triggered, this, &Worksheet::setTextBold);
-
-    // Italic
-    m_italicAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-italic")),
-                                       i18nc("@action italicize selected text", "&Italic"),
-                                       m_collection);
-    m_italicAction->setPriority(QAction::LowPriority);
-    QFont italic;
-    italic.setItalic(true);
-    m_italicAction->setFont(italic);
-    m_richTextActionList.append(m_italicAction);
-    connect(m_italicAction, &QAction::triggered, this, &Worksheet::setTextItalic);
-
-    // Underline
-    m_underlineAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-underline")),
-                                          i18nc("@action underline selected text",
-                                                "&Underline"),
-                                          m_collection);
-    m_underlineAction->setPriority(QAction::LowPriority);
-    QFont underline;
-    underline.setUnderline(true);
-    m_underlineAction->setFont(underline);
-    m_richTextActionList.append(m_underlineAction);
-    connect(m_underlineAction, &QAction::triggered, this, &Worksheet::setTextUnderline);
-
-    // Strike
-    m_strikeOutAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-strikethrough")),
-                                          i18nc("@action", "&Strike Out"),
-                                          m_collection);
-    m_strikeOutAction->setPriority(QAction::LowPriority);
-    m_richTextActionList.append(m_strikeOutAction);
-    connect(m_strikeOutAction, &QAction::triggered, this, &Worksheet::setTextStrikeOut);
-
-    // Alignment
-    auto* alignmentGroup = new QActionGroup(this);
-
-    //   Align left
-    m_alignLeftAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-left")),
-                                          i18nc("@action", "Align &Left"),
-                                          m_collection);
-    m_alignLeftAction->setPriority(QAction::LowPriority);
-    m_alignLeftAction->setIconText(i18nc("@label left justify", "Left"));
-    m_richTextActionList.append(m_alignLeftAction);
-    connect(m_alignLeftAction, &QAction::triggered, this, &Worksheet::setAlignLeft);
-    alignmentGroup->addAction(m_alignLeftAction);
-
-     //   Align center
-    m_alignCenterAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-center")),
-                                            i18nc("@action", "Align &Center"),
-                                            m_collection);
-    m_alignCenterAction->setPriority(QAction::LowPriority);
-    m_alignCenterAction->setIconText(i18nc("@label center justify", "Center"));
-    m_richTextActionList.append(m_alignCenterAction);
-    connect(m_alignCenterAction, &QAction::triggered, this, &Worksheet::setAlignCenter);
-    alignmentGroup->addAction(m_alignCenterAction);
-
-    //   Align right
-    m_alignRightAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-right")),
-                                           i18nc("@action", "Align &Right"),
-                                           m_collection);
-    m_alignRightAction->setPriority(QAction::LowPriority);
-    m_alignRightAction->setIconText(i18nc("@label right justify", "Right"));
-    m_richTextActionList.append(m_alignRightAction);
-    connect(m_alignRightAction, &QAction::triggered, this, &Worksheet::setAlignRight);
-    alignmentGroup->addAction(m_alignRightAction);
-
-    //   Align justify
-    m_alignJustifyAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-fill")),
-                                             i18nc("@action", "&Justify"),
-                                             m_collection);
-    m_alignJustifyAction->setPriority(QAction::LowPriority);
-    m_alignJustifyAction->setIconText(i18nc("@label justify fill", "Justify"));
-    m_richTextActionList.append(m_alignJustifyAction);
-    connect(m_alignJustifyAction, &QAction::triggered, this, &Worksheet::setAlignJustify);
-    alignmentGroup->addAction(m_alignJustifyAction);
-
-    if (m_collection)
-    {
-        m_collection->addAction(QLatin1String("format_text_foreground_color"), action);
-        m_collection->addAction(QLatin1String("format_text_background_color"), action);
-        m_collection->addAction(QLatin1String("format_font_family"), m_fontAction);
-        m_collection->addAction(QLatin1String("format_font_size"), m_fontSizeAction);
-        m_collection->addAction(QLatin1String("format_text_bold"), m_boldAction);
-        m_collection->setDefaultShortcut(m_boldAction, Qt::CTRL | Qt::Key_B);
-        m_collection->addAction(QLatin1String("format_text_italic"), m_italicAction);
-        m_collection->setDefaultShortcut(m_italicAction, Qt::CTRL | Qt::Key_I);
-        m_collection->addAction(QLatin1String("format_text_underline"), m_underlineAction);
-        m_collection->setDefaultShortcut(m_underlineAction, Qt::CTRL | Qt::Key_U);
-        m_collection->addAction(QLatin1String("format_text_strikeout"), m_strikeOutAction);
-        m_collection->setDefaultShortcut(m_strikeOutAction, Qt::CTRL | Qt::Key_L);
-        m_collection->addAction(QLatin1String("format_align_left"), m_alignLeftAction);
-        m_collection->addAction(QLatin1String("format_align_center"), m_alignCenterAction);
-        m_collection->addAction(QLatin1String("format_align_right"), m_alignRightAction);
-        m_collection->addAction(QLatin1String("format_align_justify"), m_alignJustifyAction);
-    }
-
-     /*
-     // List style
-     KSelectAction* selAction;
-     selAction = new KSelectAction(QIcon::fromTheme("format-list-unordered"),
-                                   i18nc("@title:menu", "List Style"),
-                                   collection);
-     QStringList listStyles;
-     listStyles      << i18nc("@item:inmenu no list style", "None")
-                     << i18nc("@item:inmenu disc list style", "Disc")
-                     << i18nc("@item:inmenu circle list style", "Circle")
-                     << i18nc("@item:inmenu square list style", "Square")
-                     << i18nc("@item:inmenu numbered lists", "123")
-                     << i18nc("@item:inmenu lowercase abc lists", "abc")
-                     << i18nc("@item:inmenu uppercase abc lists", "ABC");
-     selAction->setItems(listStyles);
-     selAction->setCurrentItem(0);
-     action = selAction;
-     m_richTextActionList.append(action);
-     collection->addAction("format_list_style", action);
-     connect(action, SIGNAL(triggered(int)),
-             this, &Worksheet::_k_setListStyle(int)));
-     connect(action, &QAction::triggered,
-             this, &Worksheet::_k_updateMiscActions()));
-
-     // Indent
-     action = new QAction(QIcon::fromTheme("format-indent-more"),
-                          i18nc("@action", "Increase Indent"), collection);
-     action->setPriority(QAction::LowPriority);
-     m_richTextActionList.append(action);
-     collection->addAction("format_list_indent_more", action);
-     connect(action, &QAction::triggered,
-             this, &Worksheet::indentListMore()));
-     connect(action, &QAction::triggered,
-             this, &Worksheet::_k_updateMiscActions()));
-
-     // Dedent
-     action = new QAction(QIcon::fromTheme("format-indent-less"),
-                          i18nc("@action", "Decrease Indent"), collection);
-     action->setPriority(QAction::LowPriority);
-     m_richTextActionList.append(action);
-     collection->addAction("format_list_indent_less", action);
-     connect(action, &QAction::triggered, this, &Worksheet::indentListLess()));
-     connect(action, &QAction::triggered, this, &Worksheet::_k_updateMiscActions()));
-     */
-}
-
-WorksheetTextEditorItem* Worksheet::LastFocusedTextItem()
-{
-    return m_lastFocusedTextItem;
-}
-
-WorksheetTextItem* Worksheet::lastFocusedTextItem()
-{
-    return m_legacylastFocusedTextItem;
-}
-
-void Worksheet::updateFocusedTextItem(WorksheetTextItem* newItem)
-{
-    if (newItem)
-    {
-        auto* entry = qobject_cast<WorksheetEntry*>(newItem->parentObject());
-
-        if (entry && isValidEntry(entry))
-            updateCurrentHierarchyFromEntry(entry);
-    }
-
-    if (m_lastFocusedTextItem) {
-        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::undoAvailable, this, &Worksheet::undoAvailable);
-        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::redoAvailable, this, &Worksheet::redoAvailable);
-        disconnect(this, &Worksheet::undo, m_lastFocusedTextItem, &WorksheetTextEditorItem::undo);
-        disconnect(this, &Worksheet::redo, m_lastFocusedTextItem, &WorksheetTextEditorItem::redo);
-        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::cutAvailable, this, &Worksheet::cutAvailable);
-        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::copyAvailable, this, &Worksheet::copyAvailable);
-        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::pasteAvailable, this, &Worksheet::pasteAvailable);
-        disconnect(this, &Worksheet::cut, m_lastFocusedTextItem, &WorksheetTextEditorItem::cut);
-        disconnect(this, &Worksheet::copy, m_lastFocusedTextItem, &WorksheetTextEditorItem::copy);
-        m_lastFocusedTextItem = nullptr;
-    }
-
-    if (m_legacylastFocusedTextItem && m_legacylastFocusedTextItem != newItem) {
-        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::undoAvailable, this, &Worksheet::undoAvailable);
-        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::redoAvailable, this, &Worksheet::redoAvailable);
-        disconnect(this, &Worksheet::undo, m_legacylastFocusedTextItem, &WorksheetTextItem::undo);
-        disconnect(this, &Worksheet::redo, m_legacylastFocusedTextItem, &WorksheetTextItem::redo);
-        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::cutAvailable, this, &Worksheet::cutAvailable);
-        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::copyAvailable, this, &Worksheet::copyAvailable);
-        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::pasteAvailable, this, &Worksheet::pasteAvailable);
-        disconnect(this, &Worksheet::cut, m_legacylastFocusedTextItem, &WorksheetTextItem::cut);
-        disconnect(this, &Worksheet::copy, m_legacylastFocusedTextItem, &WorksheetTextItem::copy);
-        m_legacylastFocusedTextItem->clearSelection();
-    }
-
-    if (newItem) {
-        WorksheetEntry* entry = qobject_cast<WorksheetEntry*>(newItem->parentObject());
-        if (entry)
-            setAcceptRichText(entry->acceptRichText());
-
-        Q_EMIT undoAvailable(newItem->isUndoAvailable());
-        Q_EMIT redoAvailable(newItem->isRedoAvailable());
-        connect(newItem, &WorksheetTextItem::undoAvailable, this, &Worksheet::undoAvailable);
-        connect(newItem, &WorksheetTextItem::redoAvailable, this, &Worksheet::redoAvailable);
-        connect(this, &Worksheet::undo, newItem, &WorksheetTextItem::undo);
-        connect(this, &Worksheet::redo, newItem, &WorksheetTextItem::redo);
-        Q_EMIT cutAvailable(newItem->isCutAvailable());
-        Q_EMIT copyAvailable(newItem->isCopyAvailable());
-        Q_EMIT pasteAvailable(newItem->isPasteAvailable());
-        connect(newItem, &WorksheetTextItem::cutAvailable, this, &Worksheet::cutAvailable);
-        connect(newItem, &WorksheetTextItem::copyAvailable, this, &Worksheet::copyAvailable);
-        connect(newItem, &WorksheetTextItem::pasteAvailable, this, &Worksheet::pasteAvailable);
-        connect(this, &Worksheet::cut, newItem, &WorksheetTextItem::cut);
-        connect(this, &Worksheet::copy, newItem, &WorksheetTextItem::copy);
-    }
-    else {
-        setAcceptRichText(false);
-        Q_EMIT undoAvailable(false);
-        Q_EMIT redoAvailable(false);
-        Q_EMIT cutAvailable(false);
-        Q_EMIT copyAvailable(false);
-        Q_EMIT pasteAvailable(false);
-    }
-    m_legacylastFocusedTextItem = newItem;
-}
-
-void Worksheet::updateFocusedTextItem(WorksheetTextEditorItem* newItem)
-{
-    if (newItem)
-    {
-        auto* entry = qobject_cast<WorksheetEntry*>(newItem->parentObject());
-
-        if (entry && isValidEntry(entry))
-            updateCurrentHierarchyFromEntry(entry);
-    }
-    if (m_legacylastFocusedTextItem) {
-        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::undoAvailable, this, &Worksheet::undoAvailable);
-        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::redoAvailable, this, &Worksheet::redoAvailable);
-        disconnect(this, &Worksheet::undo, m_legacylastFocusedTextItem, &WorksheetTextItem::undo);
-        disconnect(this, &Worksheet::redo, m_legacylastFocusedTextItem, &WorksheetTextItem::redo);
-        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::cutAvailable, this, &Worksheet::cutAvailable);
-        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::copyAvailable, this, &Worksheet::copyAvailable);
-        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::pasteAvailable, this, &Worksheet::pasteAvailable);
-        disconnect(this, &Worksheet::cut, m_legacylastFocusedTextItem, &WorksheetTextItem::cut);
-        disconnect(this, &Worksheet::copy, m_legacylastFocusedTextItem, &WorksheetTextItem::copy);
-        // m_legacylastFocusedTextItem = nullptr;
-    }
-
-    if (m_readOnly) {
-        if (m_lastFocusedTextItem && m_lastFocusedTextItem != newItem) {
-            disconnect(this, &Worksheet::copy, m_lastFocusedTextItem, &WorksheetTextEditorItem::copy);
-            m_lastFocusedTextItem->clearSelection();
-        }
-        if (newItem && m_lastFocusedTextItem != newItem) {
-            connect(this, &Worksheet::copy, newItem, &WorksheetTextEditorItem::copy);
-            Q_EMIT copyAvailable(newItem->isCopyAvailable());
-        }
-        else if (!newItem)
-            Q_EMIT copyAvailable(false);
-        m_lastFocusedTextItem = newItem;
-        return;
-    }
-
-    if (m_lastFocusedTextItem && m_lastFocusedTextItem != newItem) {
-        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::undoAvailable, this, &Worksheet::undoAvailable);
-        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::redoAvailable, this, &Worksheet::redoAvailable);
-        disconnect(this, &Worksheet::undo, m_lastFocusedTextItem, &WorksheetTextEditorItem::undo);
-        disconnect(this, &Worksheet::redo, m_lastFocusedTextItem, &WorksheetTextEditorItem::redo);
-        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::cutAvailable, this, &Worksheet::cutAvailable);
-        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::copyAvailable, this, &Worksheet::copyAvailable);
-        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::pasteAvailable, this, &Worksheet::pasteAvailable);
-        disconnect(this, &Worksheet::cut, m_lastFocusedTextItem, &WorksheetTextEditorItem::cut);
-        disconnect(this, &Worksheet::copy, m_lastFocusedTextItem, &WorksheetTextEditorItem::copy);
-        m_lastFocusedTextItem->clearSelection();
-    }
-
-    if (newItem && m_lastFocusedTextItem != newItem) {
-        setAcceptRichText(false);
-        Q_EMIT undoAvailable(newItem->isUndoAvailable());
-        Q_EMIT redoAvailable(newItem->isRedoAvailable());
-        connect(newItem, &WorksheetTextEditorItem::undoAvailable, this, &Worksheet::undoAvailable);
-        connect(newItem, &WorksheetTextEditorItem::redoAvailable, this, &Worksheet::redoAvailable);
-        connect(this, &Worksheet::undo, newItem, &WorksheetTextEditorItem::undo);
-        connect(this, &Worksheet::redo, newItem, &WorksheetTextEditorItem::redo);
-        Q_EMIT cutAvailable(newItem->isCutAvailable());
-        Q_EMIT copyAvailable(newItem->isCopyAvailable());
-        Q_EMIT pasteAvailable(newItem->isPasteAvailable());
-        connect(newItem, &WorksheetTextEditorItem::cutAvailable, this, &Worksheet::cutAvailable);
-        connect(newItem, &WorksheetTextEditorItem::copyAvailable, this, &Worksheet::copyAvailable);
-        connect(newItem, &WorksheetTextEditorItem::pasteAvailable, this, &Worksheet::pasteAvailable);
-        connect(this, &Worksheet::cut, newItem, &WorksheetTextEditorItem::cut);
-        connect(this, &Worksheet::copy, newItem, &WorksheetTextEditorItem::copy);
-    } else if (!newItem) {
-        setAcceptRichText(false);
-        Q_EMIT undoAvailable(false);
-        Q_EMIT redoAvailable(false);
-        Q_EMIT cutAvailable(false);
-        Q_EMIT copyAvailable(false);
-        Q_EMIT pasteAvailable(false);
-    }
-    m_lastFocusedTextItem = newItem;
-}
-
-
-/*!
- * handles the paste action triggered in cantor_part.
- * Pastes into the last focused text item.
- * In case the "new entry"-cursor is currently shown,
- * a new entry is created first which the content will be pasted into.
- */
-void Worksheet::paste() {
-    if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry)
-        addEntryFromEntryCursor();
-
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->paste();
-    else if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->paste();
-}
-
-void Worksheet::setRichTextInformation(const RichTextInfo& info)
-{
-    if (!m_boldAction)
-        return;
-
-    m_boldAction->setChecked(info.bold);
-    m_italicAction->setChecked(info.italic);
-    m_underlineAction->setChecked(info.underline);
-    m_strikeOutAction->setChecked(info.strikeOut);
-    m_fontAction->setFont(info.font);
-    if (info.fontSize > 0)
-        m_fontSizeAction->setFontSize(info.fontSize);
-
-    if (info.align & Qt::AlignLeft)
-        m_alignLeftAction->setChecked(true);
-    else if (info.align & Qt::AlignCenter)
-        m_alignCenterAction->setChecked(true);
-    else if (info.align & Qt::AlignRight)
-        m_alignRightAction->setChecked(true);
-    else if (info.align & Qt::AlignJustify)
-        m_alignJustifyAction->setChecked(true);
-}
-
-void Worksheet::setAcceptRichText(bool b)
-{
-    if (!m_readOnly)
-    {
-        for(auto* action : m_richTextActionList)
-        {
-            if (!action) continue;
-            action->setVisible(b);
-        }
-    }
-}
-
-WorksheetTextEditorItem* Worksheet::currentTextItem()
-{
-    auto* item = focusItem();
-    if (!item)
-        item = m_lastFocusedTextItem;
-    while (item && item->type() != WorksheetTextEditorItem::Type)
-        item = item->parentItem();
-
-    return qgraphicsitem_cast<WorksheetTextEditorItem*>(item);
-}
-
-void Worksheet::setTextForegroundColor()
-{
-    if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->setTextForegroundColor();
-}
-
-void Worksheet::setTextBackgroundColor()
-{
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->setTextBackgroundColor();
-    else if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->setTextBackgroundColor();
-}
-
-void Worksheet::setTextBold(bool b)
-{
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->setTextBold(b);
-    else if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->setTextBold(b);
-}
-
-void Worksheet::setTextItalic(bool b)
-{
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->setTextItalic(b);
-    else if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->setTextItalic(b);
-}
-
-void Worksheet::setTextUnderline(bool b)
-{
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->setTextUnderline(b);
-    else if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->setTextUnderline(b);
-}
-
-void Worksheet::setTextStrikeOut(bool b)
-{
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->setTextStrikeOut(b);
-    else if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->setTextStrikeOut(b);
-}
-
-void Worksheet::setAlignLeft()
-{
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->setAlignment(Qt::AlignLeft);
-    else if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->setAlignment(Qt::AlignLeft);
-}
-
-void Worksheet::setAlignRight()
-{
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->setAlignment(Qt::AlignRight);
-    else if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->setAlignment(Qt::AlignRight);
-}
-
-void Worksheet::setAlignCenter()
-{
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->setAlignment(Qt::AlignCenter);
-    else if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->setAlignment(Qt::AlignCenter);
-}
-
-void Worksheet::setAlignJustify()
-{
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->setAlignment(Qt::AlignJustify);
-    else if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->setAlignment(Qt::AlignJustify);
-}
-
-void Worksheet::setFontFamily(const QString& font)
-{
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->setFontFamily(font);
-    else if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->setFontFamily(font);
-}
-
-void Worksheet::setFontSize(int size)
-{
-    if (m_lastFocusedTextItem)
-        m_lastFocusedTextItem->setFontSize(size);
-    else if (m_legacylastFocusedTextItem)
-        m_legacylastFocusedTextItem->setFontSize(size);
-}
-
-
-bool Worksheet::isShortcut(const QKeySequence& sequence)
-{
-    return m_shortcuts.contains(sequence);
-}
-
-void Worksheet::registerShortcut(QAction* action)
-{
-    for (auto& shortcut : action->shortcuts())
-        m_shortcuts.insert(shortcut, action);
-
-    connect(action, &QAction::changed, this, &Worksheet::updateShortcut);
-}
-
-void Worksheet::updateShortcut()
-{
-    QAction* action = qobject_cast<QAction*>(sender());
-    if (!action)
-        return;
-
-    // delete the old shortcuts of this action
-    QList<QKeySequence> shortcuts = m_shortcuts.keys(action);
-    for (auto& shortcut : shortcuts)
-        m_shortcuts.remove(shortcut);
-
-    // add the new shortcuts
-    for (auto& shortcut : action->shortcuts())
-        m_shortcuts.insert(shortcut, action);
-}
-
-void Worksheet::dragEnterEvent(QGraphicsSceneDragDropEvent* event)
-{
-    if (m_dragEntry)
-        event->accept();
-    else
-        QGraphicsScene::dragEnterEvent(event);
-}
-
-void Worksheet::dragLeaveEvent(QGraphicsSceneDragDropEvent* event)
-{
-    if (!m_dragEntry)
-    {
-        QGraphicsScene::dragLeaveEvent(event);
-        return;
-    }
-
-    event->accept();
-
-    removeDragPlaceholder();
-    updateLayout();
-}
-
-void Worksheet::dragMoveEvent(QGraphicsSceneDragDropEvent* event)
-{
-    if (!m_dragEntry) {
-        QGraphicsScene::dragMoveEvent(event);
-        return;
-    }
-
-    QPointF pos = event->scenePos();
-    auto* entry = entryAt(pos);
-    WorksheetEntry* prev = nullptr;
-    WorksheetEntry* next = nullptr;
-    if (entry) {
-        if (pos.y() < entry->y() + entry->size().height()/2) {
-            prev = entry->previous();
-            next = entry;
-        } else if (pos.y() >= entry->y() + entry->size().height()/2) {
-            prev = entry;
-            next = entry->next();
-        }
-    } else {
-        auto* last = lastEntry();
-        if (last && pos.y() > last->y() + last->size().height()) {
-            prev = last;
-            next = nullptr;
-        }
-    }
-
-    const bool dragWithHierarchy = !m_hierarchySubentriesDrag.empty();
-
-    if (m_placeholderEntry)
-    {
-        if (prev == m_placeholderEntry)
-            prev = m_placeholderEntry->previous();
-
-        if (next == m_placeholderEntry)
-            next = m_placeholderEntry->next();
-    }
-
-    if (prev || next)
-    {
-        const QSizeF placeholderSize = dragWithHierarchy ? m_hierarchyDragSize : m_dragEntry->size();
-
-        if (!m_placeholderEntry)
-            m_placeholderEntry = new PlaceHolderEntry(this, QSizeF(0, 0));
-
-        const bool positionChanged = m_placeholderEntry->previous() != prev || m_placeholderEntry->next() != next;
-
-        if (positionChanged)
-        {
-            auto* oldPrevious = m_placeholderEntry->previous();
-            auto* oldNext = m_placeholderEntry->next();
-
-            if (oldPrevious && oldPrevious->next() == m_placeholderEntry)
-                oldPrevious->setNext(oldNext);
-            else if (firstEntry() == m_placeholderEntry)
-                setFirstEntry(oldNext);
-
-            if (oldNext && oldNext->previous() == m_placeholderEntry)
-                oldNext->setPrevious(oldPrevious);
-            else if (lastEntry() == m_placeholderEntry)
-                setLastEntry(oldPrevious);
-
-            m_placeholderEntry->setPrevious(prev);
-            m_placeholderEntry->setNext(next);
-
-            if (prev)
-                prev->setNext(m_placeholderEntry);
-            else
-                setFirstEntry(m_placeholderEntry);
-
-            if (next)
-                next->setPrevious(m_placeholderEntry);
-            else
-                setLastEntry(m_placeholderEntry);
-        }
-
-        m_placeholderEntry->changeSize(placeholderSize);
-
-        updateLayout();
-    }
-
-    const QPoint viewPos = worksheetView()->mapFromScene(pos);
-    const int viewHeight = worksheetView()->viewport()->height();
-    if ((viewPos.y() < 10 || viewPos.y() > viewHeight - 10) &&
-        !m_dragScrollTimer) {
-        m_dragScrollTimer = new QTimer(this);
-        m_dragScrollTimer->setSingleShot(true);
-        m_dragScrollTimer->setInterval(100);
-        connect(m_dragScrollTimer, SIGNAL(timeout()), this,
-                SLOT(updateDragScrollTimer()));
-        m_dragScrollTimer->start();
-    }
-
-    event->accept();
-}
-
-void Worksheet::dropEvent(QGraphicsSceneDragDropEvent* event)
-{
-    if (!m_dragEntry)
-        QGraphicsScene::dropEvent(event);
-    event->accept();
-}
-
-void Worksheet::updateDragScrollTimer()
-{
-    if (!m_dragScrollTimer)
-        return;
-
-    const QPoint viewPos = worksheetView()->viewCursorPos();
-    const QWidget* viewport = worksheetView()->viewport();
-    const int viewHeight = viewport->height();
-    if (!m_dragEntry || !(viewport->rect().contains(viewPos)) ||
-        (viewPos.y() >= 10 && viewPos.y() <= viewHeight - 10)) {
-        delete m_dragScrollTimer;
-        m_dragScrollTimer = nullptr;
-        return;
-    }
-
-    if (viewPos.y() < 10)
-        worksheetView()->scrollBy(-10*(10 - viewPos.y()));
-    else
-        worksheetView()->scrollBy(10*(viewHeight - viewPos.y()));
+    // Italic
+    m_italicAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-italic")),
+                                       i18nc("@action italicize selected text", "&Italic"),
+                                       m_collection);
+    m_italicAction->setPriority(QAction::LowPriority);
+    QFont italic;
+    italic.setItalic(true);
+    m_italicAction->setFont(italic);
+    m_richTextActionList.append(m_italicAction);
+    connect(m_italicAction, &QAction::triggered, this, &Worksheet::setTextItalic);
 
-    m_dragScrollTimer->start();
-}
+    // Underline
+    m_underlineAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-underline")),
+                                          i18nc("@action underline selected text",
+                                                "&Underline"),
+                                          m_collection);
+    m_underlineAction->setPriority(QAction::LowPriority);
+    QFont underline;
+    underline.setUnderline(true);
+    m_underlineAction->setFont(underline);
+    m_richTextActionList.append(m_underlineAction);
+    connect(m_underlineAction, &QAction::triggered, this, &Worksheet::setTextUnderline);
 
-void Worksheet::removeDragPlaceholder()
-{
-    if (!m_placeholderEntry)
-        return;
+    // Strike
+    m_strikeOutAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-strikethrough")),
+                                          i18nc("@action", "&Strike Out"),
+                                          m_collection);
+    m_strikeOutAction->setPriority(QAction::LowPriority);
+    m_richTextActionList.append(m_strikeOutAction);
+    connect(m_strikeOutAction, &QAction::triggered, this, &Worksheet::setTextStrikeOut);
 
-    auto* placeholder = m_placeholderEntry;
-    auto* previous = placeholder->previous();
-    auto* next = placeholder->next();
+    // Alignment
+    auto* alignmentGroup = new QActionGroup(this);
 
-    if (previous && previous->next() == placeholder)
-        previous->setNext(next);
-    else if (firstEntry() == placeholder)
-        setFirstEntry(next);
+    //   Align left
+    m_alignLeftAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-left")),
+                                          i18nc("@action", "Align &Left"),
+                                          m_collection);
+    m_alignLeftAction->setPriority(QAction::LowPriority);
+    m_alignLeftAction->setIconText(i18nc("@label left justify", "Left"));
+    m_richTextActionList.append(m_alignLeftAction);
+    connect(m_alignLeftAction, &QAction::triggered, this, &Worksheet::setAlignLeft);
+    alignmentGroup->addAction(m_alignLeftAction);
 
-    if (next && next->previous() == placeholder)
-        next->setPrevious(previous);
-    else if (lastEntry() == placeholder)
-        setLastEntry(previous);
+     //   Align center
+    m_alignCenterAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-center")),
+                                            i18nc("@action", "Align &Center"),
+                                            m_collection);
+    m_alignCenterAction->setPriority(QAction::LowPriority);
+    m_alignCenterAction->setIconText(i18nc("@label center justify", "Center"));
+    m_richTextActionList.append(m_alignCenterAction);
+    connect(m_alignCenterAction, &QAction::triggered, this, &Worksheet::setAlignCenter);
+    alignmentGroup->addAction(m_alignCenterAction);
 
-    placeholder->setPrevious(nullptr);
-    placeholder->setNext(nullptr);
-    placeholder->hide();
-    placeholder->deleteLater();
+    //   Align right
+    m_alignRightAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-right")),
+                                           i18nc("@action", "Align &Right"),
+                                           m_collection);
+    m_alignRightAction->setPriority(QAction::LowPriority);
+    m_alignRightAction->setIconText(i18nc("@label right justify", "Right"));
+    m_richTextActionList.append(m_alignRightAction);
+    connect(m_alignRightAction, &QAction::triggered, this, &Worksheet::setAlignRight);
+    alignmentGroup->addAction(m_alignRightAction);
 
-    m_placeholderEntry = nullptr;
-}
+    //   Align justify
+    m_alignJustifyAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-fill")),
+                                             i18nc("@action", "&Justify"),
+                                             m_collection);
+    m_alignJustifyAction->setPriority(QAction::LowPriority);
+    m_alignJustifyAction->setIconText(i18nc("@label justify fill", "Justify"));
+    m_richTextActionList.append(m_alignJustifyAction);
+    connect(m_alignJustifyAction, &QAction::triggered, this, &Worksheet::setAlignJustify);
+    alignmentGroup->addAction(m_alignJustifyAction);
 
-void Worksheet::updateEntryCursor(QGraphicsSceneMouseEvent* event)
-{
-    // determine the worksheet entry near which the entry cursor will be shown
-    resetEntryCursor();
-    if (event->button() == Qt::LeftButton && !focusItem())
+    if (m_collection)
     {
-        const qreal y = event->scenePos().y();
-        for (auto* entry = firstEntry(); entry; entry = entry->next())
-        {
-            if (entry == firstEntry() && y < entry->y() )
-            {
-                m_choosenCursorEntry = firstEntry();
-                break;
-            }
-            else if (entry->y() < y && (entry->next() && y < entry->next()->y()))
-            {
-                m_choosenCursorEntry = entry->next();
-                break;
-            }
-            else if (entry->y() < y && entry == lastEntry())
-            {
-                m_isCursorEntryAfterLastEntry = true;
-                break;
-            }
-        }
+        m_collection->addAction(QLatin1String("format_text_foreground_color"), action);
+        m_collection->addAction(QLatin1String("format_text_background_color"), action);
+        m_collection->addAction(QLatin1String("format_font_family"), m_fontAction);
+        m_collection->addAction(QLatin1String("format_font_size"), m_fontSizeAction);
+        m_collection->addAction(QLatin1String("format_text_bold"), m_boldAction);
+        m_collection->setDefaultShortcut(m_boldAction, Qt::CTRL | Qt::Key_B);
+        m_collection->addAction(QLatin1String("format_text_italic"), m_italicAction);
+        m_collection->setDefaultShortcut(m_italicAction, Qt::CTRL | Qt::Key_I);
+        m_collection->addAction(QLatin1String("format_text_underline"), m_underlineAction);
+        m_collection->setDefaultShortcut(m_underlineAction, Qt::CTRL | Qt::Key_U);
+        m_collection->addAction(QLatin1String("format_text_strikeout"), m_strikeOutAction);
+        m_collection->setDefaultShortcut(m_strikeOutAction, Qt::CTRL | Qt::Key_L);
+        m_collection->addAction(QLatin1String("format_align_left"), m_alignLeftAction);
+        m_collection->addAction(QLatin1String("format_align_center"), m_alignCenterAction);
+        m_collection->addAction(QLatin1String("format_align_right"), m_alignRightAction);
+        m_collection->addAction(QLatin1String("format_align_justify"), m_alignJustifyAction);
     }
 
-    if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry)
-        drawEntryCursor();
-}
-
-void Worksheet::addEntryFromEntryCursor()
-{
-    qDebug() << "Add new entry from entry cursor";
-    if (m_isCursorEntryAfterLastEntry)
-        insertCommandEntry(lastEntry());
-    else
-        insertCommandEntryBefore(m_choosenCursorEntry);
-    resetEntryCursor();
-}
-
-void Worksheet::animateEntryCursor()
-{
-    if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && m_entryCursorItem)
-        m_entryCursorItem->setVisible(!m_entryCursorItem->isVisible());
-}
-
-void Worksheet::resetEntryCursor()
-{
-    m_choosenCursorEntry = nullptr;
-    m_isCursorEntryAfterLastEntry = false;
-    m_entryCursorItem->hide();
-}
-
-void Worksheet::drawEntryCursor()
-{
-    if (m_entryCursorItem && (m_choosenCursorEntry || (m_isCursorEntryAfterLastEntry && lastEntry())))
-    {
-        qreal x;
-        qreal y;
-        if (m_isCursorEntryAfterLastEntry)
-        {
-            x = lastEntry()->x();
-            y = lastEntry()->y() + lastEntry()->size().height() - (EntryCursorWidth - 1);
-        }
-        else
-        {
-            x = m_choosenCursorEntry->x();
-            y = m_choosenCursorEntry->y();
-        }
-        m_entryCursorItem->setLine(x,y,x+EntryCursorLength,y);
-        m_entryCursorItem->show();
-    }
+     /*
+     // List style
+     KSelectAction* selAction;
+     selAction = new KSelectAction(QIcon::fromTheme("format-list-unordered"),
+                                   i18nc("@title:menu", "List Style"),
+                                   collection);
+     QStringList listStyles;
+     listStyles      << i18nc("@item:inmenu no list style", "None")
+                     << i18nc("@item:inmenu disc list style", "Disc")
+                     << i18nc("@item:inmenu circle list style", "Circle")
+                     << i18nc("@item:inmenu square list style", "Square")
+                     << i18nc("@item:inmenu numbered lists", "123")
+                     << i18nc("@item:inmenu lowercase abc lists", "abc")
+                     << i18nc("@item:inmenu uppercase abc lists", "ABC");
+     selAction->setItems(listStyles);
+     selAction->setCurrentItem(0);
+     action = selAction;
+     m_richTextActionList.append(action);
+     collection->addAction("format_list_style", action);
+     connect(action, SIGNAL(triggered(int)),
+             this, &Worksheet::_k_setListStyle(int)));
+     connect(action, &QAction::triggered,
+             this, &Worksheet::_k_updateMiscActions()));
+
+     // Indent
+     action = new QAction(QIcon::fromTheme("format-indent-more"),
+                          i18nc("@action", "Increase Indent"), collection);
+     action->setPriority(QAction::LowPriority);
+     m_richTextActionList.append(action);
+     collection->addAction("format_list_indent_more", action);
+     connect(action, &QAction::triggered,
+             this, &Worksheet::indentListMore()));
+     connect(action, &QAction::triggered,
+             this, &Worksheet::_k_updateMiscActions()));
+
+     // Dedent
+     action = new QAction(QIcon::fromTheme("format-indent-less"),
+                          i18nc("@action", "Decrease Indent"), collection);
+     action->setPriority(QAction::LowPriority);
+     m_richTextActionList.append(action);
+     collection->addAction("format_list_indent_less", action);
+     connect(action, &QAction::triggered, this, &Worksheet::indentListLess()));
+     connect(action, &QAction::triggered, this, &Worksheet::_k_updateMiscActions()));
+     */
 }
 
-void Worksheet::setType(Worksheet::Type type)
+WorksheetTextEditorItem* Worksheet::LastFocusedTextItem()
 {
-    m_type = type;
+    return m_lastFocusedTextItem;
 }
 
-Worksheet::Type Worksheet::type() const
+WorksheetTextItem* Worksheet::lastFocusedTextItem()
 {
-    return m_type;
+    return m_legacylastFocusedTextItem;
 }
 
-void Worksheet::changeEntryType(WorksheetEntry* target, int newType)
+void Worksheet::updateFocusedTextItem(WorksheetTextItem* newItem)
 {
-    if (target && target->type() != newType)
+    if (newItem)
     {
-        bool animation_state = m_animationsEnabled;
-        m_animationsEnabled = false;
+        auto* entry = qobject_cast<WorksheetEntry*>(newItem->parentObject());
 
-        QString content;
+        if (entry && isValidEntry(entry))
+            updateCurrentHierarchyFromEntry(entry);
+    }
 
-        int targetEntryType = target->type();
-        switch(targetEntryType)
-        {
-            case CommandEntry::Type:
-                content = static_cast<CommandEntry*>(target)->command();
-                break;
-            case MarkdownEntry::Type:
-                content = static_cast<MarkdownEntry*>(target)->plainText();
-                break;
-            case TextEntry::Type:
-                content = static_cast<TextEntry*>(target)->text();
-                break;
-            case LatexEntry::Type:
-                content = static_cast<LatexEntry*>(target)->plain();
-        }
+    if (m_lastFocusedTextItem) {
+        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::undoAvailable, this, &Worksheet::undoAvailable);
+        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::redoAvailable, this, &Worksheet::redoAvailable);
+        disconnect(this, &Worksheet::undo, m_lastFocusedTextItem, &WorksheetTextEditorItem::undo);
+        disconnect(this, &Worksheet::redo, m_lastFocusedTextItem, &WorksheetTextEditorItem::redo);
+        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::cutAvailable, this, &Worksheet::cutAvailable);
+        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::copyAvailable, this, &Worksheet::copyAvailable);
+        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::pasteAvailable, this, &Worksheet::pasteAvailable);
+        disconnect(this, &Worksheet::cut, m_lastFocusedTextItem, &WorksheetTextEditorItem::cut);
+        disconnect(this, &Worksheet::copy, m_lastFocusedTextItem, &WorksheetTextEditorItem::copy);
+        m_lastFocusedTextItem = nullptr;
+    }
 
-        auto* newEntry = WorksheetEntry::create(newType, this);
-        if (newEntry)
-        {
-            newEntry->setContent(content);
-            auto* tmp = target;
+    if (m_legacylastFocusedTextItem && m_legacylastFocusedTextItem != newItem) {
+        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::undoAvailable, this, &Worksheet::undoAvailable);
+        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::redoAvailable, this, &Worksheet::redoAvailable);
+        disconnect(this, &Worksheet::undo, m_legacylastFocusedTextItem, &WorksheetTextItem::undo);
+        disconnect(this, &Worksheet::redo, m_legacylastFocusedTextItem, &WorksheetTextItem::redo);
+        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::cutAvailable, this, &Worksheet::cutAvailable);
+        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::copyAvailable, this, &Worksheet::copyAvailable);
+        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::pasteAvailable, this, &Worksheet::pasteAvailable);
+        disconnect(this, &Worksheet::cut, m_legacylastFocusedTextItem, &WorksheetTextItem::cut);
+        disconnect(this, &Worksheet::copy, m_legacylastFocusedTextItem, &WorksheetTextItem::copy);
+        m_legacylastFocusedTextItem->clearSelection();
+    }
 
-            newEntry->setPrevious(tmp->previous());
-            newEntry->setNext(tmp->next());
+    if (newItem) {
+        WorksheetEntry* entry = qobject_cast<WorksheetEntry*>(newItem->parentObject());
+        if (entry)
+            setAcceptRichText(entry->acceptRichText());
 
-            tmp->setPrevious(nullptr);
-            tmp->setNext(nullptr);
-            tmp->clearFocus();
-            tmp->forceRemove();
+        Q_EMIT undoAvailable(newItem->isUndoAvailable());
+        Q_EMIT redoAvailable(newItem->isRedoAvailable());
+        connect(newItem, &WorksheetTextItem::undoAvailable, this, &Worksheet::undoAvailable);
+        connect(newItem, &WorksheetTextItem::redoAvailable, this, &Worksheet::redoAvailable);
+        connect(this, &Worksheet::undo, newItem, &WorksheetTextItem::undo);
+        connect(this, &Worksheet::redo, newItem, &WorksheetTextItem::redo);
+        Q_EMIT cutAvailable(newItem->isCutAvailable());
+        Q_EMIT copyAvailable(newItem->isCopyAvailable());
+        Q_EMIT pasteAvailable(newItem->isPasteAvailable());
+        connect(newItem, &WorksheetTextItem::cutAvailable, this, &Worksheet::cutAvailable);
+        connect(newItem, &WorksheetTextItem::copyAvailable, this, &Worksheet::copyAvailable);
+        connect(newItem, &WorksheetTextItem::pasteAvailable, this, &Worksheet::pasteAvailable);
+        connect(this, &Worksheet::cut, newItem, &WorksheetTextItem::cut);
+        connect(this, &Worksheet::copy, newItem, &WorksheetTextItem::copy);
+    }
+    else {
+        setAcceptRichText(false);
+        Q_EMIT undoAvailable(false);
+        Q_EMIT redoAvailable(false);
+        Q_EMIT cutAvailable(false);
+        Q_EMIT copyAvailable(false);
+        Q_EMIT pasteAvailable(false);
+    }
+    m_legacylastFocusedTextItem = newItem;
+}
 
-            if (newEntry->previous())
-                newEntry->previous()->setNext(newEntry);
-            else
-                setFirstEntry(newEntry);
+void Worksheet::updateFocusedTextItem(WorksheetTextEditorItem* newItem)
+{
+    if (newItem)
+    {
+        auto* entry = qobject_cast<WorksheetEntry*>(newItem->parentObject());
 
-            if (newEntry->next())
-                newEntry->next()->setPrevious(newEntry);
-            else
-                setLastEntry(newEntry);
+        if (entry && isValidEntry(entry))
+            updateCurrentHierarchyFromEntry(entry);
+    }
+    if (m_legacylastFocusedTextItem) {
+        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::undoAvailable, this, &Worksheet::undoAvailable);
+        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::redoAvailable, this, &Worksheet::redoAvailable);
+        disconnect(this, &Worksheet::undo, m_legacylastFocusedTextItem, &WorksheetTextItem::undo);
+        disconnect(this, &Worksheet::redo, m_legacylastFocusedTextItem, &WorksheetTextItem::redo);
+        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::cutAvailable, this, &Worksheet::cutAvailable);
+        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::copyAvailable, this, &Worksheet::copyAvailable);
+        disconnect(m_legacylastFocusedTextItem, &WorksheetTextItem::pasteAvailable, this, &Worksheet::pasteAvailable);
+        disconnect(this, &Worksheet::cut, m_legacylastFocusedTextItem, &WorksheetTextItem::cut);
+        disconnect(this, &Worksheet::copy, m_legacylastFocusedTextItem, &WorksheetTextItem::copy);
+        // m_legacylastFocusedTextItem = nullptr;
+    }
 
-            if (newType == HierarchyEntry::Type || targetEntryType == HierarchyEntry::Type)
-                updateHierarchyLayout();
-            updateLayout();
-            makeVisible(newEntry);
-            focusEntry(newEntry);
-            setModified();
-            newEntry->focusEntry();
+    if (m_readOnly) {
+        if (m_lastFocusedTextItem && m_lastFocusedTextItem != newItem) {
+            disconnect(this, &Worksheet::copy, m_lastFocusedTextItem, &WorksheetTextEditorItem::copy);
+            m_lastFocusedTextItem->clearSelection();
         }
-        m_animationsEnabled = animation_state;
+        if (newItem && m_lastFocusedTextItem != newItem) {
+            connect(this, &Worksheet::copy, newItem, &WorksheetTextEditorItem::copy);
+            Q_EMIT copyAvailable(newItem->isCopyAvailable());
+        }
+        else if (!newItem)
+            Q_EMIT copyAvailable(false);
+        m_lastFocusedTextItem = newItem;
+        return;
+    }
+
+    if (m_lastFocusedTextItem && m_lastFocusedTextItem != newItem) {
+        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::undoAvailable, this, &Worksheet::undoAvailable);
+        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::redoAvailable, this, &Worksheet::redoAvailable);
+        disconnect(this, &Worksheet::undo, m_lastFocusedTextItem, &WorksheetTextEditorItem::undo);
+        disconnect(this, &Worksheet::redo, m_lastFocusedTextItem, &WorksheetTextEditorItem::redo);
+        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::cutAvailable, this, &Worksheet::cutAvailable);
+        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::copyAvailable, this, &Worksheet::copyAvailable);
+        disconnect(m_lastFocusedTextItem, &WorksheetTextEditorItem::pasteAvailable, this, &Worksheet::pasteAvailable);
+        disconnect(this, &Worksheet::cut, m_lastFocusedTextItem, &WorksheetTextEditorItem::cut);
+        disconnect(this, &Worksheet::copy, m_lastFocusedTextItem, &WorksheetTextEditorItem::copy);
+        m_lastFocusedTextItem->clearSelection();
+    }
+
+    if (newItem && m_lastFocusedTextItem != newItem) {
+        setAcceptRichText(false);
+        Q_EMIT undoAvailable(newItem->isUndoAvailable());
+        Q_EMIT redoAvailable(newItem->isRedoAvailable());
+        connect(newItem, &WorksheetTextEditorItem::undoAvailable, this, &Worksheet::undoAvailable);
+        connect(newItem, &WorksheetTextEditorItem::redoAvailable, this, &Worksheet::redoAvailable);
+        connect(this, &Worksheet::undo, newItem, &WorksheetTextEditorItem::undo);
+        connect(this, &Worksheet::redo, newItem, &WorksheetTextEditorItem::redo);
+        Q_EMIT cutAvailable(newItem->isCutAvailable());
+        Q_EMIT copyAvailable(newItem->isCopyAvailable());
+        Q_EMIT pasteAvailable(newItem->isPasteAvailable());
+        connect(newItem, &WorksheetTextEditorItem::cutAvailable, this, &Worksheet::cutAvailable);
+        connect(newItem, &WorksheetTextEditorItem::copyAvailable, this, &Worksheet::copyAvailable);
+        connect(newItem, &WorksheetTextEditorItem::pasteAvailable, this, &Worksheet::pasteAvailable);
+        connect(this, &Worksheet::cut, newItem, &WorksheetTextEditorItem::cut);
+        connect(this, &Worksheet::copy, newItem, &WorksheetTextEditorItem::copy);
+    } else if (!newItem) {
+        setAcceptRichText(false);
+        Q_EMIT undoAvailable(false);
+        Q_EMIT redoAvailable(false);
+        Q_EMIT cutAvailable(false);
+        Q_EMIT copyAvailable(false);
+        Q_EMIT pasteAvailable(false);
     }
+    m_lastFocusedTextItem = newItem;
 }
 
-bool Worksheet::isValidEntry(WorksheetEntry* entry)
-{
-    for (auto* iter = firstEntry(); iter; iter = iter->next())
-        if (entry == iter)
-            return true;
 
-    return false;
+/*!
+ * handles the paste action triggered in cantor_part.
+ * Pastes into the last focused text item.
+ * In case the "new entry"-cursor is currently shown,
+ * a new entry is created first which the content will be pasted into.
+ */
+void Worksheet::paste() {
+    if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry)
+        addEntryFromEntryCursor();
+
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->paste();
+    else if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->paste();
 }
 
-void Worksheet::selectionRemove()
+void Worksheet::setRichTextInformation(const RichTextInfo& info)
 {
-    if (m_selectedEntries.isEmpty())
+    if (!m_boldAction)
         return;
 
-    if (Settings::warnAboutEntryDelete())
-    {
-        const auto result = KMessageBox::warningTwoActions(worksheetView(),
-                i18n("This step cannot be undone. "
-                     "Do you really want to delete "
-                     "the selected entries?"),
-                i18n("Delete Entries"),
-                KStandardGuiItem::remove(),
-                KStandardGuiItem::cancel());
+    m_boldAction->setChecked(info.bold);
+    m_italicAction->setChecked(info.italic);
+    m_underlineAction->setChecked(info.underline);
+    m_strikeOutAction->setChecked(info.strikeOut);
+    m_fontAction->setFont(info.font);
+    if (info.fontSize > 0)
+        m_fontSizeAction->setFontSize(info.fontSize);
 
-        if (result != KMessageBox::PrimaryAction)
-            return;
-    }
+    if (info.align & Qt::AlignLeft)
+        m_alignLeftAction->setChecked(true);
+    else if (info.align & Qt::AlignCenter)
+        m_alignCenterAction->setChecked(true);
+    else if (info.align & Qt::AlignRight)
+        m_alignRightAction->setChecked(true);
+    else if (info.align & Qt::AlignJustify)
+        m_alignJustifyAction->setChecked(true);
+}
 
-    for (auto* entry : m_selectedEntries)
+void Worksheet::setAcceptRichText(bool b)
+{
+    if (!m_readOnly)
     {
-        if (isValidEntry(entry))
-            entry->startRemoving(false);
+        for(auto* action : m_richTextActionList)
+        {
+            if (!action) continue;
+            action->setVisible(b);
+        }
     }
-
-    m_selectedEntries.clear();
 }
 
-void Worksheet::selectionEvaluate()
+WorksheetTextEditorItem* Worksheet::currentTextItem()
 {
-    // run entries in worksheet order: from top to down
-    for (auto* entry = firstEntry(); entry; entry = entry->next())
-        if (m_selectedEntries.indexOf(entry) != -1)
-            entry->evaluate();
+    auto* item = focusItem();
+    if (!item)
+        item = m_lastFocusedTextItem;
+    while (item && item->type() != WorksheetTextEditorItem::Type)
+        item = item->parentItem();
+
+    return qgraphicsitem_cast<WorksheetTextEditorItem*>(item);
 }
 
-void Worksheet::selectionMoveUp()
+void Worksheet::setTextForegroundColor()
 {
-    bool moveHierarchyEntry = false;
-    // movement up should have an order from top to down.
-    for(auto* entry = firstEntry(); entry; entry = entry->next())
-        if(m_selectedEntries.indexOf(entry) != -1)
-            if (entry->previous() && m_selectedEntries.indexOf(entry->previous()) == -1)
-            {
-                entry->moveToPrevious(false);
-                if (entry->type() == HierarchyEntry::Type)
-                    moveHierarchyEntry = true;
-            }
-    if (moveHierarchyEntry)
-        updateHierarchyLayout();
-    updateLayout();
+    if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->setTextForegroundColor();
 }
 
-void Worksheet::selectionMoveDown()
+void Worksheet::setTextBackgroundColor()
 {
-    bool moveHierarchyEntry = false;
-    // movement up should have an order from down to top.
-    for(auto* entry = lastEntry(); entry; entry = entry->previous())
-        if(m_selectedEntries.indexOf(entry) != -1)
-            if (entry->next() && m_selectedEntries.indexOf(entry->next()) == -1)
-            {
-                entry->moveToNext(false);
-                if (entry->type() == HierarchyEntry::Type)
-                    moveHierarchyEntry = true;
-            }
-    if (moveHierarchyEntry)
-        updateHierarchyLayout();
-    updateLayout();
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->setTextBackgroundColor();
+    else if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->setTextBackgroundColor();
 }
 
-void Worksheet::notifyEntryFocus(WorksheetEntry* entry)
+void Worksheet::setTextBold(bool b)
 {
-    if (entry)
-    {
-        m_circularFocusBuffer.enqueue(entry);
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->setTextBold(b);
+    else if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->setTextBold(b);
+}
 
-        if (m_circularFocusBuffer.size() > 2)
-            m_circularFocusBuffer.dequeue();
-    }
-    else
-        m_circularFocusBuffer.clear();
+void Worksheet::setTextItalic(bool b)
+{
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->setTextItalic(b);
+    else if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->setTextItalic(b);
 }
 
-void Worksheet::collapseAllResults()
+void Worksheet::setTextUnderline(bool b)
 {
-    for (auto* entry = firstEntry(); entry; entry = entry->next())
-        if (entry->type() == CommandEntry::Type)
-            static_cast<CommandEntry*>(entry)->collapseResults();
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->setTextUnderline(b);
+    else if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->setTextUnderline(b);
 }
 
-void Worksheet::uncollapseAllResults()
+void Worksheet::setTextStrikeOut(bool b)
 {
-    for (auto* entry = firstEntry(); entry; entry = entry->next())
-        if (entry->type() == CommandEntry::Type)
-            static_cast<CommandEntry*>(entry)->expandResults();
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->setTextStrikeOut(b);
+    else if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->setTextStrikeOut(b);
 }
 
-void Worksheet::removeAllResults()
+void Worksheet::setAlignLeft()
 {
-    bool remove = false;
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->setAlignment(Qt::AlignLeft);
+    else if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->setAlignment(Qt::AlignLeft);
+}
 
-    if (KMessageBox::shouldBeShownContinue(QLatin1String("WarnAboutAllResultsRemoving")))
-    {
-        KMessageBox::ButtonCode btn = KMessageBox::warningContinueCancel(
-            views().first(),
-            i18n("This step cannot be undone. Do you really want to delete all results?"),
-            i18n("Delete all results"),
-            KStandardGuiItem::cont(),
-            KStandardGuiItem::cancel(),
-            QLatin1String("WarnAboutAllResultsRemoving")
-        );
-        remove = (btn == KMessageBox::Continue);
-    }
-    else
-        remove = true;
+void Worksheet::setAlignRight()
+{
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->setAlignment(Qt::AlignRight);
+    else if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->setAlignment(Qt::AlignRight);
+}
 
-    if (remove)
-    {
-        for (auto *entry = firstEntry(); entry; entry = entry->next())
-            if (entry->type() == CommandEntry::Type)
-                static_cast<CommandEntry*>(entry)->removeResults();
-    }
+void Worksheet::setAlignCenter()
+{
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->setAlignment(Qt::AlignCenter);
+    else if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->setAlignment(Qt::AlignCenter);
 }
 
-void Worksheet::addToExectuionSelection()
+void Worksheet::setAlignJustify()
 {
-    for (auto* entry : m_selectedEntries)
-        if (entry->type() == CommandEntry::Type)
-            static_cast<CommandEntry*>(entry)->addToExecution();
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->setAlignment(Qt::AlignJustify);
+    else if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->setAlignment(Qt::AlignJustify);
 }
 
-void Worksheet::excludeFromExecutionSelection()
+void Worksheet::setFontFamily(const QString& font)
 {
-    for (auto* entry : m_selectedEntries)
-        if (entry->type() == CommandEntry::Type)
-            static_cast<CommandEntry*>(entry)->excludeFromExecution();
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->setFontFamily(font);
+    else if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->setFontFamily(font);
 }
 
-void Worksheet::collapseSelectionResults()
+void Worksheet::setFontSize(int size)
 {
-    for (auto* entry : m_selectedEntries)
-        if (entry->type() == CommandEntry::Type)
-            static_cast<CommandEntry*>(entry)->collapseResults();
+    if (m_lastFocusedTextItem)
+        m_lastFocusedTextItem->setFontSize(size);
+    else if (m_legacylastFocusedTextItem)
+        m_legacylastFocusedTextItem->setFontSize(size);
 }
 
-void Worksheet::uncollapseSelectionResults()
+
+bool Worksheet::isShortcut(const QKeySequence& sequence)
 {
-    for (auto* entry : m_selectedEntries)
-        if (entry->type() == CommandEntry::Type)
-            static_cast<CommandEntry*>(entry)->expandResults();
+    return m_shortcuts.contains(sequence);
 }
 
-void Worksheet::removeSelectionResults()
+void Worksheet::registerShortcut(QAction* action)
 {
-    for (auto* entry : m_selectedEntries)
-        if (entry->type() == CommandEntry::Type)
-            static_cast<CommandEntry*>(entry)->removeResults();
+    for (auto& shortcut : action->shortcuts())
+        m_shortcuts.insert(shortcut, action);
+
+    connect(action, &QAction::changed, this, &Worksheet::updateShortcut);
 }
 
-void Worksheet::navigateToTocNode(QString nodeId)
+void Worksheet::updateShortcut()
 {
-    QString plotCommandId;
-    QString plotResultId;
-    if (parsePlotNodeId(nodeId, &plotCommandId, &plotResultId))
-    {
-        const CommandSearchResult commandSearch = findCommandEntryById(plotCommandId);
-        if (!commandSearch.entry)
-        {
-            scheduleTocStructureRefresh();
-            return;
-        }
+    QAction* action = qobject_cast<QAction*>(sender());
+    if (!action)
+        return;
 
-        const bool expanded = expandHierarchyAncestors(commandSearch.collapsedAncestors);
-        if (expanded)
-        {
-            updateHierarchyLayout();
-            updateLayout();
-        }
+    // delete the old shortcuts of this action
+    QList<QKeySequence> shortcuts = m_shortcuts.keys(action);
+    for (auto& shortcut : shortcuts)
+        m_shortcuts.remove(shortcut);
+
+    // add the new shortcuts
+    for (auto& shortcut : action->shortcuts())
+        m_shortcuts.insert(shortcut, action);
+}
 
-        if (!navigateToPlotResult(commandSearch.entry, plotResultId))
-            scheduleTocStructureRefresh();
+void Worksheet::dragEnterEvent(QGraphicsSceneDragDropEvent* event)
+{
+    if (m_dragEntry)
+        event->accept();
+    else
+        QGraphicsScene::dragEnterEvent(event);
+}
 
+void Worksheet::dragLeaveEvent(QGraphicsSceneDragDropEvent* event)
+{
+    if (!m_dragEntry)
+    {
+        QGraphicsScene::dragLeaveEvent(event);
         return;
     }
 
-    QString commandId;
-    if (parseCommandNodeId(nodeId, &commandId))
-    {
-        const CommandSearchResult commandSearch = findCommandEntryById(commandId);
-        if (!commandSearch.entry)
-            return;
+    event->accept();
 
+    removeDragPlaceholder();
+    updateLayout();
+}
 
-        const bool expanded = expandHierarchyAncestors(commandSearch.collapsedAncestors);
-        if (expanded)
-        {
-            updateHierarchyLayout();
-            updateLayout();
+void Worksheet::dragMoveEvent(QGraphicsSceneDragDropEvent* event)
+{
+    if (!m_dragEntry) {
+        QGraphicsScene::dragMoveEvent(event);
+        return;
+    }
+
+    QPointF pos = event->scenePos();
+    auto* entry = entryAt(pos);
+    WorksheetEntry* prev = nullptr;
+    WorksheetEntry* next = nullptr;
+    if (entry) {
+        if (pos.y() < entry->y() + entry->size().height()/2) {
+            prev = entry->previous();
+            next = entry;
+        } else if (pos.y() >= entry->y() + entry->size().height()/2) {
+            prev = entry;
+            next = entry->next();
+        }
+    } else {
+        auto* last = lastEntry();
+        if (last && pos.y() > last->y() + last->size().height()) {
+            prev = last;
+            next = nullptr;
         }
+    }
 
-        auto* commandEntry = commandSearch.entry;
-        updateCurrentHierarchyFromEntry(commandEntry);
-
-        worksheetView()->scrollTo(qRound(commandEntry->scenePos().y()));
-
-        worksheetView()->setFocus();
+    const bool dragWithHierarchy = !m_hierarchySubentriesDrag.empty();
 
-        commandEntry->focusEntry(WorksheetTextItem::TopLeft);
+    if (m_placeholderEntry)
+    {
+        if (prev == m_placeholderEntry)
+            prev = m_placeholderEntry->previous();
 
-        resetEntryCursor();
-        return;
+        if (next == m_placeholderEntry)
+            next = m_placeholderEntry->next();
     }
 
-    const HierarchySearchResult hierarchySearch = findHierarchyEntryById(nodeId);
-    if (hierarchySearch.entry)
+    if (prev || next)
     {
-        const bool expanded = expandHierarchyAncestors(hierarchySearch.collapsedAncestors);
+        const QSizeF placeholderSize = dragWithHierarchy ? m_hierarchyDragSize : m_dragEntry->size();
+
+        if (!m_placeholderEntry)
+            m_placeholderEntry = new PlaceHolderEntry(this, QSizeF(0, 0));
 
-        if (expanded)
+        const bool positionChanged = m_placeholderEntry->previous() != prev || m_placeholderEntry->next() != next;
+
+        if (positionChanged)
         {
-            updateHierarchyLayout();
-            updateLayout();
-        }
+            auto* oldPrevious = m_placeholderEntry->previous();
+            auto* oldNext = m_placeholderEntry->next();
 
-        auto* hierarchyEntry = hierarchySearch.entry;
-        updateCurrentHierarchyFromEntry(hierarchyEntry);
+            if (oldPrevious && oldPrevious->next() == m_placeholderEntry)
+                oldPrevious->setNext(oldNext);
+            else if (firstEntry() == m_placeholderEntry)
+                setFirstEntry(oldNext);
 
-        worksheetView()->scrollTo(qRound(hierarchyEntry->scenePos().y()));
+            if (oldNext && oldNext->previous() == m_placeholderEntry)
+                oldNext->setPrevious(oldPrevious);
+            else if (lastEntry() == m_placeholderEntry)
+                setLastEntry(oldPrevious);
 
-        worksheetView()->setFocus();
+            m_placeholderEntry->setPrevious(prev);
+            m_placeholderEntry->setNext(next);
 
-        hierarchyEntry->focusEntry(WorksheetTextItem::BottomRight);
+            if (prev)
+                prev->setNext(m_placeholderEntry);
+            else
+                setFirstEntry(m_placeholderEntry);
 
-        resetEntryCursor();
-    }
-}
+            if (next)
+                next->setPrevious(m_placeholderEntry);
+            else
+                setLastEntry(m_placeholderEntry);
+        }
 
-bool Worksheet::navigateToPlotResult(CommandEntry* commandEntry, const QString& resultId)
-{
-    if (!commandEntry || resultId.isEmpty())
-        return false;
+        m_placeholderEntry->changeSize(placeholderSize);
 
-    if (commandEntry->isResultCollapsed())
-    {
-        commandEntry->expandResults();
         updateLayout();
     }
 
-    auto* resultItem = commandEntry->resultItemById(resultId);
-    if (!resultItem || !resultItem->result() || !isPlotResult(resultItem->result()))
-        return false;
-
-    auto* object = resultItem->graphicsObject();
-    if (!object)
-        return false;
-
-    const QString nodeId = buildPlotNodeId(commandEntry->commandId(), resultId);
-
-    worksheetView()->scrollTo(qRound(object->sceneBoundingRect().top()));
-    worksheetView()->setFocus();
-    object->setFocus();
+    const QPoint viewPos = worksheetView()->mapFromScene(pos);
+    const int viewHeight = worksheetView()->viewport()->height();
+    if ((viewPos.y() < 10 || viewPos.y() > viewHeight - 10) &&
+        !m_dragScrollTimer) {
+        m_dragScrollTimer = new QTimer(this);
+        m_dragScrollTimer->setSingleShot(true);
+        m_dragScrollTimer->setInterval(100);
+        connect(m_dragScrollTimer, SIGNAL(timeout()), this,
+                SLOT(updateDragScrollTimer()));
+        m_dragScrollTimer->start();
+    }
 
-    m_hierarchyTrackingSource = HierarchyTrackingSource::FocusedEntry;
-    setCurrentTocNode(nodeId);
+    event->accept();
+}
 
-    resetEntryCursor();
-    return true;
+void Worksheet::dropEvent(QGraphicsSceneDragDropEvent* event)
+{
+    if (!m_dragEntry)
+        QGraphicsScene::dropEvent(event);
+    event->accept();
 }
 
-void Worksheet::updateCurrentTocNodeFromResult(CommandEntry* commandEntry, Cantor::Result* result)
+void Worksheet::updateDragScrollTimer()
 {
-    if (!commandEntry || !result || !isValidEntry(commandEntry))
+    if (!m_dragScrollTimer)
         return;
 
-    m_hierarchyTrackingSource = HierarchyTrackingSource::FocusedEntry;
+    const QPoint viewPos = worksheetView()->viewCursorPos();
+    const QWidget* viewport = worksheetView()->viewport();
+    const int viewHeight = viewport->height();
+    if (!m_dragEntry || !(viewport->rect().contains(viewPos)) ||
+        (viewPos.y() >= 10 && viewPos.y() <= viewHeight - 10)) {
+        delete m_dragScrollTimer;
+        m_dragScrollTimer = nullptr;
+        return;
+    }
 
-    if (isPlotResult(result))
-        setCurrentTocNode(buildPlotNodeId(commandEntry->commandId(), result->resultId()));
+    if (viewPos.y() < 10)
+        worksheetView()->scrollBy(-10*(10 - viewPos.y()));
     else
-        updateCurrentHierarchyFromEntry(commandEntry);
+        worksheetView()->scrollBy(10*(viewHeight - viewPos.y()));
+
+    m_dragScrollTimer->start();
 }
 
-void Worksheet::renamePlot(const QString& commandId, const QString& resultId, const QString& newTitle)
+void Worksheet::removeDragPlaceholder()
 {
-    if (m_readOnly || commandId.isEmpty() || resultId.isEmpty())
+    if (!m_placeholderEntry)
         return;
 
-    QString normalizedTitle = newTitle;
-    normalizedTitle.replace(QLatin1Char('\r'), QLatin1Char(' '));
-    normalizedTitle.replace(QLatin1Char('\n'), QLatin1Char(' '));
-    normalizedTitle = normalizedTitle.trimmed();
+    auto* placeholder = m_placeholderEntry;
+    auto* previous = placeholder->previous();
+    auto* next = placeholder->next();
 
-    const CommandSearchResult commandSearch = findCommandEntryById(commandId);
-    auto* commandEntry = commandSearch.entry;
-    auto* expression = commandEntry ? commandEntry->expression() : nullptr;
+    if (previous && previous->next() == placeholder)
+        previous->setNext(next);
+    else if (firstEntry() == placeholder)
+        setFirstEntry(next);
 
-    if (!commandEntry || !expression)
-        return;
+    if (next && next->previous() == placeholder)
+        next->setPrevious(previous);
+    else if (lastEntry() == placeholder)
+        setLastEntry(previous);
 
-    for (auto* result : expression->results())
-    {
-        if (!result || result->resultId() != resultId || !isPlotResult(result))
-            continue;
+    placeholder->setPrevious(nullptr);
+    placeholder->setNext(nullptr);
+    placeholder->hide();
+    placeholder->deleteLater();
 
-        if (result->displayName() == normalizedTitle)
-            return;
+    m_placeholderEntry = nullptr;
+}
 
-        result->setDisplayName(normalizedTitle);
-        setModified();
-        scheduleTocStructureRefresh();
-        return;
+void Worksheet::updateEntryCursor(QGraphicsSceneMouseEvent* event)
+{
+    // determine the worksheet entry near which the entry cursor will be shown
+    resetEntryCursor();
+    if (event->button() == Qt::LeftButton && !focusItem())
+    {
+        const qreal y = event->scenePos().y();
+        for (auto* entry = firstEntry(); entry; entry = entry->next())
+        {
+            if (entry == firstEntry() && y < entry->y() )
+            {
+                m_choosenCursorEntry = firstEntry();
+                break;
+            }
+            else if (entry->y() < y && (entry->next() && y < entry->next()->y()))
+            {
+                m_choosenCursorEntry = entry->next();
+                break;
+            }
+            else if (entry->y() < y && entry == lastEntry())
+            {
+                m_isCursorEntryAfterLastEntry = true;
+                break;
+            }
+        }
     }
+
+    if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry)
+        drawEntryCursor();
 }
 
-void Worksheet::deletePlot(const QString& commandId, const QString& resultId)
+void Worksheet::addEntryFromEntryCursor()
 {
-    if (m_readOnly || commandId.isEmpty() || resultId.isEmpty())
-        return;
+    qDebug() << "Add new entry from entry cursor";
+    if (m_isCursorEntryAfterLastEntry)
+        insertCommandEntry(lastEntry());
+    else
+        insertCommandEntryBefore(m_choosenCursorEntry);
+    resetEntryCursor();
+}
 
-    const CommandSearchResult commandSearch = findCommandEntryById(commandId);
-    auto* commandEntry = commandSearch.entry;
-    auto* expression = commandEntry ? commandEntry->expression() : nullptr;
+void Worksheet::animateEntryCursor()
+{
+    if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && m_entryCursorItem)
+        m_entryCursorItem->setVisible(!m_entryCursorItem->isVisible());
+}
 
-    if (!commandEntry || !expression)
-        return;
+void Worksheet::resetEntryCursor()
+{
+    m_choosenCursorEntry = nullptr;
+    m_isCursorEntryAfterLastEntry = false;
+    m_entryCursorItem->hide();
+}
 
-    for (auto* result : expression->results())
+void Worksheet::drawEntryCursor()
+{
+    if (m_entryCursorItem && (m_choosenCursorEntry || (m_isCursorEntryAfterLastEntry && lastEntry())))
     {
-        if (!result || result->resultId() != resultId || !isPlotResult(result))
-            continue;
-
-        const QString plotNodeId = buildPlotNodeId(commandId, resultId);
-        const bool wasCurrentNode = m_currentTocNodeId == plotNodeId;
-
-        expression->removeResult(result);
-
-        if (wasCurrentNode)
-            setCurrentTocNode(buildCommandNodeId(commandEntry));
-
-        setModified();
-        scheduleTocStructureRefresh();
-        return;
+        qreal x;
+        qreal y;
+        if (m_isCursorEntryAfterLastEntry)
+        {
+            x = lastEntry()->x();
+            y = lastEntry()->y() + lastEntry()->size().height() - (EntryCursorWidth - 1);
+        }
+        else
+        {
+            x = m_choosenCursorEntry->x();
+            y = m_choosenCursorEntry->y();
+        }
+        m_entryCursorItem->setLine(x,y,x+EntryCursorLength,y);
+        m_entryCursorItem->show();
     }
 }
 
-void Worksheet::renameHierarchyEntry(const QString& hierarchyId, const QString& newName)
+void Worksheet::setType(Worksheet::Type type)
 {
-    if (m_readOnly || hierarchyId.isEmpty())
-        return;
-
-    QString normalizedName = newName;
-
-    normalizedName.replace(QLatin1Char('\r'), QLatin1Char(' '));
-    normalizedName.replace(QLatin1Char('\n'), QLatin1Char(' '));
-
-    normalizedName = normalizedName.trimmed();
-
-    const HierarchySearchResult hierarchySearch = findHierarchyEntryById(hierarchyId);
-    auto* hierarchyEntry = hierarchySearch.entry;
-
-    if (!hierarchyEntry || hierarchyEntry->text() == normalizedName)
-        return;
-
-    hierarchyEntry->setContent(normalizedName);
+    m_type = type;
 }
 
-void Worksheet::changeHierarchyLevel(QString hierarchyId, int levelDelta)
+Worksheet::Type Worksheet::type() const
 {
-    if (m_readOnly || hierarchyId.isEmpty() || (levelDelta != -1 && levelDelta != 1))
-        return;
-
-    const HierarchySearchResult hierarchySearch = findHierarchyEntryById(hierarchyId);
-    HierarchyEntry* targetEntry = hierarchySearch.entry;
-
-    if (!targetEntry)
-        return;
-
-    bool expanded = false;
-
-    const int minimumLevel = static_cast<int>(HierarchyEntry::HierarchyLevel::Chapter);
-
-    const int maximumLevel = static_cast<int>(HierarchyEntry::HierarchyLevel::Subparagraph);
-
-    const int currentRootLevel = static_cast<int>(targetEntry->level());
-
-    if (levelDelta < 0)
-    {
-        if (currentRootLevel <= minimumLevel)
-            return;
+    return m_type;
+}
 
-        expanded = expandHierarchyAncestors(hierarchySearch.collapsedAncestors);
-    }
-    else
+void Worksheet::changeEntryType(WorksheetEntry* target, int newType)
+{
+    if (target && target->type() != newType)
     {
-        if (currentRootLevel >= maximumLevel)
-            return;
-
-        expanded = expandHierarchyAncestors(hierarchySearch.collapsedAncestors);
+        bool animation_state = m_animationsEnabled;
+        m_animationsEnabled = false;
 
-        bool hasPreviousSibling = false;
+        QString content;
 
-        for (auto* entry = targetEntry->previous(); entry; entry = entry->previous())
+        int targetEntryType = target->type();
+        switch(targetEntryType)
         {
-            if (entry->type() != HierarchyEntry::Type)
-                continue;
-
-            const int entryLevel = static_cast<int>(static_cast<HierarchyEntry*>(entry)->level());
-
-            if (entryLevel < currentRootLevel)
+            case CommandEntry::Type:
+                content = static_cast<CommandEntry*>(target)->command();
                 break;
-
-            if (entryLevel == currentRootLevel)
-            {
-                hasPreviousSibling = true;
+            case MarkdownEntry::Type:
+                content = static_cast<MarkdownEntry*>(target)->plainText();
                 break;
-            }
-        }
-
-        if (!hasPreviousSibling)
-            return;
-    }
-
-    expanded = expandHierarchyForStructureChange(targetEntry) || expanded;
-    const std::vector<WorksheetEntry*>subentries = hierarchySubelements(targetEntry);
-
-    if (levelDelta > 0)
-    {
-        for (auto* entry : subentries)
-        {
-            if (!entry || entry->type() != HierarchyEntry::Type)
-                continue;
-
-            const int entryLevel = static_cast<int>(static_cast<HierarchyEntry*>(entry)->level());
-
-            if (entryLevel >= maximumLevel)
-            {
-                updateHierarchyLayout();
-                if (expanded)
-                    updateLayout();
-                return;
-            }
+            case TextEntry::Type:
+                content = static_cast<TextEntry*>(target)->text();
+                break;
+            case LatexEntry::Type:
+                content = static_cast<LatexEntry*>(target)->plain();
         }
-    }
 
-    const auto shiftHierarchyEntry = [levelDelta](HierarchyEntry* hierarchyEntry)
-    {
-        if (!hierarchyEntry)
-            return;
+        auto* newEntry = WorksheetEntry::create(newType, this);
+        if (newEntry)
+        {
+            newEntry->setContent(content);
+            auto* tmp = target;
 
-        const int newLevel = static_cast<int>(hierarchyEntry->level()) + levelDelta;
+            newEntry->setPrevious(tmp->previous());
+            newEntry->setNext(tmp->next());
 
-        hierarchyEntry->setLevel(static_cast<HierarchyEntry::HierarchyLevel>(newLevel));
-    };
+            tmp->setPrevious(nullptr);
+            tmp->setNext(nullptr);
+            tmp->clearFocus();
+            tmp->forceRemove();
 
-    shiftHierarchyEntry(targetEntry);
+            if (newEntry->previous())
+                newEntry->previous()->setNext(newEntry);
+            else
+                setFirstEntry(newEntry);
 
-    for (auto* entry : subentries)
-    {
-        if (!entry || entry->type() != HierarchyEntry::Type)
-            continue;
+            if (newEntry->next())
+                newEntry->next()->setPrevious(newEntry);
+            else
+                setLastEntry(newEntry);
 
-        shiftHierarchyEntry(static_cast<HierarchyEntry*>(entry));
+            if (newType == HierarchyEntry::Type || targetEntryType == HierarchyEntry::Type)
+                updateHierarchyLayout();
+            updateLayout();
+            makeVisible(newEntry);
+            focusEntry(newEntry);
+            setModified();
+            newEntry->focusEntry();
+        }
+        m_animationsEnabled = animation_state;
     }
-
-    updateHierarchyLayout();
-    updateLayout();
-
-    setModified();
 }
 
-void Worksheet::deleteHierarchyEntry(const QString& hierarchyId, bool deleteContents)
+bool Worksheet::isValidEntry(WorksheetEntry* entry)
 {
-    if (m_readOnly || hierarchyId.isEmpty())
-        return;
+    for (auto* iter = firstEntry(); iter; iter = iter->next())
+        if (entry == iter)
+            return true;
 
-    const HierarchySearchResult hierarchySearch = findHierarchyEntryById(hierarchyId);
-    HierarchyEntry* targetEntry = hierarchySearch.entry;
+    return false;
+}
 
-    if (!targetEntry)
+void Worksheet::selectionRemove()
+{
+    if (m_selectedEntries.isEmpty())
         return;
 
-    QString headingLabel = targetEntry->text().trimmed();
-
-    if (headingLabel.isEmpty())
-        headingLabel = targetEntry->hierarchyText();
-
-    QString warningText;
-    QString dialogTitle;
-
-    if (deleteContents)
-    {
-        warningText = i18n("Do you really want to delete "
-                 "the heading \"%1\" and all "
-                 "entries in this section? "
-                 "This action cannot be undone.",
-                 headingLabel);
-
-        dialogTitle = i18n("Delete Section");
-    }
-    else
-    {
-        warningText = i18n("Do you really want to delete "
-                 "only the heading \"%1\"? "
-                 "The contents of the section "
-                 "will remain in the worksheet. "
-                 "This action cannot be undone.",
-                 headingLabel);
-
-        dialogTitle = i18n("Delete Heading");
-    }
-
     if (Settings::warnAboutEntryDelete())
     {
-        const auto result = KMessageBox::warningTwoActions(
-                worksheetView(),
-                warningText,
-                dialogTitle,
+        const auto result = KMessageBox::warningTwoActions(worksheetView(),
+                i18n("This step cannot be undone. "
+                     "Do you really want to delete "
+                     "the selected entries?"),
+                i18n("Delete Entries"),
                 KStandardGuiItem::remove(),
                 KStandardGuiItem::cancel());
 
@@ -4137,204 +2981,194 @@ void Worksheet::deleteHierarchyEntry(const QString& hierarchyId, bool deleteCont
             return;
     }
 
-    expandHierarchyAncestors(hierarchySearch.collapsedAncestors);
-    expandHierarchyForStructureChange(targetEntry);
-
-    const std::vector<WorksheetEntry*>subentries = hierarchySubelements(targetEntry);
-
-    clearAllSelections();
-    notifyEntryFocus(nullptr);
-
-    QList<WorksheetEntry*> entriesToRemove;
-    entriesToRemove.append(targetEntry);
-
-    if (deleteContents)
-    {
-        for (auto* entry : subentries)
-            entriesToRemove.append(entry);
-    }
-    else
+    for (auto* entry : m_selectedEntries)
     {
-        const int minimumLevel = static_cast<int>(HierarchyEntry::HierarchyLevel::Chapter);
-
-        for (auto* entry : subentries)
-        {
-            if (!entry || entry->type() != HierarchyEntry::Type)
-                continue;
-
-            auto* childHeading = static_cast<HierarchyEntry*>(entry);
-            const int newLevel = qMax(minimumLevel, static_cast<int>(childHeading->level()) - 1);
-            childHeading->setLevel(static_cast<HierarchyEntry::HierarchyLevel>(newLevel));
-        }
+        if (isValidEntry(entry))
+            entry->startRemoving(false);
     }
 
-    WorksheetEntry* previousEntry = targetEntry->previous();
-    WorksheetEntry* lastRemovedEntry = entriesToRemove.constLast();
-    WorksheetEntry* nextEntry = lastRemovedEntry->next();
-
-    if (previousEntry)
-        previousEntry->setNext(nextEntry);
-    else
-        setFirstEntry(nextEntry);
+    m_selectedEntries.clear();
+}
 
-    if (nextEntry)
-        nextEntry->setPrevious(previousEntry);
-    else
-        setLastEntry(previousEntry);
+void Worksheet::selectionEvaluate()
+{
+    // run entries in worksheet order: from top to down
+    for (auto* entry = firstEntry(); entry; entry = entry->next())
+        if (m_selectedEntries.indexOf(entry) != -1)
+            entry->evaluate();
+}
 
-    clearFocus();
+void Worksheet::selectionMoveUp()
+{
+    bool moveHierarchyEntry = false;
+    // movement up should have an order from top to down.
+    for(auto* entry = firstEntry(); entry; entry = entry->next())
+        if(m_selectedEntries.indexOf(entry) != -1)
+            if (entry->previous() && m_selectedEntries.indexOf(entry->previous()) == -1)
+            {
+                entry->moveToPrevious(false);
+                if (entry->type() == HierarchyEntry::Type)
+                    moveHierarchyEntry = true;
+            }
+    if (moveHierarchyEntry)
+        updateHierarchyLayout();
+    updateLayout();
+}
 
-    updateFocusedTextItem(static_cast<WorksheetTextItem*>(nullptr));
-    updateFocusedTextItem(static_cast<WorksheetTextEditorItem*>(nullptr));
+void Worksheet::selectionMoveDown()
+{
+    bool moveHierarchyEntry = false;
+    // movement up should have an order from down to top.
+    for(auto* entry = lastEntry(); entry; entry = entry->previous())
+        if(m_selectedEntries.indexOf(entry) != -1)
+            if (entry->next() && m_selectedEntries.indexOf(entry->next()) == -1)
+            {
+                entry->moveToNext(false);
+                if (entry->type() == HierarchyEntry::Type)
+                    moveHierarchyEntry = true;
+            }
+    if (moveHierarchyEntry)
+        updateHierarchyLayout();
+    updateLayout();
+}
 
-    for (auto* entry : entriesToRemove)
+void Worksheet::notifyEntryFocus(WorksheetEntry* entry)
+{
+    if (entry)
     {
-        if (!entry)
-            continue;
-
-        entry->setPrevious(nullptr);
-        entry->setNext(nullptr);
-        entry->clearFocus();
-        entry->hide();
-        entry->deleteLater();
+        m_circularFocusBuffer.enqueue(entry);
+
+        if (m_circularFocusBuffer.size() > 2)
+            m_circularFocusBuffer.dequeue();
     }
+    else
+        m_circularFocusBuffer.clear();
+}
 
-    WorksheetEntry* focusTarget = nextEntry ? nextEntry : previousEntry;
+void Worksheet::collapseAllResults()
+{
+    for (auto* entry = firstEntry(); entry; entry = entry->next())
+        if (entry->type() == CommandEntry::Type)
+            static_cast<CommandEntry*>(entry)->collapseResults();
+}
 
-    if (!firstEntry())
-        focusTarget = appendCommandEntry();
+void Worksheet::uncollapseAllResults()
+{
+    for (auto* entry = firstEntry(); entry; entry = entry->next())
+        if (entry->type() == CommandEntry::Type)
+            static_cast<CommandEntry*>(entry)->expandResults();
+}
 
-    updateHierarchyLayout();
-    updateLayout();
+void Worksheet::removeAllResults()
+{
+    bool remove = false;
 
-    if (focusTarget)
+    if (KMessageBox::shouldBeShownContinue(QLatin1String("WarnAboutAllResultsRemoving")))
     {
-        focusTarget->focusEntry();
-        makeVisible(focusTarget);
-        updateCurrentHierarchyFromEntry(focusTarget);
+        KMessageBox::ButtonCode btn = KMessageBox::warningContinueCancel(
+            views().first(),
+            i18n("This step cannot be undone. Do you really want to delete all results?"),
+            i18n("Delete all results"),
+            KStandardGuiItem::cont(),
+            KStandardGuiItem::cancel(),
+            QLatin1String("WarnAboutAllResultsRemoving")
+        );
+        remove = (btn == KMessageBox::Continue);
     }
     else
+        remove = true;
+
+    if (remove)
     {
-        m_hierarchyTrackingSource = HierarchyTrackingSource::FocusedEntry;
-        updateCurrentHierarchy(nullptr);
+        for (auto *entry = firstEntry(); entry; entry = entry->next())
+            if (entry->type() == CommandEntry::Type)
+                static_cast<CommandEntry*>(entry)->removeResults();
     }
-
-    resetEntryCursor();
-    setModified();
 }
 
-WorksheetEntry* Worksheet::cutSubentriesForHierarchy(HierarchyEntry* hierarchyEntry)
+void Worksheet::addToExectuionSelection()
 {
-    if (!hierarchyEntry || !hierarchyEntry->next())
-        return nullptr;
+    for (auto* entry : m_selectedEntries)
+        if (entry->type() == CommandEntry::Type)
+            static_cast<CommandEntry*>(entry)->addToExecution();
+}
 
-    WorksheetEntry* cutBegin = hierarchyEntry->next();
-    if (cutBegin->type() == HierarchyEntry::Type && static_cast<int>(static_cast<HierarchyEntry*>(cutBegin)->level()) <= static_cast<int>(hierarchyEntry->level()))
-        return nullptr;
-    WorksheetEntry* cutEnd = cutBegin;
+void Worksheet::excludeFromExecutionSelection()
+{
+    for (auto* entry : m_selectedEntries)
+        if (entry->type() == CommandEntry::Type)
+            static_cast<CommandEntry*>(entry)->excludeFromExecution();
+}
 
-    const int hierarchyLevel = static_cast<int>(hierarchyEntry->level());
+void Worksheet::collapseSelectionResults()
+{
+    for (auto* entry : m_selectedEntries)
+        if (entry->type() == CommandEntry::Type)
+            static_cast<CommandEntry*>(entry)->collapseResults();
+}
 
-    while (cutEnd->next())
-    {
-        WorksheetEntry* candidate = cutEnd->next();
+void Worksheet::uncollapseSelectionResults()
+{
+    for (auto* entry : m_selectedEntries)
+        if (entry->type() == CommandEntry::Type)
+            static_cast<CommandEntry*>(entry)->expandResults();
+}
 
-        if (candidate->type() == HierarchyEntry::Type)
-        {
-            const int candidateLevel = static_cast<int>(static_cast<HierarchyEntry*>(candidate)->level());
+void Worksheet::removeSelectionResults()
+{
+    for (auto* entry : m_selectedEntries)
+        if (entry->type() == CommandEntry::Type)
+            static_cast<CommandEntry*>(entry)->removeResults();
+}
 
-            if (candidateLevel <= hierarchyLevel)
-                break;
-        }
+void Worksheet::navigateToTocNode(QString nodeId)
+{
+    m_hierarchyManager->navigateToTocNode(nodeId);
+}
 
-        cutEnd = candidate;
-    }
+void Worksheet::updateCurrentTocNodeFromResult(CommandEntry* commandEntry, Cantor::Result* result)
+{
+    m_hierarchyManager->updateCurrentTocNodeFromResult(commandEntry, result);
+}
 
-    WorksheetEntry* entryAfterSection = cutEnd->next();
+void Worksheet::renamePlot(const QString& commandId, const QString& resultId, const QString& newTitle)
+{
+    m_hierarchyManager->renamePlot(commandId, resultId, newTitle);
+}
 
-    hierarchyEntry->setNext(entryAfterSection);
+void Worksheet::deletePlot(const QString& commandId, const QString& resultId)
+{
+    m_hierarchyManager->deletePlot(commandId, resultId);
+}
 
-    if (entryAfterSection)
-        entryAfterSection->setPrevious(hierarchyEntry);
-    else
-        setLastEntry(hierarchyEntry);
+void Worksheet::renameHierarchyEntry(const QString& hierarchyId, const QString& newName)
+{
+    m_hierarchyManager->renameHierarchyEntry(hierarchyId, newName);
+}
 
-    cutBegin->setPrevious(nullptr);
-    cutEnd->setNext(nullptr);
+void Worksheet::changeHierarchyLevel(QString hierarchyId, int levelDelta)
+{
+    m_hierarchyManager->changeHierarchyLevel(hierarchyId, levelDelta);
+}
 
-    for (auto* entry = cutBegin; entry; entry = entry->next())
-        entry->hide();
+void Worksheet::deleteHierarchyEntry(const QString& hierarchyId, bool deleteContents)
+{
+    m_hierarchyManager->deleteHierarchyEntry(hierarchyId, deleteContents);
+}
 
-    return cutBegin;
+WorksheetEntry* Worksheet::cutSubentriesForHierarchy(HierarchyEntry* hierarchyEntry)
+{
+    return m_hierarchyManager->cutSubentriesForHierarchy(hierarchyEntry);
 }
 
 void Worksheet::insertSubentriesForHierarchy(HierarchyEntry* hierarchyEntry, WorksheetEntry* storedSubentriesBegin)
 {
-    if (!hierarchyEntry || !storedSubentriesBegin)
-        return;
-
-    WorksheetEntry* previousNext = hierarchyEntry->next();
-
-    hierarchyEntry->setNext(storedSubentriesBegin);
-    storedSubentriesBegin->setPrevious(hierarchyEntry);
-
-    WorksheetEntry* storedEnd = storedSubentriesBegin;
-
-    for (auto* entry = storedSubentriesBegin; entry; entry = entry->next())
-    {
-        entry->show();
-        storedEnd = entry;
-    }
-
-    storedEnd->setNext(previousNext);
-
-    if (previousNext)
-        previousNext->setPrevious(storedEnd);
-    else
-        setLastEntry(storedEnd);
+    m_hierarchyManager->insertSubentriesForHierarchy(hierarchyEntry, storedSubentriesBegin);
 }
 
 bool Worksheet::expandHierarchyForStructureChange(HierarchyEntry* hierarchyEntry)
 {
-    if (!hierarchyEntry)
-        return false;
-
-    bool hierarchyExpanded = false;
-    const auto expandEntry = [this, &hierarchyExpanded](HierarchyEntry* entry)
-    {
-        if (!entry || !entry->hasHiddenSubentries())
-            return;
-
-        WorksheetEntry* hiddenSubentries = entry->takeHiddenSubentries();
-
-        if (!hiddenSubentries)
-            return;
-
-        insertSubentriesForHierarchy(entry, hiddenSubentries);
-
-        hierarchyExpanded = true;
-    };
-
-    expandEntry(hierarchyEntry);
-
-    const int rootLevel = static_cast<int>(hierarchyEntry->level());
-    for (auto* entry = hierarchyEntry->next(); entry;)
-    {
-        if (entry->type() == HierarchyEntry::Type)
-        {
-            auto* childHierarchy = static_cast<HierarchyEntry*>(entry);
-            const int childLevel = static_cast<int>(childHierarchy->level());
-
-            if (childLevel <= rootLevel)
-                break;
-
-            expandEntry(childHierarchy);
-        }
-
-        entry = entry->next();
-    }
-
-    return hierarchyExpanded;
+    return m_hierarchyManager->expandHierarchyForStructureChange(hierarchyEntry);
 }
 
 void Worksheet::handleSettingsChanges()
diff --git a/src/worksheet.h b/src/worksheet.h
index 6fb142da..262f90d1 100644
--- a/src/worksheet.h
+++ b/src/worksheet.h
@@ -17,8 +17,6 @@
 #include <QQueue>
 #include <QVariantList>
 
-#include <functional>
-
 #include "lib/renderer.h"
 #include "mathrender.h"
 #include "worksheetcursor.h"
@@ -34,6 +32,7 @@ class WorksheetEntry;
 class CommandEntry;
 class WorksheetView;
 class HierarchyEntry;
+class WorksheetHierarchyManager;
 class PlaceHolderEntry;
 class WorksheetTextItem;
 
@@ -59,12 +58,6 @@ class Worksheet : public QGraphicsScene
       JupyterNotebook
     };
 
-    enum class HierarchyTrackingSource
-    {
-        Viewport,
-        FocusedEntry
-    };
-
     Worksheet(Cantor::Backend*, QWidget*, bool useDeafultWorksheetParameters = true);
     ~Worksheet() override;
 
@@ -325,38 +318,7 @@ class Worksheet : public QGraphicsScene
     bool isValidEntry(WorksheetEntry*);
 
   private:
-    struct HierarchySearchResult
-    {
-        HierarchyEntry* entry{nullptr};
-        QVector<HierarchyEntry*> collapsedAncestors;
-    };
-
-    struct CommandSearchResult
-    {
-        CommandEntry* entry{nullptr};
-        QVector<HierarchyEntry*> collapsedAncestors;
-    };
-
-    QVariantList collectTocNodes();
-    bool visitLogicalEntries(WorksheetEntry* first, const std::function<bool(WorksheetEntry*)>& visitor);
-    bool visitLogicalEntries(const std::function<bool(WorksheetEntry*)>& visitor);
-    bool findHierarchyEntryById(WorksheetEntry* first, const QString& hierarchyId, QVector<HierarchyEntry*> collapsedAncestors, HierarchySearchResult& result);
-    HierarchySearchResult findHierarchyEntryById(const QString& hierarchyId);
-    bool findCommandEntryById(WorksheetEntry* first, const QString& commandId, QVector<HierarchyEntry*> collapsedAncestors, CommandSearchResult& result);
-    CommandSearchResult findCommandEntryById(const QString& commandId);
-    bool expandHierarchyAncestors(const QVector<HierarchyEntry*>& ancestors);
-    QString buildCommandNodeId(CommandEntry* entry);
-    QString buildPlotNodeId(const QString& commandId, const QString& resultId) const;
-    bool parseCommandNodeId(const QString& nodeId, QString* commandId) const;
-    bool parsePlotNodeId(const QString& nodeId, QString* commandId, QString* resultId) const;
-    bool navigateToPlotResult(CommandEntry* commandEntry, const QString& resultId);
-    void setCurrentTocNode(const QString& nodeId);
-    QString hierarchyIdForEntry(WorksheetEntry* entry) const;
-    QString commandTocTitle() const;
-    QString commandTocDisplayText(CommandEntry* entry) const;
-    QString plotTocTitle(Cantor::Result* result) const;
-    QString plotTocDisplayText(CommandEntry* entry, Cantor::Result* result, int plotOrdinal, int plotCount) const;
-    bool isPlotResult(Cantor::Result* result);
+    friend class WorksheetHierarchyManager;
 
   private Q_SLOTS:
     //void checkEntriesForSanity();
@@ -390,9 +352,6 @@ class Worksheet : public QGraphicsScene
     void updateCurrentHierarchy(WorksheetEntry* entry);
     void normalizeDraggedHierarchyLevels(HierarchyEntry* rootEntry, WorksheetEntry* previousEntry, const std::vector<WorksheetEntry*>& subentries);
 
-    QString m_currentTocNodeId;
-    HierarchyTrackingSource m_hierarchyTrackingSource{HierarchyTrackingSource::Viewport};
-
     bool m_layoutUpdateInProgress{false};
 
     static const double LeftMargin;
@@ -401,6 +360,8 @@ class Worksheet : public QGraphicsScene
     static const double EntryCursorLength;
     static const double EntryCursorWidth;
 
+    WorksheetHierarchyManager* m_hierarchyManager{nullptr};
+
     Cantor::Session* m_session{nullptr};
     Cantor::Renderer m_renderer;
     MathRenderer m_mathRenderer;
@@ -461,9 +422,6 @@ class Worksheet : public QGraphicsScene
     QVector<WorksheetEntry*> m_selectedEntries;
     QQueue<WorksheetEntry*> m_circularFocusBuffer;
 
-    size_t m_hierarchyMaxDepth{0};
-    QVariantList m_tocNodeSnapshot;
-    bool m_tocRefreshScheduled{false};
 };
 
 #endif // WORKSHEET_H
diff --git a/src/worksheethierarchymanager.cpp b/src/worksheethierarchymanager.cpp
new file mode 100644
index 00000000..c97edc87
--- /dev/null
+++ b/src/worksheethierarchymanager.cpp
@@ -0,0 +1,1308 @@
+#include "worksheethierarchymanager.h"
+
+#include "commandentry.h"
+#include "hierarchyentry.h"
+#include "placeholderentry.h"
+#include "resultitem.h"
+#include "settings.h"
+#include "worksheet.h"
+#include "worksheetentry.h"
+#include "worksheettextitem.h"
+#include "worksheetview.h"
+#include "lib/animationresult.h"
+#include "lib/expression.h"
+#include "lib/imageresult.h"
+#include "lib/pdfresult.h"
+#include "lib/result.h"
+
+#include <KLocalizedString>
+#include <KMessageBox>
+#include <KStandardGuiItem>
+
+#include <QList>
+#include <QRectF>
+#include <QSet>
+#include <QTimer>
+#include <QVariantMap>
+
+#include <algorithm>
+
+namespace
+{
+    const QLatin1String TocNodeTypeChapter("chapter");
+    const QLatin1String TocNodeTypeSection("section");
+    const QLatin1String TocNodeTypeCommand("command");
+    const QLatin1String TocNodeTypePlot("plot");
+}
+
+WorksheetHierarchyManager::WorksheetHierarchyManager(Worksheet* worksheet)
+    : QObject(worksheet)
+    , m_worksheet(worksheet)
+{
+    Q_ASSERT(m_worksheet);
+}
+
+size_t WorksheetHierarchyManager::hierarchyMaxDepth() const
+{
+    return m_hierarchyMaxDepth;
+}
+
+void WorksheetHierarchyManager::refreshTocStructure()
+{
+    if (m_worksheet->m_isClosing || m_worksheet->m_isLoadingFromFile)
+        return;
+
+    m_tocRefreshScheduled = false;
+    m_tocNodeSnapshot = collectTocNodes();
+
+    if (!m_currentTocNodeId.isEmpty())
+    {
+        bool currentNodeStillExists = false;
+        for (const QVariant& nodeValue : m_tocNodeSnapshot)
+        {
+            if (nodeValue.toMap().value(QStringLiteral("id")).toString() == m_currentTocNodeId)
+            {
+                currentNodeStillExists = true;
+                break;
+            }
+        }
+
+        if (!currentNodeStillExists)
+            setCurrentTocNode(QString());
+    }
+
+    Q_EMIT m_worksheet->tocNodesChanged(m_tocNodeSnapshot);
+}
+
+void WorksheetHierarchyManager::scheduleTocStructureRefresh()
+{
+    if (m_worksheet->m_isClosing || m_worksheet->m_isLoadingFromFile || m_tocRefreshScheduled)
+        return;
+
+    m_tocRefreshScheduled = true;
+    QTimer::singleShot(0, this, [this]()
+    {
+        if (!m_tocRefreshScheduled)
+            return;
+
+        refreshTocStructure();
+    });
+}
+
+void WorksheetHierarchyManager::emitTocNodeSnapshot()
+{
+    if (m_worksheet->m_isClosing)
+        return;
+
+    if (m_tocNodeSnapshot.isEmpty() && m_worksheet->firstEntry())
+        m_tocNodeSnapshot = collectTocNodes();
+
+    Q_EMIT m_worksheet->tocNodesChanged(m_tocNodeSnapshot);
+}
+
+QVariantList WorksheetHierarchyManager::collectTocNodes() const
+{
+    QVariantList nodes;
+    QVector<QString> hierarchyNodeIds;
+    QVector<int> hierarchyDepths;
+
+    visitLogicalEntries([&](WorksheetEntry* entry)
+    {
+        if (entry->type() == HierarchyEntry::Type)
+        {
+            auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
+            const int depth = static_cast<int>(hierarchyEntry->level()) - static_cast<int>(HierarchyEntry::HierarchyLevel::Chapter);
+
+            while (!hierarchyDepths.isEmpty() && hierarchyDepths.last() >= depth)
+            {
+                hierarchyDepths.removeLast();
+                hierarchyNodeIds.removeLast();
+            }
+
+            const QString parentNodeId = hierarchyNodeIds.isEmpty() ? QString() : hierarchyNodeIds.last();
+            const QString hierarchyId = hierarchyEntry->hierarchyId();
+            const QString title = hierarchyEntry->text();
+            const QString displayText = hierarchyEntry->hierarchyText().isEmpty()
+                ? hierarchyEntry->text()
+                : hierarchyEntry->hierarchyText() + QLatin1Char(' ') + hierarchyEntry->text();
+
+            QVariantMap node;
+            node.insert(QStringLiteral("id"), hierarchyId);
+            node.insert(QStringLiteral("parentId"), parentNodeId);
+            node.insert(QStringLiteral("type"), depth == 0 ? QString(TocNodeTypeChapter) : QString(TocNodeTypeSection));
+            node.insert(QStringLiteral("title"), title);
+            node.insert(QStringLiteral("displayText"), displayText);
+            node.insert(QStringLiteral("hierarchyText"), hierarchyEntry->hierarchyText());
+            node.insert(QStringLiteral("depth"), depth);
+            node.insert(QStringLiteral("editable"), true);
+            node.insert(QStringLiteral("navigable"), true);
+            node.insert(QStringLiteral("hierarchyId"), hierarchyId);
+            node.insert(QStringLiteral("resultIndex"), -1);
+            node.insert(QStringLiteral("entryId"), hierarchyId);
+            nodes.append(node);
+
+            hierarchyNodeIds.append(hierarchyId);
+            hierarchyDepths.append(depth);
+        }
+        else if (entry->type() == CommandEntry::Type)
+        {
+            auto* commandEntry = static_cast<CommandEntry*>(entry);
+            const QString parentNodeId = hierarchyNodeIds.isEmpty() ? QString() : hierarchyNodeIds.last();
+            const QString commandNodeId = buildCommandNodeId(commandEntry);
+
+            QVariantMap commandNode;
+            commandNode.insert(QStringLiteral("id"), commandNodeId);
+            commandNode.insert(QStringLiteral("parentId"), parentNodeId);
+            commandNode.insert(QStringLiteral("type"), QString(TocNodeTypeCommand));
+            commandNode.insert(QStringLiteral("title"), commandTocTitle());
+            commandNode.insert(QStringLiteral("displayText"), commandTocDisplayText(commandEntry));
+            commandNode.insert(QStringLiteral("hierarchyText"), QString());
+            commandNode.insert(QStringLiteral("depth"), hierarchyDepths.isEmpty() ? 0 : hierarchyDepths.last() + 1);
+            commandNode.insert(QStringLiteral("editable"), false);
+            commandNode.insert(QStringLiteral("navigable"), true);
+            commandNode.insert(QStringLiteral("hierarchyId"), QString());
+            commandNode.insert(QStringLiteral("resultIndex"), -1);
+            commandNode.insert(QStringLiteral("entryId"), commandEntry->commandId());
+            nodes.append(commandNode);
+
+            if (auto* expression = commandEntry->expression())
+            {
+                const auto& results = expression->results();
+                int plotCount = 0;
+                for (auto* result : results)
+                {
+                    if (isPlotResult(result))
+                        ++plotCount;
+                }
+
+                int plotOrdinal = 0;
+                for (int index = 0; index < results.size(); ++index)
+                {
+                    auto* result = results.at(index);
+                    if (!isPlotResult(result))
+                        continue;
+
+                    ++plotOrdinal;
+
+                    QVariantMap plotNode;
+                    const QString customTitle = result->displayName();
+                    plotNode.insert(QStringLiteral("id"), buildPlotNodeId(commandEntry->commandId(), result->resultId()));
+                    plotNode.insert(QStringLiteral("parentId"), commandNodeId);
+                    plotNode.insert(QStringLiteral("type"), QString(TocNodeTypePlot));
+                    plotNode.insert(QStringLiteral("title"), plotTocTitle(result));
+                    plotNode.insert(QStringLiteral("customTitle"), customTitle);
+                    plotNode.insert(QStringLiteral("displayText"), plotTocDisplayText(commandEntry, result, plotOrdinal, plotCount));
+                    plotNode.insert(QStringLiteral("hierarchyText"), QString());
+                    plotNode.insert(QStringLiteral("depth"), hierarchyDepths.isEmpty() ? 1 : hierarchyDepths.last() + 2);
+                    plotNode.insert(QStringLiteral("editable"), true);
+                    plotNode.insert(QStringLiteral("navigable"), true);
+                    plotNode.insert(QStringLiteral("hierarchyId"), QString());
+                    plotNode.insert(QStringLiteral("resultIndex"), index);
+                    plotNode.insert(QStringLiteral("resultId"), result->resultId());
+                    plotNode.insert(QStringLiteral("entryId"), commandEntry->commandId());
+                    nodes.append(plotNode);
+                }
+            }
+        }
+        return true;
+    });
+
+    return nodes;
+}
+
+QString WorksheetHierarchyManager::buildCommandNodeId(CommandEntry* entry) const
+{
+    if (!entry)
+        return QString();
+
+    const QString& commandId = entry->commandId();
+    if (commandId.isEmpty())
+        return QString();
+
+    return QStringLiteral("command:") + commandId;
+}
+
+QString WorksheetHierarchyManager::buildPlotNodeId(const QString& commandId, const QString& resultId) const
+{
+    if (commandId.isEmpty() || resultId.isEmpty())
+        return QString();
+
+    return QStringLiteral("plot:%1:%2").arg(commandId, resultId);
+}
+
+bool WorksheetHierarchyManager::parseCommandNodeId(const QString& nodeId, QString* commandId) const
+{
+    const QString prefix = QStringLiteral("command:");
+    if (!nodeId.startsWith(prefix))
+        return false;
+
+    const QString parsedCommandId = nodeId.mid(prefix.size());
+    if (parsedCommandId.isEmpty() || parsedCommandId.contains(QLatin1Char(':')))
+        return false;
+
+    if (commandId)
+        *commandId = parsedCommandId;
+
+    return true;
+}
+
+bool WorksheetHierarchyManager::parsePlotNodeId(const QString& nodeId, QString* commandId, QString* resultId) const
+{
+    const QString prefix = QStringLiteral("plot:");
+    if (!nodeId.startsWith(prefix))
+        return false;
+
+    const QStringList parts = nodeId.mid(prefix.size()).split(QLatin1Char(':'));
+    if (parts.size() != 2 || parts.at(0).isEmpty() || parts.at(1).isEmpty())
+        return false;
+
+    if (commandId)
+        *commandId = parts.at(0);
+    if (resultId)
+        *resultId = parts.at(1);
+
+    return true;
+}
+
+QString WorksheetHierarchyManager::commandTocTitle() const
+{
+    return i18n("Command");
+}
+
+QString WorksheetHierarchyManager::commandTocDisplayText(CommandEntry* entry) const
+{
+    auto* expression = entry ? entry->expression() : nullptr;
+    const QString title = commandTocTitle();
+    if (m_worksheet->m_showExpressionIds && expression && expression->id() != -1)
+        return i18n("%1 %2", title, expression->id());
+
+    return title;
+}
+
+QString WorksheetHierarchyManager::plotTocTitle(Cantor::Result* result) const
+{
+    if (result && !result->displayName().isEmpty())
+        return result->displayName();
+
+    return i18n("Plot");
+}
+
+QString WorksheetHierarchyManager::plotTocDisplayText(CommandEntry* entry, Cantor::Result* result, int plotOrdinal, int plotCount) const
+{
+    const QString title = plotTocTitle(result);
+    auto* expression = entry ? entry->expression() : nullptr;
+    if (m_worksheet->m_showExpressionIds && expression && expression->id() != -1)
+    {
+        if (plotCount > 1)
+            return i18n("%1 %2.%3", title, expression->id(), plotOrdinal);
+
+        return i18n("%1 %2", title, expression->id());
+    }
+
+    if (plotCount > 1)
+        return i18n("%1 %2", title, plotOrdinal);
+
+    return title;
+}
+
+bool WorksheetHierarchyManager::isPlotResult(Cantor::Result* result) const
+{
+    if (!result || result->role() != Cantor::Result::Role::Plot)
+        return false;
+
+    return result->type() == Cantor::ImageResult::Type
+        || result->type() == Cantor::AnimationResult::Type
+        || result->type() == Cantor::PdfResult::Type;
+}
+
+bool WorksheetHierarchyManager::visitLogicalEntries(WorksheetEntry* first, const std::function<bool(WorksheetEntry*)>& visitor) const
+{
+    for (auto* entry = first; entry; entry = entry->next())
+    {
+        if (!visitor(entry))
+            return false;
+
+        if (entry->type() != HierarchyEntry::Type)
+            continue;
+
+        auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
+        if (auto* hiddenEntry = hierarchyEntry->hiddenSubentries())
+        {
+            if (!visitLogicalEntries(hiddenEntry, visitor))
+                return false;
+        }
+    }
+
+    return true;
+}
+
+bool WorksheetHierarchyManager::visitLogicalEntries(const std::function<bool(WorksheetEntry*)>& visitor) const
+{
+    return visitLogicalEntries(m_worksheet->firstEntry(), visitor);
+}
+
+bool WorksheetHierarchyManager::findHierarchyEntryById(WorksheetEntry* first, const QString& hierarchyId, QVector<HierarchyEntry*> collapsedAncestors, HierarchySearchResult& result) const
+{
+    for (auto* entry = first; entry; entry = entry->next())
+    {
+        if (entry->type() != HierarchyEntry::Type)
+            continue;
+
+        auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
+
+        if (hierarchyEntry->hierarchyId() == hierarchyId)
+        {
+            result.entry = hierarchyEntry;
+            result.collapsedAncestors = collapsedAncestors;
+            return true;
+        }
+
+        if (auto* hiddenEntry = hierarchyEntry->hiddenSubentries())
+        {
+            auto childAncestors = collapsedAncestors;
+            childAncestors.append(hierarchyEntry);
+
+            if (findHierarchyEntryById(hiddenEntry, hierarchyId, childAncestors, result))
+                return true;
+        }
+    }
+
+    return false;
+}
+
+WorksheetHierarchyManager::HierarchySearchResult WorksheetHierarchyManager::findHierarchyEntryById(const QString& hierarchyId) const
+{
+    HierarchySearchResult result;
+
+    if (hierarchyId.isEmpty())
+        return result;
+
+    findHierarchyEntryById(m_worksheet->firstEntry(), hierarchyId, {}, result);
+    return result;
+}
+
+bool WorksheetHierarchyManager::findCommandEntryById(WorksheetEntry* first, const QString& commandId, QVector<HierarchyEntry*> collapsedAncestors, CommandSearchResult& result) const
+{
+    for (auto* entry = first; entry; entry = entry->next())
+    {
+        if (entry->type() == CommandEntry::Type)
+        {
+            auto* commandEntry = static_cast<CommandEntry*>(entry);
+            if (commandEntry->commandId() == commandId)
+            {
+                result.entry = commandEntry;
+                result.collapsedAncestors = collapsedAncestors;
+                return true;
+            }
+        }
+
+        if (entry->type() != HierarchyEntry::Type)
+            continue;
+
+        auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
+        if (auto* hiddenEntry = hierarchyEntry->hiddenSubentries())
+        {
+            auto childAncestors = collapsedAncestors;
+            childAncestors.append(hierarchyEntry);
+
+            if (findCommandEntryById(hiddenEntry, commandId, childAncestors, result))
+                return true;
+        }
+    }
+
+    return false;
+}
+
+WorksheetHierarchyManager::CommandSearchResult WorksheetHierarchyManager::findCommandEntryById(const QString& commandId) const
+{
+    CommandSearchResult result;
+
+    if (commandId.isEmpty())
+        return result;
+
+    findCommandEntryById(m_worksheet->firstEntry(), commandId, {}, result);
+    return result;
+}
+
+bool WorksheetHierarchyManager::expandHierarchyAncestors(const QVector<HierarchyEntry*>& ancestors)
+{
+    bool expanded = false;
+
+    for (auto* ancestor : ancestors)
+    {
+        if (!ancestor || !ancestor->hasHiddenSubentries())
+            continue;
+
+        WorksheetEntry* hiddenSubentries = ancestor->takeHiddenSubentries();
+
+        if (!hiddenSubentries)
+            continue;
+
+        insertSubentriesForHierarchy(ancestor, hiddenSubentries);
+        expanded = true;
+    }
+
+    return expanded;
+}
+
+void WorksheetHierarchyManager::updateHierarchyLayout()
+{
+    QSet<QString> usedHierarchyIds;
+    QSet<QString> usedCommandIds;
+    QSet<QString> usedResultIds;
+
+    m_hierarchyMaxDepth = 0;
+    std::vector<int> hierarchyNumbers;
+
+    visitLogicalEntries([&](WorksheetEntry* entry)
+    {
+        if (entry->type() == HierarchyEntry::Type)
+        {
+            auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
+
+            hierarchyEntry->updateHierarchyLevel(hierarchyNumbers);
+
+            m_hierarchyMaxDepth = std::max(m_hierarchyMaxDepth, hierarchyNumbers.size());
+
+            // Keep IDs valid for old files and duplicated entries.
+            QString hierarchyId = hierarchyEntry->hierarchyId();
+
+            if (hierarchyId.isEmpty() || usedHierarchyIds.contains(hierarchyId))
+            {
+                hierarchyEntry->regenerateHierarchyId();
+                hierarchyId = hierarchyEntry->hierarchyId();
+            }
+
+            usedHierarchyIds.insert(hierarchyId);
+        }
+        else if (entry->type() == CommandEntry::Type)
+        {
+            auto* commandEntry = static_cast<CommandEntry*>(entry);
+            QString commandId = commandEntry->commandId();
+
+            if (commandId.isEmpty() || usedCommandIds.contains(commandId))
+            {
+                commandEntry->regenerateCommandId();
+                commandId = commandEntry->commandId();
+            }
+
+            usedCommandIds.insert(commandId);
+
+            if (auto* expression = commandEntry->expression())
+            {
+                const auto& results = expression->results();
+                for (auto* result : results)
+                {
+                    if (!result)
+                        continue;
+
+                    QString resultId = result->resultId();
+                    if (resultId.isEmpty() || usedResultIds.contains(resultId))
+                    {
+                        result->regenerateResultId();
+                        resultId = result->resultId();
+                    }
+
+                    usedResultIds.insert(resultId);
+                }
+            }
+        }
+
+        return true;
+    });
+
+    refreshTocStructure();
+}
+
+void WorksheetHierarchyManager::updateHierarchyControlsLayout(WorksheetEntry* startEntry)
+{
+    Q_UNUSED(startEntry);
+
+    std::vector<HierarchyEntry*> levelEntries;
+    const int numerationBegin = static_cast<int>(HierarchyEntry::HierarchyLevel::Chapter);
+    const int numerationEnd = static_cast<int>(HierarchyEntry::HierarchyLevel::EndValue);
+
+    for (int i = numerationBegin; i < numerationEnd; ++i)
+        levelEntries.push_back(nullptr);
+
+    WorksheetEntry* lastRealEntry = nullptr;
+
+    for (auto* entry = m_worksheet->firstEntry(); entry; entry = entry->next())
+    {
+        if (entry->type() == PlaceHolderEntry::Type || entry->aboutToBeRemoved())
+            continue;
+
+        lastRealEntry = entry;
+
+        if (entry->type() != HierarchyEntry::Type)
+            continue;
+
+        auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
+
+        const int index = static_cast<int>(hierarchyEntry->level()) - numerationBegin;
+
+        if (index < 0 || index >= static_cast<int>(levelEntries.size()))
+            continue;
+
+        if (!levelEntries[index])
+        {
+            levelEntries[index] = hierarchyEntry;
+            continue;
+        }
+
+        // Close previous controls at this level and below.
+        for (int i = index; i < static_cast<int>(levelEntries.size()); ++i)
+        {
+            auto* openEntry = levelEntries[i];
+
+            if (!openEntry)
+                continue;
+
+            const qreal ownBottom = openEntry->y() + openEntry->size().height() - WorksheetEntry::VerticalMargin;
+            const qreal controlEnd = hierarchyEntry->y() - WorksheetEntry::VerticalMargin;
+            const bool hasSubelements = controlEnd > ownBottom;
+
+            openEntry->updateControlElementForHierarchy(qMax(controlEnd, ownBottom), m_hierarchyMaxDepth, hasSubelements);
+
+            levelEntries[i] = nullptr;
+        }
+
+        levelEntries[index] = hierarchyEntry;
+    }
+
+    if (!lastRealEntry)
+        return;
+
+    const qreal documentEnd = lastRealEntry->y() + lastRealEntry->size().height() - WorksheetEntry::VerticalMargin;
+
+    for (auto* openEntry : levelEntries)
+    {
+        if (!openEntry)
+            continue;
+
+        const qreal ownBottom = openEntry->y() + openEntry->size().height() - WorksheetEntry::VerticalMargin;
+        const qreal controlEnd = qMax(documentEnd, ownBottom);
+        const bool hasSubelements = controlEnd > ownBottom;
+
+        openEntry->updateControlElementForHierarchy(controlEnd, m_hierarchyMaxDepth, hasSubelements);
+    }
+}
+
+std::vector<WorksheetEntry*> WorksheetHierarchyManager::hierarchySubelements(HierarchyEntry* hierarchyEntry) const
+{
+    std::vector<WorksheetEntry*> subentries;
+
+    Q_ASSERT(hierarchyEntry);
+
+    bool subentriesEnd = false;
+    const int level = (int)hierarchyEntry->level();
+    for (auto* entry = hierarchyEntry->next(); entry && !subentriesEnd; entry = entry->next())
+    {
+        if (entry->type() == HierarchyEntry::Type)
+        {
+            if ((int)(static_cast<HierarchyEntry*>(entry)->level()) <= level)
+                subentriesEnd = true;
+            else
+                subentries.push_back(entry);
+        }
+        else
+            subentries.push_back(entry);
+    }
+    return subentries;
+}
+
+WorksheetEntry* WorksheetHierarchyManager::cutSubentriesForHierarchy(HierarchyEntry* hierarchyEntry)
+{
+    if (!hierarchyEntry || !hierarchyEntry->next())
+        return nullptr;
+
+    WorksheetEntry* cutBegin = hierarchyEntry->next();
+    if (cutBegin->type() == HierarchyEntry::Type && static_cast<int>(static_cast<HierarchyEntry*>(cutBegin)->level()) <= static_cast<int>(hierarchyEntry->level()))
+        return nullptr;
+    WorksheetEntry* cutEnd = cutBegin;
+
+    const int hierarchyLevel = static_cast<int>(hierarchyEntry->level());
+
+    while (cutEnd->next())
+    {
+        WorksheetEntry* candidate = cutEnd->next();
+
+        if (candidate->type() == HierarchyEntry::Type)
+        {
+            const int candidateLevel = static_cast<int>(static_cast<HierarchyEntry*>(candidate)->level());
+
+            if (candidateLevel <= hierarchyLevel)
+                break;
+        }
+
+        cutEnd = candidate;
+    }
+
+    WorksheetEntry* entryAfterSection = cutEnd->next();
+
+    hierarchyEntry->setNext(entryAfterSection);
+
+    if (entryAfterSection)
+        entryAfterSection->setPrevious(hierarchyEntry);
+    else
+        m_worksheet->setLastEntry(hierarchyEntry);
+
+    cutBegin->setPrevious(nullptr);
+    cutEnd->setNext(nullptr);
+
+    for (auto* entry = cutBegin; entry; entry = entry->next())
+        entry->hide();
+
+    return cutBegin;
+}
+
+void WorksheetHierarchyManager::insertSubentriesForHierarchy(HierarchyEntry* hierarchyEntry, WorksheetEntry* storedSubentriesBegin)
+{
+    if (!hierarchyEntry || !storedSubentriesBegin)
+        return;
+
+    WorksheetEntry* previousNext = hierarchyEntry->next();
+
+    hierarchyEntry->setNext(storedSubentriesBegin);
+    storedSubentriesBegin->setPrevious(hierarchyEntry);
+
+    WorksheetEntry* storedEnd = storedSubentriesBegin;
+
+    for (auto* entry = storedSubentriesBegin; entry; entry = entry->next())
+    {
+        entry->show();
+        storedEnd = entry;
+    }
+
+    storedEnd->setNext(previousNext);
+
+    if (previousNext)
+        previousNext->setPrevious(storedEnd);
+    else
+        m_worksheet->setLastEntry(storedEnd);
+}
+
+bool WorksheetHierarchyManager::expandHierarchyForStructureChange(HierarchyEntry* hierarchyEntry)
+{
+    if (!hierarchyEntry)
+        return false;
+
+    bool hierarchyExpanded = false;
+    const auto expandEntry = [this, &hierarchyExpanded](HierarchyEntry* entry)
+    {
+        if (!entry || !entry->hasHiddenSubentries())
+            return;
+
+        WorksheetEntry* hiddenSubentries = entry->takeHiddenSubentries();
+
+        if (!hiddenSubentries)
+            return;
+
+        insertSubentriesForHierarchy(entry, hiddenSubentries);
+
+        hierarchyExpanded = true;
+    };
+
+    expandEntry(hierarchyEntry);
+
+    const int rootLevel = static_cast<int>(hierarchyEntry->level());
+    for (auto* entry = hierarchyEntry->next(); entry;)
+    {
+        if (entry->type() == HierarchyEntry::Type)
+        {
+            auto* childHierarchy = static_cast<HierarchyEntry*>(entry);
+            const int childLevel = static_cast<int>(childHierarchy->level());
+
+            if (childLevel <= rootLevel)
+                break;
+
+            expandEntry(childHierarchy);
+        }
+
+        entry = entry->next();
+    }
+
+    return hierarchyExpanded;
+}
+
+void WorksheetHierarchyManager::navigateToTocNode(QString nodeId)
+{
+    QString plotCommandId;
+    QString plotResultId;
+    if (parsePlotNodeId(nodeId, &plotCommandId, &plotResultId))
+    {
+        const CommandSearchResult commandSearch = findCommandEntryById(plotCommandId);
+        if (!commandSearch.entry)
+        {
+            scheduleTocStructureRefresh();
+            return;
+        }
+
+        const bool expanded = expandHierarchyAncestors(commandSearch.collapsedAncestors);
+        if (expanded)
+        {
+            updateHierarchyLayout();
+            m_worksheet->updateLayout();
+        }
+
+        if (!navigateToPlotResult(commandSearch.entry, plotResultId))
+            scheduleTocStructureRefresh();
+        return;
+    }
+
+    QString commandId;
+    if (parseCommandNodeId(nodeId, &commandId))
+    {
+        const CommandSearchResult commandSearch = findCommandEntryById(commandId);
+        if (!commandSearch.entry)
+            return;
+
+        const bool expanded = expandHierarchyAncestors(commandSearch.collapsedAncestors);
+        if (expanded)
+        {
+            updateHierarchyLayout();
+            m_worksheet->updateLayout();
+        }
+
+        auto* commandEntry = commandSearch.entry;
+        updateCurrentHierarchyFromEntry(commandEntry);
+
+        m_worksheet->worksheetView()->scrollTo(qRound(commandEntry->scenePos().y()));
+        m_worksheet->worksheetView()->setFocus();
+        commandEntry->focusEntry(WorksheetTextItem::TopLeft);
+
+        m_worksheet->resetEntryCursor();
+        return;
+    }
+
+    const HierarchySearchResult hierarchySearch = findHierarchyEntryById(nodeId);
+    auto* hierarchyEntry = hierarchySearch.entry;
+
+    if (hierarchyEntry)
+    {
+        const bool expanded = expandHierarchyAncestors(hierarchySearch.collapsedAncestors);
+        if (expanded)
+        {
+            updateHierarchyLayout();
+            m_worksheet->updateLayout();
+        }
+
+        updateCurrentHierarchyFromEntry(hierarchyEntry);
+
+        m_worksheet->worksheetView()->scrollTo(qRound(hierarchyEntry->scenePos().y()));
+        m_worksheet->worksheetView()->setFocus();
+        hierarchyEntry->focusEntry(WorksheetTextItem::BottomRight);
+
+        m_worksheet->resetEntryCursor();
+    }
+}
+
+bool WorksheetHierarchyManager::navigateToPlotResult(CommandEntry* commandEntry, const QString& resultId)
+{
+    if (!commandEntry || resultId.isEmpty())
+        return false;
+
+    if (commandEntry->isResultCollapsed())
+    {
+        commandEntry->expandResults();
+        m_worksheet->updateLayout();
+    }
+
+    auto* resultItem = commandEntry->resultItemById(resultId);
+    if (!resultItem || !resultItem->result() || !isPlotResult(resultItem->result()))
+        return false;
+
+    QGraphicsObject* object = resultItem->graphicsObject();
+    if (!object)
+        return false;
+
+    const QString nodeId = buildPlotNodeId(commandEntry->commandId(), resultId);
+
+    m_worksheet->worksheetView()->scrollTo(qRound(object->sceneBoundingRect().top()));
+    m_worksheet->worksheetView()->setFocus();
+    object->setFocus();
+
+    m_trackingSource = TrackingSource::FocusedEntry;
+    setCurrentTocNode(nodeId);
+
+    m_worksheet->resetEntryCursor();
+    return true;
+}
+
+void WorksheetHierarchyManager::updateCurrentTocNodeFromResult(CommandEntry* commandEntry, Cantor::Result* result)
+{
+    if (!commandEntry || !result || !m_worksheet->isValidEntry(commandEntry))
+        return;
+
+    m_trackingSource = TrackingSource::FocusedEntry;
+
+    if (isPlotResult(result))
+        setCurrentTocNode(buildPlotNodeId(commandEntry->commandId(), result->resultId()));
+    else
+        updateCurrentHierarchyFromEntry(commandEntry);
+}
+
+void WorksheetHierarchyManager::renamePlot(const QString& commandId, const QString& resultId, const QString& newTitle)
+{
+    if (m_worksheet->m_readOnly || commandId.isEmpty() || resultId.isEmpty())
+        return;
+
+    QString normalizedTitle = newTitle;
+    normalizedTitle.replace(QLatin1Char('\r'), QLatin1Char(' '));
+    normalizedTitle.replace(QLatin1Char('\n'), QLatin1Char(' '));
+    normalizedTitle = normalizedTitle.trimmed();
+
+    const CommandSearchResult commandSearch = findCommandEntryById(commandId);
+    auto* commandEntry = commandSearch.entry;
+    auto* expression = commandEntry ? commandEntry->expression() : nullptr;
+
+    if (!commandEntry || !expression)
+        return;
+
+    for (auto* result : expression->results())
+    {
+        if (!result || result->resultId() != resultId || !isPlotResult(result))
+            continue;
+
+        if (result->displayName() == normalizedTitle)
+            return;
+
+        result->setDisplayName(normalizedTitle);
+        m_worksheet->setModified();
+        scheduleTocStructureRefresh();
+        return;
+    }
+}
+
+void WorksheetHierarchyManager::deletePlot(const QString& commandId, const QString& resultId)
+{
+    if (m_worksheet->m_readOnly || commandId.isEmpty() || resultId.isEmpty())
+        return;
+
+    const CommandSearchResult commandSearch = findCommandEntryById(commandId);
+    auto* commandEntry = commandSearch.entry;
+    auto* expression = commandEntry ? commandEntry->expression() : nullptr;
+
+    if (!commandEntry || !expression)
+        return;
+
+    for (auto* result : expression->results())
+    {
+        if (!result || result->resultId() != resultId || !isPlotResult(result))
+            continue;
+
+        const QString plotNodeId = buildPlotNodeId(commandId, resultId);
+        const bool wasCurrentNode = m_currentTocNodeId == plotNodeId;
+
+        expression->removeResult(result);
+
+        if (wasCurrentNode)
+            setCurrentTocNode(buildCommandNodeId(commandEntry));
+
+        m_worksheet->setModified();
+        scheduleTocStructureRefresh();
+        return;
+    }
+}
+
+void WorksheetHierarchyManager::renameHierarchyEntry(const QString& hierarchyId, const QString& newName)
+{
+    if (m_worksheet->m_readOnly || hierarchyId.isEmpty())
+        return;
+
+    QString normalizedName = newName;
+
+    normalizedName.replace(QLatin1Char('\r'), QLatin1Char(' '));
+    normalizedName.replace(QLatin1Char('\n'), QLatin1Char(' '));
+
+    normalizedName = normalizedName.trimmed();
+
+    const HierarchySearchResult hierarchySearch = findHierarchyEntryById(hierarchyId);
+    auto* hierarchyEntry = hierarchySearch.entry;
+
+    if (!hierarchyEntry || hierarchyEntry->text() == normalizedName)
+        return;
+
+    hierarchyEntry->setContent(normalizedName);
+}
+
+void WorksheetHierarchyManager::changeHierarchyLevel(const QString& hierarchyId, int levelDelta)
+{
+    if (m_worksheet->m_readOnly || hierarchyId.isEmpty() || (levelDelta != -1 && levelDelta != 1))
+        return;
+
+    const HierarchySearchResult hierarchySearch = findHierarchyEntryById(hierarchyId);
+    HierarchyEntry* targetEntry = hierarchySearch.entry;
+
+    if (!targetEntry)
+        return;
+
+    bool expanded = false;
+
+    const int minimumLevel = static_cast<int>(HierarchyEntry::HierarchyLevel::Chapter);
+
+    const int maximumLevel = static_cast<int>(HierarchyEntry::HierarchyLevel::Subparagraph);
+
+    const int currentRootLevel = static_cast<int>(targetEntry->level());
+
+    if (levelDelta < 0)
+    {
+        if (currentRootLevel <= minimumLevel)
+            return;
+
+        expanded = expandHierarchyAncestors(hierarchySearch.collapsedAncestors);
+    }
+    else
+    {
+        if (currentRootLevel >= maximumLevel)
+            return;
+
+        expanded = expandHierarchyAncestors(hierarchySearch.collapsedAncestors);
+
+        bool hasPreviousSibling = false;
+
+        for (auto* entry = targetEntry->previous(); entry; entry = entry->previous())
+        {
+            if (entry->type() != HierarchyEntry::Type)
+                continue;
+
+            const int entryLevel = static_cast<int>(static_cast<HierarchyEntry*>(entry)->level());
+
+            if (entryLevel < currentRootLevel)
+                break;
+
+            if (entryLevel == currentRootLevel)
+            {
+                hasPreviousSibling = true;
+                break;
+            }
+        }
+
+        if (!hasPreviousSibling)
+        {
+            if (expanded)
+            {
+                updateHierarchyLayout();
+                m_worksheet->updateLayout();
+            }
+
+            return;
+        }
+    }
+
+    expanded = expandHierarchyForStructureChange(targetEntry) || expanded;
+    const std::vector<WorksheetEntry*> subentries = hierarchySubelements(targetEntry);
+
+    if (levelDelta > 0)
+    {
+        for (auto* entry : subentries)
+        {
+            if (!entry || entry->type() != HierarchyEntry::Type)
+                continue;
+
+            const int entryLevel = static_cast<int>(static_cast<HierarchyEntry*>(entry)->level());
+
+            if (entryLevel >= maximumLevel)
+            {
+                updateHierarchyLayout();
+                if (expanded)
+                    m_worksheet->updateLayout();
+                return;
+            }
+        }
+    }
+
+    const auto shiftHierarchyEntry = [levelDelta](HierarchyEntry* hierarchyEntry)
+    {
+        if (!hierarchyEntry)
+            return;
+
+        const int newLevel = static_cast<int>(hierarchyEntry->level()) + levelDelta;
+
+        hierarchyEntry->setLevel(static_cast<HierarchyEntry::HierarchyLevel>(newLevel));
+    };
+
+    shiftHierarchyEntry(targetEntry);
+
+    for (auto* entry : subentries)
+    {
+        if (!entry || entry->type() != HierarchyEntry::Type)
+            continue;
+
+        shiftHierarchyEntry(static_cast<HierarchyEntry*>(entry));
+    }
+
+    updateHierarchyLayout();
+    m_worksheet->updateLayout();
+
+    m_worksheet->setModified();
+}
+
+void WorksheetHierarchyManager::deleteHierarchyEntry(const QString& hierarchyId, bool deleteContents)
+{
+    if (m_worksheet->m_readOnly || hierarchyId.isEmpty())
+        return;
+
+    const HierarchySearchResult hierarchySearch = findHierarchyEntryById(hierarchyId);
+    HierarchyEntry* targetEntry = hierarchySearch.entry;
+
+    if (!targetEntry)
+        return;
+
+    QString headingLabel = targetEntry->text().trimmed();
+
+    if (headingLabel.isEmpty())
+        headingLabel = targetEntry->hierarchyText();
+
+    QString warningText;
+    QString dialogTitle;
+
+    if (deleteContents)
+    {
+        warningText = i18n("Do you really want to delete "
+                           "the heading \"%1\" and all "
+                           "entries in this section? "
+                           "This action cannot be undone.",
+                           headingLabel);
+
+        dialogTitle = i18n("Delete Section");
+    }
+    else
+    {
+        warningText = i18n("Do you really want to delete "
+                           "only the heading \"%1\"? "
+                           "The contents of the section "
+                           "will remain in the worksheet. "
+                           "This action cannot be undone.",
+                           headingLabel);
+
+        dialogTitle = i18n("Delete Heading");
+    }
+
+    if (Settings::warnAboutEntryDelete())
+    {
+        const auto result = KMessageBox::warningTwoActions(
+                m_worksheet->worksheetView(),
+                warningText,
+                dialogTitle,
+                KStandardGuiItem::remove(),
+                KStandardGuiItem::cancel());
+
+        if (result != KMessageBox::PrimaryAction)
+            return;
+    }
+
+    expandHierarchyAncestors(hierarchySearch.collapsedAncestors);
+    expandHierarchyForStructureChange(targetEntry);
+
+    const std::vector<WorksheetEntry*> subentries = hierarchySubelements(targetEntry);
+
+    m_worksheet->clearAllSelections();
+    m_worksheet->notifyEntryFocus(nullptr);
+
+    QList<WorksheetEntry*> entriesToRemove;
+    entriesToRemove.append(targetEntry);
+
+    if (deleteContents)
+    {
+        for (auto* entry : subentries)
+            entriesToRemove.append(entry);
+    }
+    else
+    {
+        const int minimumLevel = static_cast<int>(HierarchyEntry::HierarchyLevel::Chapter);
+
+        for (auto* entry : subentries)
+        {
+            if (!entry || entry->type() != HierarchyEntry::Type)
+                continue;
+
+            auto* childHeading = static_cast<HierarchyEntry*>(entry);
+            const int newLevel = qMax(minimumLevel, static_cast<int>(childHeading->level()) - 1);
+            childHeading->setLevel(static_cast<HierarchyEntry::HierarchyLevel>(newLevel));
+        }
+    }
+
+    WorksheetEntry* previousEntry = targetEntry->previous();
+    WorksheetEntry* lastRemovedEntry = entriesToRemove.constLast();
+    WorksheetEntry* nextEntry = lastRemovedEntry->next();
+
+    if (previousEntry)
+        previousEntry->setNext(nextEntry);
+    else
+        m_worksheet->setFirstEntry(nextEntry);
+
+    if (nextEntry)
+        nextEntry->setPrevious(previousEntry);
+    else
+        m_worksheet->setLastEntry(previousEntry);
+
+    m_worksheet->clearFocus();
+
+    m_worksheet->updateFocusedTextItem(static_cast<WorksheetTextItem*>(nullptr));
+    m_worksheet->updateFocusedTextItem(static_cast<WorksheetTextEditorItem*>(nullptr));
+
+    for (auto* entry : entriesToRemove)
+    {
+        if (!entry)
+            continue;
+
+        entry->setPrevious(nullptr);
+        entry->setNext(nullptr);
+        entry->clearFocus();
+        entry->hide();
+        entry->deleteLater();
+    }
+
+    WorksheetEntry* focusTarget = nextEntry ? nextEntry : previousEntry;
+
+    if (!m_worksheet->firstEntry())
+        focusTarget = m_worksheet->appendCommandEntry();
+
+    updateHierarchyLayout();
+    m_worksheet->updateLayout();
+
+    if (focusTarget)
+    {
+        focusTarget->focusEntry();
+        m_worksheet->makeVisible(focusTarget);
+        updateCurrentHierarchyFromEntry(focusTarget);
+    }
+    else
+    {
+        m_trackingSource = TrackingSource::FocusedEntry;
+        updateCurrentHierarchy(nullptr);
+    }
+
+    m_worksheet->resetEntryCursor();
+    m_worksheet->setModified();
+}
+
+QString WorksheetHierarchyManager::hierarchyIdForEntry(WorksheetEntry* entry) const
+{
+    for (auto* current = entry; current; current = current->previous())
+    {
+        if (current->type() == HierarchyEntry::Type)
+            return static_cast<HierarchyEntry*>(current)->hierarchyId();
+    }
+
+    return QString();
+}
+
+void WorksheetHierarchyManager::setCurrentTocNode(const QString& nodeId)
+{
+    if (m_currentTocNodeId == nodeId)
+        return;
+
+    m_currentTocNodeId = nodeId;
+
+    Q_EMIT m_worksheet->currentTocNodeChanged(nodeId);
+}
+
+void WorksheetHierarchyManager::updateCurrentHierarchy(WorksheetEntry* entry)
+{
+    QString nodeId;
+
+    if (entry && entry->type() == CommandEntry::Type)
+        nodeId = buildCommandNodeId(static_cast<CommandEntry*>(entry));
+    else
+        nodeId = hierarchyIdForEntry(entry);
+
+    setCurrentTocNode(nodeId);
+}
+
+void WorksheetHierarchyManager::updateCurrentHierarchyFromView(const QRectF& viewRect)
+{
+    if (m_trackingSource != TrackingSource::Viewport || m_worksheet->m_layoutUpdateInProgress || m_worksheet->m_isLoadingFromFile || viewRect.isEmpty())
+        return;
+
+    const qreal activationOffset = qMin<qreal>(48.0, viewRect.height() * 0.15);
+
+    const qreal activationY = viewRect.top() + activationOffset;
+
+    WorksheetEntry* activeEntry = nullptr;
+
+    for (auto* entry = m_worksheet->firstEntry(); entry; entry = entry->next())
+    {
+        if (!entry->isVisible())
+            continue;
+
+        if (entry->scenePos().y() > activationY)
+            break;
+
+        activeEntry = entry;
+    }
+
+    updateCurrentHierarchy(activeEntry);
+}
+
+void WorksheetHierarchyManager::updateCurrentHierarchyFromEntry(WorksheetEntry* entry)
+{
+    m_trackingSource = TrackingSource::FocusedEntry;
+
+    updateCurrentHierarchy(entry);
+}
+
+void WorksheetHierarchyManager::followHierarchyFromView()
+{
+    m_trackingSource = TrackingSource::Viewport;
+
+    // Defer until viewRect() reflects the final scroll position.
+    QTimer::singleShot(0, this, [this]() {
+        if (m_trackingSource != TrackingSource::Viewport)
+            return;
+
+        updateCurrentHierarchyFromView(m_worksheet->worksheetView()->viewRect());
+    });
+}
+
+void WorksheetHierarchyManager::normalizeDraggedHierarchyLevels(HierarchyEntry* rootEntry, WorksheetEntry* previousEntry, const std::vector<WorksheetEntry*>& subentries)
+{
+    if (!rootEntry)
+        return;
+
+    HierarchyEntry* previousHierarchyEntry = nullptr;
+
+    for (auto* entry = previousEntry; entry; entry = entry->previous())
+    {
+        if (entry->type() != HierarchyEntry::Type)
+            continue;
+
+        previousHierarchyEntry = static_cast<HierarchyEntry*>(entry);
+        break;
+    }
+
+    const int minimumLevel = static_cast<int>(HierarchyEntry::HierarchyLevel::Chapter);
+    const int maximumLevel = static_cast<int>(HierarchyEntry::HierarchyLevel::Subparagraph);
+
+    int maximumAllowedRootLevel = minimumLevel;
+
+    if (previousHierarchyEntry)
+        maximumAllowedRootLevel = qMin(maximumLevel, static_cast<int>(previousHierarchyEntry->level()) + 1);
+
+    const int oldRootLevel = static_cast<int>(rootEntry->level());
+    if (oldRootLevel <= maximumAllowedRootLevel)
+        return;
+
+    const int levelOffset = maximumAllowedRootLevel - oldRootLevel;
+
+    const auto shiftLevel = [levelOffset, minimumLevel, maximumLevel](HierarchyEntry* hierarchyEntry)
+    {
+        if (!hierarchyEntry)
+            return;
+
+        const int newLevel = qBound(minimumLevel, static_cast<int>(hierarchyEntry->level()) + levelOffset, maximumLevel);
+
+        hierarchyEntry->setLevel(static_cast<HierarchyEntry::HierarchyLevel>(newLevel));
+    };
+
+    shiftLevel(rootEntry);
+
+    for (auto* entry : subentries)
+    {
+        if (entry->type() != HierarchyEntry::Type)
+            continue;
+
+        shiftLevel(static_cast<HierarchyEntry*>(entry));
+    }
+}
diff --git a/src/worksheethierarchymanager.h b/src/worksheethierarchymanager.h
new file mode 100644
index 00000000..38a5db2a
--- /dev/null
+++ b/src/worksheethierarchymanager.h
@@ -0,0 +1,110 @@
+#ifndef WORKSHEETHIERARCHYMANAGER_H
+#define WORKSHEETHIERARCHYMANAGER_H
+
+#include <QObject>
+#include <QString>
+#include <QVariantList>
+#include <QVector>
+
+#include <functional>
+#include <vector>
+
+class QRectF;
+class Worksheet;
+class CommandEntry;
+class WorksheetEntry;
+class HierarchyEntry;
+
+namespace Cantor {
+    class Result;
+}
+
+/*
+ * Helper owned by Worksheet for managing the structure shown in the
+ * Table of Contents panel. This includes hierarchy entries, command
+ * entries, plot result nodes, navigation, and structure-related editing
+ * actions.
+ */
+class WorksheetHierarchyManager : public QObject
+{
+  public:
+    explicit WorksheetHierarchyManager(Worksheet* worksheet);
+
+    size_t hierarchyMaxDepth() const;
+
+    void refreshTocStructure();
+    void scheduleTocStructureRefresh();
+    void emitTocNodeSnapshot();
+
+    void updateHierarchyLayout();
+    void updateHierarchyControlsLayout(WorksheetEntry* startEntry = nullptr);
+
+    std::vector<WorksheetEntry*> hierarchySubelements(HierarchyEntry*) const;
+    WorksheetEntry* cutSubentriesForHierarchy(HierarchyEntry*);
+    void insertSubentriesForHierarchy(HierarchyEntry*, WorksheetEntry*);
+    bool expandHierarchyForStructureChange(HierarchyEntry*);
+
+    void renameHierarchyEntry(const QString& hierarchyId, const QString& newName);
+    void changeHierarchyLevel(const QString& hierarchyId, int levelDelta);
+    void deleteHierarchyEntry(const QString& hierarchyId, bool deleteContents);
+    void renamePlot(const QString& commandId, const QString& resultId, const QString& newTitle);
+    void deletePlot(const QString& commandId, const QString& resultId);
+    void navigateToTocNode(QString nodeId);
+    void updateCurrentTocNodeFromResult(CommandEntry* commandEntry, Cantor::Result* result);
+
+    void updateCurrentHierarchy(WorksheetEntry* entry);
+    void updateCurrentHierarchyFromView(const QRectF& viewRect);
+    void updateCurrentHierarchyFromEntry(WorksheetEntry* entry);
+    void followHierarchyFromView();
+
+    void normalizeDraggedHierarchyLevels(HierarchyEntry* rootEntry, WorksheetEntry* previousEntry, const std::vector<WorksheetEntry*>& subentries);
+
+  private:
+    enum class TrackingSource
+    {
+        Viewport,
+        FocusedEntry
+    };
+
+    struct HierarchySearchResult
+    {
+        HierarchyEntry* entry{nullptr};
+        QVector<HierarchyEntry*> collapsedAncestors;
+    };
+
+    struct CommandSearchResult
+    {
+        CommandEntry* entry{nullptr};
+        QVector<HierarchyEntry*> collapsedAncestors;
+    };
+
+    QVariantList collectTocNodes() const;
+    bool visitLogicalEntries(WorksheetEntry* first, const std::function<bool(WorksheetEntry*)>& visitor) const;
+    bool visitLogicalEntries(const std::function<bool(WorksheetEntry*)>& visitor) const;
+    bool findHierarchyEntryById(WorksheetEntry* first, const QString& hierarchyId, QVector<HierarchyEntry*> collapsedAncestors, HierarchySearchResult& result) const;
+    HierarchySearchResult findHierarchyEntryById(const QString& hierarchyId) const;
+    bool findCommandEntryById(WorksheetEntry* first, const QString& commandId, QVector<HierarchyEntry*> collapsedAncestors, CommandSearchResult& result) const;
+    CommandSearchResult findCommandEntryById(const QString& commandId) const;
+    bool expandHierarchyAncestors(const QVector<HierarchyEntry*>& ancestors);
+    QString buildCommandNodeId(CommandEntry* entry) const;
+    QString buildPlotNodeId(const QString& commandId, const QString& resultId) const;
+    bool parseCommandNodeId(const QString& nodeId, QString* commandId) const;
+    bool parsePlotNodeId(const QString& nodeId, QString* commandId, QString* resultId) const;
+    bool navigateToPlotResult(CommandEntry* commandEntry, const QString& resultId);
+    void setCurrentTocNode(const QString& nodeId);
+    QString hierarchyIdForEntry(WorksheetEntry* entry) const;
+    QString commandTocTitle() const;
+    QString commandTocDisplayText(CommandEntry* entry) const;
+    QString plotTocTitle(Cantor::Result* result) const;
+    QString plotTocDisplayText(CommandEntry* entry, Cantor::Result* result, int plotOrdinal, int plotCount) const;
+    bool isPlotResult(Cantor::Result* result) const;
+
+    Worksheet* m_worksheet;
+    QString m_currentTocNodeId;
+    TrackingSource m_trackingSource{TrackingSource::Viewport};
+    size_t m_hierarchyMaxDepth{0};
+    QVariantList m_tocNodeSnapshot;
+    bool m_tocRefreshScheduled{false};
+};
+
+#endif // WORKSHEETHIERARCHYMANAGER_H
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.