[education/cantor] src: Add worksheet TOC navigation model
Alexander Semke <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit ffd719c2fcd914ca9776904fbb78e13c07669bf5 by Alexander Semke, on behalf of Nanhao Lv.
Committed on 26/07/2026 at 19:50.
Pushed by asemke into branch 'master'.
Add worksheet TOC navigation model
M +84 -21 src/hierarchyentry.cpp
M +8 -4 src/hierarchyentry.h
M +2504 -1270 src/worksheet.cpp
M +73 -4 src/worksheet.h
M +1 -2 src/worksheetentry.cpp
M +4 -1 src/worksheettexteditoritem.cpp
M +2 -0 src/worksheettextitem.cpp
M +24 -14 src/worksheetview.cpp
M +1 -0 src/worksheetview.h
https://invent.kde.org/education/cantor/-/commit/ffd719c2fcd914ca9776904fbb78e13c07669bf5
diff --git a/src/hierarchyentry.cpp b/src/hierarchyentry.cpp
index 86acd511..87c4d8a8 100644
--- a/src/hierarchyentry.cpp
+++ b/src/hierarchyentry.cpp
@@ -16,6 +16,7 @@
#include <QPainter>
#include <QDebug>
#include <QActionGroup>
+#include <QUuid>
#include <KLocalizedString>
@@ -26,18 +27,25 @@ HierarchyEntry::HierarchyEntry(Worksheet* worksheet) : WorksheetEntry(worksheet)
, m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction))
, m_depth(HierarchyLevel::Chapter)
, m_hierarchyNumber(1)
+ , m_hierarchyId(QUuid::createUuid().toString(QUuid::WithoutBraces))
, m_hidedSubentries(nullptr)
{
// Font and sizes should be regulated from future Settings "Styles" option
m_textItem->enableRichText(false);
+ connect(m_textItem->document(), &QTextDocument::contentsChanged, this, [this]() {
+ auto* ws = this->worksheet();
+ if (!ws || ws->isLoadingFromFile())
+ return;
+
+ ws->updateHierarchyLayout();
+ ws->setModified();
+ });
connect(m_textItem, &WorksheetTextItem::moveToPrevious, this, &HierarchyEntry::moveToPreviousEntry);
connect(m_textItem, &WorksheetTextItem::moveToNext, this, &HierarchyEntry::moveToNextEntry);
// Modern syntax of signal/stots don't work on this connection (arguments don't match)
connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate()));
- connect(this, &HierarchyEntry::hierarhyEntryNameChange, worksheet, &Worksheet::hierarhyEntryNameChange);
-
connect(&m_controlElement, &WorksheetControlItem::doubleClick, this, &HierarchyEntry::handleControlElementDoubleClick);
m_setLevelActionGroup = new QActionGroup(this);
@@ -91,6 +99,30 @@ bool HierarchyEntry::focusEntry(int pos, qreal xCoord)
return true;
}
+bool HierarchyEntry::hasHiddenSubentries() const
+{
+ return m_hidedSubentries != nullptr;
+}
+
+WorksheetEntry* HierarchyEntry::hiddenSubentries() const
+{
+ return m_hidedSubentries;
+}
+
+WorksheetEntry* HierarchyEntry::takeHiddenSubentries()
+{
+ WorksheetEntry* hiddenSubentries = m_hidedSubentries;
+
+ m_hidedSubentries = nullptr;
+
+ if (hiddenSubentries)
+ {
+ m_controlElement.isCollapsed = false;
+ m_controlElement.update();
+ }
+
+ return hiddenSubentries;
+}
void HierarchyEntry::setContent(const QString& content)
{
@@ -104,6 +136,10 @@ void HierarchyEntry::setContent(const QDomElement& content, const KZip& file)
return;
m_textItem->setPlainText(content.firstChildElement(QLatin1String("body")).text());
+ const QString storedHierarchyId = content.attribute(QLatin1String("hierarchy-id"));
+
+ if (!storedHierarchyId.isEmpty())
+ m_hierarchyId = storedHierarchyId;
const QDomElement& subentriesMainElem = content.firstChildElement(QLatin1String("HidedSubentries"));
if (!subentriesMainElem.isNull())
@@ -157,6 +193,10 @@ void HierarchyEntry::setContentFromJupyter(const QJsonObject& cell)
m_depth = (HierarchyLevel)cantorMetadata.value(QLatin1String("level")).toInt();
m_hierarchyNumber= cantorMetadata.value(QLatin1String("level-number")).toInt();
+ const QString storedHierarchyId = cantorMetadata.value(QLatin1String("hierarchy-id")).toString();
+
+ if (!storedHierarchyId.isEmpty())
+ m_hierarchyId = storedHierarchyId;
updateFonts(true);
}
@@ -188,6 +228,7 @@ QJsonValue HierarchyEntry::toJupyterJson()
cantorMetadata.insert(QLatin1String("level"), (int)m_depth);
cantorMetadata.insert(QLatin1String("level-number"), m_hierarchyNumber);
+ cantorMetadata.insert(QLatin1String("hierarchy-id"), m_hierarchyId);
// Don't store subentriesMainElem, because actually too complex
// Maybe this is a place for future work
@@ -239,6 +280,7 @@ QDomElement HierarchyEntry::toXml(QDomDocument& doc, KZip* archive)
el.setAttribute(QLatin1String("level"), (int)m_depth);
el.setAttribute(QLatin1String("level-number"), m_hierarchyNumber);
+ el.setAttribute(QLatin1String("hierarchy-id"), m_hierarchyId);
return el;
}
@@ -259,9 +301,7 @@ QString HierarchyEntry::toPlain(const QString& commandSep, const QString& commen
bool HierarchyEntry::evaluate(EvaluationOption evalOp)
{
- Q_EMIT hierarhyEntryNameChange(text(), hierarchyText(), ((int)m_depth)-1);
evaluateNext(evalOp);
-
return true;
}
@@ -337,6 +377,16 @@ HierarchyEntry::HierarchyLevel HierarchyEntry::level() const
return m_depth;
}
+const QString& HierarchyEntry::hierarchyId() const
+{
+ return m_hierarchyId;
+}
+
+void HierarchyEntry::regenerateHierarchyId()
+{
+ m_hierarchyId = QUuid::createUuid().toString(QUuid::WithoutBraces);
+}
+
void HierarchyEntry::setLevel(HierarchyEntry::HierarchyLevel depth)
{
m_depth = depth;
@@ -417,32 +467,40 @@ void HierarchyEntry::recalculateControlGeometry()
void HierarchyEntry::startDrag(QPointF grabPos)
{
// We need reset entry cursor manually, because otherwise the entry cursor will be visible on draggable item
+ if (worksheet()->expandHierarchyForStructureChange(this))
+ {
+ worksheet()->updateHierarchyLayout();
+ worksheet()->updateLayout();
+ }
+
worksheet()->resetEntryCursor();
- QDrag* drag = new QDrag(worksheetView());
+ auto* drag = new QDrag(worksheetView());
const qreal scale = worksheet()->renderer()->scale();
+ const QRectF hierarchyBound(boundingRect().x(), boundingRect().y(), boundingRect().width(), m_controlElement.boundingRect().height());
+ const QSizeF hierarchyZoneSize(size().width(), m_controlElement.boundingRect().height());
- QRectF hierarchyBound(boundingRect().x(), boundingRect().y(), boundingRect().width(), m_controlElement.boundingRect().height());
- QSizeF hierarchyZoneSize(size().width(), m_controlElement.boundingRect().height());
-
- QPixmap pixmap((hierarchyZoneSize*scale).toSize());
+ QPixmap pixmap((hierarchyZoneSize * scale).toSize());
pixmap.fill(QColor(255, 255, 255, 0));
QPainter painter(&pixmap);
+
const QRectF sceneRect = mapRectToScene(hierarchyBound);
worksheet()->render(&painter, pixmap.rect(), sceneRect);
+
painter.end();
+ const QBitmap mask = pixmap.createMaskFromColor(QColor(255, 255, 255), Qt::MaskInColor);
- QBitmap mask = pixmap.createMaskFromColor(QColor(255, 255, 255), Qt::MaskInColor);
pixmap.setMask(mask);
-
drag->setPixmap(pixmap);
- if (grabPos.isNull()) {
+
+ if (grabPos.isNull())
+ {
const QPointF scenePos = worksheetView()->sceneCursorPos();
drag->setHotSpot((mapFromScene(scenePos) * scale).toPoint());
- } else {
- drag->setHotSpot((grabPos * scale).toPoint());
}
+ else
+ drag->setHotSpot((grabPos * scale).toPoint());
drag->setMimeData(new QMimeData());
worksheet()->startDragWithHierarchy(this, drag, hierarchyZoneSize);
@@ -527,25 +585,30 @@ void HierarchyEntry::updateFonts(bool force)
}
}
-
void HierarchyEntry::handleControlElementDoubleClick()
{
- qDebug() << "HierarchyEntry::handleControlElementDoubleClick";
if (m_controlElement.isCollapsed)
{
- worksheet()->insertSubentriesForHierarchy(this, m_hidedSubentries);
- m_controlElement.isCollapsed = false;
+ WorksheetEntry* hiddenSubentries = takeHiddenSubentries();
+
+ if (hiddenSubentries)
+ worksheet()->insertSubentriesForHierarchy(this, hiddenSubentries);
}
else
{
- m_hidedSubentries = worksheet()->cutSubentriesForHierarchy(this);
- m_controlElement.isCollapsed = true;
+ WorksheetEntry* hiddenSubentries = worksheet()->cutSubentriesForHierarchy(this);
+
+ if (hiddenSubentries)
+ {
+ m_hidedSubentries = hiddenSubentries;
+ m_controlElement.isCollapsed = true;
+ }
}
m_controlElement.update();
- worksheet()->updateLayout();
worksheet()->updateHierarchyLayout();
+ worksheet()->updateLayout();
}
void HierarchyEntry::updateAfterSettingsChanges()
diff --git a/src/hierarchyentry.h b/src/hierarchyentry.h
index e263aea0..57047070 100644
--- a/src/hierarchyentry.h
+++ b/src/hierarchyentry.h
@@ -39,8 +39,14 @@ class HierarchyEntry : public WorksheetEntry
enum {Type = UserType + 9};
int type() const override;
+ bool hasHiddenSubentries() const;
+ WorksheetEntry* hiddenSubentries() const;
+ WorksheetEntry* takeHiddenSubentries();
+
QString text() const;
QString hierarchyText() const;
+ const QString& hierarchyId() const;
+ void regenerateHierarchyId();
HierarchyLevel level() const;
void setLevel(HierarchyLevel);
@@ -76,9 +82,6 @@ class HierarchyEntry : public WorksheetEntry
void startDrag(QPointF grabPos = QPointF()) override;
- Q_SIGNALS:
- void hierarhyEntryNameChange(QString name, QString searchName, int depth);
-
public Q_SLOTS:
bool evaluate(WorksheetEntry::EvaluationOption evalOp = FocusNext) override;
void updateEntry() override;
@@ -97,14 +100,15 @@ class HierarchyEntry : public WorksheetEntry
void updateFonts(bool force = false);
private:
-
WorksheetTextItem* m_hierarchyLevelItem;
WorksheetTextItem* m_textItem;
HierarchyLevel m_depth;
int m_hierarchyNumber;
+ QString m_hierarchyId;
QActionGroup* m_setLevelActionGroup;
QMenu* m_setLevelMenu;
WorksheetEntry* m_hidedSubentries;
+
};
#endif // HIEARARCHYENTRY_H
diff --git a/src/worksheet.cpp b/src/worksheet.cpp
index 0a60a30d..4aa7a198 100644
--- a/src/worksheet.cpp
+++ b/src/worksheet.cpp
@@ -14,19 +14,25 @@
#include "markdownentry.h"
#include "pagebreakentry.h"
#include "placeholderentry.h"
+#include "resultitem.h"
#include "settings.h"
#include "textentry.h"
#include "worksheetview.h"
-#include "lib/jupyterutils.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"
#include <config-cantor.h>
#include <QApplication>
#include <QBuffer>
+#include <QByteArray>
#include <QDrag>
#include <QGraphicsSceneMouseEvent>
#include <QJsonArray>
@@ -37,6 +43,8 @@
#include <QTimer>
#include <QActionGroup>
#include <QFile>
+#include <QScopedValueRollback>
+#include <QSet>
#include <KMessageBox>
#include <KActionCollection>
@@ -44,6 +52,7 @@
#include <KFontSizeAction>
#include <KToggleAction>
#include <KLocalizedString>
+
#include <KZip>
#include <KSyntaxHighlighting/Repository>
@@ -51,6 +60,14 @@
#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;
@@ -134,7 +151,12 @@ Worksheet::~Worksheet()
// This is necessary, because a SearchBar might access firstEntry()
// while the scene is deleted. Maybe there is a better solution to
// this problem, but I can't seem to find it.
+ if (m_firstEntry)
+ disconnect(m_firstEntry, &WorksheetEntry::aboutToBeDeleted, this, &Worksheet::invalidateFirstEntry);
+ if (m_lastEntry)
+ disconnect(m_lastEntry, &WorksheetEntry::aboutToBeDeleted, this, &Worksheet::invalidateLastEntry);
m_firstEntry = nullptr;
+ m_lastEntry = nullptr;
if (m_session)
{
@@ -245,1191 +267,1841 @@ void Worksheet::setViewSize(qreal w, qreal h, qreal s, bool forceUpdate)
void Worksheet::updateLayout()
{
+ QScopedValueRollback<bool> layoutGuard(m_layoutUpdateInProgress, true);
bool cursorRectVisible = false;
bool atEnd = worksheetView()->isAtEnd();
- if (currentTextItem()) {
- QRectF cursorRect = currentTextItem()->sceneCursorRect();
+
+ if (currentTextItem())
+ {
+ const QRectF cursorRect = currentTextItem()->sceneCursorRect();
cursorRectVisible = worksheetView()->isVisible(cursorRect);
}
m_maxPromptWidth = 0;
- if (Settings::useOldCantorEntriesIndent() == false)
+
+ if (!Settings::useOldCantorEntriesIndent())
{
for (auto* entry = firstEntry(); entry; entry = entry->next())
+ {
if (entry->type() == CommandEntry::Type)
m_maxPromptWidth = std::max(static_cast<CommandEntry*>(entry)->promptItemWidth(), m_maxPromptWidth);
else if (entry->type() == HierarchyEntry::Type)
m_maxPromptWidth = std::max(static_cast<HierarchyEntry*>(entry)->hierarchyItemWidth(), m_maxPromptWidth);
+ }
}
- const qreal w = m_viewWidth - LeftMargin - RightMargin;
+ // Hierarchy controls are hidden while printing.
+ const qreal hierarchyControlsWidth = m_isPrinting ? 0.0 : static_cast<qreal>(m_hierarchyMaxDepth) * (WorksheetEntry::ControlElementWidth + WorksheetEntry::ControlElementBorder);
+
+ const qreal w = m_viewWidth - LeftMargin - RightMargin - hierarchyControlsWidth;
+
qreal y = TopMargin;
const qreal x = LeftMargin;
+
for (auto* entry = firstEntry(); entry; entry = entry->next())
- y += entry->setGeometry(x, x+m_maxPromptWidth, y, w);
+ y += entry->setGeometry(x, x + m_maxPromptWidth, y, w);
updateHierarchyControlsLayout();
setSceneRect(QRectF(0, 0, m_viewWidth, y));
+
if (cursorRectVisible)
makeVisible(worksheetCursor());
else if (atEnd)
worksheetView()->scrollToEnd();
+
drawEntryCursor();
}
-void Worksheet::updateHierarchyLayout()
+void Worksheet::refreshTocStructure()
{
- QStringList names;
- QStringList searchStrings;
- QList<int> depths;
+ if (m_isClosing || m_isLoadingFromFile)
+ return;
- m_hierarchyMaxDepth = 0;
- std::vector<int> hierarchyNumbers;
- for (auto* entry = firstEntry(); entry; entry = entry->next())
+ m_tocRefreshScheduled = false;
+ m_tocNodeSnapshot = collectTocNodes();
+
+ if (!m_currentTocNodeId.isEmpty())
{
- if (entry->type() == HierarchyEntry::Type)
+ bool currentNodeStillExists = false;
+ for (const QVariant& nodeValue : m_tocNodeSnapshot)
{
- auto* hierarchEntry = static_cast<HierarchyEntry*>(entry);
- hierarchEntry->updateHierarchyLevel(hierarchyNumbers);
- m_hierarchyMaxDepth = std::max(m_hierarchyMaxDepth, hierarchyNumbers.size());
-
- names.append(hierarchEntry->text());
- searchStrings.append(hierarchEntry->hierarchyText());
- depths.append(static_cast<int>(hierarchyNumbers.size()) - 1);
+ if (nodeValue.toMap().value(QStringLiteral("id")).toString() == m_currentTocNodeId)
+ {
+ currentNodeStillExists = true;
+ break;
+ }
}
+
+ if (!currentNodeStillExists)
+ setCurrentTocNode(QString());
}
- Q_EMIT hierarchyChanged(names, searchStrings, depths);
+ Q_EMIT tocNodesChanged(m_tocNodeSnapshot);
}
-void Worksheet::updateHierarchyControlsLayout(WorksheetEntry* startEntry)
+void Worksheet::scheduleTocStructureRefresh()
+{
+ if (m_isClosing || m_isLoadingFromFile || m_tocRefreshScheduled)
+ return;
+
+ m_tocRefreshScheduled = true;
+ QTimer::singleShot(0, this, [this]()
+ {
+ if (!m_tocRefreshScheduled)
+ return;
+
+ refreshTocStructure();
+ });
+}
+
+void Worksheet::emitTocNodeSnapshot()
{
- if (startEntry == nullptr)
- startEntry = firstEntry();
+ if (m_isClosing)
+ return;
- // Update sizes of control elements for hierarchy entries
- std::vector<HierarchyEntry*> levelsEntries;
- const int numerationBegin = (int)HierarchyEntry::HierarchyLevel::Chapter;
- for (int i = numerationBegin; i < (int)HierarchyEntry::HierarchyLevel::EndValue; i++)
- levelsEntries.push_back(nullptr);
+ if (m_tocNodeSnapshot.isEmpty() && firstEntry())
+ m_tocNodeSnapshot = collectTocNodes();
+
+ Q_EMIT tocNodesChanged(m_tocNodeSnapshot);
+}
+
+QVariantList Worksheet::collectTocNodes()
+{
+ QVariantList nodes;
+ QVector<QString> hierarchyNodeIds;
+ QVector<int> hierarchyDepths;
- for (auto* entry = startEntry; entry; entry = entry->next())
+ visitLogicalEntries([&](WorksheetEntry* entry)
{
if (entry->type() == HierarchyEntry::Type)
{
- HierarchyEntry* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
- int idx = (int)hierarchyEntry->level() - numerationBegin;
- if (levelsEntries[idx] == nullptr)
+ 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)
{
- levelsEntries[idx] = hierarchyEntry;
+ hierarchyDepths.removeLast();
+ hierarchyNodeIds.removeLast();
}
- else
+
+ 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())
{
- for (int i = idx; i < (int)levelsEntries.size(); i++)
- if (levelsEntries[i] != nullptr)
- {
- bool haveSubelements = levelsEntries[i]->next() ? levelsEntries[i]->next() != entry : false;
- levelsEntries[i]->updateControlElementForHierarchy(hierarchyEntry->y() - WorksheetEntry::VerticalMargin, m_hierarchyMaxDepth, haveSubelements);
- levelsEntries[i] = nullptr;
- }
- levelsEntries[idx] = hierarchyEntry;
+ 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;
+ });
- if (lastEntry())
- for (int i = 0; i < (int)levelsEntries.size(); i++)
- if (levelsEntries[i] != nullptr)
- {
- bool haveSubelements = levelsEntries[i] != lastEntry();
- levelsEntries[i]->updateControlElementForHierarchy(lastEntry()->y() + lastEntry()->size().height() - WorksheetEntry::VerticalMargin, m_hierarchyMaxDepth, haveSubelements);
- levelsEntries[i] = nullptr;
- }
+ return nodes;
}
-std::vector<WorksheetEntry*> Worksheet::hierarchySubelements(HierarchyEntry* hierarchyEntry) const
+QString Worksheet::buildCommandNodeId(CommandEntry* entry)
{
- std::vector<WorksheetEntry*> subentries;
+ if (!entry)
+ return QString();
- Q_ASSERT(hierarchyEntry);
+ const QString& commandId = entry->commandId();
+ if (commandId.isEmpty())
+ return QString();
- 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;
+ return QStringLiteral("command:") + commandId;
}
-void Worksheet::updateEntrySize(WorksheetEntry* entry)
+QString Worksheet::buildPlotNodeId(const QString& commandId, const QString& resultId) const
{
- bool cursorRectVisible = false;
- bool atEnd = worksheetView()->isAtEnd();
- if (currentTextItem()) {
- QRectF cursorRect = currentTextItem()->sceneCursorRect();
- cursorRectVisible = worksheetView()->isVisible(cursorRect);
- }
+ if (commandId.isEmpty() || resultId.isEmpty())
+ return QString();
- 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();
+ return QStringLiteral("plot:%1:%2").arg(commandId, resultId);
+}
- // 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;
- }
- }
+bool Worksheet::parseCommandNodeId(const QString& nodeId, QString* commandId) const
+{
+ const QString prefix = QStringLiteral("command:");
+ if (!nodeId.startsWith(prefix))
+ return false;
- qreal y = entry->y() + entry->size().height();
- for (entry = entry->next(); entry; entry = entry->next()) {
- entry->setY(y);
- y += entry->size().height();
- }
+ const QString parsedCommandId = nodeId.mid(prefix.size());
+ if (parsedCommandId.isEmpty() || parsedCommandId.contains(QLatin1Char(':')))
+ return false;
- if (!m_isLoadingFromFile)
- updateHierarchyControlsLayout(entry);
+ if (commandId)
+ *commandId = parsedCommandId;
- setSceneRect(QRectF(0, 0, sceneRect().width(), y));
- if (cursorRectVisible)
- makeVisible(worksheetCursor());
- else if (atEnd)
- worksheetView()->scrollToEnd();
- drawEntryCursor();
+ return true;
}
-void Worksheet::setRequestedWidth(QGraphicsObject* object, qreal width)
+bool Worksheet::parsePlotNodeId(const QString& nodeId, QString* commandId, QString* resultId) const
{
- qreal oldWidth = m_itemWidths[object];
- m_itemWidths[object] = width;
-
- 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));
- }
-}
+ const QString prefix = QStringLiteral("plot:");
+ if (!nodeId.startsWith(prefix))
+ return false;
-void Worksheet::removeRequestedWidth(QGraphicsObject* object)
-{
- if (!m_itemWidths.contains(object))
- return;
+ const QStringList parts = nodeId.mid(prefix.size()).split(QLatin1Char(':'));
+ if (parts.size() != 2 || parts.at(0).isEmpty() || parts.at(1).isEmpty())
+ return false;
- qreal width = m_itemWidths[object];
- m_itemWidths.remove(object);
+ if (commandId)
+ *commandId = parts.at(0);
+ if (resultId)
+ *resultId = parts.at(1);
- 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::isEmpty()
+QString Worksheet::commandTocTitle() const
{
- return !m_firstEntry;
+ return i18n("Command");
}
-bool Worksheet::isLoadingFromFile()
+QString Worksheet::commandTocDisplayText(CommandEntry* entry) const
{
- return m_isLoadingFromFile;
+ auto* expression = entry ? entry->expression() : nullptr;
+ if (m_showExpressionIds && expression && expression->id() != -1)
+ return i18n("Command %1", expression->id());
+
+ return commandTocTitle();
}
-void Worksheet::makeVisible(WorksheetEntry* entry)
+QString Worksheet::plotTocTitle(Cantor::Result* result) const
{
- QRectF r = entry->boundingRect();
- r = entry->mapRectToScene(r);
- r.adjust(0, -10, 0, 10);
- worksheetView()->makeVisible(r);
+ if (result && !result->displayName().isEmpty())
+ return result->displayName();
+
+ return i18n("Plot");
}
-void Worksheet::makeVisible(const KWorksheetCursor& cursor)
+QString Worksheet::plotTocDisplayText(CommandEntry* entry, Cantor::Result* result, int plotOrdinal, int plotCount) const
{
- if(!cursor.cursor().isValid())
+ const QString title = plotTocTitle(result);
+ auto* expression = entry ? entry->expression() : nullptr;
+ if (m_showExpressionIds && expression && expression->id() != -1)
{
- if(cursor.entry())
- makeVisible(cursor.entry());
- return;
+ if (plotCount > 1)
+ return i18n("%1 %2.%3", title, expression->id(), plotOrdinal);
+
+ return i18n("%1 %2", title, expression->id());
}
- 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);
+
+ if (plotCount > 1)
+ return i18n("%1 %2", title, plotOrdinal);
+
+ return title;
}
-void Worksheet::makeVisible(const WorksheetCursor& cursor)
+bool Worksheet::isPlotResult(Cantor::Result* 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);
+ 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;
+
+ return result->type() == Cantor::PdfResult::Type && dynamic_cast<Cantor::PdfResult*>(result);
}
-WorksheetView* Worksheet::worksheetView()
+bool Worksheet::visitLogicalEntries(WorksheetEntry* first, const std::function<bool(WorksheetEntry*)>& visitor)
{
- return static_cast<WorksheetView*>(views().first());
+ 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;
}
-void Worksheet::setModified()
+bool Worksheet::visitLogicalEntries(const std::function<bool(WorksheetEntry*)>& visitor)
{
- if (!m_isClosing && !m_isLoadingFromFile)
- Q_EMIT modified();
+ return visitLogicalEntries(firstEntry(), visitor);
}
-KWorksheetCursor Worksheet::worksheetCursor()
+bool Worksheet::findHierarchyEntryById(WorksheetEntry* first, const QString& hierarchyId, QVector<HierarchyEntry*> collapsedAncestors, HierarchySearchResult& result)
{
- auto* entry = currentEntry();
- auto* item = currentTextItem();
+ for (auto* entry = first; entry; entry = entry->next())
+ {
+ if (entry->type() != HierarchyEntry::Type)
+ continue;
- if (!entry || !item)
- return KWorksheetCursor();
- return KWorksheetCursor(entry, item, item->view()->cursorPosition());
-}
+ auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
-void Worksheet::setWorksheetCursor(const WorksheetCursor& cursor)
-{
- if (!cursor.isValid() || !cursor.textItem())
- return;
+ if (hierarchyEntry->hierarchyId() == hierarchyId)
+ {
+ result.entry = hierarchyEntry;
+ result.collapsedAncestors = collapsedAncestors;
+ return true;
+ }
- if (m_lastFocusedTextItem)
- m_lastFocusedTextItem->clearSelection();
- if (m_legacylastFocusedTextItem)
- m_legacylastFocusedTextItem->clearSelection();
+ if (auto* hiddenEntry = hierarchyEntry->hiddenSubentries())
+ {
+ auto childAncestors = collapsedAncestors;
+ childAncestors.append(hierarchyEntry);
- m_legacylastFocusedTextItem = cursor.textItem();
- m_lastFocusedTextItem = nullptr;
+ if (findHierarchyEntryById(hiddenEntry, hierarchyId, childAncestors, result))
+ return true;
+ }
+ }
- cursor.textItem()->setTextCursor(cursor.textCursor());
+ return false;
}
-void Worksheet::setWorksheetCursor(const KWorksheetCursor& cursor)
+Worksheet::HierarchySearchResult Worksheet::findHierarchyEntryById(const QString& hierarchyId)
{
- if(!cursor.isValid())
- return;
-
- if (m_lastFocusedTextItem)
- m_lastFocusedTextItem->clearSelection();
- if (m_legacylastFocusedTextItem)
- m_legacylastFocusedTextItem->clearSelection();
+ HierarchySearchResult result;
- m_lastFocusedTextItem = cursor.textItem();
- m_legacylastFocusedTextItem = nullptr;
+ if (hierarchyId.isEmpty())
+ return result;
- cursor.textItem()->view()->setSelection(cursor.foundRange());
+ findHierarchyEntryById(firstEntry(), hierarchyId, {}, result);
+ return result;
}
-
-WorksheetEntry* Worksheet::currentEntry()
+bool Worksheet::findCommandEntryById(WorksheetEntry* first, const QString& commandId, QVector<HierarchyEntry*> collapsedAncestors, CommandSearchResult& result)
{
- // Entry cursor activate
- if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry)
- return nullptr;
+ 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;
+ }
+ }
- 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;
+ 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 entry;
}
- return nullptr;
-}
-WorksheetEntry* Worksheet::firstEntry()
-{
- return m_firstEntry;
+ return false;
}
-WorksheetEntry* Worksheet::lastEntry()
+Worksheet::CommandSearchResult Worksheet::findCommandEntryById(const QString& commandId)
{
- return m_lastEntry;
-}
+ CommandSearchResult result;
-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);
-}
+ if (commandId.isEmpty())
+ return result;
-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);
+ findCommandEntryById(firstEntry(), commandId, {}, result);
+ return result;
}
-void Worksheet::invalidateFirstEntry()
+bool Worksheet::expandHierarchyAncestors(const QVector<HierarchyEntry*>& ancestors)
{
- if (m_firstEntry)
- setFirstEntry(m_firstEntry->next());
-}
+ bool expanded = false;
-void Worksheet::invalidateLastEntry()
-{
- if (m_lastEntry)
- setLastEntry(m_lastEntry->previous());
-}
+ for (auto* ancestor : ancestors)
+ {
+ if (!ancestor || !ancestor->hasHiddenSubentries())
+ 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;
-}
+ WorksheetEntry* hiddenSubentries = ancestor->takeHiddenSubentries();
-WorksheetEntry* Worksheet::entryAt(QPointF p)
-{
- return entryAt(p.x(), p.y());
-}
+ if (!hiddenSubentries)
+ continue;
-void Worksheet::focusEntry(WorksheetEntry* entry)
-{
- if (!entry)
- return;
- entry->focusEntry();
- resetEntryCursor();
- //bool rt = entry->acceptRichText();
- //setActionsEnabled(rt);
- //setAcceptRichText(rt);
- //ensureCursorVisible();
+ insertSubentriesForHierarchy(ancestor, hiddenSubentries);
+ expanded = true;
+ }
+
+ return expanded;
}
-void Worksheet::startDrag(WorksheetEntry* entry, QDrag* drag)
+void Worksheet::updateHierarchyLayout()
{
- if (m_readOnly)
- return;
+ QSet<QString> usedHierarchyIds;
+ QSet<QString> usedCommandIds;
+ QSet<QString> usedResultIds;
- resetEntryCursor();
- m_dragEntry = entry;
- auto* prev = entry->previous();
- auto* next = entry->next();
- m_placeholderEntry = new PlaceHolderEntry(this, entry->size());
- 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_dragEntry->hide();
- Qt::DropAction action = drag->exec();
+ m_hierarchyMaxDepth = 0;
+ std::vector<int> hierarchyNumbers;
- qDebug() << action;
- if (action == Qt::MoveAction && m_placeholderEntry) {
- qDebug() << "insert in new position";
- prev = m_placeholderEntry->previous();
- next = m_placeholderEntry->next();
- }
- m_dragEntry->setPrevious(prev);
- m_dragEntry->setNext(next);
- if (prev)
- prev->setNext(m_dragEntry);
- else
- setFirstEntry(m_dragEntry);
- if (next)
- next->setPrevious(m_dragEntry);
- else
- setLastEntry(m_dragEntry);
- m_dragEntry->show();
- if (m_dragEntry->type() == HierarchyEntry::Type)
- updateHierarchyLayout();
- m_dragEntry->focusEntry();
- const QPointF scenePos = worksheetView()->sceneCursorPos();
- if (entryAt(scenePos) != m_dragEntry)
- m_dragEntry->hideActionBar();
- updateLayout();
- if (m_placeholderEntry) {
- m_placeholderEntry->setPrevious(nullptr);
- m_placeholderEntry->setNext(nullptr);
- m_placeholderEntry->hide();
- m_placeholderEntry->deleteLater();
- m_placeholderEntry = nullptr;
- }
- m_dragEntry = nullptr;
+ 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 Worksheet::startDragWithHierarchy(HierarchyEntry* entry, QDrag* drag, QSizeF responsibleZoneSize)
+void Worksheet::updateHierarchyControlsLayout(WorksheetEntry* startEntry)
{
- if (m_readOnly)
- return;
+ Q_UNUSED(startEntry);
- resetEntryCursor();
- m_dragEntry = entry;
- auto* prev = entry->previous();
- m_hierarchySubentriesDrag = hierarchySubelements(entry);
+ std::vector<HierarchyEntry*> levelEntries;
+ const int numerationBegin = static_cast<int>(HierarchyEntry::HierarchyLevel::Chapter);
+ const int numerationEnd = static_cast<int>(HierarchyEntry::HierarchyLevel::EndValue);
- WorksheetEntry* next;
- if (m_hierarchySubentriesDrag.size() != 0)
- next = m_hierarchySubentriesDrag.back()->next();
- else
- next = entry->next();
+ for (int i = numerationBegin; i < numerationEnd; ++i)
+ levelEntries.push_back(nullptr);
- m_placeholderEntry = new PlaceHolderEntry(this, responsibleZoneSize);
- m_hierarchyDragSize = responsibleZoneSize;
- 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);
+ WorksheetEntry* lastRealEntry = nullptr;
- m_dragEntry->hide();
- for(auto* subEntry : m_hierarchySubentriesDrag)
- subEntry->hide();
+ for (auto* entry = firstEntry(); entry; entry = entry->next())
+ {
+ if (entry->type() == PlaceHolderEntry::Type || entry->aboutToBeRemoved())
+ continue;
- Qt::DropAction action = drag->exec();
+ lastRealEntry = entry;
- qDebug() << action;
- if (action == Qt::MoveAction && m_placeholderEntry) {
- qDebug() << "insert in new position";
- prev = m_placeholderEntry->previous();
- next = m_placeholderEntry->next();
- }
- m_dragEntry->setPrevious(prev);
+ if (entry->type() != HierarchyEntry::Type)
+ continue;
- WorksheetEntry* lastDraggingEntry;
- if (m_hierarchySubentriesDrag.size() != 0)
- lastDraggingEntry = m_hierarchySubentriesDrag.back();
- else
- lastDraggingEntry = entry;
+ auto* hierarchyEntry = static_cast<HierarchyEntry*>(entry);
- lastDraggingEntry->setNext(next);
+ const int index = static_cast<int>(hierarchyEntry->level()) - numerationBegin;
- if (prev)
- prev->setNext(m_dragEntry);
- else
- setFirstEntry(m_dragEntry);
+ if (index < 0 || index >= static_cast<int>(levelEntries.size()))
+ continue;
- if (next)
- next->setPrevious(lastDraggingEntry);
- else
- setLastEntry(lastDraggingEntry);
+ if (!levelEntries[index])
+ {
+ levelEntries[index] = hierarchyEntry;
+ continue;
+ }
- m_dragEntry->show();
- for(auto* subEntry : m_hierarchySubentriesDrag)
- subEntry->show();
+ // Close previous controls at this level and below.
+ for (int i = index; i < static_cast<int>(levelEntries.size()); ++i)
+ {
+ auto* openEntry = levelEntries[i];
- updateHierarchyLayout();
- m_dragEntry->focusEntry();
- const QPointF scenePos = worksheetView()->sceneCursorPos();
- if (entryAt(scenePos) != m_dragEntry)
- m_dragEntry->hideActionBar();
- updateLayout();
+ 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);
- if (m_placeholderEntry) {
- m_placeholderEntry->setPrevious(nullptr);
- m_placeholderEntry->setNext(nullptr);
- m_placeholderEntry->hide();
- m_placeholderEntry->deleteLater();
- m_placeholderEntry = nullptr;
+ levelEntries[i] = nullptr;
+ }
+
+ levelEntries[index] = hierarchyEntry;
}
- m_dragEntry = nullptr;
- m_hierarchySubentriesDrag.clear();
-}
-void Worksheet::evaluate()
-{
- qDebug()<<"evaluate worksheet";
- // login if not done yet
- if (!m_readOnly && m_session && m_session->status() == Cantor::Session::Disable)
- loginToSession();
+ if (!lastRealEntry)
+ return;
- // evaluate the worksheet if the login was successful
- if (m_session && m_session->status() == Cantor::Session::Done) {
- firstEntry()->evaluate(WorksheetEntry::EvaluateNext);
- setModified();
+ 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);
}
}
-void Worksheet::evaluateCurrentEntry()
+std::vector<WorksheetEntry*> Worksheet::hierarchySubelements(HierarchyEntry* hierarchyEntry) const
{
- // login if not done yet
- if (!m_readOnly && m_session && m_session->status() == Cantor::Session::Disable)
- loginToSession();
+ std::vector<WorksheetEntry*> subentries;
- // evaluate the current entry if the login was successful
- if (!m_session)
- return;
+ Q_ASSERT(hierarchyEntry);
- // 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)
+ bool subentriesEnd = false;
+ const int level = (int)hierarchyEntry->level();
+ for (auto* entry = hierarchyEntry->next(); entry && !subentriesEnd; entry = entry->next())
{
- if(auto* entry = currentEntry())
- entry->evaluateCurrentItem();
+ 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;
}
-bool Worksheet::completionEnabled()
+void Worksheet::updateCurrentHierarchy(WorksheetEntry* entry)
{
- return m_completionEnabled;
+ QString nodeId;
+
+ if (entry && entry->type() == CommandEntry::Type)
+ nodeId = buildCommandNodeId(static_cast<CommandEntry*>(entry));
+ else
+ nodeId = hierarchyIdForEntry(entry);
+
+ setCurrentTocNode(nodeId);
}
-void Worksheet::showCompletion()
+QString Worksheet::hierarchyIdForEntry(WorksheetEntry* entry) const
{
- auto* current = currentEntry();
- if (current)
- current->showCompletion();
+ for (auto* current = entry; current; current = current->previous())
+ {
+ if (current->type() == HierarchyEntry::Type)
+ return static_cast<HierarchyEntry*>(current)->hierarchyId();
+ }
+
+ return QString();
}
-WorksheetEntry* Worksheet::appendEntry(const int type, bool focus)
+void Worksheet::setCurrentTocNode(const QString& nodeId)
{
- auto* entry = WorksheetEntry::create(type, this);
+ if (m_currentTocNodeId == nodeId)
+ return;
- if (entry)
+ m_currentTocNodeId = nodeId;
+
+ Q_EMIT currentTocNodeChanged(nodeId);
+}
+
+void Worksheet::normalizeDraggedHierarchyLevels(HierarchyEntry* rootEntry, WorksheetEntry* previousEntry, const std::vector<WorksheetEntry*>& subentries)
+{
+ if (!rootEntry)
+ return;
+
+ HierarchyEntry* previousHierarchyEntry = nullptr;
+
+ for (auto* entry = previousEntry; entry; entry = entry->previous())
{
- qDebug() << "Entry Appended";
- entry->setPrevious(lastEntry());
- if (lastEntry())
- lastEntry()->setNext(entry);
- if (!firstEntry())
- setFirstEntry(entry);
- setLastEntry(entry);
- if (!m_isLoadingFromFile)
- {
- if (type == HierarchyEntry::Type)
- updateHierarchyLayout();
- updateLayout();
- if (focus)
- {
- makeVisible(entry);
- focusEntry(entry);
- }
- setModified();
- }
+ if (entry->type() != HierarchyEntry::Type)
+ continue;
+
+ previousHierarchyEntry = static_cast<HierarchyEntry*>(entry);
+ break;
}
- return entry;
-}
-WorksheetEntry* Worksheet::appendCommandEntry()
-{
- return appendEntry(CommandEntry::Type);
-}
+ const int minimumLevel = static_cast<int>(HierarchyEntry::HierarchyLevel::Chapter);
+ const int maximumLevel = static_cast<int>(HierarchyEntry::HierarchyLevel::Subparagraph);
-WorksheetEntry* Worksheet::appendTextEntry()
-{
- return appendEntry(TextEntry::Type);
-}
+ int maximumAllowedRootLevel = minimumLevel;
-WorksheetEntry* Worksheet::appendMarkdownEntry()
-{
- return appendEntry(MarkdownEntry::Type);
-}
+ if (previousHierarchyEntry)
+ maximumAllowedRootLevel = qMin(maximumLevel, static_cast<int>(previousHierarchyEntry->level()) + 1);
-WorksheetEntry* Worksheet::appendPageBreakEntry()
-{
- return appendEntry(PageBreakEntry::Type);
-}
+ const int oldRootLevel = static_cast<int>(rootEntry->level());
+ if (oldRootLevel <= maximumAllowedRootLevel)
+ return;
-WorksheetEntry* Worksheet::appendImageEntry()
-{
- return appendEntry(ImageEntry::Type);
-}
+ const int levelOffset = maximumAllowedRootLevel - oldRootLevel;
-WorksheetEntry* Worksheet::appendLatexEntry()
-{
- return appendEntry(LatexEntry::Type);
-}
+ const auto shiftLevel = [levelOffset, minimumLevel, maximumLevel](HierarchyEntry* hierarchyEntry)
+ {
+ if (!hierarchyEntry)
+ return;
-void Worksheet::appendCommandEntry(const QString& text)
-{
- auto* entry = lastEntry();
- if(!entry->isEmpty())
- entry = appendCommandEntry();
+ const int newLevel = qBound(minimumLevel, static_cast<int>(hierarchyEntry->level()) + levelOffset, maximumLevel);
- if (entry)
+ hierarchyEntry->setLevel(static_cast<HierarchyEntry::HierarchyLevel>(newLevel));
+ };
+
+ shiftLevel(rootEntry);
+
+ for (auto* entry : subentries)
{
- focusEntry(entry);
- entry->setContent(text);
- evaluateCurrentEntry();
- }
-}
+ if (entry->type() != HierarchyEntry::Type)
+ continue;
-WorksheetEntry* Worksheet::appendHorizontalRuleEntry()
-{
- return appendEntry(HorizontalRuleEntry::Type);
+ shiftLevel(static_cast<HierarchyEntry*>(entry));
+ }
}
-WorksheetEntry* Worksheet::appendHierarchyEntry()
+void Worksheet::updateCurrentHierarchyFromView(const QRectF& viewRect)
{
- return appendEntry(HierarchyEntry::Type);
-}
+ if (m_hierarchyTrackingSource != HierarchyTrackingSource::Viewport || m_layoutUpdateInProgress || m_isLoadingFromFile || viewRect.isEmpty())
+ return;
-WorksheetEntry* Worksheet::insertEntry(const int type, WorksheetEntry* current)
-{
- if (!current)
- current = currentEntry();
+ const qreal activationOffset = qMin<qreal>(48.0, viewRect.height() * 0.15);
- if (!current)
- return appendEntry(type);
+ const qreal activationY = viewRect.top() + activationOffset;
- auto* next = current->next();
- WorksheetEntry* entry = nullptr;
+ WorksheetEntry* activeEntry = nullptr;
- if (!next || next->type() != type || !next->isEmpty())
+ for (auto* entry = firstEntry(); entry; entry = entry->next())
{
- entry = WorksheetEntry::create(type, this);
- entry->setPrevious(current);
- entry->setNext(next);
- current->setNext(entry);
- if (next)
- next->setPrevious(entry);
- else
- setLastEntry(entry);
- if (type == HierarchyEntry::Type)
- updateHierarchyLayout();
- updateLayout();
- setModified();
- } else {
- entry = next;
- }
-
- focusEntry(entry);
- makeVisible(entry);
-
- return entry;
-}
+ if (!entry->isVisible())
+ continue;
-WorksheetEntry* Worksheet::insertTextEntry(WorksheetEntry* current)
-{
- return insertEntry(TextEntry::Type, current);
-}
+ if (entry->scenePos().y() > activationY)
+ break;
-WorksheetEntry* Worksheet::insertMarkdownEntry(WorksheetEntry* current)
-{
- return insertEntry(MarkdownEntry::Type, current);
-}
+ activeEntry = entry;
+ }
-WorksheetEntry* Worksheet::insertCommandEntry(WorksheetEntry* current)
-{
- return insertEntry(CommandEntry::Type, current);
+ updateCurrentHierarchy(activeEntry);
}
-WorksheetEntry* Worksheet::insertImageEntry(WorksheetEntry* current)
+void Worksheet::updateCurrentHierarchyFromEntry(WorksheetEntry* entry)
{
- auto* entry = insertEntry(ImageEntry::Type, current);
- auto* imageEntry = static_cast<ImageEntry*>(entry);
- QTimer::singleShot(0, this, [=] () {imageEntry->startConfigDialog();});
- return entry;
-}
+ m_hierarchyTrackingSource = HierarchyTrackingSource::FocusedEntry;
-WorksheetEntry* Worksheet::insertPageBreakEntry(WorksheetEntry* current)
-{
- return insertEntry(PageBreakEntry::Type, current);
+ updateCurrentHierarchy(entry);
}
-WorksheetEntry* Worksheet::insertLatexEntry(WorksheetEntry* current)
+void Worksheet::followHierarchyFromView()
{
- return insertEntry(LatexEntry::Type, current);
-}
+ m_hierarchyTrackingSource = HierarchyTrackingSource::Viewport;
-WorksheetEntry* Worksheet::insertHorizontalRuleEntry(WorksheetEntry* current)
-{
- return insertEntry(HorizontalRuleEntry::Type, current);
-}
+ // Defer until viewRect() reflects the final scroll position.
+ QTimer::singleShot(0, this, [this]() {
+ if (m_hierarchyTrackingSource != HierarchyTrackingSource::Viewport)
+ return;
-WorksheetEntry* Worksheet::insertHierarchyEntry(WorksheetEntry* current)
-{
- return insertEntry(HierarchyEntry::Type, current);
+ updateCurrentHierarchyFromView(worksheetView()->viewRect());
+ });
}
-WorksheetEntry* Worksheet::insertEntryBefore(int type, WorksheetEntry* current)
+void Worksheet::updateEntrySize(WorksheetEntry* entry)
{
- if (!current)
- current = currentEntry();
+ QScopedValueRollback<bool> layoutGuard(m_layoutUpdateInProgress, true);
+ bool cursorRectVisible = false;
+ bool atEnd = worksheetView()->isAtEnd();
+ if (currentTextItem()) {
+ QRectF cursorRect = currentTextItem()->sceneCursorRect();
+ cursorRectVisible = worksheetView()->isVisible(cursorRect);
+ }
- if (!current)
- return nullptr;
+ 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();
- auto* prev = current->previous();
- WorksheetEntry* entry = nullptr;
+ // 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;
+ }
+ }
- 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);
- if (type == HierarchyEntry::Type)
- updateHierarchyLayout();
- updateLayout();
- setModified();
+ qreal y = entry->y() + entry->size().height();
+ for (entry = entry->next(); entry; entry = entry->next()) {
+ entry->setY(y);
+ y += entry->size().height();
}
- else
- entry = prev;
- focusEntry(entry);
- return entry;
-}
+ if (!m_isLoadingFromFile)
+ updateHierarchyControlsLayout(entry);
-WorksheetEntry* Worksheet::insertTextEntryBefore(WorksheetEntry* current)
-{
- return insertEntryBefore(TextEntry::Type, current);
+ setSceneRect(QRectF(0, 0, sceneRect().width(), y));
+ if (cursorRectVisible)
+ makeVisible(worksheetCursor());
+ else if (atEnd)
+ worksheetView()->scrollToEnd();
+ drawEntryCursor();
}
-WorksheetEntry* Worksheet::insertMarkdownEntryBefore(WorksheetEntry* current)
+void Worksheet::setRequestedWidth(QGraphicsObject* object, qreal width)
{
- return insertEntryBefore(MarkdownEntry::Type, current);
-}
+ qreal oldWidth = m_itemWidths[object];
+ m_itemWidths[object] = width;
-WorksheetEntry* Worksheet::insertCommandEntryBefore(WorksheetEntry* current)
-{
- return insertEntryBefore(CommandEntry::Type, current);
+ 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));
+ }
}
-WorksheetEntry* Worksheet::insertPageBreakEntryBefore(WorksheetEntry* current)
+void Worksheet::removeRequestedWidth(QGraphicsObject* object)
{
- return insertEntryBefore(PageBreakEntry::Type, current);
-}
+ if (!m_itemWidths.contains(object))
+ return;
-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;
+ qreal width = m_itemWidths[object];
+ m_itemWidths.remove(object);
+
+ 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));
+ }
}
-WorksheetEntry* Worksheet::insertLatexEntryBefore(WorksheetEntry* current)
+bool Worksheet::isEmpty()
{
- return insertEntryBefore(LatexEntry::Type, current);
+ return !m_firstEntry;
}
-WorksheetEntry* Worksheet::insertHorizontalRuleEntryBefore(WorksheetEntry* current)
+bool Worksheet::isLoadingFromFile()
{
- return insertEntryBefore(HorizontalRuleEntry::Type, current);
+ return m_isLoadingFromFile;
}
-WorksheetEntry* Worksheet::insertHierarchyEntryBefore(WorksheetEntry* current)
+void Worksheet::makeVisible(WorksheetEntry* entry)
{
- return insertEntryBefore(HierarchyEntry::Type, current);
+ QRectF r = entry->boundingRect();
+ r = entry->mapRectToScene(r);
+ r.adjust(0, -10, 0, 10);
+ worksheetView()->makeVisible(r);
}
-void Worksheet::interrupt()
+void Worksheet::makeVisible(const KWorksheetCursor& cursor)
{
- if (m_session->status() == Cantor::Session::Running)
+ if(!cursor.cursor().isValid())
{
- m_session->interrupt();
- Q_EMIT updatePrompt();
+ if(cursor.entry())
+ makeVisible(cursor.entry());
+ return;
}
+ 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);
}
-void Worksheet::interruptCurrentEntryEvaluation()
+void Worksheet::makeVisible(const WorksheetCursor& cursor)
{
- currentEntry()->interruptEvaluation();
+ 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);
}
+WorksheetView* Worksheet::worksheetView()
+{
+ return static_cast<WorksheetView*>(views().first());
+}
-bool Worksheet::variableHighlightingEnabled() const
+void Worksheet::setModified()
{
- return m_variableHighlightingEnabled;
+ if (!m_isClosing && !m_isLoadingFromFile)
+ Q_EMIT modified();
}
-void Worksheet::setVariableHighlightingEnabled(bool enabled)
+KWorksheetCursor Worksheet::worksheetCursor()
{
- if (m_variableHighlightingEnabled == enabled)
- {
- return;
- }
- m_variableHighlightingEnabled = enabled;
+ auto* entry = currentEntry();
+ auto* item = currentTextItem();
- for (auto* entry = firstEntry(); entry; entry = entry->next())
- {
- if (entry->type() == CommandEntry::Type)
- static_cast<CommandEntry*>(entry)->setVariableHighlightingEnabled(enabled);
- }
+ if (!entry || !item)
+ return KWorksheetCursor();
+ return KWorksheetCursor(entry, item, item->view()->cursorPosition());
}
-void Worksheet::enableCompletion(bool enable)
+void Worksheet::setWorksheetCursor(const WorksheetCursor& cursor)
{
- m_completionEnabled=enable;
+ 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());
}
-Cantor::Session* Worksheet::session() const
+void Worksheet::setWorksheetCursor(const KWorksheetCursor& cursor)
{
- return m_session;
+ 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());
}
-bool Worksheet::isRunning()
+
+WorksheetEntry* Worksheet::currentEntry()
{
- return m_session && m_session->status()==Cantor::Session::Running;
+ // Entry cursor activate
+ if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry)
+ return nullptr;
+
+ 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;
+ }
+ return nullptr;
}
-bool Worksheet::isReadOnly()
+WorksheetEntry* Worksheet::firstEntry()
{
- return m_readOnly;
+ return m_firstEntry;
}
-bool Worksheet::showExpressionIds()
+WorksheetEntry* Worksheet::lastEntry()
{
- return m_showExpressionIds;
+ return m_lastEntry;
}
-bool Worksheet::animationsEnabled()
+void Worksheet::setFirstEntry(WorksheetEntry* entry)
{
- return m_animationsEnabled;
+ 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);
}
-void Worksheet::enableAnimations(bool enable)
+void Worksheet::setLastEntry(WorksheetEntry* entry)
{
- m_animationsEnabled = enable;
+ 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);
}
-bool Worksheet::embeddedMathEnabled()
+void Worksheet::invalidateFirstEntry()
{
- return m_embeddedMathEnabled && m_mathRenderer.mathRenderAvailable();
+ if (m_firstEntry)
+ setFirstEntry(m_firstEntry->next());
}
-void Worksheet::enableEmbeddedMath(bool enable)
+void Worksheet::invalidateLastEntry()
{
- m_embeddedMathEnabled = enable;
+ if (m_lastEntry)
+ setLastEntry(m_lastEntry->previous());
}
-void Worksheet::enableExpressionNumbering(bool enable)
+WorksheetEntry* Worksheet::entryAt(qreal x, qreal y)
{
- m_showExpressionIds=enable;
- Q_EMIT updatePrompt();
- if (views().size() != 0)
- updateLayout();
+ 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;
}
-QDomDocument Worksheet::toXML(KZip* archive)
+WorksheetEntry* Worksheet::entryAt(QPointF p)
{
- 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);
+ return entryAt(p.x(), p.y());
+}
- for( auto* entry = firstEntry(); entry; entry = entry->next())
- {
- QDomElement el = entry->toXml(doc, archive);
- root.appendChild( el );
- }
- return doc;
+void Worksheet::focusEntry(WorksheetEntry* entry)
+{
+ if (!entry)
+ return;
+ entry->focusEntry();
+ resetEntryCursor();
+ //bool rt = entry->acceptRichText();
+ //setActionsEnabled(rt);
+ //setAcceptRichText(rt);
+ //ensureCursorVisible();
}
-QJsonDocument Worksheet::toJupyterJson()
+void Worksheet::startDrag(WorksheetEntry* entry, QDrag* drag)
{
- QJsonDocument doc;
- QJsonObject root;
+ if (m_readOnly || !entry || !drag)
+ return;
- QJsonObject metadata(m_jupyterMetadata ? *m_jupyterMetadata : QJsonObject());
+ resetEntryCursor();
- 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);
+ m_dragEntry = entry;
- root.insert(QLatin1String("metadata"), metadata);
+ WorksheetEntry* originalPrevious = entry->previous();
+ WorksheetEntry* originalNext = entry->next();
+ WorksheetEntry* previous = originalPrevious;
+ WorksheetEntry* next = originalNext;
- // Not sure, but it looks like we support nbformat version 4.5
- root.insert(QLatin1String("nbformat"), 4);
- root.insert(QLatin1String("nbformat_minor"), 5);
+ m_placeholderEntry = new PlaceHolderEntry(this, entry->size());
+ m_placeholderEntry->setPrevious(previous);
+ m_placeholderEntry->setNext(next);
- QJsonArray cells;
- for( auto* entry = firstEntry(); entry; entry = entry->next())
- {
- const QJsonValue entryJson = entry->toJupyterJson();
+ if (previous)
+ previous->setNext(m_placeholderEntry);
+ else
+ setFirstEntry(m_placeholderEntry);
- if (!entryJson.isNull())
- cells.append(entryJson);
- }
- root.insert(QLatin1String("cells"), cells);
+ if (next)
+ next->setPrevious(m_placeholderEntry);
+ else
+ setLastEntry(m_placeholderEntry);
- doc.setObject(root);
- return doc;
-}
+ m_dragEntry->hide();
-void Worksheet::save( const QString& filename )
-{
- QFile file(filename);
- if ( !file.open(QIODevice::WriteOnly) )
+ const Qt::DropAction action = drag->exec();
+
+ bool positionChanged = false;
+
+ if (action == Qt::MoveAction && m_placeholderEntry)
{
- KMessageBox::error( worksheetView(),
- i18n( "Cannot write file %1." , filename ),
- i18n( "Error - Cantor" ));
- return;
+ previous = m_placeholderEntry->previous();
+ next = m_placeholderEntry->next();
+ positionChanged = previous != originalPrevious || next != originalNext;
}
- save(&file);
-}
+ removeDragPlaceholder();
-QByteArray Worksheet::saveToByteArray()
-{
- QBuffer buffer;
- save(&buffer);
+ m_dragEntry->setPrevious(previous);
+ m_dragEntry->setNext(next);
- return buffer.buffer();
-}
+ if (previous)
+ previous->setNext(m_dragEntry);
+ else
+ setFirstEntry(m_dragEntry);
-void Worksheet::save( QIODevice* device)
-{
- qDebug()<<"saving to filename";
- switch (m_type)
- {
- case CantorWorksheet:
- {
- KZip zipFile( device );
+ if (next)
+ next->setPrevious(m_dragEntry);
+ else
+ setLastEntry(m_dragEntry);
- if ( !zipFile.open(QIODevice::WriteOnly) )
- {
- KMessageBox::error( worksheetView(),
- i18n( "Cannot write file." ),
- i18n( "Error - Cantor" ));
- return;
- }
+ m_dragEntry->show();
+ const bool hierarchyMoved = m_dragEntry->type() == HierarchyEntry::Type;
- QByteArray content = toXML(&zipFile).toByteArray();
- zipFile.writeFile( QLatin1String("content.xml"), content.data());
- break;
- }
+ m_dragEntry->focusEntry();
+ const QPointF scenePosition = worksheetView()->sceneCursorPos();
- case JupyterNotebook:
- {
- if (!device->isWritable())
- {
- KMessageBox::error( worksheetView(),
- i18n( "Cannot write file." ),
- i18n( "Error - Cantor" ));
- return;
- }
+ if (entryAt(scenePosition) != m_dragEntry)
+ m_dragEntry->hideActionBar();
- const QJsonDocument& doc = toJupyterJson();
- device->write(doc.toJson(QJsonDocument::Indented));
- break;
- }
- }
+ m_dragEntry = nullptr;
+
+ if (hierarchyMoved && positionChanged)
+ updateHierarchyLayout();
+
+ updateLayout();
+
+ if (positionChanged)
+ setModified();
}
-void Worksheet::savePlain(const QString& filename)
+void Worksheet::startDragWithHierarchy(HierarchyEntry* entry, QDrag* drag, QSizeF responsibleZoneSize)
{
- QFile file(filename);
- if(!file.open(QIODevice::WriteOnly))
- {
- KMessageBox::error(worksheetView(), i18n("Error saving file %1", filename), i18n("Error - Cantor"));
+ if (m_readOnly || !entry || !drag)
return;
- }
- QString cmdSep=QLatin1String(";\n");
- QString commentStartingSeq = QLatin1String("");
- QString commentEndingSeq = QLatin1String("");
+ resetEntryCursor();
- if (!m_readOnly)
- {
- Cantor::Backend * const backend=session()->backend();
- if (backend->extensions().contains(QLatin1String("ScriptExtension")))
- {
- Cantor::ScriptExtension* e=dynamic_cast<Cantor::ScriptExtension*>(backend->extension(QLatin1String(("ScriptExtension"))));
- if (e)
- {
- cmdSep=e->commandSeparator();
- commentStartingSeq = e->commentStartingSequence();
- commentEndingSeq = e->commentEndingSequence();
- }
- }
- }
+ m_dragEntry = entry;
+ m_hierarchySubentriesDrag = hierarchySubelements(entry);
+ m_hierarchyDragSize = responsibleZoneSize;
+
+ WorksheetEntry* originalPrevious = entry->previous();
+ WorksheetEntry* originalNext = nullptr;
+
+ if (!m_hierarchySubentriesDrag.empty())
+ originalNext = m_hierarchySubentriesDrag.back()->next();
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"));
+ originalNext = entry->next();
- QTextStream stream(&file);
+ WorksheetEntry* previous = originalPrevious;
+ WorksheetEntry* next = originalNext;
- for(auto* entry = firstEntry(); entry; entry = entry->next())
- {
- const QString& str=entry->toPlain(cmdSep, commentStartingSeq, commentEndingSeq);
- if(!str.isEmpty())
- stream << str + QLatin1Char('\n');
- }
+ m_placeholderEntry = new PlaceHolderEntry(this, responsibleZoneSize);
+ m_placeholderEntry->setPrevious(previous);
+ m_placeholderEntry->setNext(next);
- file.close();
-}
+ if (previous)
+ previous->setNext(m_placeholderEntry);
+ else
+ setFirstEntry(m_placeholderEntry);
-void Worksheet::saveLatex(const QString& filename)
-{
- qDebug()<<"exporting to Latex: " <<filename;
+ if (next)
+ next->setPrevious(m_placeholderEntry);
+ else
+ setLastEntry(m_placeholderEntry);
- QFile file(filename);
- if(!file.open(QIODevice::WriteOnly))
+ m_dragEntry->hide();
+
+ for (auto* subentry : m_hierarchySubentriesDrag)
+ subentry->hide();
+
+ const Qt::DropAction action = drag->exec();
+
+ bool positionChanged = false;
+
+ if (action == Qt::MoveAction && m_placeholderEntry)
{
- KMessageBox::error(worksheetView(), i18n("Error saving file %1", filename), i18n("Export to LaTeX"));
- return;
+ previous = m_placeholderEntry->previous();
+ next = m_placeholderEntry->next();
+ positionChanged = previous != originalPrevious || next != originalNext;
}
- 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;
- }
+ removeDragPlaceholder();
- 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);
+ m_dragEntry->setPrevious(previous);
- 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);
+ if (previous)
+ previous->setNext(m_dragEntry);
+ else
+ setFirstEntry(m_dragEntry);
- // Transform HTML escaped special characters to valid LaTeX characters (&, <, >)
- QTextStream stream(&file);
- stream << outString.replace(QLatin1String("&"), QLatin1String("&"))
- .replace(QLatin1String(">"), QLatin1String(">"))
- .replace(QLatin1String("<"), QLatin1String("<"));
- file.close();
- }
+ WorksheetEntry* lastDraggingEntry = m_hierarchySubentriesDrag.empty() ? static_cast<WorksheetEntry*>(entry) : m_hierarchySubentriesDrag.back();
- xmlFree(xmlResultBuffer);
- }
+ lastDraggingEntry->setNext(next);
- xsltFreeStylesheet(xsltStyleSheet);
- xmlFreeDoc(res);
- xmlFreeDoc(output);
+ if (next)
+ next->setPrevious(lastDraggingEntry);
+ else
+ setLastEntry(lastDraggingEntry);
- xsltCleanupGlobals();
- xmlCleanupParser();
+ if (positionChanged)
+ normalizeDraggedHierarchyLevels(entry, previous, m_hierarchySubentriesDrag);
+
+ m_dragEntry->show();
+
+ for (auto* subentry : m_hierarchySubentriesDrag)
+ subentry->show();
+
+ m_dragEntry->focusEntry();
+
+ const QPointF scenePosition = worksheetView()->sceneCursorPos();
+ if (entryAt(scenePosition) != m_dragEntry)
+ m_dragEntry->hideActionBar();
+
+#ifndef NDEBUG
+ for (auto* current = firstEntry(); current; current = current->next())
+ Q_ASSERT(current->type() != PlaceHolderEntry::Type);
+#endif
+
+ m_hierarchySubentriesDrag.clear();
+ m_dragEntry = nullptr;
+
+ updateHierarchyLayout();
+ updateLayout();
+
+ if (positionChanged)
+ setModified();
}
-bool Worksheet::load(const QString& filename )
+void Worksheet::evaluate()
{
- 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;
+ qDebug()<<"evaluate worksheet";
+ // login if not done yet
+ if (!m_readOnly && m_session && m_session->status() == Cantor::Session::Disable)
+ loginToSession();
+
+ // evaluate the worksheet if the login was successful
+ if (m_session && m_session->status() == Cantor::Session::Done) {
+ firstEntry()->evaluate(WorksheetEntry::EvaluateNext);
+ setModified();
}
+}
- bool rc = load(&file);
- if (rc && !m_readOnly)
- m_session->setWorksheetPath(filename);
+void Worksheet::evaluateCurrentEntry()
+{
+ // login if not done yet
+ if (!m_readOnly && m_session && m_session->status() == Cantor::Session::Disable)
+ loginToSession();
- return rc;
+ // evaluate the current entry if the login was successful
+ if (!m_session)
+ 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();
+ }
}
-void Worksheet::load(QByteArray* data)
+bool Worksheet::completionEnabled()
{
- QBuffer buf(data);
- buf.open(QIODevice::ReadOnly);
- load(&buf);
+ return m_completionEnabled;
}
-bool Worksheet::load(QIODevice* device)
+void Worksheet::showCompletion()
{
- if (!device->isReadable())
- {
- QApplication::restoreOverrideCursor();
- KMessageBox::error(worksheetView(), i18n("Couldn't open the selected file for reading."), i18n("Open File"));
- return false;
- }
+ auto* current = currentEntry();
+ if (current)
+ current->showCompletion();
+}
- KZip archive(device);
+WorksheetEntry* Worksheet::appendEntry(const int type, bool focus)
+{
+ auto* entry = WorksheetEntry::create(type, this);
- if (archive.open(QIODevice::ReadOnly))
- return loadCantorWorksheet(archive);
- else
+ if (entry)
{
- qDebug() <<"not a zip file";
- // Go to begin of data, we need read all data in second time
- device->seek(0);
-
- QJsonParseError error;
- const QJsonDocument& doc = QJsonDocument::fromJson(device->readAll(), &error);
- if (error.error != QJsonParseError::NoError)
+ qDebug() << "Entry Appended";
+ entry->setPrevious(lastEntry());
+ if (lastEntry())
+ lastEntry()->setNext(entry);
+ if (!firstEntry())
+ setFirstEntry(entry);
+ setLastEntry(entry);
+ if (!m_isLoadingFromFile)
{
- 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;
+ updateHierarchyLayout();
+ updateLayout();
+ if (focus)
+ {
+ makeVisible(entry);
+ focusEntry(entry);
+ }
+ setModified();
}
- else
- return loadJupyterNotebook(doc);
}
+ return entry;
}
-bool Worksheet::loadCantorWorksheet(const KZip& archive)
+WorksheetEntry* Worksheet::appendCommandEntry()
{
- m_type = Type::CantorWorksheet;
+ return appendEntry(CommandEntry::Type);
+}
- 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;
- }
+WorksheetEntry* Worksheet::appendTextEntry()
+{
+ return appendEntry(TextEntry::Type);
+}
- const KArchiveFile* content = static_cast<const KArchiveFile*>(contentEntry);
+WorksheetEntry* Worksheet::appendMarkdownEntry()
+{
+ return appendEntry(MarkdownEntry::Type);
+}
+
+WorksheetEntry* Worksheet::appendPageBreakEntry()
+{
+ return appendEntry(PageBreakEntry::Type);
+}
+
+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)
+ {
+ focusEntry(entry);
+ entry->setContent(text);
+ evaluateCurrentEntry();
+ }
+}
+
+WorksheetEntry* Worksheet::appendHorizontalRuleEntry()
+{
+ return appendEntry(HorizontalRuleEntry::Type);
+}
+
+WorksheetEntry* Worksheet::appendHierarchyEntry()
+{
+ return appendEntry(HierarchyEntry::Type);
+}
+
+WorksheetEntry* Worksheet::insertEntry(const int type, WorksheetEntry* current)
+{
+ if (!current)
+ current = currentEntry();
+
+ 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;
+}
+
+WorksheetEntry* Worksheet::insertTextEntry(WorksheetEntry* current)
+{
+ return insertEntry(TextEntry::Type, current);
+}
+
+WorksheetEntry* Worksheet::insertMarkdownEntry(WorksheetEntry* current)
+{
+ return insertEntry(MarkdownEntry::Type, current);
+}
+
+WorksheetEntry* Worksheet::insertCommandEntry(WorksheetEntry* current)
+{
+ return insertEntry(CommandEntry::Type, current);
+}
+
+WorksheetEntry* Worksheet::insertImageEntry(WorksheetEntry* current)
+{
+ auto* entry = insertEntry(ImageEntry::Type, current);
+ auto* imageEntry = static_cast<ImageEntry*>(entry);
+ QTimer::singleShot(0, this, [=] () {imageEntry->startConfigDialog();});
+ return entry;
+}
+
+WorksheetEntry* Worksheet::insertPageBreakEntry(WorksheetEntry* current)
+{
+ return insertEntry(PageBreakEntry::Type, current);
+}
+
+WorksheetEntry* Worksheet::insertLatexEntry(WorksheetEntry* current)
+{
+ return insertEntry(LatexEntry::Type, current);
+}
+
+WorksheetEntry* Worksheet::insertHorizontalRuleEntry(WorksheetEntry* current)
+{
+ return insertEntry(HorizontalRuleEntry::Type, current);
+}
+
+WorksheetEntry* Worksheet::insertHierarchyEntry(WorksheetEntry* current)
+{
+ return insertEntry(HierarchyEntry::Type, current);
+}
+
+WorksheetEntry* Worksheet::insertEntryBefore(int type, WorksheetEntry* current)
+{
+ if (!current)
+ current = currentEntry();
+
+ if (!current)
+ return nullptr;
+
+ auto* prev = current->previous();
+ WorksheetEntry* entry = nullptr;
+
+ 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
+ entry = prev;
+
+ focusEntry(entry);
+ return entry;
+}
+
+WorksheetEntry* Worksheet::insertTextEntryBefore(WorksheetEntry* current)
+{
+ return insertEntryBefore(TextEntry::Type, current);
+}
+
+WorksheetEntry* Worksheet::insertMarkdownEntryBefore(WorksheetEntry* current)
+{
+ return insertEntryBefore(MarkdownEntry::Type, current);
+}
+
+WorksheetEntry* Worksheet::insertCommandEntryBefore(WorksheetEntry* current)
+{
+ return insertEntryBefore(CommandEntry::Type, current);
+}
+
+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();
+}
+
+
+bool Worksheet::variableHighlightingEnabled() const
+{
+ return m_variableHighlightingEnabled;
+}
+
+void Worksheet::setVariableHighlightingEnabled(bool enabled)
+{
+ if (m_variableHighlightingEnabled == enabled)
+ {
+ return;
+ }
+ m_variableHighlightingEnabled = enabled;
+
+ for (auto* entry = firstEntry(); entry; entry = entry->next())
+ {
+ if (entry->type() == CommandEntry::Type)
+ static_cast<CommandEntry*>(entry)->setVariableHighlightingEnabled(enabled);
+ }
+}
+
+void Worksheet::enableCompletion(bool enable)
+{
+ m_completionEnabled=enable;
+}
+
+Cantor::Session* Worksheet::session() const
+{
+ return m_session;
+}
+
+bool Worksheet::isRunning()
+{
+ return m_session && m_session->status()==Cantor::Session::Running;
+}
+
+bool Worksheet::isReadOnly()
+{
+ return m_readOnly;
+}
+
+bool Worksheet::showExpressionIds()
+{
+ return m_showExpressionIds;
+}
+
+bool Worksheet::animationsEnabled()
+{
+ return m_animationsEnabled;
+}
+
+void Worksheet::enableAnimations(bool enable)
+{
+ m_animationsEnabled = enable;
+}
+
+bool Worksheet::embeddedMathEnabled()
+{
+ return m_embeddedMathEnabled && m_mathRenderer.mathRenderAvailable();
+}
+
+void Worksheet::enableEmbeddedMath(bool enable)
+{
+ m_embeddedMathEnabled = enable;
+}
+
+void Worksheet::enableExpressionNumbering(bool enable)
+{
+ m_showExpressionIds=enable;
+ Q_EMIT updatePrompt();
+ refreshTocStructure();
+ if (views().size() != 0)
+ updateLayout();
+}
+
+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);
+
+ for( auto* entry = firstEntry(); entry; entry = entry->next())
+ {
+ QDomElement el = entry->toXml(doc, archive);
+ root.appendChild( el );
+ }
+ 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);
+
+ root.insert(QLatin1String("metadata"), metadata);
+
+ // Not sure, but it looks like we support nbformat version 4.5
+ root.insert(QLatin1String("nbformat"), 4);
+ root.insert(QLatin1String("nbformat_minor"), 5);
+
+ QJsonArray cells;
+ for( auto* entry = firstEntry(); entry; entry = entry->next())
+ {
+ const QJsonValue entryJson = entry->toJupyterJson();
+
+ if (!entryJson.isNull())
+ cells.append(entryJson);
+ }
+ root.insert(QLatin1String("cells"), cells);
+
+ doc.setObject(root);
+ return doc;
+}
+
+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;
+ }
+
+ save(&file);
+}
+
+QByteArray Worksheet::saveToByteArray()
+{
+ QBuffer buffer;
+ save(&buffer);
+
+ return buffer.buffer();
+}
+
+void Worksheet::save( QIODevice* device)
+{
+ qDebug()<<"saving to filename";
+ switch (m_type)
+ {
+ case CantorWorksheet:
+ {
+ KZip zipFile( device );
+
+ if ( !zipFile.open(QIODevice::WriteOnly) )
+ {
+ KMessageBox::error( worksheetView(),
+ i18n( "Cannot write file." ),
+ i18n( "Error - Cantor" ));
+ return;
+ }
+
+ QByteArray content = toXML(&zipFile).toByteArray();
+ zipFile.writeFile( QLatin1String("content.xml"), content.data());
+ break;
+ }
+
+ case JupyterNotebook:
+ {
+ if (!device->isWritable())
+ {
+ KMessageBox::error( worksheetView(),
+ i18n( "Cannot write file." ),
+ i18n( "Error - Cantor" ));
+ return;
+ }
+
+ const QJsonDocument& doc = toJupyterJson();
+ device->write(doc.toJson(QJsonDocument::Indented));
+ break;
+ }
+ }
+}
+
+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("");
+
+ if (!m_readOnly)
+ {
+ Cantor::Backend * const backend=session()->backend();
+ if (backend->extensions().contains(QLatin1String("ScriptExtension")))
+ {
+ Cantor::ScriptExtension* e=dynamic_cast<Cantor::ScriptExtension*>(backend->extension(QLatin1String(("ScriptExtension"))));
+ if (e)
+ {
+ cmdSep=e->commandSeparator();
+ commentStartingSeq = e->commentStartingSequence();
+ commentEndingSeq = e->commentEndingSequence();
+ }
+ }
+ }
+ 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);
+
+ for(auto* entry = firstEntry(); entry; entry = entry->next())
+ {
+ const QString& str=entry->toPlain(cmdSep, commentStartingSeq, commentEndingSeq);
+ if(!str.isEmpty())
+ stream << str + QLatin1Char('\n');
+ }
+
+ file.close();
+}
+
+void Worksheet::saveLatex(const QString& filename)
+{
+ qDebug()<<"exporting to Latex: " <<filename;
+
+ QFile file(filename);
+ if(!file.open(QIODevice::WriteOnly))
+ {
+ KMessageBox::error(worksheetView(), i18n("Error saving file %1", filename), i18n("Export to LaTeX"));
+ return;
+ }
+
+ 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;
+ }
+
+ 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);
+
+ 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);
+
+ // Transform HTML escaped special characters to valid LaTeX characters (&, <, >)
+ QTextStream stream(&file);
+ stream << outString.replace(QLatin1String("&"), QLatin1String("&"))
+ .replace(QLatin1String(">"), QLatin1String(">"))
+ .replace(QLatin1String("<"), QLatin1String("<"));
+ file.close();
+ }
+
+ xmlFree(xmlResultBuffer);
+ }
+
+ xsltFreeStylesheet(xsltStyleSheet);
+ xmlFreeDoc(res);
+ xmlFreeDoc(output);
+
+ xsltCleanupGlobals();
+ xmlCleanupParser();
+}
+
+bool Worksheet::load(const QString& filename )
+{
+ 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;
+ }
+
+ bool rc = load(&file);
+ if (rc && !m_readOnly)
+ m_session->setWorksheetPath(filename);
+
+ return rc;
+}
+
+void Worksheet::load(QByteArray* data)
+{
+ QBuffer buf(data);
+ buf.open(QIODevice::ReadOnly);
+ load(&buf);
+}
+
+bool Worksheet::load(QIODevice* device)
+{
+ 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);
+
+ 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);
+
+ 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);
+ }
+}
+
+bool Worksheet::loadCantorWorksheet(const KZip& archive)
+{
+ m_type = Type::CantorWorksheet;
+
+ 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;
+ }
+
+ const KArchiveFile* content = static_cast<const KArchiveFile*>(contentEntry);
QByteArray data = content->data();
QDomDocument doc;
@@ -1794,6 +2466,7 @@ void Worksheet::gotResult(Cantor::Expression* expr)
break;
}
}
+
}
void Worksheet::removeCurrentEntry()
@@ -2281,6 +2954,14 @@ WorksheetTextItem* Worksheet::lastFocusedTextItem()
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);
@@ -2340,6 +3021,13 @@ void Worksheet::updateFocusedTextItem(WorksheetTextItem* 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);
@@ -2427,8 +3115,8 @@ void Worksheet::paste() {
void Worksheet::setRichTextInformation(const RichTextInfo& info)
{
- // if (!m_boldAction)
- // initActions();
+ if (!m_boldAction)
+ return;
m_boldAction->setChecked(info.bold);
m_italicAction->setChecked(info.italic);
@@ -2517,570 +3205,1064 @@ void Worksheet::setTextStrikeOut(bool b)
m_legacylastFocusedTextItem->setTextStrikeOut(b);
}
-void Worksheet::setAlignLeft()
+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()));
+
+ m_dragScrollTimer->start();
+}
+
+void Worksheet::removeDragPlaceholder()
+{
+ if (!m_placeholderEntry)
+ return;
+
+ auto* placeholder = m_placeholderEntry;
+ auto* previous = placeholder->previous();
+ auto* next = placeholder->next();
+
+ if (previous && previous->next() == placeholder)
+ previous->setNext(next);
+ else if (firstEntry() == placeholder)
+ setFirstEntry(next);
+
+ if (next && next->previous() == placeholder)
+ next->setPrevious(previous);
+ else if (lastEntry() == placeholder)
+ setLastEntry(previous);
+
+ placeholder->setPrevious(nullptr);
+ placeholder->setNext(nullptr);
+ placeholder->hide();
+ placeholder->deleteLater();
+
+ m_placeholderEntry = nullptr;
+}
+
+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::addEntryFromEntryCursor()
+{
+ qDebug() << "Add new entry from entry cursor";
+ if (m_isCursorEntryAfterLastEntry)
+ insertCommandEntry(lastEntry());
+ else
+ insertCommandEntryBefore(m_choosenCursorEntry);
+ resetEntryCursor();
+}
+
+void Worksheet::animateEntryCursor()
{
- if (m_lastFocusedTextItem)
- m_lastFocusedTextItem->setAlignment(Qt::AlignLeft);
- else if (m_legacylastFocusedTextItem)
- m_legacylastFocusedTextItem->setAlignment(Qt::AlignLeft);
+ if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && m_entryCursorItem)
+ m_entryCursorItem->setVisible(!m_entryCursorItem->isVisible());
}
-void Worksheet::setAlignRight()
+void Worksheet::resetEntryCursor()
{
- if (m_lastFocusedTextItem)
- m_lastFocusedTextItem->setAlignment(Qt::AlignRight);
- else if (m_legacylastFocusedTextItem)
- m_legacylastFocusedTextItem->setAlignment(Qt::AlignRight);
+ m_choosenCursorEntry = nullptr;
+ m_isCursorEntryAfterLastEntry = false;
+ m_entryCursorItem->hide();
}
-void Worksheet::setAlignCenter()
+void Worksheet::drawEntryCursor()
{
- if (m_lastFocusedTextItem)
- m_lastFocusedTextItem->setAlignment(Qt::AlignCenter);
- else if (m_legacylastFocusedTextItem)
- m_legacylastFocusedTextItem->setAlignment(Qt::AlignCenter);
+ 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();
+ }
}
-void Worksheet::setAlignJustify()
+void Worksheet::setType(Worksheet::Type type)
{
- if (m_lastFocusedTextItem)
- m_lastFocusedTextItem->setAlignment(Qt::AlignJustify);
- else if (m_legacylastFocusedTextItem)
- m_legacylastFocusedTextItem->setAlignment(Qt::AlignJustify);
+ m_type = type;
}
-void Worksheet::setFontFamily(const QString& font)
+Worksheet::Type Worksheet::type() const
{
- if (m_lastFocusedTextItem)
- m_lastFocusedTextItem->setFontFamily(font);
- else if (m_legacylastFocusedTextItem)
- m_legacylastFocusedTextItem->setFontFamily(font);
+ return m_type;
}
-void Worksheet::setFontSize(int size)
+void Worksheet::changeEntryType(WorksheetEntry* target, int newType)
{
- if (m_lastFocusedTextItem)
- m_lastFocusedTextItem->setFontSize(size);
- else if (m_legacylastFocusedTextItem)
- m_legacylastFocusedTextItem->setFontSize(size);
-}
+ if (target && target->type() != newType)
+ {
+ bool animation_state = m_animationsEnabled;
+ m_animationsEnabled = false;
+ QString content;
-bool Worksheet::isShortcut(const QKeySequence& sequence)
-{
- return m_shortcuts.contains(sequence);
-}
+ 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();
+ }
-void Worksheet::registerShortcut(QAction* action)
-{
- for (auto& shortcut : action->shortcuts())
- m_shortcuts.insert(shortcut, action);
+ auto* newEntry = WorksheetEntry::create(newType, this);
+ if (newEntry)
+ {
+ newEntry->setContent(content);
+ auto* tmp = target;
- connect(action, &QAction::changed, this, &Worksheet::updateShortcut);
-}
+ newEntry->setPrevious(tmp->previous());
+ newEntry->setNext(tmp->next());
-void Worksheet::updateShortcut()
-{
- QAction* action = qobject_cast<QAction*>(sender());
- if (!action)
- return;
+ tmp->setPrevious(nullptr);
+ tmp->setNext(nullptr);
+ tmp->clearFocus();
+ tmp->forceRemove();
- // delete the old shortcuts of this action
- QList<QKeySequence> shortcuts = m_shortcuts.keys(action);
- for (auto& shortcut : shortcuts)
- m_shortcuts.remove(shortcut);
+ if (newEntry->previous())
+ newEntry->previous()->setNext(newEntry);
+ else
+ setFirstEntry(newEntry);
- // add the new shortcuts
- for (auto& shortcut : action->shortcuts())
- m_shortcuts.insert(shortcut, action);
+ if (newEntry->next())
+ newEntry->next()->setPrevious(newEntry);
+ else
+ setLastEntry(newEntry);
+
+ if (newType == HierarchyEntry::Type || targetEntryType == HierarchyEntry::Type)
+ updateHierarchyLayout();
+ updateLayout();
+ makeVisible(newEntry);
+ focusEntry(newEntry);
+ setModified();
+ newEntry->focusEntry();
+ }
+ m_animationsEnabled = animation_state;
+ }
}
-void Worksheet::dragEnterEvent(QGraphicsSceneDragDropEvent* event)
+bool Worksheet::isValidEntry(WorksheetEntry* entry)
{
- if (m_dragEntry)
- event->accept();
- else
- QGraphicsScene::dragEnterEvent(event);
+ for (auto* iter = firstEntry(); iter; iter = iter->next())
+ if (entry == iter)
+ return true;
+
+ return false;
}
-void Worksheet::dragLeaveEvent(QGraphicsSceneDragDropEvent* event)
+void Worksheet::selectionRemove()
{
- if (!m_dragEntry) {
- QGraphicsScene::dragLeaveEvent(event);
+ if (m_selectedEntries.isEmpty())
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());
+
+ if (result != KMessageBox::PrimaryAction)
+ return;
}
- event->accept();
- if (m_placeholderEntry) {
- m_placeholderEntry->startRemoving();
- m_placeholderEntry = nullptr;
+ for (auto* entry : m_selectedEntries)
+ {
+ if (isValidEntry(entry))
+ entry->startRemoving(false);
}
+
+ m_selectedEntries.clear();
}
-void Worksheet::dragMoveEvent(QGraphicsSceneDragDropEvent* event)
+void Worksheet::selectionEvaluate()
{
- 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;
- }
- }
+ // 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();
+}
- bool dragWithHierarchy = m_hierarchySubentriesDrag.size() != 0;
+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();
+}
- if (prev || next) {
- auto* oldPlaceHolder = m_placeholderEntry;
- if (prev && prev->type() == PlaceHolderEntry::Type &&
- (!prev->aboutToBeRemoved() || prev->stopRemoving())) {
- m_placeholderEntry = qgraphicsitem_cast<PlaceHolderEntry*>(prev);
- if (dragWithHierarchy)
- m_placeholderEntry->changeSize(m_hierarchyDragSize);
- else
- m_placeholderEntry->changeSize(m_dragEntry->size());
- } else if (next && next->type() == PlaceHolderEntry::Type &&
- (!next->aboutToBeRemoved() || next->stopRemoving())) {
- m_placeholderEntry = qgraphicsitem_cast<PlaceHolderEntry*>(next);
- if (dragWithHierarchy)
- m_placeholderEntry->changeSize(m_hierarchyDragSize);
- else
- m_placeholderEntry->changeSize(m_dragEntry->size());
- } else {
- m_placeholderEntry = new PlaceHolderEntry(this, QSizeF(0,0));
- 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);
- if (dragWithHierarchy)
- m_placeholderEntry->changeSize(m_hierarchyDragSize);
- else
- m_placeholderEntry->changeSize(m_dragEntry->size());
- }
- if (oldPlaceHolder && oldPlaceHolder != m_placeholderEntry)
- oldPlaceHolder->startRemoving();
- updateLayout();
- }
+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();
+}
- 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();
+void Worksheet::notifyEntryFocus(WorksheetEntry* entry)
+{
+ if (entry)
+ {
+ m_circularFocusBuffer.enqueue(entry);
+
+ if (m_circularFocusBuffer.size() > 2)
+ m_circularFocusBuffer.dequeue();
}
+ else
+ m_circularFocusBuffer.clear();
+}
- event->accept();
+void Worksheet::collapseAllResults()
+{
+ for (auto* entry = firstEntry(); entry; entry = entry->next())
+ if (entry->type() == CommandEntry::Type)
+ static_cast<CommandEntry*>(entry)->collapseResults();
}
-void Worksheet::dropEvent(QGraphicsSceneDragDropEvent* event)
+void Worksheet::uncollapseAllResults()
{
- if (!m_dragEntry)
- QGraphicsScene::dropEvent(event);
- event->accept();
+ for (auto* entry = firstEntry(); entry; entry = entry->next())
+ if (entry->type() == CommandEntry::Type)
+ static_cast<CommandEntry*>(entry)->expandResults();
}
-void Worksheet::updateDragScrollTimer()
+void Worksheet::removeAllResults()
{
- if (!m_dragScrollTimer)
- return;
+ bool remove = false;
- 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 (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);
}
-
- if (viewPos.y() < 10)
- worksheetView()->scrollBy(-10*(10 - viewPos.y()));
else
- worksheetView()->scrollBy(10*(viewHeight - viewPos.y()));
+ remove = true;
- m_dragScrollTimer->start();
+ if (remove)
+ {
+ for (auto *entry = firstEntry(); entry; entry = entry->next())
+ if (entry->type() == CommandEntry::Type)
+ static_cast<CommandEntry*>(entry)->removeResults();
+ }
}
-void Worksheet::updateEntryCursor(QGraphicsSceneMouseEvent* event)
+void Worksheet::addToExectuionSelection()
{
- // 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;
- }
- }
- }
+ for (auto* entry : m_selectedEntries)
+ if (entry->type() == CommandEntry::Type)
+ static_cast<CommandEntry*>(entry)->addToExecution();
+}
- if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry)
- drawEntryCursor();
+void Worksheet::excludeFromExecutionSelection()
+{
+ for (auto* entry : m_selectedEntries)
+ if (entry->type() == CommandEntry::Type)
+ static_cast<CommandEntry*>(entry)->excludeFromExecution();
}
-void Worksheet::addEntryFromEntryCursor()
+void Worksheet::collapseSelectionResults()
{
- qDebug() << "Add new entry from entry cursor";
- if (m_isCursorEntryAfterLastEntry)
- insertCommandEntry(lastEntry());
- else
- insertCommandEntryBefore(m_choosenCursorEntry);
- resetEntryCursor();
+ for (auto* entry : m_selectedEntries)
+ if (entry->type() == CommandEntry::Type)
+ static_cast<CommandEntry*>(entry)->collapseResults();
}
-void Worksheet::animateEntryCursor()
+void Worksheet::uncollapseSelectionResults()
{
- if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && m_entryCursorItem)
- m_entryCursorItem->setVisible(!m_entryCursorItem->isVisible());
+ for (auto* entry : m_selectedEntries)
+ if (entry->type() == CommandEntry::Type)
+ static_cast<CommandEntry*>(entry)->expandResults();
}
-void Worksheet::resetEntryCursor()
+void Worksheet::removeSelectionResults()
{
- m_choosenCursorEntry = nullptr;
- m_isCursorEntryAfterLastEntry = false;
- m_entryCursorItem->hide();
+ for (auto* entry : m_selectedEntries)
+ if (entry->type() == CommandEntry::Type)
+ static_cast<CommandEntry*>(entry)->removeResults();
}
-void Worksheet::drawEntryCursor()
+void Worksheet::navigateToTocNode(QString nodeId)
{
- if (m_entryCursorItem && (m_choosenCursorEntry || (m_isCursorEntryAfterLastEntry && lastEntry())))
+ QString plotCommandId;
+ QString plotResultId;
+ if (parsePlotNodeId(nodeId, &plotCommandId, &plotResultId))
{
- qreal x;
- qreal y;
- if (m_isCursorEntryAfterLastEntry)
+ const CommandSearchResult commandSearch = findCommandEntryById(plotCommandId);
+ if (!commandSearch.entry)
{
- x = lastEntry()->x();
- y = lastEntry()->y() + lastEntry()->size().height() - (EntryCursorWidth - 1);
+ scheduleTocStructureRefresh();
+ return;
}
- else
+
+ const bool expanded = expandHierarchyAncestors(commandSearch.collapsedAncestors);
+ if (expanded)
{
- x = m_choosenCursorEntry->x();
- y = m_choosenCursorEntry->y();
+ updateHierarchyLayout();
+ updateLayout();
}
- m_entryCursorItem->setLine(x,y,x+EntryCursorLength,y);
- m_entryCursorItem->show();
+
+ 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();
+ updateLayout();
+ }
+
+ auto* commandEntry = commandSearch.entry;
+ updateCurrentHierarchyFromEntry(commandEntry);
+
+ worksheetView()->scrollTo(qRound(commandEntry->scenePos().y()));
+
+ worksheetView()->setFocus();
+
+ commandEntry->focusEntry(WorksheetTextItem::TopLeft);
+
+ resetEntryCursor();
+ return;
+ }
+
+ const HierarchySearchResult hierarchySearch = findHierarchyEntryById(nodeId);
+ if (hierarchySearch.entry)
+ {
+ const bool expanded = expandHierarchyAncestors(hierarchySearch.collapsedAncestors);
+
+ if (expanded)
+ {
+ updateHierarchyLayout();
+ updateLayout();
+ }
+
+ auto* hierarchyEntry = hierarchySearch.entry;
+ updateCurrentHierarchyFromEntry(hierarchyEntry);
+
+ worksheetView()->scrollTo(qRound(hierarchyEntry->scenePos().y()));
+
+ worksheetView()->setFocus();
+
+ hierarchyEntry->focusEntry(WorksheetTextItem::BottomRight);
+
+ resetEntryCursor();
}
}
-void Worksheet::setType(Worksheet::Type type)
+bool Worksheet::navigateToPlotResult(CommandEntry* commandEntry, const QString& resultId)
{
- m_type = type;
+ if (!commandEntry || resultId.isEmpty())
+ return false;
+
+ 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();
+
+ m_hierarchyTrackingSource = HierarchyTrackingSource::FocusedEntry;
+ setCurrentTocNode(nodeId);
+
+ resetEntryCursor();
+ return true;
}
-Worksheet::Type Worksheet::type() const
+void Worksheet::updateCurrentTocNodeFromResult(CommandEntry* commandEntry, Cantor::Result* result)
{
- return m_type;
+ if (!commandEntry || !result || !isValidEntry(commandEntry))
+ return;
+
+ m_hierarchyTrackingSource = HierarchyTrackingSource::FocusedEntry;
+
+ if (isPlotResult(result))
+ setCurrentTocNode(buildPlotNodeId(commandEntry->commandId(), result->resultId()));
+ else
+ updateCurrentHierarchyFromEntry(commandEntry);
}
-void Worksheet::changeEntryType(WorksheetEntry* target, int newType)
+void Worksheet::renamePlot(const QString& commandId, const QString& resultId, const QString& newTitle)
{
- if (target && target->type() != newType)
+ if (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())
{
- bool animation_state = m_animationsEnabled;
- m_animationsEnabled = false;
+ if (!result || result->resultId() != resultId || !isPlotResult(result))
+ continue;
- QString content;
+ if (result->displayName() == normalizedTitle)
+ return;
- 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();
- }
+ result->setDisplayName(normalizedTitle);
+ setModified();
+ scheduleTocStructureRefresh();
+ return;
+ }
+}
- auto* newEntry = WorksheetEntry::create(newType, this);
- if (newEntry)
- {
- newEntry->setContent(content);
- auto* tmp = target;
+void Worksheet::deletePlot(const QString& commandId, const QString& resultId)
+{
+ if (m_readOnly || commandId.isEmpty() || resultId.isEmpty())
+ return;
- newEntry->setPrevious(tmp->previous());
- newEntry->setNext(tmp->next());
+ const CommandSearchResult commandSearch = findCommandEntryById(commandId);
+ auto* commandEntry = commandSearch.entry;
+ auto* expression = commandEntry ? commandEntry->expression() : nullptr;
- tmp->setPrevious(nullptr);
- tmp->setNext(nullptr);
- tmp->clearFocus();
- tmp->forceRemove();
+ if (!commandEntry || !expression)
+ return;
- if (newEntry->previous())
- newEntry->previous()->setNext(newEntry);
- else
- setFirstEntry(newEntry);
+ for (auto* result : expression->results())
+ {
+ if (!result || result->resultId() != resultId || !isPlotResult(result))
+ continue;
- if (newEntry->next())
- newEntry->next()->setPrevious(newEntry);
- else
- setLastEntry(newEntry);
+ const QString plotNodeId = buildPlotNodeId(commandId, resultId);
+ const bool wasCurrentNode = m_currentTocNodeId == plotNodeId;
- if (newType == HierarchyEntry::Type || targetEntryType == HierarchyEntry::Type)
- updateHierarchyLayout();
- updateLayout();
- makeVisible(newEntry);
- focusEntry(newEntry);
- setModified();
- newEntry->focusEntry();
- }
- m_animationsEnabled = animation_state;
+ expression->removeResult(result);
+
+ if (wasCurrentNode)
+ setCurrentTocNode(buildCommandNodeId(commandEntry));
+
+ setModified();
+ scheduleTocStructureRefresh();
+ return;
}
}
-bool Worksheet::isValidEntry(WorksheetEntry* entry)
+void Worksheet::renameHierarchyEntry(const QString& hierarchyId, const QString& newName)
{
- for (auto* iter = firstEntry(); iter; iter = iter->next())
- if (entry == iter)
- return true;
+ if (m_readOnly || hierarchyId.isEmpty())
+ return;
- return false;
+ 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 Worksheet::selectionRemove()
+void Worksheet::changeHierarchyLevel(QString hierarchyId, int levelDelta)
{
- if (m_selectedEntries.isEmpty())
+ if (m_readOnly || hierarchyId.isEmpty() || (levelDelta != -1 && levelDelta != 1))
return;
- if (Settings::warnAboutEntryDelete())
+ 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)
{
- int rc = KMessageBox::warningContinueCancel(nullptr,
- i18n("This step cannot be undone. Do you really want to delete the selected entries?"),
- i18n("Delete Entries"));
- if (rc == KMessageBox::SecondaryAction)
+ if (currentRootLevel <= minimumLevel)
return;
+
+ expanded = expandHierarchyAncestors(hierarchySearch.collapsedAncestors);
}
+ else
+ {
+ if (currentRootLevel >= maximumLevel)
+ return;
- for (auto* entry : m_selectedEntries)
- if (isValidEntry(entry))
- entry->startRemoving(false);
+ expanded = expandHierarchyAncestors(hierarchySearch.collapsedAncestors);
- m_selectedEntries.clear();
-}
+ bool hasPreviousSibling = false;
-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();
-}
+ for (auto* entry = targetEntry->previous(); entry; entry = entry->previous())
+ {
+ if (entry->type() != HierarchyEntry::Type)
+ continue;
-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)
+ const int entryLevel = static_cast<int>(static_cast<HierarchyEntry*>(entry)->level());
+
+ if (entryLevel < currentRootLevel)
+ break;
+
+ if (entryLevel == currentRootLevel)
{
- entry->moveToPrevious(false);
- if (entry->type() == HierarchyEntry::Type)
- moveHierarchyEntry = true;
+ hasPreviousSibling = true;
+ break;
}
- if (moveHierarchyEntry)
- updateHierarchyLayout();
- updateLayout();
-}
+ }
-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)
+ 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)
{
- entry->moveToNext(false);
- if (entry->type() == HierarchyEntry::Type)
- moveHierarchyEntry = true;
+ updateHierarchyLayout();
+ if (expanded)
+ updateLayout();
+ return;
}
- if (moveHierarchyEntry)
- updateHierarchyLayout();
- updateLayout();
-}
+ }
+ }
-void Worksheet::notifyEntryFocus(WorksheetEntry* entry)
-{
- if (entry)
+ const auto shiftHierarchyEntry = [levelDelta](HierarchyEntry* hierarchyEntry)
{
- m_circularFocusBuffer.enqueue(entry);
+ if (!hierarchyEntry)
+ return;
- if (m_circularFocusBuffer.size() > 2)
- m_circularFocusBuffer.dequeue();
+ 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));
}
- else
- m_circularFocusBuffer.clear();
-}
-void Worksheet::collapseAllResults()
-{
- for (auto* entry = firstEntry(); entry; entry = entry->next())
- if (entry->type() == CommandEntry::Type)
- static_cast<CommandEntry*>(entry)->collapseResults();
-}
+ updateHierarchyLayout();
+ updateLayout();
-void Worksheet::uncollapseAllResults()
-{
- for (auto* entry = firstEntry(); entry; entry = entry->next())
- if (entry->type() == CommandEntry::Type)
- static_cast<CommandEntry*>(entry)->expandResults();
+ setModified();
}
-void Worksheet::removeAllResults()
+void Worksheet::deleteHierarchyEntry(const QString& hierarchyId, bool deleteContents)
{
- bool remove = false;
+ if (m_readOnly || hierarchyId.isEmpty())
+ return;
- if (KMessageBox::shouldBeShownContinue(QLatin1String("WarnAboutAllResultsRemoving")))
+ 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)
{
- 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);
+ 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
- remove = true;
-
- if (remove)
{
- for (auto *entry = firstEntry(); entry; entry = entry->next())
- if (entry->type() == CommandEntry::Type)
- static_cast<CommandEntry*>(entry)->removeResults();
+ 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");
}
-}
-void Worksheet::addToExectuionSelection()
-{
- for (auto* entry : m_selectedEntries)
- if (entry->type() == CommandEntry::Type)
- static_cast<CommandEntry*>(entry)->addToExecution();
-}
+ if (Settings::warnAboutEntryDelete())
+ {
+ const auto result = KMessageBox::warningTwoActions(
+ worksheetView(),
+ warningText,
+ dialogTitle,
+ KStandardGuiItem::remove(),
+ KStandardGuiItem::cancel());
+
+ if (result != KMessageBox::PrimaryAction)
+ return;
+ }
-void Worksheet::excludeFromExecutionSelection()
-{
- for (auto* entry : m_selectedEntries)
- if (entry->type() == CommandEntry::Type)
- static_cast<CommandEntry*>(entry)->excludeFromExecution();
-}
+ expandHierarchyAncestors(hierarchySearch.collapsedAncestors);
+ expandHierarchyForStructureChange(targetEntry);
-void Worksheet::collapseSelectionResults()
-{
- for (auto* entry : m_selectedEntries)
- if (entry->type() == CommandEntry::Type)
- static_cast<CommandEntry*>(entry)->collapseResults();
-}
+ const std::vector<WorksheetEntry*>subentries = hierarchySubelements(targetEntry);
-void Worksheet::uncollapseSelectionResults()
-{
- for (auto* entry : m_selectedEntries)
- if (entry->type() == CommandEntry::Type)
- static_cast<CommandEntry*>(entry)->expandResults();
-}
+ clearAllSelections();
+ notifyEntryFocus(nullptr);
-void Worksheet::removeSelectionResults()
-{
- for (auto* entry : m_selectedEntries)
- if (entry->type() == CommandEntry::Type)
- static_cast<CommandEntry*>(entry)->removeResults();
-}
+ QList<WorksheetEntry*> entriesToRemove;
+ entriesToRemove.append(targetEntry);
-void Worksheet::requestScrollToHierarchyEntry(QString hierarchyText)
-{
- for (auto* entry = firstEntry(); entry; entry = entry->next())
+ if (deleteContents)
{
- if (entry->type() == HierarchyEntry::Type)
+ for (auto* entry : subentries)
+ entriesToRemove.append(entry);
+ }
+ else
+ {
+ const int minimumLevel = static_cast<int>(HierarchyEntry::HierarchyLevel::Chapter);
+
+ for (auto* entry : subentries)
{
- auto* hierarchEntry = static_cast<HierarchyEntry*>(entry);
- if (hierarchEntry->hierarchyText() == hierarchyText)
- worksheetView()->scrollTo(hierarchEntry->y());
+ 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* Worksheet::cutSubentriesForHierarchy(HierarchyEntry* hierarchyEntry)
-{
- Q_ASSERT(hierarchyEntry->next());
- auto* cutBegin = hierarchyEntry->next();
- auto* cutEnd = cutBegin;
+ WorksheetEntry* previousEntry = targetEntry->previous();
+ WorksheetEntry* lastRemovedEntry = entriesToRemove.constLast();
+ WorksheetEntry* nextEntry = lastRemovedEntry->next();
+
+ if (previousEntry)
+ previousEntry->setNext(nextEntry);
+ else
+ setFirstEntry(nextEntry);
+
+ if (nextEntry)
+ nextEntry->setPrevious(previousEntry);
+ else
+ setLastEntry(previousEntry);
+
+ clearFocus();
+
+ updateFocusedTextItem(static_cast<WorksheetTextItem*>(nullptr));
+ updateFocusedTextItem(static_cast<WorksheetTextEditorItem*>(nullptr));
- bool isCutEnd = false;
- int level = (int)hierarchyEntry->level();
- while (!isCutEnd && cutEnd && cutEnd->next())
+ for (auto* entry : entriesToRemove)
{
- auto* next = cutEnd->next();
- if (next->type() == HierarchyEntry::Type && (int)static_cast<HierarchyEntry*>(next)->level() <= level)
- isCutEnd = true;
- else
- cutEnd = next;
+ if (!entry)
+ continue;
+
+ entry->setPrevious(nullptr);
+ entry->setNext(nullptr);
+ entry->clearFocus();
+ entry->hide();
+ entry->deleteLater();
}
- //cutEnd not an end of all entries
- if (cutEnd->next())
+ WorksheetEntry* focusTarget = nextEntry ? nextEntry : previousEntry;
+
+ if (!firstEntry())
+ focusTarget = appendCommandEntry();
+
+ updateHierarchyLayout();
+ updateLayout();
+
+ if (focusTarget)
{
- hierarchyEntry->setNext(cutEnd->next());
- cutEnd->setNext(nullptr);
+ focusTarget->focusEntry();
+ makeVisible(focusTarget);
+ updateCurrentHierarchyFromEntry(focusTarget);
}
else
{
- hierarchyEntry->setNext(nullptr);
- setLastEntry(hierarchyEntry);
+ m_hierarchyTrackingSource = HierarchyTrackingSource::FocusedEntry;
+ updateCurrentHierarchy(nullptr);
+ }
+
+ resetEntryCursor();
+ setModified();
+}
+
+WorksheetEntry* Worksheet::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
+ setLastEntry(hierarchyEntry);
+
cutBegin->setPrevious(nullptr);
+ cutEnd->setNext(nullptr);
- for(auto* entry = cutBegin; entry; entry = entry->next())
+ for (auto* entry = cutBegin; entry; entry = entry->next())
entry->hide();
return cutBegin;
@@ -3088,21 +4270,73 @@ WorksheetEntry* Worksheet::cutSubentriesForHierarchy(HierarchyEntry* hierarchyEn
void Worksheet::insertSubentriesForHierarchy(HierarchyEntry* hierarchyEntry, WorksheetEntry* storedSubentriesBegin)
{
- auto* previousNext = hierarchyEntry->next();
+ if (!hierarchyEntry || !storedSubentriesBegin)
+ return;
+
+ WorksheetEntry* previousNext = hierarchyEntry->next();
+
hierarchyEntry->setNext(storedSubentriesBegin);
- storedSubentriesBegin->show();
+ storedSubentriesBegin->setPrevious(hierarchyEntry);
- auto* storedEnd = storedSubentriesBegin;
- while(storedEnd->next())
+ WorksheetEntry* storedEnd = storedSubentriesBegin;
+
+ for (auto* entry = storedSubentriesBegin; entry; entry = entry->next())
{
- storedEnd = storedEnd->next();
- storedEnd->show();
+ entry->show();
+ storedEnd = entry;
}
+
storedEnd->setNext(previousNext);
- if (!previousNext)
+
+ if (previousNext)
+ previousNext->setPrevious(storedEnd);
+ else
setLastEntry(storedEnd);
}
+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;
+}
+
void Worksheet::handleSettingsChanges()
{
const QString themeSetting = Settings::self()->defaultTheme();
diff --git a/src/worksheet.h b/src/worksheet.h
index c36819f4..6fb142da 100644
--- a/src/worksheet.h
+++ b/src/worksheet.h
@@ -15,6 +15,9 @@
#include <QDomDocument>
#include <QGraphicsScene>
#include <QQueue>
+#include <QVariantList>
+
+#include <functional>
#include "lib/renderer.h"
#include "mathrender.h"
@@ -24,9 +27,11 @@ namespace Cantor {
class Backend;
class Session;
class Expression;
+ class Result;
}
class WorksheetEntry;
+class CommandEntry;
class WorksheetView;
class HierarchyEntry;
class PlaceHolderEntry;
@@ -48,11 +53,18 @@ class Worksheet : public QGraphicsScene
{
Q_OBJECT
public:
- enum Type {
+ enum Type
+ {
CantorWorksheet,
JupyterNotebook
};
+ enum class HierarchyTrackingSource
+ {
+ Viewport,
+ FocusedEntry
+ };
+
Worksheet(Cantor::Backend*, QWidget*, bool useDeafultWorksheetParameters = true);
~Worksheet() override;
@@ -99,6 +111,7 @@ class Worksheet : public QGraphicsScene
WorksheetEntry* cutSubentriesForHierarchy(HierarchyEntry*);
void insertSubentriesForHierarchy(HierarchyEntry*, WorksheetEntry*);
+ bool expandHierarchyForStructureChange(HierarchyEntry*);
KWorksheetCursor worksheetCursor();
void setWorksheetCursor(const KWorksheetCursor&);
@@ -183,6 +196,9 @@ class Worksheet : public QGraphicsScene
void updateLayout();
void updateHierarchyLayout();
void updateHierarchyControlsLayout(WorksheetEntry* startEntry = nullptr);
+ void updateCurrentHierarchyFromView(const QRectF& viewRect);
+ void updateCurrentHierarchyFromEntry(WorksheetEntry* entry);
+ void followHierarchyFromView();
void updateEntrySize(WorksheetEntry*);
void print(QPrinter*);
@@ -217,6 +233,9 @@ class Worksheet : public QGraphicsScene
bool load(const QString&);
void gotResult(Cantor::Expression* expr = nullptr);
+ void refreshTocStructure();
+ void scheduleTocStructureRefresh();
+ void emitTocNodeSnapshot();
void removeCurrentEntry();
@@ -262,7 +281,13 @@ class Worksheet : public QGraphicsScene
void addToExectuionSelection();
void excludeFromExecutionSelection();
- void requestScrollToHierarchyEntry(QString);
+ void renameHierarchyEntry(const QString& hierarchyId, const QString& newName);
+ void changeHierarchyLevel(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 clearAllSelections();
void handleSettingsChanges();
@@ -270,8 +295,8 @@ class Worksheet : public QGraphicsScene
void modified();
void loaded();
void showHelp(const QString&);
- void hierarchyChanged(const QStringList&, const QStringList&, const QList<int>&);
- void hierarhyEntryNameChange(QString name, QString searchName, int depth);
+ void tocNodesChanged(QVariantList nodes);
+ void currentTocNodeChanged(QString nodeId);
void updatePrompt();
void undoAvailable(bool);
void redoAvailable(bool);
@@ -299,6 +324,40 @@ 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);
+
private Q_SLOTS:
//void checkEntriesForSanity();
@@ -318,6 +377,7 @@ class Worksheet : public QGraphicsScene
WorksheetEntry* entryAt(qreal x, qreal y);
WorksheetEntry* entryAt(QPointF);
WorksheetEntry* entryAt(int row);
+ void removeDragPlaceholder();
void updateEntryCursor(QGraphicsSceneMouseEvent*);
void addEntryFromEntryCursor();
void drawEntryCursor();
@@ -327,6 +387,13 @@ class Worksheet : public QGraphicsScene
void showInvalidNotebookSchemeError(QString additionalInfo = QString());
void initSession(Cantor::Backend*);
std::vector<WorksheetEntry*> hierarchySubelements(HierarchyEntry*) const;
+ 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;
static const double RightMargin;
@@ -395,6 +462,8 @@ class Worksheet : public QGraphicsScene
QQueue<WorksheetEntry*> m_circularFocusBuffer;
size_t m_hierarchyMaxDepth{0};
+ QVariantList m_tocNodeSnapshot;
+ bool m_tocRefreshScheduled{false};
};
#endif // WORKSHEET_H
diff --git a/src/worksheetentry.cpp b/src/worksheetentry.cpp
index 1a27cc9f..cac97243 100644
--- a/src/worksheetentry.cpp
+++ b/src/worksheetentry.cpp
@@ -828,8 +828,7 @@ void WorksheetEntry::remove()
else
worksheet()->setLastEntry(previous());
- if (type() == HierarchyEntry::Type)
- worksheet()->updateHierarchyLayout();
+ worksheet()->updateHierarchyLayout();
// make the entry invisible to QGraphicsScene's itemAt() function
forceRemove();
diff --git a/src/worksheettexteditoritem.cpp b/src/worksheettexteditoritem.cpp
index b0a78405..31713322 100644
--- a/src/worksheettexteditoritem.cpp
+++ b/src/worksheettexteditoritem.cpp
@@ -89,7 +89,8 @@ WorksheetTextEditorItem::WorksheetTextEditorItem(EditorMode initialMode, Workshe
m_view->registerCompletionModel(m_completionModel);
connect(m_completionModel, &CantorCompletionModel::modelIsReady, this, &WorksheetTextEditorItem::showCustomCompleter);
- auto* model = session()->variableModel();
+ auto* currentSession = session();
+ auto* model = currentSession ? currentSession->variableModel() : nullptr;
if (model) {
connect(model, &Cantor::DefaultVariableModel::initialModelPopulated, this, [this]() {
if (m_view && m_document)
@@ -987,6 +988,7 @@ bool WorksheetTextEditorItem::eventFilter(QObject* object, QEvent* event)
void WorksheetTextEditorItem::keyPressEvent(QKeyEvent* event)
{
+ worksheet()->updateFocusedTextItem(this);
const int key = event->key();
const auto modifiers = event->modifiers();
worksheet()->resetEntryCursor();
@@ -1122,6 +1124,7 @@ void WorksheetTextEditorItem::focusOutEvent(QFocusEvent* event)
void WorksheetTextEditorItem::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
+ worksheet()->updateFocusedTextItem(this);
QGraphicsProxyWidget::mousePressEvent(event);
KTextEditor::View* view = m_view;
diff --git a/src/worksheettextitem.cpp b/src/worksheettextitem.cpp
index 4d93f196..9fb9fcb0 100644
--- a/src/worksheettextitem.cpp
+++ b/src/worksheettextitem.cpp
@@ -403,6 +403,7 @@ Cantor::Session* WorksheetTextItem::session()
void WorksheetTextItem::keyPressEvent(QKeyEvent* event)
{
+ worksheet()->updateFocusedTextItem(this);
switch (event->key()) {
case Qt::Key_Left:
if (event->modifiers() == Qt::NoModifier && textCursor().atStart()) {
@@ -521,6 +522,7 @@ void WorksheetTextItem::focusOutEvent(QFocusEvent* event)
void WorksheetTextItem::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
+ worksheet()->updateFocusedTextItem(this);
int p = textCursor().position();
bool b = textCursor().hasSelection();
diff --git a/src/worksheetview.cpp b/src/worksheetview.cpp
index dd8f6af0..13760b0a 100644
--- a/src/worksheetview.cpp
+++ b/src/worksheetview.cpp
@@ -25,6 +25,14 @@ WorksheetView::WorksheetView(Worksheet* scene, QWidget* parent) : QGraphicsView(
setRenderHint(QPainter::Antialiasing, true);
setRenderHint(QPainter::TextAntialiasing, true);
setRenderHint(QPainter::SmoothPixmapTransform, true);
+
+ connect(verticalScrollBar(), &QScrollBar::sliderMoved, this, [this](int) {
+ Q_EMIT userScrollStarted();
+ });
+
+ connect(verticalScrollBar(), &QScrollBar::actionTriggered, this, [this](int) {
+ Q_EMIT userScrollStarted();
+ });
}
void WorksheetView::makeVisible(const QRectF& sceneRect)
@@ -128,11 +136,9 @@ void WorksheetView::makeVisible(const QRectF& sceneRect)
void WorksheetView::scrollTo(int y)
{
- if (!verticalScrollBar())
- return;
-
- qreal dy = y - verticalScrollBar()->value();
- scrollBy(dy);
+ QRectF targetRect = viewRect();
+ targetRect.moveTop(y);
+ makeVisible(targetRect);
}
@@ -220,11 +226,10 @@ QPointF WorksheetView::sceneCursorPos() const
QRectF WorksheetView::viewRect() const
{
- const qreal w = viewport()->width() / m_scale;
- const qreal h = viewport()->height() / m_scale;
- qreal y = verticalScrollBar()->value();
- qreal x = horizontalScrollBar() ? horizontalScrollBar()->value() : 0;
- return QRectF(x, y, w, h);
+ if (!viewport())
+ return {};
+
+ return mapToScene(viewport()->rect()).boundingRect();
}
void WorksheetView::resizeEvent(QResizeEvent* event)
@@ -250,11 +255,16 @@ void WorksheetView::wheelEvent(QWheelEvent* event)
{
if ((QApplication::keyboardModifiers() & Qt::ControlModifier)) {
//https://wiki.qt.io/Smooth_Zoom_In_QGraphicsView
- QPoint numDegrees = event->angleDelta() / 8;
- int numSteps = numDegrees.y() / 15; // see QWheelEvent documentation
+ const QPoint numDegrees = event->angleDelta() / 8;
+ const int numSteps = numDegrees.y() / 15;
zoom(numSteps);
- } else
- QGraphicsView::wheelEvent(event);
+ return;
+ }
+ const bool hasVerticalMovement = event->angleDelta().y() != 0 || event->pixelDelta().y() != 0;
+ if (hasVerticalMovement)
+ Q_EMIT userScrollStarted();
+
+ QGraphicsView::wheelEvent(event);
}
void WorksheetView::zoom(int numSteps)
diff --git a/src/worksheetview.h b/src/worksheetview.h
index b93a6ece..80ea0f8c 100644
--- a/src/worksheetview.h
+++ b/src/worksheetview.h
@@ -37,6 +37,7 @@ public:
Q_SIGNALS:
void viewRectChanged(QRectF) const;
void scaleFactorChanged(double scale);
+ void userScrollStarted();
public Q_SLOTS:
void applyThemeToBackground();