[education/cantor] src: Add custom command titles in the structure navigator
Alexander Semke <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit ac5e314c6b6a5a541d00bba64a4138c7fb0447b8 by Alexander Semke, on behalf of Nanhao Lv.
Committed on 26/07/2026 at 19:50.
Pushed by asemke into branch 'master'.
Add custom command titles in the structure navigator
M +14 -0 src/cantor.cpp
M +4 -0 src/cantor.h
M +2 -0 src/cantor_part.cpp
M +2 -0 src/cantor_part.h
M +69 -1 src/commandentry.cpp
M +9 -0 src/commandentry.h
M +56 -7 src/panelplugins/tocpanel/tocpanelplugin.cpp
M +2 -0 src/panelplugins/tocpanel/tocpanelplugin.h
M +10 -0 src/worksheet.cpp
M +2 -0 src/worksheet.h
M +18 -2 src/worksheetentry.cpp
M +52 -4 src/worksheethierarchymanager.cpp
M +3 -7 src/worksheethierarchymanager.h
https://invent.kde.org/education/cantor/-/commit/ac5e314c6b6a5a541d00bba64a4138c7fb0447b8
diff --git a/src/cantor.cpp b/src/cantor.cpp
index 4c0efda2..703008bb 100644
--- a/src/cantor.cpp
+++ b/src/cantor.cpp
@@ -68,6 +68,8 @@ CantorShell::CantorShell() : KParts::MainWindow(), m_tabWidget(new QTabWidget(th
connect(this, &CantorShell::requestChangeHierarchyLevel, this, &CantorShell::forwardChangeHierarchyLevel);
connect(this, &CantorShell::requestDeleteHierarchyEntry, this, &CantorShell::forwardDeleteHierarchyEntry);
connect(this, &CantorShell::requestRenameHierarchyEntry, this, &CantorShell::forwardRenameHierarchyEntry);
+ connect(this, &CantorShell::requestRenameCommandEntry, this, &CantorShell::forwardRenameCommandEntry);
+ connect(this, &CantorShell::requestDeleteCommandEntry, this, &CantorShell::forwardDeleteCommandEntry);
connect(this, &CantorShell::requestRenamePlot, this, &CantorShell::forwardRenamePlot);
connect(this, &CantorShell::requestDeletePlot, this, &CantorShell::forwardDeletePlot);
@@ -266,6 +268,18 @@ void CantorShell::forwardDeleteHierarchyEntry(const QString& hierarchyId, bool d
QMetaObject::invokeMethod(m_part, "requestDeleteHierarchyEntry", Qt::DirectConnection, Q_ARG(QString, hierarchyId), Q_ARG(bool, deleteContents));
}
+void CantorShell::forwardRenameCommandEntry(const QString& commandId, const QString& newTitle)
+{
+ if (m_part)
+ QMetaObject::invokeMethod(m_part, "requestRenameCommandEntry", Qt::DirectConnection, Q_ARG(QString, commandId), Q_ARG(QString, newTitle));
+}
+
+void CantorShell::forwardDeleteCommandEntry(const QString& commandId)
+{
+ if (m_part)
+ QMetaObject::invokeMethod(m_part, "requestDeleteCommandEntry", Qt::DirectConnection, Q_ARG(QString, commandId));
+}
+
void CantorShell::forwardRenamePlot(const QString& commandId, const QString& resultId, const QString& newTitle)
{
if (m_part)
diff --git a/src/cantor.h b/src/cantor.h
index 9fd7880d..282770b0 100644
--- a/src/cantor.h
+++ b/src/cantor.h
@@ -73,6 +73,8 @@ Q_SIGNALS:
void requestRenameHierarchyEntry(QString hierarchyId, QString newName);
void requestChangeHierarchyLevel(QString hierarchyId, int levelDelta);
void requestDeleteHierarchyEntry(QString hierarchyId, bool deleteContents);
+ void requestRenameCommandEntry(QString commandId, QString newTitle);
+ void requestDeleteCommandEntry(QString commandId);
void requestRenamePlot(QString commandId, QString resultId, QString newTitle);
void requestDeletePlot(QString commandId, QString resultId);
void tocReadOnlyChanged(bool readOnly);
@@ -98,6 +100,8 @@ private Q_SLOTS:
void forwardRenameHierarchyEntry(const QString& hierarchyId, const QString& newName);
void forwardChangeHierarchyLevel(const QString& hierarchyId, int levelDelta);
void forwardDeleteHierarchyEntry(const QString& hierarchyId, bool deleteContents);
+ void forwardRenameCommandEntry(const QString& commandId, const QString& newTitle);
+ void forwardDeleteCommandEntry(const QString& commandId);
void forwardRenamePlot(const QString& commandId, const QString& resultId, const QString& newTitle);
void forwardDeletePlot(const QString& commandId, const QString& resultId);
void handleTocNodesChanged(QVariantList nodes);
diff --git a/src/cantor_part.cpp b/src/cantor_part.cpp
index ad3e43d9..aeb2e4bf 100644
--- a/src/cantor_part.cpp
+++ b/src/cantor_part.cpp
@@ -139,6 +139,8 @@ CantorPart::CantorPart(QObject* parent, const QVariantList& args)
connect(this, &CantorPart::requestRenameHierarchyEntry, m_worksheet, &Worksheet::renameHierarchyEntry);
connect(this, &CantorPart::requestChangeHierarchyLevel, m_worksheet, &Worksheet::changeHierarchyLevel);
connect(this, &CantorPart::requestDeleteHierarchyEntry, m_worksheet, &Worksheet::deleteHierarchyEntry);
+ connect(this, &CantorPart::requestRenameCommandEntry, m_worksheet, &Worksheet::renameCommandEntry);
+ connect(this, &CantorPart::requestDeleteCommandEntry, m_worksheet, &Worksheet::deleteCommandEntry);
connect(this, &CantorPart::requestRenamePlot, m_worksheet, &Worksheet::renamePlot);
connect(this, &CantorPart::requestDeletePlot, m_worksheet, &Worksheet::deletePlot);
connect(this, &CantorPart::requestTocNodeSnapshot, m_worksheet, &Worksheet::emitTocNodeSnapshot);
diff --git a/src/cantor_part.h b/src/cantor_part.h
index 3076ecb1..b036aeac 100644
--- a/src/cantor_part.h
+++ b/src/cantor_part.h
@@ -76,6 +76,8 @@ Q_SIGNALS:
void requestNavigateToTocNode(QString nodeId);
void requestChangeHierarchyLevel(QString hierarchyId, int levelDelta);
void requestDeleteHierarchyEntry(QString hierarchyId, bool deleteContents);
+ void requestRenameCommandEntry(QString commandId, QString newTitle);
+ void requestDeleteCommandEntry(QString commandId);
void requestRenamePlot(QString commandId, QString resultId, QString newTitle);
void requestDeletePlot(QString commandId, QString resultId);
void tocReadOnlyChanged(bool readOnly);
diff --git a/src/commandentry.cpp b/src/commandentry.cpp
index d0be704b..bf4e2115 100644
--- a/src/commandentry.cpp
+++ b/src/commandentry.cpp
@@ -427,6 +427,16 @@ void CommandEntry::regenerateCommandId()
m_commandId = QUuid::createUuid().toString(QUuid::WithoutBraces);
}
+QString CommandEntry::displayName() const
+{
+ return m_displayName;
+}
+
+void CommandEntry::setDisplayName(const QString& name)
+{
+ m_displayName = name.trimmed();
+}
+
int CommandEntry::resultItemCount() const
{
return m_resultItems.size();
@@ -457,6 +467,37 @@ ResultItem* CommandEntry::resultItemById(const QString& resultId) const
return nullptr;
}
+void CommandEntry::cachePlotResultMetadata()
+{
+ m_plotResultMetadataToRestore.clear();
+
+ if (!m_expression)
+ return;
+
+ for (auto* result : m_expression->results())
+ {
+ if (!result || result->role() != Cantor::Result::Role::Plot)
+ continue;
+
+ m_plotResultMetadataToRestore.append({result->resultId(), result->displayName()});
+ }
+}
+
+void CommandEntry::restorePlotResultMetadata(Cantor::Result* result, int plotIndex)
+{
+ if (!result || result->role() != Cantor::Result::Role::Plot)
+ return;
+
+ if (plotIndex < 0 || plotIndex >= m_plotResultMetadataToRestore.size())
+ return;
+
+ const PlotResultMetadata& metadata = m_plotResultMetadataToRestore.at(plotIndex);
+ if (!metadata.first.isEmpty())
+ result->setResultId(metadata.first);
+ if (!metadata.second.isEmpty())
+ result->setDisplayName(metadata.second);
+}
+
void CommandEntry::setExpression(Cantor::Expression* expr)
{
/*
@@ -536,6 +577,8 @@ void CommandEntry::setContent(const QDomElement& content, const KZip& file)
if (!storedCommandId.isEmpty())
m_commandId = storedCommandId;
+ m_displayName = content.attribute(QLatin1String("command-title")).trimmed();
+
LoadedExpression* expr = new LoadedExpression( worksheet()->session() );
expr->loadFromXml(content, file);
@@ -598,6 +641,12 @@ void CommandEntry::setContentFromJupyter(const QJsonObject& cell)
if (!storedCommandId.isEmpty())
m_commandId = storedCommandId;
+ const QJsonValue storedTitle = cantorMetadata.value(QLatin1String("command-title"));
+ if (storedTitle.isString())
+ m_displayName = storedTitle.toString().trimmed();
+ else
+ m_displayName.clear();
+
LoadedExpression* expr=new LoadedExpression( worksheet()->session() );
expr->loadFromJupyter(cell);
setExpression(expr);
@@ -640,6 +689,10 @@ QJsonValue CommandEntry::toJupyterJson()
QJsonObject cantorMetadata = metadata.value(Cantor::JupyterUtils::cantorMetadataKey).toObject();
cantorMetadata.insert(QLatin1String("command-id"), m_commandId);
+ if (m_displayName.isEmpty())
+ cantorMetadata.remove(QLatin1String("command-title"));
+ else
+ cantorMetadata.insert(QLatin1String("command-title"), m_displayName);
metadata.insert(Cantor::JupyterUtils::cantorMetadataKey, cantorMetadata);
entry.insert(QLatin1String("metadata"), metadata);
@@ -699,6 +752,8 @@ QDomElement CommandEntry::toXml(QDomDocument& doc, KZip* archive)
{
QDomElement exprElem = doc.createElement( QLatin1String("Expression") );
exprElem.setAttribute(QLatin1String("command-id"), m_commandId);
+ if (!m_displayName.isEmpty())
+ exprElem.setAttribute(QLatin1String("command-title"), m_displayName);
QDomElement cmdElem = doc.createElement( QLatin1String("Command") );
cmdElem.appendChild(doc.createTextNode( command() ));
exprElem.appendChild(cmdElem);
@@ -839,6 +894,7 @@ bool CommandEntry::evaluate(EvaluationOption evalOp)
auto* expr = worksheet()->session()->evaluateExpression(cmd);
connect(expr, &Cantor::Expression::gotResult, this, [=]() { worksheet()->gotResult(expr); });
+ cachePlotResultMetadata();
setExpression(expr);
return true;
@@ -879,9 +935,21 @@ void CommandEntry::updateEntry()
expandResults();
bool addedResultItem = false;
+ int plotIndex = 0;
+ for (int i = 0; i < m_resultItems.size() && i < expr->results().size(); ++i)
+ {
+ auto* result = expr->results().at(i);
+ if (result && result->role() == Cantor::Result::Role::Plot)
+ ++plotIndex;
+ }
+
for (int i = m_resultItems.size(); i < expr->results().size(); i++)
{
- if (auto* resultItem = ResultItem::create(this, expr->results()[i]))
+ auto* result = expr->results()[i];
+ if (result && result->role() == Cantor::Result::Role::Plot)
+ restorePlotResultMetadata(result, plotIndex++);
+
+ if (auto* resultItem = ResultItem::create(this, result))
{
m_resultItems << resultItem;
addedResultItem = true;
diff --git a/src/commandentry.h b/src/commandentry.h
index 5da8fd6e..98a93038 100644
--- a/src/commandentry.h
+++ b/src/commandentry.h
@@ -9,6 +9,7 @@
#define COMMANDENTRY_H
#include <QPointer>
+#include <QPair>
#include <QTimer>
#include "worksheetentry.h"
@@ -41,6 +42,8 @@ class CommandEntry : public WorksheetEntry
QString command();
const QString& commandId() const;
void regenerateCommandId();
+ QString displayName() const;
+ void setDisplayName(const QString& name);
void setExpression(Cantor::Expression*);
Cantor::Expression* expression();
@@ -122,6 +125,10 @@ class CommandEntry : public WorksheetEntry
QPoint toGlobalPosition(QPointF);
void initMenus();
+ using PlotResultMetadata = QPair<QString, QString>;
+
+ void cachePlotResultMetadata();
+ void restorePlotResultMetadata(Cantor::Result* result, int plotIndex);
enum CompletionMode {PreliminaryCompletion, FinalCompletion};
static const double VerticalSpacing;
@@ -134,6 +141,8 @@ class CommandEntry : public WorksheetEntry
WorksheetTextItem* m_errorItem;
QList<WorksheetTextItem*> m_informationItems;
Cantor::Expression* m_expression;
+ QVector<PlotResultMetadata> m_plotResultMetadataToRestore;
+ QString m_displayName;
DynamicHighlighter* m_dynamicHighlighter;
bool m_variableHighlightingEnabled = true;
diff --git a/src/panelplugins/tocpanel/tocpanelplugin.cpp b/src/panelplugins/tocpanel/tocpanelplugin.cpp
index a04937f9..4f96fa7c 100644
--- a/src/panelplugins/tocpanel/tocpanelplugin.cpp
+++ b/src/panelplugins/tocpanel/tocpanelplugin.cpp
@@ -74,13 +74,13 @@ public:
}
const QString nodeType = index.data(m_nodeTypeRole).toString();
- const bool isPlotNode = nodeType == TocNodeTypePlot;
+ const bool usesCustomTitle = nodeType == TocNodeTypePlot || nodeType == TocNodeTypeCommand;
const QString title = index.data(m_nameRole).toString();
const QString customTitle = index.data(m_customTitleRole).toString();
- const QString editTitle = isPlotNode && !customTitle.isEmpty() ? customTitle : title;
+ const QString editTitle = usesCustomTitle && !customTitle.isEmpty() ? customTitle : title;
- lineEdit->setText(isPlotNode ? editTitle : title);
- if (isPlotNode)
+ lineEdit->setText(usesCustomTitle ? editTitle : title);
+ if (usesCustomTitle)
lineEdit->setPlaceholderText(title);
lineEdit->setProperty("tocNodeId", index.data(m_nodeIdRole));
@@ -88,7 +88,7 @@ public:
lineEdit->setProperty("tocHierarchyId", index.data(m_hierarchyIdRole));
lineEdit->setProperty("tocCommandId", index.data(m_entryIdRole));
lineEdit->setProperty("tocResultId", index.data(m_resultIdRole));
- lineEdit->setProperty("tocOriginalTitle", isPlotNode ? editTitle : title);
+ lineEdit->setProperty("tocOriginalTitle", usesCustomTitle ? editTitle : title);
lineEdit->selectAll();
}
@@ -141,6 +141,8 @@ void TableOfContentPanelPlugin::connectToShell(QObject* cantorShell)
connect(cantorShell, SIGNAL(currentTocNodeChanged(QString)), this, SLOT(handleCurrentTocNodeChanged(QString)));
connect(this, SIGNAL(requestChangeHierarchyLevel(QString,int)), cantorShell, SIGNAL(requestChangeHierarchyLevel(QString,int)));
connect(this, SIGNAL(requestDeleteHierarchyEntry(QString,bool)), cantorShell, SIGNAL(requestDeleteHierarchyEntry(QString,bool)));
+ connect(this, SIGNAL(requestRenameCommandEntry(QString,QString)), cantorShell, SIGNAL(requestRenameCommandEntry(QString,QString)));
+ connect(this, SIGNAL(requestDeleteCommandEntry(QString)), cantorShell, SIGNAL(requestDeleteCommandEntry(QString)));
connect(this, SIGNAL(requestRenamePlot(QString,QString,QString)), cantorShell, SIGNAL(requestRenamePlot(QString,QString,QString)));
connect(this, SIGNAL(requestDeletePlot(QString,QString)), cantorShell, SIGNAL(requestDeletePlot(QString,QString)));
connect(cantorShell, SIGNAL(tocReadOnlyChanged(bool)), this, SLOT(handleReadOnlyChanged(bool)));
@@ -523,11 +525,15 @@ void TableOfContentPanelPlugin::beginRename(const QModelIndex& index)
const QString hierarchyId = index.data(HierarchyIdRole).toString();
const QString nodeType = index.data(NodeTypeRole).toString();
const bool isPlotNode = nodeType == TocNodeTypePlot;
+ const bool isCommandNode = nodeType == TocNodeTypeCommand;
if (nodeId.isEmpty() || !index.data(EditableRole).toBool())
return;
- if (!isPlotNode && hierarchyId.isEmpty())
+ if (!isPlotNode && !isCommandNode && hierarchyId.isEmpty())
+ return;
+
+ if (isCommandNode && index.data(EntryIdRole).toString().isEmpty())
return;
if (isPlotNode && (index.data(EntryIdRole).toString().isEmpty() || index.data(ResultIdRole).toString().isEmpty()))
@@ -589,7 +595,8 @@ void TableOfContentPanelPlugin::handleEditorCommit(QWidget* editor)
if (position >= 0 && position < m_nodes.size())
{
const TocNode& node = m_nodes.at(position);
- oldName = nodeType == TocNodeTypePlot && !node.customTitle.isEmpty() ? node.customTitle : node.title;
+ const bool usesCustomTitle = nodeType == TocNodeTypePlot || nodeType == TocNodeTypeCommand;
+ oldName = usesCustomTitle && !node.customTitle.isEmpty() ? node.customTitle : node.title;
}
if (newName == oldName)
@@ -603,6 +610,13 @@ void TableOfContentPanelPlugin::handleEditorCommit(QWidget* editor)
m_pendingRenameCommandId = commandId;
m_pendingRenameResultId = resultId;
}
+ else if (nodeType == TocNodeTypeCommand)
+ {
+ if (commandId.isEmpty())
+ return;
+
+ m_pendingRenameCommandId = commandId;
+ }
else
{
if (hierarchyId.isEmpty())
@@ -655,6 +669,8 @@ void TableOfContentPanelPlugin::finishEditorSession()
if (nodeType == TocNodeTypePlot)
Q_EMIT requestRenamePlot(commandId, resultId, title);
+ else if (nodeType == TocNodeTypeCommand)
+ Q_EMIT requestRenameCommandEntry(commandId, title);
else
Q_EMIT requestRenameHierarchyEntry(hierarchyId, title);
}
@@ -899,6 +915,8 @@ void TableOfContentPanelPlugin::handleContextMenuRequested(const QPoint& positio
}
else if (nodeType == TocNodeTypeCommand)
{
+ const QString commandId = index.data(EntryIdRole).toString();
+ const QString displayText = index.data(DisplayTextRole).toString();
QAction* goToCommandAction = menu.addAction(i18n("Go to Command"));
goToCommandAction->setEnabled(!nodeId.isEmpty() && index.data(NavigableRole).toBool());
connect(goToCommandAction, &QAction::triggered, this, [this, nodeId]()
@@ -906,6 +924,37 @@ void TableOfContentPanelPlugin::handleContextMenuRequested(const QPoint& positio
if (!nodeId.isEmpty())
Q_EMIT requestNavigateToTocNode(nodeId);
});
+
+ menu.addSeparator();
+
+ QAction* renameAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18n("Rename Command"));
+ renameAction->setEnabled(!m_readOnly && !nodeId.isEmpty() && !commandId.isEmpty());
+ connect(renameAction, &QAction::triggered, this, [this, nodeId]()
+ {
+ if (auto* item = m_itemsByNodeId.value(nodeId, nullptr))
+ beginRename(item->index());
+ });
+
+ QAction* deleteAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete Command"));
+ deleteAction->setEnabled(!m_readOnly && !commandId.isEmpty());
+ connect(deleteAction, &QAction::triggered, this, [this, commandId, displayText]()
+ {
+ if (Settings::warnAboutEntryDelete())
+ {
+ const QString commandTitle = displayText.isEmpty() ? i18n("Command") : displayText;
+ const auto result = KMessageBox::warningTwoActions(
+ m_mainWidget,
+ i18n("Do you really want to delete \"%1\"? This action cannot be undone.", commandTitle),
+ i18n("Delete Command"),
+ KStandardGuiItem::remove(),
+ KStandardGuiItem::cancel());
+
+ if (result != KMessageBox::PrimaryAction)
+ return;
+ }
+
+ Q_EMIT requestDeleteCommandEntry(commandId);
+ });
}
else if (nodeType == TocNodeTypePlot)
{
diff --git a/src/panelplugins/tocpanel/tocpanelplugin.h b/src/panelplugins/tocpanel/tocpanelplugin.h
index 3a26c9b0..6acc0335 100644
--- a/src/panelplugins/tocpanel/tocpanelplugin.h
+++ b/src/panelplugins/tocpanel/tocpanelplugin.h
@@ -46,6 +46,8 @@ class TableOfContentPanelPlugin : public Cantor::PanelPlugin
void requestRenameHierarchyEntry(QString hierarchyId, QString newName);
void requestChangeHierarchyLevel(QString hierarchyId, int levelDelta);
void requestDeleteHierarchyEntry(QString hierarchyId, bool deleteContents);
+ void requestRenameCommandEntry(QString commandId, QString newTitle);
+ void requestDeleteCommandEntry(QString commandId);
void requestRenamePlot(QString commandId, QString resultId, QString newTitle);
void requestDeletePlot(QString commandId, QString resultId);
diff --git a/src/worksheet.cpp b/src/worksheet.cpp
index d8f566a3..efbd9cf2 100644
--- a/src/worksheet.cpp
+++ b/src/worksheet.cpp
@@ -3131,6 +3131,16 @@ void Worksheet::updateCurrentTocNodeFromResult(CommandEntry* commandEntry, Canto
m_hierarchyManager->updateCurrentTocNodeFromResult(commandEntry, result);
}
+void Worksheet::renameCommandEntry(const QString& commandId, const QString& newTitle)
+{
+ m_hierarchyManager->renameCommandEntry(commandId, newTitle);
+}
+
+void Worksheet::deleteCommandEntry(const QString& commandId)
+{
+ m_hierarchyManager->deleteCommandEntry(commandId);
+}
+
void Worksheet::renamePlot(const QString& commandId, const QString& resultId, const QString& newTitle)
{
m_hierarchyManager->renamePlot(commandId, resultId, newTitle);
diff --git a/src/worksheet.h b/src/worksheet.h
index 262f90d1..7f6bed38 100644
--- a/src/worksheet.h
+++ b/src/worksheet.h
@@ -277,6 +277,8 @@ class Worksheet : public QGraphicsScene
void renameHierarchyEntry(const QString& hierarchyId, const QString& newName);
void changeHierarchyLevel(QString hierarchyId, int levelDelta);
void deleteHierarchyEntry(const QString& hierarchyId, bool deleteContents);
+ void renameCommandEntry(const QString& commandId, const QString& newTitle);
+ void deleteCommandEntry(const QString& commandId);
void renamePlot(const QString& commandId, const QString& resultId, const QString& newTitle);
void deletePlot(const QString& commandId, const QString& resultId);
void navigateToTocNode(QString nodeId);
diff --git a/src/worksheetentry.cpp b/src/worksheetentry.cpp
index cac97243..cee2c97b 100644
--- a/src/worksheetentry.cpp
+++ b/src/worksheetentry.cpp
@@ -331,15 +331,31 @@ bool WorksheetEntry::focusEntry(int pos, qreal xCoord)
void WorksheetEntry::moveToPreviousEntry(int pos, qreal x)
{
WorksheetEntry* entry = previous();
- while (entry && !(entry->wantFocus() && entry->focusEntry(pos, x)))
+ while (entry)
+ {
+ if (entry->wantFocus() && entry->focusEntry(pos, x))
+ {
+ worksheet()->makeVisible(entry);
+ return;
+ }
+
entry = entry->previous();
+ }
}
void WorksheetEntry::moveToNextEntry(int pos, qreal x)
{
WorksheetEntry* entry = next();
- while (entry && !(entry->wantFocus() && entry->focusEntry(pos, x)))
+ while (entry)
+ {
+ if (entry->wantFocus() && entry->focusEntry(pos, x))
+ {
+ worksheet()->makeVisible(entry);
+ return;
+ }
+
entry = entry->next();
+ }
}
Worksheet* WorksheetEntry::worksheet()
diff --git a/src/worksheethierarchymanager.cpp b/src/worksheethierarchymanager.cpp
index c97edc87..3d0c272e 100644
--- a/src/worksheethierarchymanager.cpp
+++ b/src/worksheethierarchymanager.cpp
@@ -154,11 +154,12 @@ QVariantList WorksheetHierarchyManager::collectTocNodes() const
commandNode.insert(QStringLiteral("id"), commandNodeId);
commandNode.insert(QStringLiteral("parentId"), parentNodeId);
commandNode.insert(QStringLiteral("type"), QString(TocNodeTypeCommand));
- commandNode.insert(QStringLiteral("title"), commandTocTitle());
+ commandNode.insert(QStringLiteral("title"), commandTocTitle(commandEntry));
+ commandNode.insert(QStringLiteral("customTitle"), commandEntry->displayName());
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("editable"), true);
commandNode.insert(QStringLiteral("navigable"), true);
commandNode.insert(QStringLiteral("hierarchyId"), QString());
commandNode.insert(QStringLiteral("resultIndex"), -1);
@@ -264,15 +265,18 @@ bool WorksheetHierarchyManager::parsePlotNodeId(const QString& nodeId, QString*
return true;
}
-QString WorksheetHierarchyManager::commandTocTitle() const
+QString WorksheetHierarchyManager::commandTocTitle(CommandEntry* entry) const
{
+ if (entry && !entry->displayName().isEmpty())
+ return entry->displayName();
+
return i18n("Command");
}
QString WorksheetHierarchyManager::commandTocDisplayText(CommandEntry* entry) const
{
auto* expression = entry ? entry->expression() : nullptr;
- const QString title = commandTocTitle();
+ const QString title = commandTocTitle(entry);
if (m_worksheet->m_showExpressionIds && expression && expression->id() != -1)
return i18n("%1 %2", title, expression->id());
@@ -842,6 +846,50 @@ void WorksheetHierarchyManager::updateCurrentTocNodeFromResult(CommandEntry* com
updateCurrentHierarchyFromEntry(commandEntry);
}
+void WorksheetHierarchyManager::renameCommandEntry(const QString& commandId, const QString& newTitle)
+{
+ if (m_worksheet->m_readOnly || commandId.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;
+
+ if (!commandEntry || commandEntry->displayName() == normalizedTitle)
+ return;
+
+ commandEntry->setDisplayName(normalizedTitle);
+ m_worksheet->setModified();
+ scheduleTocStructureRefresh();
+}
+
+void WorksheetHierarchyManager::deleteCommandEntry(const QString& commandId)
+{
+ if (m_worksheet->m_readOnly || commandId.isEmpty())
+ return;
+
+ const CommandSearchResult commandSearch = findCommandEntryById(commandId);
+ auto* commandEntry = commandSearch.entry;
+
+ if (!commandEntry)
+ return;
+
+ const bool expanded = expandHierarchyAncestors(commandSearch.collapsedAncestors);
+ if (expanded)
+ {
+ updateHierarchyLayout();
+ m_worksheet->updateLayout();
+ }
+
+ m_worksheet->clearAllSelections();
+ m_worksheet->notifyEntryFocus(nullptr);
+ commandEntry->startRemoving(false);
+}
+
void WorksheetHierarchyManager::renamePlot(const QString& commandId, const QString& resultId, const QString& newTitle)
{
if (m_worksheet->m_readOnly || commandId.isEmpty() || resultId.isEmpty())
diff --git a/src/worksheethierarchymanager.h b/src/worksheethierarchymanager.h
index 38a5db2a..9dca2eba 100644
--- a/src/worksheethierarchymanager.h
+++ b/src/worksheethierarchymanager.h
@@ -19,12 +19,6 @@ namespace Cantor {
class Result;
}
-/*
- * Helper owned by Worksheet for managing the structure shown in the
- * Table of Contents panel. This includes hierarchy entries, command
- * entries, plot result nodes, navigation, and structure-related editing
- * actions.
- */
class WorksheetHierarchyManager : public QObject
{
public:
@@ -47,6 +41,8 @@ class WorksheetHierarchyManager : public QObject
void renameHierarchyEntry(const QString& hierarchyId, const QString& newName);
void changeHierarchyLevel(const QString& hierarchyId, int levelDelta);
void deleteHierarchyEntry(const QString& hierarchyId, bool deleteContents);
+ void renameCommandEntry(const QString& commandId, const QString& newTitle);
+ void deleteCommandEntry(const QString& commandId);
void renamePlot(const QString& commandId, const QString& resultId, const QString& newTitle);
void deletePlot(const QString& commandId, const QString& resultId);
void navigateToTocNode(QString nodeId);
@@ -93,7 +89,7 @@ class WorksheetHierarchyManager : public QObject
bool navigateToPlotResult(CommandEntry* commandEntry, const QString& resultId);
void setCurrentTocNode(const QString& nodeId);
QString hierarchyIdForEntry(WorksheetEntry* entry) const;
- QString commandTocTitle() const;
+ QString commandTocTitle(CommandEntry* entry) const;
QString commandTocDisplayText(CommandEntry* entry) const;
QString plotTocTitle(Cantor::Result* result) const;
QString plotTocDisplayText(CommandEntry* entry, Cantor::Result* result, int plotOrdinal, int plotCount) const;