From 463677108199d5da576611761b87b6f695a59612 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Thu, 24 Dec 2020 20:18:37 +0800 Subject: [PATCH] support section number in edit mode --- libs/vtextedit | 2 +- src/core/global.h | 7 + src/core/htmltemplatehelper.cpp | 2 +- src/core/markdowneditorconfig.cpp | 67 +++++-- src/core/markdowneditorconfig.h | 22 ++- src/data/core/core.qrc | 1 + src/data/core/icons/section_number_editor.svg | 7 + src/data/core/vnotex.json | 6 +- .../dialogs/settings/markdowneditorpage.cpp | 73 ++++++-- .../dialogs/settings/markdowneditorpage.h | 10 +- .../dialogs/settings/settingsdialog.cpp | 48 +++-- src/widgets/dialogs/settings/settingsdialog.h | 4 + src/widgets/dialogs/settings/themepage.cpp | 32 ++++ src/widgets/dialogs/settings/themepage.h | 26 +++ src/widgets/editors/markdowneditor.cpp | 171 +++++++++++++++++- src/widgets/editors/markdowneditor.h | 24 ++- src/widgets/mainwindow.cpp | 5 + src/widgets/markdownviewwindow.cpp | 17 +- src/widgets/markdownviewwindow.h | 2 + src/widgets/textviewwindow.cpp | 17 +- src/widgets/textviewwindow.h | 2 + src/widgets/viewwindow.cpp | 23 +++ src/widgets/viewwindow.h | 2 + src/widgets/viewwindowtoolbarhelper.cpp | 35 ++++ src/widgets/viewwindowtoolbarhelper.h | 3 +- src/widgets/widgets.pri | 2 + 26 files changed, 535 insertions(+), 75 deletions(-) create mode 100644 src/data/core/icons/section_number_editor.svg create mode 100644 src/widgets/dialogs/settings/themepage.cpp create mode 100644 src/widgets/dialogs/settings/themepage.h diff --git a/libs/vtextedit b/libs/vtextedit index 12a74cb3..47347580 160000 --- a/libs/vtextedit +++ b/libs/vtextedit @@ -1 +1 @@ -Subproject commit 12a74cb32438b28f002dea51c484133d3058a882 +Subproject commit 47347580cbcd460ebbbc8358e37b409530edaa2f diff --git a/src/core/global.h b/src/core/global.h index 1d0a2380..480aa353 100644 --- a/src/core/global.h +++ b/src/core/global.h @@ -72,6 +72,13 @@ namespace vnotex IncrementalSearch = 0x10U }; Q_DECLARE_FLAGS(FindOptions, FindOption); + + enum OverrideState + { + NoOverride = 0, + ForceEnable = 1, + ForceDisable = 2 + }; } // ns vnotex Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::FindOptions); diff --git a/src/core/htmltemplatehelper.cpp b/src/core/htmltemplatehelper.cpp index 47744b5c..5a5ec44e 100644 --- a/src/core/htmltemplatehelper.cpp +++ b/src/core/htmltemplatehelper.cpp @@ -155,7 +155,7 @@ void HtmlTemplateHelper::updateMarkdownViewerTemplate(const MarkdownEditorConfig WebGlobalOptions opts; opts.m_webPlantUml = p_config.getWebPlantUml(); opts.m_webGraphviz = p_config.getWebGraphviz(); - opts.m_sectionNumberEnabled = p_config.getSectionNumberEnabled(); + opts.m_sectionNumberEnabled = p_config.getSectionNumberMode() == MarkdownEditorConfig::SectionNumberMode::Read; opts.m_constrainImageWidthEnabled = p_config.getConstrainImageWidthEnabled(); opts.m_protectFromXss = p_config.getProtectFromXss(); opts.m_htmlTagEnabled = p_config.getHtmlTagEnabled(); diff --git a/src/core/markdowneditorconfig.cpp b/src/core/markdowneditorconfig.cpp index fb0590e5..51562513 100644 --- a/src/core/markdowneditorconfig.cpp +++ b/src/core/markdowneditorconfig.cpp @@ -10,6 +10,7 @@ using namespace vnotex; #define READSTR(key) readString(appObj, userObj, (key)) #define READBOOL(key) readBool(appObj, userObj, (key)) #define READREAL(key) readReal(appObj, userObj, (key)) +#define READINT(key) readInt(appObj, userObj, (key)) MarkdownEditorConfig::MarkdownEditorConfig(ConfigMgr *p_mgr, IConfig *p_topConfig, @@ -33,7 +34,10 @@ void MarkdownEditorConfig::init(const QJsonObject &p_app, const QJsonObject &p_u m_prependDotInRelativeLink = READBOOL(QStringLiteral("prepend_dot_in_relative_link")); m_confirmBeforeClearObsoleteImages = READBOOL(QStringLiteral("confirm_before_clear_obsolete_images")); m_insertFileNameAsTitle = READBOOL(QStringLiteral("insert_file_name_as_title")); - m_sectionNumberEnabled = READBOOL(QStringLiteral("section_number")); + + m_sectionNumberMode = stringToSectionNumberMode(READSTR(QStringLiteral("section_number"))); + m_sectionNumberBaseLevel = READINT(QStringLiteral("section_number_base_level")); + m_constrainImageWidthEnabled = READBOOL(QStringLiteral("constrain_image_width")); m_constrainInPlacePreviewWidthEnabled = READBOOL(QStringLiteral("constrain_inplace_preview_width")); m_zoomFactorInReadMode = READREAL(QStringLiteral("zoom_factor_in_read_mode")); @@ -53,7 +57,10 @@ QJsonObject MarkdownEditorConfig::toJson() const obj[QStringLiteral("prepend_dot_in_relative_link")] = m_prependDotInRelativeLink; obj[QStringLiteral("confirm_before_clear_obsolete_images")] = m_confirmBeforeClearObsoleteImages; obj[QStringLiteral("insert_file_name_as_title")] = m_insertFileNameAsTitle; - obj[QStringLiteral("section_number")] = m_sectionNumberEnabled; + + obj[QStringLiteral("section_number")] = sectionNumberModeToString(m_sectionNumberMode); + obj[QStringLiteral("section_number_base_level")] = m_sectionNumberBaseLevel; + obj[QStringLiteral("constrain_image_width")] = m_constrainImageWidthEnabled; obj[QStringLiteral("constrain_inplace_preview_width")] = m_constrainInPlacePreviewWidthEnabled; obj[QStringLiteral("zoom_factor_in_read_mode")] = m_zoomFactorInReadMode; @@ -147,16 +154,6 @@ void MarkdownEditorConfig::setInsertFileNameAsTitle(bool p_enabled) updateConfig(m_insertFileNameAsTitle, p_enabled, this); } -bool MarkdownEditorConfig::getSectionNumberEnabled() const -{ - return m_sectionNumberEnabled; -} - -void MarkdownEditorConfig::setSectionNumberEnabled(bool p_enabled) -{ - updateConfig(m_sectionNumberEnabled, p_enabled, this); -} - bool MarkdownEditorConfig::getConstrainImageWidthEnabled() const { return m_constrainImageWidthEnabled; @@ -231,3 +228,49 @@ void MarkdownEditorConfig::setLinkifyEnabled(bool p_enabled) { updateConfig(m_linkifyEnabled, p_enabled, this); } + +QString MarkdownEditorConfig::sectionNumberModeToString(SectionNumberMode p_mode) const +{ + switch (p_mode) { + case SectionNumberMode::None: + return QStringLiteral("none"); + + case SectionNumberMode::Edit: + return QStringLiteral("edit"); + + default: + return QStringLiteral("read"); + } +} + +MarkdownEditorConfig::SectionNumberMode MarkdownEditorConfig::stringToSectionNumberMode(const QString &p_str) const +{ + auto mode = p_str.toLower(); + if (mode == QStringLiteral("none")) { + return SectionNumberMode::None; + } else if (mode == QStringLiteral("edit")) { + return SectionNumberMode::Edit; + } else { + return SectionNumberMode::Read; + } +} + +MarkdownEditorConfig::SectionNumberMode MarkdownEditorConfig::getSectionNumberMode() const +{ + return m_sectionNumberMode; +} + +void MarkdownEditorConfig::setSectionNumberMode(SectionNumberMode p_mode) +{ + updateConfig(m_sectionNumberMode, p_mode, this); +} + +int MarkdownEditorConfig::getSectionNumberBaseLevel() const +{ + return m_sectionNumberBaseLevel; +} + +void MarkdownEditorConfig::setSectionNumberBaseLevel(int p_level) +{ + updateConfig(m_sectionNumberBaseLevel, p_level, this); +} diff --git a/src/core/markdowneditorconfig.h b/src/core/markdowneditorconfig.h index 4a4c40ab..28525b00 100644 --- a/src/core/markdowneditorconfig.h +++ b/src/core/markdowneditorconfig.h @@ -15,6 +15,13 @@ namespace vnotex class MarkdownEditorConfig : public IConfig { public: + enum SectionNumberMode + { + None, + Read, + Edit + }; + MarkdownEditorConfig(ConfigMgr *p_mgr, IConfig *p_topConfig, const QSharedPointer &p_textEditorConfig); @@ -45,8 +52,11 @@ namespace vnotex bool getInsertFileNameAsTitle() const; void setInsertFileNameAsTitle(bool p_enabled); - bool getSectionNumberEnabled() const; - void setSectionNumberEnabled(bool p_enabled); + SectionNumberMode getSectionNumberMode() const; + void setSectionNumberMode(SectionNumberMode p_mode); + + int getSectionNumberBaseLevel() const; + void setSectionNumberBaseLevel(int p_level); bool getConstrainImageWidthEnabled() const; void setConstrainImageWidthEnabled(bool p_enabled); @@ -72,6 +82,9 @@ namespace vnotex void setLinkifyEnabled(bool p_enabled); private: + QString sectionNumberModeToString(SectionNumberMode p_mode) const; + SectionNumberMode stringToSectionNumberMode(const QString &p_str) const; + QSharedPointer m_textEditorConfig; ViewerResource m_viewerResource; @@ -91,7 +104,10 @@ namespace vnotex bool m_insertFileNameAsTitle = true; // Whether enable section numbering. - bool m_sectionNumberEnabled = true; + SectionNumberMode m_sectionNumberMode = SectionNumberMode::Read; + + // 1 based. + int m_sectionNumberBaseLevel = 2; // Whether enable image width constraint. bool m_constrainImageWidthEnabled = true; diff --git a/src/data/core/core.qrc b/src/data/core/core.qrc index 498eb222..282ccf3e 100644 --- a/src/data/core/core.qrc +++ b/src/data/core/core.qrc @@ -72,6 +72,7 @@ icons/stay_on_top.svg icons/outline_editor.svg icons/find_replace_editor.svg + icons/section_number_editor.svg logo/vnote.svg logo/vnote.png logo/256x256/vnote.png diff --git a/src/data/core/icons/section_number_editor.svg b/src/data/core/icons/section_number_editor.svg new file mode 100644 index 00000000..b8014ac6 --- /dev/null +++ b/src/data/core/icons/section_number_editor.svg @@ -0,0 +1,7 @@ + + + + Layer 2 + 1.2. + + diff --git a/src/data/core/vnotex.json b/src/data/core/vnotex.json index 7ff2dcdd..106b4ce6 100644 --- a/src/data/core/vnotex.json +++ b/src/data/core/vnotex.json @@ -226,8 +226,10 @@ "confirm_before_clear_obsolete_images" : true, "//comment" : "Whether insert the file name as title on new file", "insert_file_name_as_title" : true, - "//comment" : "Whether enable auto section numbering", - "section_number" : true, + "//comment" : "none/read/edit", + "section_number" : "read", + "//comment" : "base level to start section numbering, valid only in edit mode", + "section_number_base_level" : 2, "//comment" : "Whether enable image width constraint", "constrain_image_width" : true, "//comment" : "Whether enable in-place preview width constraint", diff --git a/src/widgets/dialogs/settings/markdowneditorpage.cpp b/src/widgets/dialogs/settings/markdowneditorpage.cpp index 27ed8ee0..d3905c1e 100644 --- a/src/widgets/dialogs/settings/markdowneditorpage.cpp +++ b/src/widgets/dialogs/settings/markdowneditorpage.cpp @@ -5,6 +5,9 @@ #include #include #include +#include +#include +#include #include #include @@ -25,6 +28,9 @@ void MarkdownEditorPage::setupUI() { auto mainLayout = new QVBoxLayout(this); + auto generalBox = setupGeneralGroup(); + mainLayout->addWidget(generalBox); + auto readBox = setupReadGroup(); mainLayout->addWidget(readBox); @@ -38,7 +44,13 @@ void MarkdownEditorPage::loadInternal() m_insertFileNameAsTitleCheckBox->setChecked(markdownConfig.getInsertFileNameAsTitle()); - m_sectionNumberCheckBox->setChecked(markdownConfig.getSectionNumberEnabled()); + { + int idx = m_sectionNumberComboBox->findData(static_cast(markdownConfig.getSectionNumberMode())); + Q_ASSERT(idx != -1); + m_sectionNumberComboBox->setCurrentIndex(idx); + + m_sectionNumberBaseLevelSpinBox->setValue(markdownConfig.getSectionNumberBaseLevel()); + } m_constrainImageWidthCheckBox->setChecked(markdownConfig.getConstrainImageWidthEnabled()); @@ -61,7 +73,14 @@ void MarkdownEditorPage::saveInternal() markdownConfig.setInsertFileNameAsTitle(m_insertFileNameAsTitleCheckBox->isChecked()); - markdownConfig.setSectionNumberEnabled(m_sectionNumberCheckBox->isChecked()); + { + auto mode = m_sectionNumberComboBox->currentData().toInt(); + markdownConfig.setSectionNumberMode(static_cast(mode)); + + if (m_sectionNumberBaseLevelSpinBox->isEnabled()) { + markdownConfig.setSectionNumberBaseLevel(m_sectionNumberBaseLevelSpinBox->value()); + } + } markdownConfig.setConstrainImageWidthEnabled(m_constrainImageWidthCheckBox->isChecked()); @@ -90,16 +109,6 @@ QGroupBox *MarkdownEditorPage::setupReadGroup() auto box = new QGroupBox(tr("Read"), this); auto layout = new QFormLayout(box); - { - const QString label(tr("Section number")); - m_sectionNumberCheckBox = WidgetsFactory::createCheckBox(label, box); - m_sectionNumberCheckBox->setToolTip(tr("Display section number of headings in read mode")); - layout->addRow(m_sectionNumberCheckBox); - addSearchItem(label, m_sectionNumberCheckBox->toolTip(), m_sectionNumberCheckBox); - connect(m_sectionNumberCheckBox, &QCheckBox::stateChanged, - this, &MarkdownEditorPage::pageIsChanged); - } - { const QString label(tr("Constrain image width")); m_constrainImageWidthCheckBox = WidgetsFactory::createCheckBox(label, box); @@ -194,3 +203,43 @@ QGroupBox *MarkdownEditorPage::setupEditGroup() return box; } + +QGroupBox *MarkdownEditorPage::setupGeneralGroup() +{ + auto box = new QGroupBox(tr("General"), this); + auto layout = new QFormLayout(box); + + { + auto sectionLayout = new QHBoxLayout(); + + m_sectionNumberComboBox = WidgetsFactory::createComboBox(this); + m_sectionNumberComboBox->setToolTip(tr("Section number mode")); + + m_sectionNumberComboBox->addItem(tr("None"), (int)MarkdownEditorConfig::SectionNumberMode::None); + m_sectionNumberComboBox->addItem(tr("Read"), (int)MarkdownEditorConfig::SectionNumberMode::Read); + m_sectionNumberComboBox->addItem(tr("Edit"), (int)MarkdownEditorConfig::SectionNumberMode::Edit); + + sectionLayout->addWidget(m_sectionNumberComboBox); + connect(m_sectionNumberComboBox, QOverload::of(&QComboBox::currentIndexChanged), + this, &MarkdownEditorPage::pageIsChanged); + + m_sectionNumberBaseLevelSpinBox = WidgetsFactory::createSpinBox(this); + m_sectionNumberBaseLevelSpinBox->setToolTip(tr("Base level to start section numbering in edit mode")); + m_sectionNumberBaseLevelSpinBox->setRange(1, 6); + m_sectionNumberBaseLevelSpinBox->setSingleStep(1); + + sectionLayout->addWidget(m_sectionNumberBaseLevelSpinBox); + connect(m_sectionNumberBaseLevelSpinBox, QOverload::of(&QSpinBox::valueChanged), + this, &MarkdownEditorPage::pageIsChanged); + connect(m_sectionNumberComboBox, QOverload::of(&QComboBox::currentIndexChanged), + this, [this](int p_index) { + m_sectionNumberBaseLevelSpinBox->setEnabled(p_index == MarkdownEditorConfig::SectionNumberMode::Edit); + }); + + const QString label(tr("Section number:")); + layout->addRow(label, sectionLayout); + addSearchItem(label, m_sectionNumberComboBox->toolTip(), m_sectionNumberComboBox); + } + + return box; +} diff --git a/src/widgets/dialogs/settings/markdowneditorpage.h b/src/widgets/dialogs/settings/markdowneditorpage.h index c80d29cf..bf64ab36 100644 --- a/src/widgets/dialogs/settings/markdowneditorpage.h +++ b/src/widgets/dialogs/settings/markdowneditorpage.h @@ -6,6 +6,8 @@ class QCheckBox; class QGroupBox; class QDoubleSpinBox; +class QSpinBox; +class QComboBox; namespace vnotex { @@ -25,14 +27,14 @@ namespace vnotex private: void setupUI(); + QGroupBox *setupGeneralGroup(); + QGroupBox *setupReadGroup(); QGroupBox *setupEditGroup(); QCheckBox *m_insertFileNameAsTitleCheckBox = nullptr; - QCheckBox *m_sectionNumberCheckBox = nullptr; - QCheckBox *m_constrainImageWidthCheckBox = nullptr; QCheckBox *m_constrainInPlacePreviewWidthCheckBox = nullptr; @@ -46,6 +48,10 @@ namespace vnotex QCheckBox *m_linkifyCheckBox = nullptr; QDoubleSpinBox *m_zoomFactorSpinBox = nullptr; + + QComboBox *m_sectionNumberComboBox = nullptr; + + QSpinBox *m_sectionNumberBaseLevelSpinBox = nullptr; }; } diff --git a/src/widgets/dialogs/settings/settingsdialog.cpp b/src/widgets/dialogs/settings/settingsdialog.cpp index 6f9ac3f1..4b25eeed 100644 --- a/src/widgets/dialogs/settings/settingsdialog.cpp +++ b/src/widgets/dialogs/settings/settingsdialog.cpp @@ -13,6 +13,7 @@ #include "texteditorpage.h" #include "markdowneditorpage.h" #include "appearancepage.h" +#include "themepage.h" using namespace vnotex; @@ -72,45 +73,36 @@ void SettingsDialog::setupPages() // General. { auto page = new GeneralPage(this); - m_pageLayout->addWidget(page); - - auto item = new QTreeWidgetItem(m_pageExplorer); - setupPage(item, page); + addPage(page); } // Appearance. { auto page = new AppearancePage(this); - m_pageLayout->addWidget(page); + auto item = addPage(page); - auto item = new QTreeWidgetItem(m_pageExplorer); - setupPage(item, page); + // Theme. + { + auto subPage = new ThemePage(this); + addSubPage(subPage, item); + } } // Editor. { auto page = new EditorPage(this); - m_pageLayout->addWidget(page); - - auto item = new QTreeWidgetItem(m_pageExplorer); - setupPage(item, page); + auto item = addPage(page); // Text Editor. { auto subPage = new TextEditorPage(this); - m_pageLayout->addWidget(subPage); - - auto subItem = new QTreeWidgetItem(item); - setupPage(subItem, subPage); + addSubPage(subPage, item); } // Markdown Editor. { auto subPage = new MarkdownEditorPage(this); - m_pageLayout->addWidget(subPage); - - auto subItem = new QTreeWidgetItem(item); - setupPage(subItem, subPage); + addSubPage(subPage, item); } } @@ -191,3 +183,21 @@ void SettingsDialog::forEachPage(const std::function &p_fu p_func(page); } } + +QTreeWidgetItem *SettingsDialog::addPage(SettingsPage *p_page) +{ + m_pageLayout->addWidget(p_page); + + auto item = new QTreeWidgetItem(m_pageExplorer); + setupPage(item, p_page); + return item; +} + +QTreeWidgetItem *SettingsDialog::addSubPage(SettingsPage *p_page, QTreeWidgetItem *p_parentItem) +{ + m_pageLayout->addWidget(p_page); + + auto subItem = new QTreeWidgetItem(p_parentItem); + setupPage(subItem, p_page); + return subItem; +} diff --git a/src/widgets/dialogs/settings/settingsdialog.h b/src/widgets/dialogs/settings/settingsdialog.h index 7c8dbcd7..9b382b34 100644 --- a/src/widgets/dialogs/settings/settingsdialog.h +++ b/src/widgets/dialogs/settings/settingsdialog.h @@ -44,6 +44,10 @@ namespace vnotex void forEachPage(const std::function &p_func); + QTreeWidgetItem *addPage(SettingsPage *p_page); + + QTreeWidgetItem *addSubPage(SettingsPage *p_page, QTreeWidgetItem *p_parentItem); + QLineEdit *m_searchEdit = nullptr; QTreeWidget *m_pageExplorer = nullptr; diff --git a/src/widgets/dialogs/settings/themepage.cpp b/src/widgets/dialogs/settings/themepage.cpp new file mode 100644 index 00000000..d9506b93 --- /dev/null +++ b/src/widgets/dialogs/settings/themepage.cpp @@ -0,0 +1,32 @@ +#include "themepage.h" + +#include +#include + +#include + +using namespace vnotex; + +ThemePage::ThemePage(QWidget *p_parent) + : SettingsPage(p_parent) +{ + setupUI(); +} + +void ThemePage::setupUI() +{ + auto mainLayout = new QVBoxLayout(this); +} + +void ThemePage::loadInternal() +{ +} + +void ThemePage::saveInternal() +{ +} + +QString ThemePage::title() const +{ + return tr("Theme"); +} diff --git a/src/widgets/dialogs/settings/themepage.h b/src/widgets/dialogs/settings/themepage.h new file mode 100644 index 00000000..7b33d086 --- /dev/null +++ b/src/widgets/dialogs/settings/themepage.h @@ -0,0 +1,26 @@ +#ifndef THEMEPAGE_H +#define THEMEPAGE_H + +#include "settingspage.h" + +namespace vnotex +{ + class ThemePage : public SettingsPage + { + Q_OBJECT + public: + explicit ThemePage(QWidget *p_parent = nullptr); + + QString title() const Q_DECL_OVERRIDE; + + protected: + void loadInternal() Q_DECL_OVERRIDE; + + void saveInternal() Q_DECL_OVERRIDE; + + private: + void setupUI(); + }; +} + +#endif // THEMEPAGE_H diff --git a/src/widgets/editors/markdowneditor.cpp b/src/widgets/editors/markdowneditor.cpp index 6b2b909b..c6af65a5 100644 --- a/src/widgets/editors/markdowneditor.cpp +++ b/src/widgets/editors/markdowneditor.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -48,9 +49,13 @@ using namespace vnotex; // We set the property of the clipboard to mark that we are requesting a rich paste. static const char *c_clipboardPropertyMark = "RichPaste"; -MarkdownEditor::Heading::Heading(const QString &p_name, int p_level, int p_blockNumber) +MarkdownEditor::Heading::Heading(const QString &p_name, + int p_level, + const QString &p_sectionNumber, + int p_blockNumber) : m_name(p_name), m_level(p_level), + m_sectionNumber(p_sectionNumber), m_blockNumber(p_blockNumber) { } @@ -71,17 +76,25 @@ MarkdownEditor::MarkdownEditor(const MarkdownEditorConfig &p_config, this, &MarkdownEditor::handleContextMenuEvent); connect(getHighlighter(), &vte::PegMarkdownHighlighter::headersUpdated, - this, [this](const QVector &p_headerRegions) { - // TODO: insert heading sequence. - updateHeadings(p_headerRegions); - }); + this, &MarkdownEditor::updateHeadings); m_headingTimer = new QTimer(this); m_headingTimer->setInterval(500); + m_headingTimer->setSingleShot(true); connect(m_headingTimer, &QTimer::timeout, this, &MarkdownEditor::currentHeadingChanged); connect(m_textEdit, &vte::VTextEdit::cursorLineChanged, m_headingTimer, QOverload<>::of(&QTimer::start)); + + m_sectionNumberTimer = new QTimer(this); + m_sectionNumberTimer->setInterval(1000); + m_sectionNumberTimer->setSingleShot(true); + connect(m_sectionNumberTimer, &QTimer::timeout, + this, [this]() { + updateSectionNumber(m_headings); + }); + + updateFromConfig(false); } MarkdownEditor::~MarkdownEditor() @@ -719,13 +732,29 @@ int MarkdownEditor::getCurrentHeadingIndex() const void MarkdownEditor::updateHeadings(const QVector &p_headerRegions) { - auto doc = document(); + bool needUpdateSectionNumber = false; + if (isReadOnly()) { + m_sectionNumberEnabled = false; + } else { + needUpdateSectionNumber = m_config.getSectionNumberMode() == MarkdownEditorConfig::SectionNumberMode::Edit; + if (m_overriddenSectionNumber != OverrideState::NoOverride) { + needUpdateSectionNumber = m_overriddenSectionNumber == OverrideState::ForceEnable; + } + if (needUpdateSectionNumber) { + m_sectionNumberEnabled = true; + } else if (m_sectionNumberEnabled) { + // On -> Off. We still need to do the clean up. + needUpdateSectionNumber = true; + m_sectionNumberEnabled = false; + } + } QVector headings; headings.reserve(p_headerRegions.size()); // Assume that each block contains only one line. // Only support # syntax for now. + auto doc = document(); QRegExp headerReg(vte::MarkdownUtils::c_headerRegExp); for (auto const ® : p_headerRegions) { auto block = doc->findBlock(reg.m_startPos); @@ -738,14 +767,22 @@ void MarkdownEditor::updateHeadings(const QVector &p_he } if (headerReg.exactMatch(block.text())) { - int level = headerReg.cap(1).length(); - Heading heading(headerReg.cap(2).trimmed(), level, block.blockNumber()); + const int level = headerReg.cap(1).length(); + Heading heading(headerReg.cap(2).trimmed(), + level, + headerReg.cap(3), + block.blockNumber()); headings.append(heading); } } OutlineProvider::makePerfectHeadings(headings, m_headings); + if (needUpdateSectionNumber) { + // Use a timer to kick off the update to let user have time to undo. + m_sectionNumberTimer->start(); + } + emit headingsChanged(); emit currentHeadingChanged(); @@ -1045,3 +1082,121 @@ void MarkdownEditor::fetchImagesToLocalAndReplace(QString &p_text) proDlg.setValue(regs.size()); } + +static void increaseSectionNumber(QVector &p_sectionNumber, int p_level, int p_baseLevel) +{ + Q_ASSERT(p_level >= 1 && p_level < p_sectionNumber.size()); + if (p_level < p_baseLevel) { + p_sectionNumber.fill(0); + return; + } + + ++p_sectionNumber[p_level]; + for (int i = p_level + 1; i < p_sectionNumber.size(); ++i) { + p_sectionNumber[i] = 0; + } +} + +static QString joinSectionNumberStr(const QVector &p_sectionNumber) +{ + QString res; + for (auto sec : p_sectionNumber) { + if (sec != 0) { + res = res + QString::number(sec) + '.'; + } else if (res.isEmpty()) { + continue; + } else { + break; + } + } + + return res; +} + +static bool updateHeadingSectionNumber(QTextCursor &p_cursor, + const QTextBlock &p_block, + QRegExp &p_headingReg, + QRegExp &p_prefixReg, + const QString &p_sectionNumber) +{ + if (!p_block.isValid()) { + return false; + } + + QString text = p_block.text(); + bool matched = p_headingReg.exactMatch(text); + Q_ASSERT(matched); + + matched = p_prefixReg.exactMatch(text); + Q_ASSERT(matched); + + int start = p_headingReg.cap(1).length() + 1; + int end = p_prefixReg.cap(1).length(); + + Q_ASSERT(start <= end); + + p_cursor.setPosition(p_block.position() + start); + if (start != end) { + p_cursor.setPosition(p_block.position() + end, QTextCursor::KeepAnchor); + } + + if (p_sectionNumber.isEmpty()) { + p_cursor.removeSelectedText(); + } else { + p_cursor.insertText(p_sectionNumber + ' '); + } + return true; +} + +bool MarkdownEditor::updateSectionNumber(const QVector &p_headings) +{ + QVector sectionNumber(7, 0); + int baseLevel = m_config.getSectionNumberBaseLevel(); + if (baseLevel < 1 || baseLevel > 6) { + baseLevel = 1; + } + + bool changed = false; + auto doc = document(); + QRegExp headerReg(vte::MarkdownUtils::c_headerRegExp); + QRegExp prefixReg(vte::MarkdownUtils::c_headerPrefixRegExp); + QTextCursor cursor(doc); + cursor.beginEditBlock(); + for (const auto &heading : p_headings) { + increaseSectionNumber(sectionNumber, heading.m_level, baseLevel); + auto sectionStr = m_sectionNumberEnabled ? joinSectionNumberStr(sectionNumber) : QString(); + if (heading.m_blockNumber > -1 && sectionStr != heading.m_sectionNumber) { + if (updateHeadingSectionNumber(cursor, + doc->findBlockByNumber(heading.m_blockNumber), + headerReg, + prefixReg, + sectionStr)) { + changed = true; + } + } + } + cursor.endEditBlock(); + + return changed; +} + +void MarkdownEditor::overrideSectionNumber(OverrideState p_state) +{ + if (m_overriddenSectionNumber == p_state) { + return; + } + + m_overriddenSectionNumber = p_state; + getHighlighter()->updateHighlight(); +} + +void MarkdownEditor::updateFromConfig(bool p_initialized) +{ + if (m_config.getTextEditorConfig().getZoomDelta() != 0) { + zoom(m_config.getTextEditorConfig().getZoomDelta()); + } + + if (p_initialized) { + getHighlighter()->updateHighlight(); + } +} diff --git a/src/widgets/editors/markdowneditor.h b/src/widgets/editors/markdowneditor.h index b51f2621..efd81d17 100644 --- a/src/widgets/editors/markdowneditor.h +++ b/src/widgets/editors/markdowneditor.h @@ -31,12 +31,18 @@ namespace vnotex { Heading() = default; - Heading(const QString &p_name, int p_level, int p_blockNumber = -1); + Heading(const QString &p_name, + int p_level, + const QString &p_sectionNumber = QString(), + int p_blockNumber = -1); QString m_name; int m_level = -1; + // 1.2. . + QString m_sectionNumber; + int m_blockNumber = -1; }; @@ -84,6 +90,10 @@ namespace vnotex void scrollToHeading(int p_idx); + void overrideSectionNumber(OverrideState p_state); + + void updateFromConfig(bool p_initialized = true); + public slots: void handleHtmlToMarkdownData(quint64 p_id, TimeStamp p_timeStamp, const QString &p_text); @@ -148,6 +158,8 @@ namespace vnotex QString getRelativeLink(const QString &p_path); + // Update section number. + // Update headings outline. void updateHeadings(const QVector &p_headerRegions); int getHeadingIndexByBlockNumber(int p_blockNumber) const; @@ -156,6 +168,9 @@ namespace vnotex void fetchImagesToLocalAndReplace(QString &p_text); + // Return true if there is change. + bool updateSectionNumber(const QVector &p_headings); + static QString generateImageFileNameToInsertAs(const QString &p_title, const QString &p_suffix); const MarkdownEditorConfig &m_config; @@ -168,6 +183,13 @@ namespace vnotex TimeStamp m_timeStamp = 0; QTimer *m_headingTimer = nullptr; + + QTimer *m_sectionNumberTimer = nullptr; + + // Used to detect the config change and do a clean up. + bool m_sectionNumberEnabled = true; + + OverrideState m_overriddenSectionNumber = OverrideState::NoOverride; }; } diff --git a/src/widgets/mainwindow.cpp b/src/widgets/mainwindow.cpp index 37b88d29..185e5635 100644 --- a/src/widgets/mainwindow.cpp +++ b/src/widgets/mainwindow.cpp @@ -292,6 +292,7 @@ void MainWindow::closeEvent(QCloseEvent *p_event) isExit = true; #endif + bool needShowMessage = false; if(!isExit && toTray == -1){ int ret = MessageBoxHelper::questionYesNo(MessageBoxHelper::Question, tr("Do you want to minimize %1 to system tray " @@ -301,6 +302,7 @@ void MainWindow::closeEvent(QCloseEvent *p_event) this); if (ret == QMessageBox::Yes) { ConfigMgr::getInst().getSessionConfig().setMinimizeToSystemTray(true); + needShowMessage = true; } else if (ret == QMessageBox::No) { ConfigMgr::getInst().getSessionConfig().setMinimizeToSystemTray(false); isExit = true; @@ -330,6 +332,9 @@ void MainWindow::closeEvent(QCloseEvent *p_event) } else { hide(); p_event->ignore(); + if (needShowMessage) { + m_trayIcon->showMessage(ConfigMgr::c_appName, tr("%1 is still running here.").arg(ConfigMgr::c_appName)); + } } } diff --git a/src/widgets/markdownviewwindow.cpp b/src/widgets/markdownviewwindow.cpp index c4851a0d..5aeeb1c4 100644 --- a/src/widgets/markdownviewwindow.cpp +++ b/src/widgets/markdownviewwindow.cpp @@ -186,9 +186,7 @@ void MarkdownViewWindow::handleEditorConfigChange() auto config = createMarkdownEditorConfig(markdownEditorConfig); m_editor->setConfig(config); - if (markdownEditorConfig.getTextEditorConfig().getZoomDelta() != 0) { - m_editor->zoom(markdownEditorConfig.getTextEditorConfig().getZoomDelta()); - } + m_editor->updateFromConfig(); } updateWebViewerConfig(markdownEditorConfig); @@ -269,6 +267,8 @@ void MarkdownViewWindow::setupToolBar() toolBar->addSeparator(); + addAction(toolBar, ViewWindowToolBarHelper::SectionNumber); + addAction(toolBar, ViewWindowToolBarHelper::TypeHeading); addAction(toolBar, ViewWindowToolBarHelper::TypeBold); addAction(toolBar, ViewWindowToolBarHelper::TypeItalic); @@ -306,10 +306,6 @@ void MarkdownViewWindow::setupTextEditor() this); m_splitter->insertWidget(0, m_editor); - if (markdownEditorConfig.getTextEditorConfig().getZoomDelta() != 0) { - m_editor->zoom(markdownEditorConfig.getTextEditorConfig().getZoomDelta()); - } - TextViewWindowHelper::connectEditor(this); // Status widget. @@ -683,6 +679,13 @@ void MarkdownViewWindow::handleTypeAction(TypeAction p_action) } } +void MarkdownViewWindow::handleSectionNumberOverride(OverrideState p_state) +{ + if (m_mode != Mode::Read) { + m_editor->overrideSectionNumber(p_state); + } +} + void MarkdownViewWindow::detachFromBufferInternal() { auto buffer = getBuffer(); diff --git a/src/widgets/markdownviewwindow.h b/src/widgets/markdownviewwindow.h index b6a24837..22ba9355 100644 --- a/src/widgets/markdownviewwindow.h +++ b/src/widgets/markdownviewwindow.h @@ -49,6 +49,8 @@ namespace vnotex void handleTypeAction(TypeAction p_action) Q_DECL_OVERRIDE; + void handleSectionNumberOverride(OverrideState p_state) Q_DECL_OVERRIDE; + void handleFindTextChanged(const QString &p_text, FindOptions p_options) Q_DECL_OVERRIDE; void handleFindNext(const QString &p_text, FindOptions p_options) Q_DECL_OVERRIDE; diff --git a/src/widgets/textviewwindow.cpp b/src/widgets/textviewwindow.cpp index 39b4fe1c..c882fa13 100644 --- a/src/widgets/textviewwindow.cpp +++ b/src/widgets/textviewwindow.cpp @@ -38,9 +38,7 @@ void TextViewWindow::setupUI() m_editor = new TextEditor(config, this); setCentralWidget(m_editor); - if (textEditorConfig.getZoomDelta() != 0) { - m_editor->zoom(textEditorConfig.getZoomDelta()); - } + updateEditorFromConfig(); } TextViewWindowHelper::connectEditor(this); @@ -135,9 +133,7 @@ void TextViewWindow::handleEditorConfigChange() auto config = createTextEditorConfig(textEditorConfig); m_editor->setConfig(config); - if (textEditorConfig.getZoomDelta() != 0) { - m_editor->zoom(textEditorConfig.getZoomDelta()); - } + updateEditorFromConfig(); } } @@ -209,3 +205,12 @@ void TextViewWindow::handleFindAndReplaceWidgetClosed() { TextViewWindowHelper::handleFindAndReplaceWidgetClosed(this); } + +void TextViewWindow::updateEditorFromConfig() +{ + const auto &editorConfig = ConfigMgr::getInst().getEditorConfig(); + const auto &textEditorConfig = editorConfig.getTextEditorConfig(); + if (textEditorConfig.getZoomDelta() != 0) { + m_editor->zoom(textEditorConfig.getZoomDelta()); + } +} diff --git a/src/widgets/textviewwindow.h b/src/widgets/textviewwindow.h index 1b1712af..9a2dc1f0 100644 --- a/src/widgets/textviewwindow.h +++ b/src/widgets/textviewwindow.h @@ -64,6 +64,8 @@ namespace vnotex // call this function to tell us now the latest buffer revision. void setBufferRevisionAfterInvalidation(int p_bufferRevision); + void updateEditorFromConfig(); + static QSharedPointer createTextEditorConfig(const TextEditorConfig &p_config); // Managed by QObject. diff --git a/src/widgets/viewwindow.cpp b/src/widgets/viewwindow.cpp index 462751c6..5d29b9b0 100644 --- a/src/widgets/viewwindow.cpp +++ b/src/widgets/viewwindow.cpp @@ -408,6 +408,23 @@ QAction *ViewWindow::addAction(QToolBar *p_toolBar, ViewWindowToolBarHelper::Act }); break; } + + case ViewWindowToolBarHelper::SectionNumber: + { + act = ViewWindowToolBarHelper::addAction(p_toolBar, p_action); + connect(this, &ViewWindow::modeChanged, + this, [this, act]() { + act->setEnabled(inModeCanInsert() && getBuffer() && !getBuffer()->isReadOnly()); + }); + auto toolBtn = dynamic_cast(p_toolBar->widgetForAction(act)); + Q_ASSERT(toolBtn); + connect(toolBtn->menu(), &QMenu::triggered, + this, [this](QAction *p_act) { + OverrideState state = static_cast(p_act->data().toInt()); + handleSectionNumberOverride(state); + }); + break; + } } return act; @@ -586,6 +603,12 @@ void ViewWindow::handleTypeAction(TypeAction p_action) Q_ASSERT(false); } +void ViewWindow::handleSectionNumberOverride(OverrideState p_state) +{ + Q_UNUSED(p_state); + Q_ASSERT(false); +} + ViewWindow::TypeAction ViewWindow::toolBarActionToTypeAction(ViewWindowToolBarHelper::Action p_action) { Q_ASSERT(p_action >= ViewWindowToolBarHelper::Action::TypeBold diff --git a/src/widgets/viewwindow.h b/src/widgets/viewwindow.h index d1acedd4..15d5caa1 100644 --- a/src/widgets/viewwindow.h +++ b/src/widgets/viewwindow.h @@ -137,6 +137,8 @@ namespace vnotex // Handle all kinds of type action. virtual void handleTypeAction(TypeAction p_action); + virtual void handleSectionNumberOverride(OverrideState p_state); + virtual void handleFindTextChanged(const QString &p_text, FindOptions p_options); virtual void handleFindNext(const QString &p_text, FindOptions p_options); diff --git a/src/widgets/viewwindowtoolbarhelper.cpp b/src/widgets/viewwindowtoolbarhelper.cpp index 16e5b34a..b61b2804 100644 --- a/src/widgets/viewwindowtoolbarhelper.cpp +++ b/src/widgets/viewwindowtoolbarhelper.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "toolbarhelper.h" #include @@ -20,6 +21,7 @@ #include "propertydefs.h" #include "outlinepopup.h" #include "viewwindow.h" +#include using namespace vnotex; @@ -306,6 +308,39 @@ QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action) break; } + case Action::SectionNumber: + { + act = p_tb->addAction(ToolBarHelper::generateIcon("section_number_editor.svg"), + ViewWindow::tr("Section Number")); + + auto toolBtn = dynamic_cast(p_tb->widgetForAction(act)); + Q_ASSERT(toolBtn); + toolBtn->setPopupMode(QToolButton::InstantPopup); + toolBtn->setProperty(PropertyDefs::s_toolButtonWithoutMenuIndicator, true); + + auto menu = WidgetsFactory::createMenu(p_tb); + + auto actGroup = new QActionGroup(menu); + auto act1 = actGroup->addAction(ViewWindow::tr("Follow Configuration")); + act1->setCheckable(true); + act1->setChecked(true); + act1->setData(OverrideState::NoOverride); + menu->addAction(act1); + + act1 = actGroup->addAction(ViewWindow::tr("Enabled")); + act1->setCheckable(true); + act1->setData(OverrideState::ForceEnable); + menu->addAction(act1); + + act1 = actGroup->addAction(ViewWindow::tr("Disabled")); + act1->setCheckable(true); + act1->setData(OverrideState::ForceDisable); + menu->addAction(act1); + + toolBtn->setMenu(menu); + break; + } + default: Q_ASSERT(false); break; diff --git a/src/widgets/viewwindowtoolbarhelper.h b/src/widgets/viewwindowtoolbarhelper.h index 10fd0b81..f37436bb 100644 --- a/src/widgets/viewwindowtoolbarhelper.h +++ b/src/widgets/viewwindowtoolbarhelper.h @@ -39,7 +39,8 @@ namespace vnotex Attachment, Outline, - FindAndReplace + FindAndReplace, + SectionNumber }; static QAction *addAction(QToolBar *p_tb, Action p_action); diff --git a/src/widgets/widgets.pri b/src/widgets/widgets.pri index 9ae5f183..f737d644 100644 --- a/src/widgets/widgets.pri +++ b/src/widgets/widgets.pri @@ -20,6 +20,7 @@ SOURCES += \ $$PWD/dialogs/settings/settingspage.cpp \ $$PWD/dialogs/settings/settingsdialog.cpp \ $$PWD/dialogs/settings/texteditorpage.cpp \ + $$PWD/dialogs/settings/themepage.cpp \ $$PWD/dragdropareaindicator.cpp \ $$PWD/editors/editormarkdownvieweradapter.cpp \ $$PWD/editors/markdowneditor.cpp \ @@ -100,6 +101,7 @@ HEADERS += \ $$PWD/dialogs/settings/settingspage.h \ $$PWD/dialogs/settings/settingsdialog.h \ $$PWD/dialogs/settings/texteditorpage.h \ + $$PWD/dialogs/settings/themepage.h \ $$PWD/dragdropareaindicator.h \ $$PWD/editors/editormarkdownvieweradapter.h \ $$PWD/editors/markdowneditor.h \