[education/cantor] src: Track result items by stable IDs
Alexander Semke <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit afe10574a996a40cffb24072330f1a509e13e8d3 by Alexander Semke, on behalf of Nanhao Lv.
Committed on 26/07/2026 at 19:50.
Pushed by asemke into branch 'master'.
Track result items by stable IDs
M +9 -0 src/animationresultitem.cpp
M +4 -1 src/animationresultitem.h
M +176 -30 src/commandentry.cpp
M +7 -0 src/commandentry.h
M +16 -1 src/imageresultitem.cpp
M +4 -0 src/imageresultitem.h
M +19 -2 src/resultitem.cpp
M +1 -0 src/resultitem.h
https://invent.kde.org/education/cantor/-/commit/afe10574a996a40cffb24072330f1a509e13e8d3
diff --git a/src/animationresultitem.cpp b/src/animationresultitem.cpp
index 7d1b858c..7cc4a00a 100644
--- a/src/animationresultitem.cpp
+++ b/src/animationresultitem.cpp
@@ -10,6 +10,7 @@
#include "lib/animationresult.h"
#include <QFileDialog>
+#include <QGraphicsSceneMouseEvent>
#include <QMovie>
#include <KLocalizedString>
@@ -131,3 +132,11 @@ void AnimationResultItem::deleteLater()
{
WorksheetImageItem::deleteLater();
}
+
+void AnimationResultItem::mousePressEvent(QGraphicsSceneMouseEvent* event)
+{
+ if (auto* commandEntry = parentEntry())
+ commandEntry->resultItemClicked(m_result);
+
+ WorksheetImageItem::mousePressEvent(event);
+}
diff --git a/src/animationresultitem.h b/src/animationresultitem.h
index a9c1f49a..a8b4a0bd 100644
--- a/src/animationresultitem.h
+++ b/src/animationresultitem.h
@@ -10,6 +10,7 @@
#include "worksheetimageitem.h"
class QMovie;
+class QGraphicsSceneMouseEvent;
class CommandEntry;
class WorksheetEntry;
@@ -34,6 +35,9 @@ class AnimationResultItem : public WorksheetImageItem, public ResultItem
double width() const override;
double height() const override;
+ protected:
+ void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
+
protected Q_SLOTS:
void saveResult();
void stopMovie();
@@ -52,4 +56,3 @@ class AnimationResultItem : public WorksheetImageItem, public ResultItem
};
#endif //ANIMATIONRESULTITEM_H
-
diff --git a/src/commandentry.cpp b/src/commandentry.cpp
index 8d777453..d0be704b 100644
--- a/src/commandentry.cpp
+++ b/src/commandentry.cpp
@@ -35,10 +35,10 @@
#include <QTextBlock>
#include <QTextDocumentFragment>
#include <QPainter>
+#include <QUuid>
#include <KLocalizedString>
#include <KColorScheme>
-#include <KSyntaxHighlighting/Repository>
#include <KSyntaxHighlighting/Definition>
const QString CommandEntry::Prompt = QLatin1String(">>> ");
@@ -46,42 +46,29 @@ const QString CommandEntry::MidPrompt = QLatin1String(">> ");
const QString CommandEntry::HidePrompt = QLatin1String("> ");
const double CommandEntry::VerticalSpacing = 4;
-namespace {
- QString getThemeNameFromIndex (int index)
- {
- if (index <= 0)
- return QString ();
- const auto& repository = KTextEditor::Editor::instance()->repository();
- const auto& themes = repository.themes();
-
- const int themeListIndex = index - 1;
- if (themeListIndex>= 0 && themeListIndex < themes.count ())
- return themes.at (themeListIndex).name ();
-
- return QString ();
- }
-}
-
CommandEntry::CommandEntry(Worksheet* worksheet) : WorksheetEntry(worksheet),
m_promptItem(new WorksheetTextItem(this, Qt::NoTextInteraction)),
+ m_commandId(QUuid::createUuid().toString(QUuid::WithoutBraces)),
m_commandItem(new WorksheetTextEditorItem(WorksheetTextEditorItem::Editable, this, this)),
m_resultsCollapsed(false),
m_errorItem(nullptr),
m_expression(nullptr),
+ m_dynamicHighlighter(nullptr),
m_evaluationOption(DoNothing),
+ m_promptItemAnimation(nullptr),
m_menusInitialized(false),
m_textColorCustom(false),
m_backgroundColorCustom(false),
m_backgroundColorActionGroup(nullptr),
m_backgroundColorMenu(nullptr),
m_textColorActionGroup(nullptr),
+ m_themeActionGroup(nullptr),
m_textColorMenu(nullptr),
m_fontMenu(nullptr),
- m_isExecutionEnabled(true),
- m_dynamicHighlighter(nullptr)
+ m_isExecutionEnabled(true)
{
m_promptItem->setPlainText(Prompt);
- m_promptItem->setItemDragable(true);;
+ m_promptItem->setItemDragable(true);
m_commandItem->enableCompletion(true);
if(worksheet && worksheet->session() && worksheet->session()->backend())
@@ -430,6 +417,46 @@ QString CommandEntry::command()
return cmd;
}
+const QString& CommandEntry::commandId() const
+{
+ return m_commandId;
+}
+
+void CommandEntry::regenerateCommandId()
+{
+ m_commandId = QUuid::createUuid().toString(QUuid::WithoutBraces);
+}
+
+int CommandEntry::resultItemCount() const
+{
+ return m_resultItems.size();
+}
+
+ResultItem* CommandEntry::resultItemAt(int index) const
+{
+ if (index < 0 || index >= m_resultItems.size())
+ return nullptr;
+
+ return m_resultItems.at(index);
+}
+
+ResultItem* CommandEntry::resultItemById(const QString& resultId) const
+{
+ if (resultId.isEmpty())
+ return nullptr;
+
+ for (auto* item : m_resultItems)
+ {
+ if (!item || !item->result())
+ continue;
+
+ if (item->result()->resultId() == resultId)
+ return item;
+ }
+
+ return nullptr;
+}
+
void CommandEntry::setExpression(Cantor::Expression* expr)
{
/*
@@ -465,7 +492,12 @@ void CommandEntry::setExpression(Cantor::Expression* expr)
connect(expr, &Cantor::Expression::resultsCleared, this, &CommandEntry::clearResultItems);
connect(expr, &Cantor::Expression::resultRemoved, this, &CommandEntry::removeResultItem);
connect(expr, &Cantor::Expression::resultReplaced, this, &CommandEntry::replaceResultItem);
- connect(expr, &Cantor::Expression::idChanged, this, [=]() { updatePrompt();} );
+ connect(expr, &Cantor::Expression::idChanged, this, [=]()
+ {
+ updatePrompt();
+ if (worksheet())
+ worksheet()->refreshTocStructure();
+ });
connect(expr, &Cantor::Expression::statusChanged, this, &CommandEntry::expressionChangedStatus);
connect(expr, &Cantor::Expression::needsAdditionalInformation, this, &CommandEntry::showAdditionalInformationPrompt);
connect(expr, &Cantor::Expression::statusChanged, this, [=]() { updatePrompt();} );
@@ -499,6 +531,10 @@ void CommandEntry::setContent(const QString& content)
void CommandEntry::setContent(const QDomElement& content, const KZip& file)
{
m_commandItem->setPlainText(content.firstChildElement(QLatin1String("Command")).text());
+ const QString storedCommandId = content.attribute(QLatin1String("command-id"));
+
+ if (!storedCommandId.isEmpty())
+ m_commandId = storedCommandId;
LoadedExpression* expr = new LoadedExpression( worksheet()->session() );
expr->loadFromXml(content, file);
@@ -556,6 +592,11 @@ void CommandEntry::setContent(const QDomElement& content, const KZip& file)
void CommandEntry::setContentFromJupyter(const QJsonObject& cell)
{
m_commandItem->setPlainText(Cantor::JupyterUtils::getSource(cell));
+ const QJsonObject cantorMetadata = Cantor::JupyterUtils::getCantorMetadata(cell);
+ const QString storedCommandId = cantorMetadata.value(QLatin1String("command-id")).toString();
+
+ if (!storedCommandId.isEmpty())
+ m_commandId = storedCommandId;
LoadedExpression* expr=new LoadedExpression( worksheet()->session() );
expr->loadFromJupyter(cell);
@@ -597,6 +638,10 @@ QJsonValue CommandEntry::toJupyterJson()
if (m_resultsCollapsed)
metadata.insert(QLatin1String("collapsed"), true);
+ QJsonObject cantorMetadata = metadata.value(Cantor::JupyterUtils::cantorMetadataKey).toObject();
+ cantorMetadata.insert(QLatin1String("command-id"), m_commandId);
+ metadata.insert(Cantor::JupyterUtils::cantorMetadataKey, cantorMetadata);
+
entry.insert(QLatin1String("metadata"), metadata);
Cantor::JupyterUtils::setSource(entry, command());
@@ -653,6 +698,7 @@ QString CommandEntry::toPlain(const QString& commandSep, const QString& commentS
QDomElement CommandEntry::toXml(QDomDocument& doc, KZip* archive)
{
QDomElement exprElem = doc.createElement( QLatin1String("Expression") );
+ exprElem.setAttribute(QLatin1String("command-id"), m_commandId);
QDomElement cmdElem = doc.createElement( QLatin1String("Command") );
cmdElem.appendChild(doc.createTextNode( command() ));
exprElem.appendChild(cmdElem);
@@ -832,13 +878,33 @@ void CommandEntry::updateEntry()
if (m_resultsCollapsed)
expandResults();
+ bool addedResultItem = false;
for (int i = m_resultItems.size(); i < expr->results().size(); i++)
- m_resultItems << ResultItem::create(this, expr->results()[i]);
+ {
+ if (auto* resultItem = ResultItem::create(this, expr->results()[i]))
+ {
+ m_resultItems << resultItem;
+ addedResultItem = true;
+ }
+ }
+
+ if (addedResultItem && worksheet())
+ worksheet()->scheduleTocStructureRefresh();
}
else
{
- for (ResultItem* item: m_resultItems)
+ for (int i = 0; i < m_resultItems.size(); ++i)
+ {
+ auto* item = m_resultItems.at(i);
+ auto* result = i < expr->results().size() ? expr->results().at(i) : nullptr;
+ if (!item || item->result() != result)
+ {
+ replaceResultItem(i);
+ continue;
+ }
+
item->update();
+ }
}
m_controlElement.isCollapsable = m_errorItem != nullptr
@@ -999,27 +1065,107 @@ void CommandEntry::removeResult(Cantor::Result* result)
void CommandEntry::removeResultItem(int index)
{
- fadeOutItem(m_resultItems[index]->graphicsObject());
- m_resultItems.remove(index);
+ if (index < 0 || index >= m_resultItems.size())
+ return;
+
+ auto* item = m_resultItems.takeAt(index);
+ if (item)
+ {
+ item->setResult(nullptr);
+ if (auto* object = item->graphicsObject())
+ object->hide();
+ item->deleteLater();
+ }
+
recalculateSize();
+ if (worksheet())
+ worksheet()->scheduleTocStructureRefresh();
}
void CommandEntry::clearResultItems()
{
- //fade out all result graphic objects
- for(auto* item : m_resultItems)
- fadeOutItem(item->graphicsObject());
+ const bool hadResultItems = !m_resultItems.isEmpty();
+
+ for (auto* item : m_resultItems)
+ {
+ if (!item)
+ continue;
+
+ item->setResult(nullptr);
+ if (auto* object = item->graphicsObject())
+ object->hide();
+ item->deleteLater();
+ }
m_resultItems.clear();
recalculateSize();
+ if (hadResultItems && worksheet())
+ worksheet()->scheduleTocStructureRefresh();
}
void CommandEntry::replaceResultItem(int index)
{
+ if (!m_expression)
+ return;
+
+ const auto& results = m_expression->results();
+ if (index < 0 || index >= results.size())
+ return;
+
+ auto* newItem = ResultItem::create(this, results[index]);
+ if (index >= m_resultItems.size())
+ {
+ if (newItem)
+ {
+ if (index == m_resultItems.size())
+ m_resultItems.append(newItem);
+ else
+ {
+ newItem->deleteLater();
+ return;
+ }
+
+ recalculateSize();
+ if (worksheet())
+ worksheet()->scheduleTocStructureRefresh();
+ }
+ return;
+ }
+
auto* previousItem = m_resultItems[index];
- m_resultItems[index] = ResultItem::create(this, m_expression->results()[index]);
- previousItem->deleteLater();
+ if (!newItem)
+ {
+ if (previousItem)
+ {
+ previousItem->setResult(nullptr);
+ if (auto* object = previousItem->graphicsObject())
+ object->hide();
+ previousItem->deleteLater();
+ }
+ m_resultItems.remove(index);
+ recalculateSize();
+ if (worksheet())
+ worksheet()->scheduleTocStructureRefresh();
+ return;
+ }
+
+ m_resultItems[index] = newItem;
+ if (previousItem)
+ {
+ previousItem->setResult(nullptr);
+ if (previousItem->graphicsObject())
+ previousItem->graphicsObject()->hide();
+ previousItem->deleteLater();
+ }
recalculateSize();
+ if (worksheet())
+ worksheet()->scheduleTocStructureRefresh();
+}
+
+void CommandEntry::resultItemClicked(Cantor::Result* result)
+{
+ if (worksheet())
+ worksheet()->updateCurrentTocNodeFromResult(this, result);
}
void CommandEntry::updatePrompt(const QString& postfix)
diff --git a/src/commandentry.h b/src/commandentry.h
index 12828a8e..5da8fd6e 100644
--- a/src/commandentry.h
+++ b/src/commandentry.h
@@ -39,6 +39,8 @@ class CommandEntry : public WorksheetEntry
int type() const override;
QString command();
+ const QString& commandId() const;
+ void regenerateCommandId();
void setExpression(Cantor::Expression*);
Cantor::Expression* expression();
@@ -47,6 +49,9 @@ class CommandEntry : public WorksheetEntry
bool isEmpty() override;
bool isExcludedFromExecution();
bool isResultCollapsed();
+ int resultItemCount() const;
+ ResultItem* resultItemAt(int index) const;
+ ResultItem* resultItemById(const QString& resultId) const;
void setContent(const QString&) override;
void setContent(const QDomElement&, const KZip&) override;
@@ -105,6 +110,7 @@ class CommandEntry : public WorksheetEntry
void populateMenu(QMenu*, QPointF) override;
void updateAfterSettingsChanges() override;
+ void resultItemClicked(Cantor::Result* result);
protected:
bool wantToEvaluate() override;
@@ -121,6 +127,7 @@ class CommandEntry : public WorksheetEntry
static const double VerticalSpacing;
WorksheetTextItem* m_promptItem;
+ QString m_commandId;
WorksheetTextEditorItem* m_commandItem;
QVector<ResultItem*> m_resultItems;
bool m_resultsCollapsed;
diff --git a/src/imageresultitem.cpp b/src/imageresultitem.cpp
index 2aa37f44..cffd2e05 100644
--- a/src/imageresultitem.cpp
+++ b/src/imageresultitem.cpp
@@ -8,11 +8,13 @@
#include "commandentry.h"
#include "worksheetview.h"
#include "lib/imageresult.h"
+#include "lib/pdfresult.h"
#include <config-cantor.h>
#include <KLocalizedString>
#include <QFileDialog>
+#include <QGraphicsSceneMouseEvent>
#include <QImageReader>
ImageResultItem::ImageResultItem(QGraphicsObject* parent, Cantor::Result* result)
@@ -35,7 +37,7 @@ void ImageResultItem::populateMenu(QMenu* menu, QPointF)
void ImageResultItem::update()
{
- Q_ASSERT(m_result->type() == Cantor::ImageResult::Type);
+ Q_ASSERT(m_result->type() == Cantor::ImageResult::Type || m_result->type() == Cantor::PdfResult::Type);
switch(m_result->type()) {
case Cantor::ImageResult::Type:
{
@@ -46,6 +48,9 @@ void ImageResultItem::update()
setImage(m_result->data().value<QImage>());
}
break;
+ case Cantor::PdfResult::Type:
+ setImage(m_result->data().value<QImage>());
+ break;
default:
break;
}
@@ -74,6 +79,8 @@ void ImageResultItem::saveResult()
auto* imageResult = static_cast<Cantor::ImageResult*>(result());
format = i18nc("%1 and %2 are file extensions", "%1 files (*.%2)", imageResult->extension().toUpper(), imageResult->extension());
}
+ else if (m_result->type() == Cantor::PdfResult::Type)
+ format = i18n("PDF files (*.pdf)");
else
format = i18n("EPS files (*.eps)");
@@ -89,3 +96,11 @@ void ImageResultItem::deleteLater()
{
WorksheetImageItem::deleteLater();
}
+
+void ImageResultItem::mousePressEvent(QGraphicsSceneMouseEvent* event)
+{
+ if (auto* commandEntry = parentEntry())
+ commandEntry->resultItemClicked(m_result);
+
+ WorksheetImageItem::mousePressEvent(event);
+}
diff --git a/src/imageresultitem.h b/src/imageresultitem.h
index 6592863d..c642074e 100644
--- a/src/imageresultitem.h
+++ b/src/imageresultitem.h
@@ -10,6 +10,7 @@
#include "worksheetimageitem.h"
class CommandEntry;
+class QGraphicsSceneMouseEvent;
class ImageResultItem : public WorksheetImageItem, public ResultItem
{
@@ -30,6 +31,9 @@ class ImageResultItem : public WorksheetImageItem, public ResultItem
void deleteLater() override;
+ protected:
+ void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
+
protected Q_SLOTS:
void saveResult();
};
diff --git a/src/resultitem.cpp b/src/resultitem.cpp
index f04af635..dc49939e 100644
--- a/src/resultitem.cpp
+++ b/src/resultitem.cpp
@@ -14,6 +14,7 @@
#include "lib/latexresult.h"
#include "lib/imageresult.h"
#include "lib/animationresult.h"
+#include "lib/pdfresult.h"
#include "lib/mimeresult.h"
#include "lib/htmlresult.h"
@@ -26,9 +27,17 @@ ResultItem::ResultItem(Cantor::Result* result):
ResultItem* ResultItem::create(WorksheetEntry* parent, Cantor::Result* result)
{
- switch(result->type()) {
+ if (!result)
+ return nullptr;
+
+ if (result->type() == Cantor::LatexResult::Type)
+ return new TextResultItem(parent, result);
+
+ if (dynamic_cast<Cantor::PdfResult*>(result))
+ return new ImageResultItem(parent, result);
+
+ switch (result->type()) {
case Cantor::TextResult::Type:
- case Cantor::LatexResult::Type:
case Cantor::MimeResult::Type:
case Cantor::HtmlResult::Type:
return new TextResultItem(parent, result);
@@ -64,7 +73,15 @@ Cantor::Result* ResultItem::result()
return m_result;
}
+void ResultItem::setResult(Cantor::Result* result)
+{
+ m_result = result;
+}
+
void ResultItem::needRemove()
{
+ if (!m_result)
+ return;
+
parentEntry()->removeResult(m_result);
}
diff --git a/src/resultitem.h b/src/resultitem.h
index f05d0d95..a4e84779 100644
--- a/src/resultitem.h
+++ b/src/resultitem.h
@@ -48,6 +48,7 @@ class ResultItem
virtual void updateTheme() {};
QGraphicsObject* graphicsObject();
Cantor::Result* result();
+ void setResult(Cantor::Result* result);
CommandEntry* parentEntry();
protected: