[education/cantor] src: Improve table of contents usability and settings

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

Improve table of contents usability and settings

M  +85   -4    src/cantor.cpp
M  +169  -25   src/formating.ui
M  +77   -7    src/panelplugins/tocpanel/tocpanelplugin.cpp
M  +6    -0    src/panelplugins/tocpanel/tocpanelplugin.h
M  +1    -38   src/settings.ui

https://invent.kde.org/education/cantor/-/commit/47e3885316ec1527f89e99e74d193431fae988a3

diff --git a/src/cantor.cpp b/src/cantor.cpp
index f7aa6245..ad689429 100644
--- a/src/cantor.cpp
+++ b/src/cantor.cpp
@@ -29,11 +29,17 @@
 #include <QDir>
 #include <QDockWidget>
 #include <QFileDialog>
+#include <QFontComboBox>
+#include <QFormLayout>
 #include <QStatusBar>
 #include <QGraphicsView>
+#include <QHBoxLayout>
+#include <QLabel>
 #include <QPushButton>
 #include <QRegularExpression>
 #include <QMenuBar>
+#include <QSpinBox>
+#include <QToolButton>
 
 #include "lib/backend.h"
 #include "lib/worksheetaccess.h"
@@ -748,14 +754,89 @@ void CantorShell::showSettings()
         base.kcfg_DefaultTheme->addItem(theme.translatedName(), theme.name());
     }
 
-    QWidget *formattingSettings = new QWidget;
-    Ui::SettingsFormatting formatting;
-    formatting.setupUi(formattingSettings);
+    QWidget *tocSettings = new QWidget;
+    Ui::SettingsToc toc;
+    toc.setupUi(tocSettings);
+
+    for (auto* fontFamilyEditor : tocSettings->findChildren<QFontComboBox*>())
+        fontFamilyEditor->setProperty("kcfg_property", QByteArray("currentFont"));
+
+    const auto addFontSizeButtons = [](QSpinBox* spinBox)
+    {
+        auto* formLayout = qobject_cast<QFormLayout*>(spinBox->parentWidget()->layout());
+        if (!formLayout)
+            return;
+
+        int row = -1;
+        QFormLayout::ItemRole role = QFormLayout::FieldRole;
+        formLayout->getWidgetPosition(spinBox, &row, &role);
+        if (row < 0)
+            return;
+
+        auto* editor = new QWidget(spinBox->parentWidget());
+        auto* layout = new QHBoxLayout(editor);
+        layout->setContentsMargins(0, 0, 0, 0);
+        layout->setSpacing(2);
+
+        formLayout->removeWidget(spinBox);
+        spinBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
+        spinBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+        layout->addWidget(spinBox);
+
+        auto* decreaseButton = new QToolButton(editor);
+        decreaseButton->setText(QStringLiteral("−"));
+        decreaseButton->setToolTip(i18n("Decrease font size"));
+        decreaseButton->setAutoRepeat(true);
+        layout->addWidget(decreaseButton);
+
+        auto* increaseButton = new QToolButton(editor);
+        increaseButton->setText(QStringLiteral("+"));
+        increaseButton->setToolTip(i18n("Increase font size"));
+        increaseButton->setAutoRepeat(true);
+        layout->addWidget(increaseButton);
+
+        connect(decreaseButton, &QToolButton::clicked, spinBox, &QSpinBox::stepDown);
+        connect(increaseButton, &QToolButton::clicked, spinBox, &QSpinBox::stepUp);
+
+        const auto updateButtons = [spinBox, decreaseButton, increaseButton](int value)
+        {
+            decreaseButton->setEnabled(value > spinBox->minimum());
+            increaseButton->setEnabled(value < spinBox->maximum());
+        };
+        connect(spinBox, &QSpinBox::valueChanged, editor, updateButtons);
+        updateButtons(spinBox->value());
+
+        formLayout->setWidget(row, role, editor);
+    };
+
+    const QList<QSpinBox*> fontSizeEditors = {
+        toc.kcfg_ChapterFontSize,
+        toc.kcfg_SubchapterFontSize,
+        toc.kcfg_SectionFontSize,
+        toc.kcfg_SubsectionFontSize,
+        toc.kcfg_ParagraphFontSize,
+        toc.kcfg_SubparagraphFontSize,
+    };
+    for (auto* fontSizeEditor : fontSizeEditors)
+        addFontSizeButtons(fontSizeEditor);
+
+    const auto tocLabels = tocSettings->findChildren<QLabel*>();
+    int tocLabelWidth = 0;
+    for (const auto* label : tocLabels)
+    {
+        if (label->buddy())
+            tocLabelWidth = qMax(tocLabelWidth, label->sizeHint().width());
+    }
+    for (auto* label : tocLabels)
+    {
+        if (label->buddy())
+            label->setMinimumWidth(tocLabelWidth);
+    }
 
     base.kcfg_DefaultBackend->addItems(Cantor::Backend::listAvailableBackends());
 
     dialog->addPage(generalSettings, i18n("General"), QLatin1String("preferences-other"));
-    dialog->addPage(formattingSettings, i18n("Formatting"), QLatin1String("preferences-other"));
+    dialog->addPage(tocSettings, i18n("Table of Contents"), QLatin1String("view-list-tree"));
     for (auto* backend : Cantor::Backend::availableBackends())
     {
         if (backend->config()) //It has something to configure, so add it to the dialog
diff --git a/src/formating.ui b/src/formating.ui
index 2bd4aeca..8c33ac98 100644
--- a/src/formating.ui
+++ b/src/formating.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>SettingsFormatting</class>
- <widget class="QWidget" name="SettingsFormatting">
+ <class>SettingsToc</class>
+ <widget class="QWidget" name="SettingsToc">
   <property name="geometry">
    <rect>
     <x>0</x>
@@ -24,7 +24,74 @@
     <number>0</number>
    </property>
    <item>
-    <widget class="QTabWidget" name="tabWidget">
+    <widget class="QLabel" name="hierarchyEntriesLabel">
+     <property name="font">
+      <font>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="text">
+      <string>Hierarchy Entries:</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QFormLayout" name="hierarchyLevelLayout">
+     <property name="leftMargin">
+      <number>9</number>
+     </property>
+     <property name="rightMargin">
+      <number>9</number>
+     </property>
+     <item row="0" column="0">
+      <widget class="QLabel" name="hierarchyLevelLabel">
+       <property name="text">
+        <string>Hierarchy level:</string>
+       </property>
+       <property name="buddy">
+        <cstring>hierarchyLevelComboBox</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QComboBox" name="hierarchyLevelComboBox">
+       <item>
+        <property name="text">
+         <string>Chapter</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Subchapter</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Section</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Subsection</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Paragraph</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>Subparagraph</string>
+        </property>
+       </item>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QStackedWidget" name="hierarchyFontStack">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
        <horstretch>0</horstretch>
@@ -34,14 +101,14 @@
      <property name="currentIndex">
       <number>0</number>
      </property>
-     <property name="documentMode">
-      <bool>true</bool>
-     </property>
      <widget class="QWidget" name="ChapterTab">
-      <attribute name="title">
-       <string>Chapter Font</string>
-      </attribute>
       <layout class="QFormLayout" name="verticalLayout">
+       <property name="leftMargin">
+        <number>9</number>
+       </property>
+       <property name="rightMargin">
+        <number>9</number>
+       </property>
        <item row="0" column="0">
         <widget class="QLabel" name="lChapterFontFamily">
          <property name="text">
@@ -95,10 +162,13 @@
       </layout>
      </widget>
      <widget class="QWidget" name="SubchapterTab">
-      <attribute name="title">
-       <string>Subchapter Font</string>
-      </attribute>
       <layout class="QFormLayout" name="verticalLayout4">
+       <property name="leftMargin">
+        <number>9</number>
+       </property>
+       <property name="rightMargin">
+        <number>9</number>
+       </property>
        <item row="0" column="0">
         <widget class="QLabel" name="lSubchapterFontFamily">
          <property name="text">
@@ -152,10 +222,13 @@
       </layout>
      </widget>
      <widget class="QWidget" name="SectionTab">
-      <attribute name="title">
-       <string>Section Font</string>
-      </attribute>
       <layout class="QFormLayout" name="verticalLayout3">
+       <property name="leftMargin">
+        <number>9</number>
+       </property>
+       <property name="rightMargin">
+        <number>9</number>
+       </property>
        <item row="0" column="0">
         <widget class="QLabel" name="lSectionFontFamily">
          <property name="text">
@@ -209,10 +282,13 @@
       </layout>
      </widget>
      <widget class="QWidget" name="SubsectionTab">
-      <attribute name="title">
-       <string>Subsection Font</string>
-      </attribute>
       <layout class="QFormLayout" name="verticalLayout6">
+       <property name="leftMargin">
+        <number>9</number>
+       </property>
+       <property name="rightMargin">
+        <number>9</number>
+       </property>
        <item row="0" column="0">
         <widget class="QLabel" name="lSubsectionFontFamily">
          <property name="text">
@@ -266,10 +342,13 @@
       </layout>
      </widget>
      <widget class="QWidget" name="ParagraphTab">
-      <attribute name="title">
-       <string>Paragraph Font</string>
-      </attribute>
       <layout class="QFormLayout" name="verticalLayout2">
+       <property name="leftMargin">
+        <number>9</number>
+       </property>
+       <property name="rightMargin">
+        <number>9</number>
+       </property>
        <item row="0" column="0">
         <widget class="QLabel" name="lParagraphFontFamily">
          <property name="text">
@@ -323,10 +402,13 @@
       </layout>
      </widget>
      <widget class="QWidget" name="SubparagraphTab">
-      <attribute name="title">
-       <string>Subparagraph Font</string>
-      </attribute>
       <layout class="QFormLayout" name="verticalLayout5">
+       <property name="leftMargin">
+        <number>9</number>
+       </property>
+       <property name="rightMargin">
+        <number>9</number>
+       </property>
        <item row="0" column="0">
         <widget class="QLabel" name="lSubparagraphFontFamily">
          <property name="text">
@@ -381,6 +463,51 @@
      </widget>
     </widget>
    </item>
+   <item>
+    <widget class="QLabel" name="visibleItemsLabel">
+     <property name="font">
+      <font>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="text">
+      <string>Visible Items:</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QGridLayout" name="tocDefaultsLayout">
+     <item row="0" column="0">
+      <widget class="QCheckBox" name="kcfg_ShowTocChaptersDefault">
+       <property name="text">
+        <string>Show chapters</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="0">
+      <widget class="QCheckBox" name="kcfg_ShowTocSectionsDefault">
+       <property name="text">
+        <string>Show sections</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QCheckBox" name="kcfg_ShowTocCommandEntriesDefault">
+       <property name="text">
+        <string>Show command entries</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QCheckBox" name="kcfg_ShowTocPlotsDefault">
+       <property name="text">
+        <string>Show plots</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
    <item>
     <spacer name="verticalSpacer">
      <property name="orientation">
@@ -397,5 +524,22 @@
   </layout>
  </widget>
  <resources/>
- <connections/>
+ <connections>
+  <connection>
+   <sender>hierarchyLevelComboBox</sender>
+   <signal>currentIndexChanged(int)</signal>
+   <receiver>hierarchyFontStack</receiver>
+   <slot>setCurrentIndex(int)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>0</x>
+     <y>0</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>0</x>
+     <y>0</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
 </ui>
diff --git a/src/panelplugins/tocpanel/tocpanelplugin.cpp b/src/panelplugins/tocpanel/tocpanelplugin.cpp
index 473537e2..f00511f0 100644
--- a/src/panelplugins/tocpanel/tocpanelplugin.cpp
+++ b/src/panelplugins/tocpanel/tocpanelplugin.cpp
@@ -10,6 +10,7 @@
 #include <QAbstractItemDelegate>
 #include <QAction>
 #include <QDebug>
+#include <QIcon>
 #include <QItemSelectionModel>
 #include <QKeyEvent>
 #include <QLabel>
@@ -41,6 +42,20 @@ const QLatin1String TocNodeTypeSection("section");
 const QLatin1String TocNodeTypeCommand("command");
 const QLatin1String TocNodeTypePlot("plot");
 
+QIcon iconForTocNodeType(const QString& nodeType)
+{
+    if (nodeType == TocNodeTypeChapter)
+        return QIcon::fromTheme(QStringLiteral("view-list-tree"));
+    if (nodeType == TocNodeTypeSection)
+        return QIcon::fromTheme(QStringLiteral("format-list-ordered"));
+    if (nodeType == TocNodeTypeCommand)
+        return QIcon::fromTheme(QStringLiteral("code-context"));
+    if (nodeType == TocNodeTypePlot)
+        return QIcon::fromTheme(QStringLiteral("office-chart-line"));
+
+    return {};
+}
+
 class HierarchyNameDelegate : public QStyledItemDelegate
 {
 public:
@@ -147,6 +162,7 @@ void TableOfContentPanelPlugin::connectToShell(QObject* cantorShell)
     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)));
+    connect(cantorShell, SIGNAL(settingsChanges()), this, SLOT(handleSettingsChanges()));
 }
 
 bool TableOfContentPanelPlugin::showOnStartup()
@@ -194,6 +210,7 @@ void TableOfContentPanelPlugin::constructMainWidget()
     auto* emptyLabel = new QLabel(container);
     emptyLabel->setAlignment(Qt::AlignCenter);
     emptyLabel->setWordWrap(true);
+    emptyLabel->setContextMenuPolicy(Qt::CustomContextMenu);
     emptyLabel->hide();
 
     view->setEditTriggers(QAbstractItemView::NoEditTriggers);
@@ -218,6 +235,10 @@ void TableOfContentPanelPlugin::constructMainWidget()
     connect(view, &QTreeView::customContextMenuRequested, this, &TableOfContentPanelPlugin::handleContextMenuRequested);
     connect(view, &QTreeView::expanded, this, &TableOfContentPanelPlugin::handleExpanded);
     connect(view, &QTreeView::collapsed, this, &TableOfContentPanelPlugin::handleCollapsed);
+    connect(emptyLabel, &QLabel::customContextMenuRequested, this, [this, emptyLabel](const QPoint& position)
+    {
+        showContextMenu(QModelIndex{}, emptyLabel->mapToGlobal(position));
+    });
     connect(delegate, &QAbstractItemDelegate::commitData, this, &TableOfContentPanelPlugin::handleEditorCommit);
     connect(delegate, &QAbstractItemDelegate::closeEditor, this, &TableOfContentPanelPlugin::handleEditorClosed);
     connect(searchEdit, &QLineEdit::textChanged, this, [this](const QString& text)
@@ -397,6 +418,7 @@ void TableOfContentPanelPlugin::rebuildModel()
         QStandardItem* parentItem = parentIndex >= 0 ? visibleItems.at(parentIndex) : m_model.invisibleRootItem();
 
         auto* item = new QStandardItem(node.displayText);
+        item->setIcon(iconForTocNodeType(node.type));
         item->setEditable(node.editable && !m_readOnly);
 
         item->setData(node.id, NodeIdRole);
@@ -961,10 +983,15 @@ void TableOfContentPanelPlugin::clearNodes()
 
 void TableOfContentPanelPlugin::resetVisibilityToDefaults()
 {
-    m_showChapters = Settings::showTocChaptersDefault();
-    m_showSections = Settings::showTocSectionsDefault();
-    m_showCommandEntries = Settings::showTocCommandEntriesDefault();
-    m_showPlots = Settings::showTocPlotsDefault();
+    m_defaultShowChapters = Settings::showTocChaptersDefault();
+    m_defaultShowSections = Settings::showTocSectionsDefault();
+    m_defaultShowCommandEntries = Settings::showTocCommandEntriesDefault();
+    m_defaultShowPlots = Settings::showTocPlotsDefault();
+
+    m_showChapters = m_defaultShowChapters;
+    m_showSections = m_defaultShowSections;
+    m_showCommandEntries = m_defaultShowCommandEntries;
+    m_showPlots = m_defaultShowPlots;
 }
 
 void TableOfContentPanelPlugin::cleanupStateAfterNodeChange()
@@ -1053,6 +1080,43 @@ void TableOfContentPanelPlugin::handleReadOnlyChanged(bool readOnly)
     rebuildModel();
 }
 
+void TableOfContentPanelPlugin::handleSettingsChanges()
+{
+    const bool showChapters = Settings::showTocChaptersDefault();
+    const bool showSections = Settings::showTocSectionsDefault();
+    const bool showCommandEntries = Settings::showTocCommandEntriesDefault();
+    const bool showPlots = Settings::showTocPlotsDefault();
+
+    bool changed = false;
+    if (showChapters != m_defaultShowChapters)
+    {
+        m_defaultShowChapters = showChapters;
+        m_showChapters = showChapters;
+        changed = true;
+    }
+    if (showSections != m_defaultShowSections)
+    {
+        m_defaultShowSections = showSections;
+        m_showSections = showSections;
+        changed = true;
+    }
+    if (showCommandEntries != m_defaultShowCommandEntries)
+    {
+        m_defaultShowCommandEntries = showCommandEntries;
+        m_showCommandEntries = showCommandEntries;
+        changed = true;
+    }
+    if (showPlots != m_defaultShowPlots)
+    {
+        m_defaultShowPlots = showPlots;
+        m_showPlots = showPlots;
+        changed = true;
+    }
+
+    if (changed)
+        rebuildModel();
+}
+
 void TableOfContentPanelPlugin::handleExpanded(const QModelIndex& index)
 {
     if (m_updatingModel)
@@ -1089,10 +1153,16 @@ void TableOfContentPanelPlugin::handleContextMenuRequested(const QPoint& positio
     if (!m_mainWidget)
         return;
 
-    if (m_editorActive)
+    showContextMenu(m_mainWidget->indexAt(position), m_mainWidget->viewport()->mapToGlobal(position));
+}
+
+void TableOfContentPanelPlugin::showContextMenu(const QModelIndex& index, const QPoint& globalPosition)
+{
+    if (!m_mainWidget)
         return;
 
-    const QModelIndex index = m_mainWidget->indexAt(position);
+    if (m_editorActive)
+        return;
 
     QMenu menu(m_mainWidget);
 
@@ -1290,7 +1360,7 @@ void TableOfContentPanelPlugin::handleContextMenuRequested(const QPoint& positio
             m_mainWidget->collapseAll();
     });
 
-    menu.exec(m_mainWidget->viewport()->mapToGlobal(position));
+    menu.exec(globalPosition);
 }
 
 K_PLUGIN_FACTORY_WITH_JSON(tocpanelplugin, "tocpanelplugin.json", registerPlugin<TableOfContentPanelPlugin>();)
diff --git a/src/panelplugins/tocpanel/tocpanelplugin.h b/src/panelplugins/tocpanel/tocpanelplugin.h
index 1f45df9f..b7bd2668 100644
--- a/src/panelplugins/tocpanel/tocpanelplugin.h
+++ b/src/panelplugins/tocpanel/tocpanelplugin.h
@@ -65,6 +65,7 @@ class TableOfContentPanelPlugin : public Cantor::PanelPlugin
     void handleCollapsed(const QModelIndex& index);
     void handleContextMenuRequested(const QPoint& position);
     void handleReadOnlyChanged(bool readOnly);
+    void handleSettingsChanges();
 
 private:
     enum ItemRole
@@ -128,6 +129,7 @@ private:
     void updateSearchVisibility();
     void saveCurrentExpansionState();
     void showContextMenuForIndex(const QModelIndex& index);
+    void showContextMenu(const QModelIndex& index, const QPoint& globalPosition);
     void deleteItemAtIndex(const QModelIndex& index);
 
 private:
@@ -146,6 +148,10 @@ private:
     bool m_showSections{true};
     bool m_showCommandEntries{false};
     bool m_showPlots{false};
+    bool m_defaultShowChapters{true};
+    bool m_defaultShowSections{true};
+    bool m_defaultShowCommandEntries{false};
+    bool m_defaultShowPlots{false};
     bool m_readOnly{false};
 
     QSet<QString> m_expandedNodeIds;
diff --git a/src/settings.ui b/src/settings.ui
index 0f6c4fc2..882c6f95 100644
--- a/src/settings.ui
+++ b/src/settings.ui
@@ -24,44 +24,7 @@
      </property>
     </widget>
    </item>
-   <item row="22" column="0" colspan="6">
-    <widget class="QGroupBox" name="tocDefaultsGroupBox">
-     <property name="title">
-      <string>Worksheet Structure Navigator:</string>
-     </property>
-     <layout class="QGridLayout" name="tocDefaultsLayout">
-      <item row="0" column="0" colspan="2">
-       <widget class="QCheckBox" name="kcfg_ShowTocChaptersDefault">
-        <property name="text">
-         <string>Show chapters by default</string>
-        </property>
-       </widget>
-      </item>
-      <item row="1" column="0" colspan="2">
-       <widget class="QCheckBox" name="kcfg_ShowTocSectionsDefault">
-        <property name="text">
-         <string>Show sections by default</string>
-        </property>
-       </widget>
-      </item>
-      <item row="2" column="0" colspan="2">
-       <widget class="QCheckBox" name="kcfg_ShowTocCommandEntriesDefault">
-        <property name="text">
-         <string>Show command entries by default</string>
-        </property>
-       </widget>
-      </item>
-      <item row="3" column="0" colspan="2">
-       <widget class="QCheckBox" name="kcfg_ShowTocPlotsDefault">
-        <property name="text">
-         <string>Show plots by default</string>
-        </property>
-       </widget>
-      </item>
-     </layout>
-    </widget>
-   </item>
-   <item row="23" column="0">
+   <item row="22" column="0">
     <spacer name="verticalSpacer">
      <property name="orientation">
       <enum>Qt::Vertical</enum>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.