support section number in edit mode

This commit is contained in:
Le Tan 2020-12-24 20:18:37 +08:00
parent 1f7acf2bc5
commit 4636771081
26 changed files with 535 additions and 75 deletions

@ -1 +1 @@
Subproject commit 12a74cb32438b28f002dea51c484133d3058a882 Subproject commit 47347580cbcd460ebbbc8358e37b409530edaa2f

View File

@ -72,6 +72,13 @@ namespace vnotex
IncrementalSearch = 0x10U IncrementalSearch = 0x10U
}; };
Q_DECLARE_FLAGS(FindOptions, FindOption); Q_DECLARE_FLAGS(FindOptions, FindOption);
enum OverrideState
{
NoOverride = 0,
ForceEnable = 1,
ForceDisable = 2
};
} // ns vnotex } // ns vnotex
Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::FindOptions); Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::FindOptions);

View File

@ -155,7 +155,7 @@ void HtmlTemplateHelper::updateMarkdownViewerTemplate(const MarkdownEditorConfig
WebGlobalOptions opts; WebGlobalOptions opts;
opts.m_webPlantUml = p_config.getWebPlantUml(); opts.m_webPlantUml = p_config.getWebPlantUml();
opts.m_webGraphviz = p_config.getWebGraphviz(); 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_constrainImageWidthEnabled = p_config.getConstrainImageWidthEnabled();
opts.m_protectFromXss = p_config.getProtectFromXss(); opts.m_protectFromXss = p_config.getProtectFromXss();
opts.m_htmlTagEnabled = p_config.getHtmlTagEnabled(); opts.m_htmlTagEnabled = p_config.getHtmlTagEnabled();

View File

@ -10,6 +10,7 @@ using namespace vnotex;
#define READSTR(key) readString(appObj, userObj, (key)) #define READSTR(key) readString(appObj, userObj, (key))
#define READBOOL(key) readBool(appObj, userObj, (key)) #define READBOOL(key) readBool(appObj, userObj, (key))
#define READREAL(key) readReal(appObj, userObj, (key)) #define READREAL(key) readReal(appObj, userObj, (key))
#define READINT(key) readInt(appObj, userObj, (key))
MarkdownEditorConfig::MarkdownEditorConfig(ConfigMgr *p_mgr, MarkdownEditorConfig::MarkdownEditorConfig(ConfigMgr *p_mgr,
IConfig *p_topConfig, 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_prependDotInRelativeLink = READBOOL(QStringLiteral("prepend_dot_in_relative_link"));
m_confirmBeforeClearObsoleteImages = READBOOL(QStringLiteral("confirm_before_clear_obsolete_images")); m_confirmBeforeClearObsoleteImages = READBOOL(QStringLiteral("confirm_before_clear_obsolete_images"));
m_insertFileNameAsTitle = READBOOL(QStringLiteral("insert_file_name_as_title")); 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_constrainImageWidthEnabled = READBOOL(QStringLiteral("constrain_image_width"));
m_constrainInPlacePreviewWidthEnabled = READBOOL(QStringLiteral("constrain_inplace_preview_width")); m_constrainInPlacePreviewWidthEnabled = READBOOL(QStringLiteral("constrain_inplace_preview_width"));
m_zoomFactorInReadMode = READREAL(QStringLiteral("zoom_factor_in_read_mode")); 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("prepend_dot_in_relative_link")] = m_prependDotInRelativeLink;
obj[QStringLiteral("confirm_before_clear_obsolete_images")] = m_confirmBeforeClearObsoleteImages; obj[QStringLiteral("confirm_before_clear_obsolete_images")] = m_confirmBeforeClearObsoleteImages;
obj[QStringLiteral("insert_file_name_as_title")] = m_insertFileNameAsTitle; 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_image_width")] = m_constrainImageWidthEnabled;
obj[QStringLiteral("constrain_inplace_preview_width")] = m_constrainInPlacePreviewWidthEnabled; obj[QStringLiteral("constrain_inplace_preview_width")] = m_constrainInPlacePreviewWidthEnabled;
obj[QStringLiteral("zoom_factor_in_read_mode")] = m_zoomFactorInReadMode; 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); 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 bool MarkdownEditorConfig::getConstrainImageWidthEnabled() const
{ {
return m_constrainImageWidthEnabled; return m_constrainImageWidthEnabled;
@ -231,3 +228,49 @@ void MarkdownEditorConfig::setLinkifyEnabled(bool p_enabled)
{ {
updateConfig(m_linkifyEnabled, p_enabled, this); 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);
}

View File

@ -15,6 +15,13 @@ namespace vnotex
class MarkdownEditorConfig : public IConfig class MarkdownEditorConfig : public IConfig
{ {
public: public:
enum SectionNumberMode
{
None,
Read,
Edit
};
MarkdownEditorConfig(ConfigMgr *p_mgr, MarkdownEditorConfig(ConfigMgr *p_mgr,
IConfig *p_topConfig, IConfig *p_topConfig,
const QSharedPointer<TextEditorConfig> &p_textEditorConfig); const QSharedPointer<TextEditorConfig> &p_textEditorConfig);
@ -45,8 +52,11 @@ namespace vnotex
bool getInsertFileNameAsTitle() const; bool getInsertFileNameAsTitle() const;
void setInsertFileNameAsTitle(bool p_enabled); void setInsertFileNameAsTitle(bool p_enabled);
bool getSectionNumberEnabled() const; SectionNumberMode getSectionNumberMode() const;
void setSectionNumberEnabled(bool p_enabled); void setSectionNumberMode(SectionNumberMode p_mode);
int getSectionNumberBaseLevel() const;
void setSectionNumberBaseLevel(int p_level);
bool getConstrainImageWidthEnabled() const; bool getConstrainImageWidthEnabled() const;
void setConstrainImageWidthEnabled(bool p_enabled); void setConstrainImageWidthEnabled(bool p_enabled);
@ -72,6 +82,9 @@ namespace vnotex
void setLinkifyEnabled(bool p_enabled); void setLinkifyEnabled(bool p_enabled);
private: private:
QString sectionNumberModeToString(SectionNumberMode p_mode) const;
SectionNumberMode stringToSectionNumberMode(const QString &p_str) const;
QSharedPointer<TextEditorConfig> m_textEditorConfig; QSharedPointer<TextEditorConfig> m_textEditorConfig;
ViewerResource m_viewerResource; ViewerResource m_viewerResource;
@ -91,7 +104,10 @@ namespace vnotex
bool m_insertFileNameAsTitle = true; bool m_insertFileNameAsTitle = true;
// Whether enable section numbering. // Whether enable section numbering.
bool m_sectionNumberEnabled = true; SectionNumberMode m_sectionNumberMode = SectionNumberMode::Read;
// 1 based.
int m_sectionNumberBaseLevel = 2;
// Whether enable image width constraint. // Whether enable image width constraint.
bool m_constrainImageWidthEnabled = true; bool m_constrainImageWidthEnabled = true;

View File

@ -72,6 +72,7 @@
<file>icons/stay_on_top.svg</file> <file>icons/stay_on_top.svg</file>
<file>icons/outline_editor.svg</file> <file>icons/outline_editor.svg</file>
<file>icons/find_replace_editor.svg</file> <file>icons/find_replace_editor.svg</file>
<file>icons/section_number_editor.svg</file>
<file>logo/vnote.svg</file> <file>logo/vnote.svg</file>
<file>logo/vnote.png</file> <file>logo/vnote.png</file>
<file>logo/256x256/vnote.png</file> <file>logo/256x256/vnote.png</file>

View File

@ -0,0 +1,7 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
<g>
<title>Layer 2</title>
<text stroke="#000000" transform="matrix(11.892995768998892,0,0,12.532065948318316,-399.2455647895517,-874.5950374851516) " xml:space="preserve" text-anchor="middle" font-family="Sans-serif" font-size="24" id="svg_1" y="98.48198" x="55.1146" stroke-width="0" fill="#000000">1.2.</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 502 B

View File

@ -226,8 +226,10 @@
"confirm_before_clear_obsolete_images" : true, "confirm_before_clear_obsolete_images" : true,
"//comment" : "Whether insert the file name as title on new file", "//comment" : "Whether insert the file name as title on new file",
"insert_file_name_as_title" : true, "insert_file_name_as_title" : true,
"//comment" : "Whether enable auto section numbering", "//comment" : "none/read/edit",
"section_number" : true, "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", "//comment" : "Whether enable image width constraint",
"constrain_image_width" : true, "constrain_image_width" : true,
"//comment" : "Whether enable in-place preview width constraint", "//comment" : "Whether enable in-place preview width constraint",

View File

@ -5,6 +5,9 @@
#include <QCheckBox> #include <QCheckBox>
#include <QGroupBox> #include <QGroupBox>
#include <QDoubleSpinBox> #include <QDoubleSpinBox>
#include <QComboBox>
#include <QSpinBox>
#include <QHBoxLayout>
#include <widgets/widgetsfactory.h> #include <widgets/widgetsfactory.h>
#include <core/editorconfig.h> #include <core/editorconfig.h>
@ -25,6 +28,9 @@ void MarkdownEditorPage::setupUI()
{ {
auto mainLayout = new QVBoxLayout(this); auto mainLayout = new QVBoxLayout(this);
auto generalBox = setupGeneralGroup();
mainLayout->addWidget(generalBox);
auto readBox = setupReadGroup(); auto readBox = setupReadGroup();
mainLayout->addWidget(readBox); mainLayout->addWidget(readBox);
@ -38,7 +44,13 @@ void MarkdownEditorPage::loadInternal()
m_insertFileNameAsTitleCheckBox->setChecked(markdownConfig.getInsertFileNameAsTitle()); m_insertFileNameAsTitleCheckBox->setChecked(markdownConfig.getInsertFileNameAsTitle());
m_sectionNumberCheckBox->setChecked(markdownConfig.getSectionNumberEnabled()); {
int idx = m_sectionNumberComboBox->findData(static_cast<int>(markdownConfig.getSectionNumberMode()));
Q_ASSERT(idx != -1);
m_sectionNumberComboBox->setCurrentIndex(idx);
m_sectionNumberBaseLevelSpinBox->setValue(markdownConfig.getSectionNumberBaseLevel());
}
m_constrainImageWidthCheckBox->setChecked(markdownConfig.getConstrainImageWidthEnabled()); m_constrainImageWidthCheckBox->setChecked(markdownConfig.getConstrainImageWidthEnabled());
@ -61,7 +73,14 @@ void MarkdownEditorPage::saveInternal()
markdownConfig.setInsertFileNameAsTitle(m_insertFileNameAsTitleCheckBox->isChecked()); markdownConfig.setInsertFileNameAsTitle(m_insertFileNameAsTitleCheckBox->isChecked());
markdownConfig.setSectionNumberEnabled(m_sectionNumberCheckBox->isChecked()); {
auto mode = m_sectionNumberComboBox->currentData().toInt();
markdownConfig.setSectionNumberMode(static_cast<MarkdownEditorConfig::SectionNumberMode>(mode));
if (m_sectionNumberBaseLevelSpinBox->isEnabled()) {
markdownConfig.setSectionNumberBaseLevel(m_sectionNumberBaseLevelSpinBox->value());
}
}
markdownConfig.setConstrainImageWidthEnabled(m_constrainImageWidthCheckBox->isChecked()); markdownConfig.setConstrainImageWidthEnabled(m_constrainImageWidthCheckBox->isChecked());
@ -90,16 +109,6 @@ QGroupBox *MarkdownEditorPage::setupReadGroup()
auto box = new QGroupBox(tr("Read"), this); auto box = new QGroupBox(tr("Read"), this);
auto layout = new QFormLayout(box); 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")); const QString label(tr("Constrain image width"));
m_constrainImageWidthCheckBox = WidgetsFactory::createCheckBox(label, box); m_constrainImageWidthCheckBox = WidgetsFactory::createCheckBox(label, box);
@ -194,3 +203,43 @@ QGroupBox *MarkdownEditorPage::setupEditGroup()
return box; 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<int>::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<int>::of(&QSpinBox::valueChanged),
this, &MarkdownEditorPage::pageIsChanged);
connect(m_sectionNumberComboBox, QOverload<int>::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;
}

View File

@ -6,6 +6,8 @@
class QCheckBox; class QCheckBox;
class QGroupBox; class QGroupBox;
class QDoubleSpinBox; class QDoubleSpinBox;
class QSpinBox;
class QComboBox;
namespace vnotex namespace vnotex
{ {
@ -25,14 +27,14 @@ namespace vnotex
private: private:
void setupUI(); void setupUI();
QGroupBox *setupGeneralGroup();
QGroupBox *setupReadGroup(); QGroupBox *setupReadGroup();
QGroupBox *setupEditGroup(); QGroupBox *setupEditGroup();
QCheckBox *m_insertFileNameAsTitleCheckBox = nullptr; QCheckBox *m_insertFileNameAsTitleCheckBox = nullptr;
QCheckBox *m_sectionNumberCheckBox = nullptr;
QCheckBox *m_constrainImageWidthCheckBox = nullptr; QCheckBox *m_constrainImageWidthCheckBox = nullptr;
QCheckBox *m_constrainInPlacePreviewWidthCheckBox = nullptr; QCheckBox *m_constrainInPlacePreviewWidthCheckBox = nullptr;
@ -46,6 +48,10 @@ namespace vnotex
QCheckBox *m_linkifyCheckBox = nullptr; QCheckBox *m_linkifyCheckBox = nullptr;
QDoubleSpinBox *m_zoomFactorSpinBox = nullptr; QDoubleSpinBox *m_zoomFactorSpinBox = nullptr;
QComboBox *m_sectionNumberComboBox = nullptr;
QSpinBox *m_sectionNumberBaseLevelSpinBox = nullptr;
}; };
} }

View File

@ -13,6 +13,7 @@
#include "texteditorpage.h" #include "texteditorpage.h"
#include "markdowneditorpage.h" #include "markdowneditorpage.h"
#include "appearancepage.h" #include "appearancepage.h"
#include "themepage.h"
using namespace vnotex; using namespace vnotex;
@ -72,45 +73,36 @@ void SettingsDialog::setupPages()
// General. // General.
{ {
auto page = new GeneralPage(this); auto page = new GeneralPage(this);
m_pageLayout->addWidget(page); addPage(page);
auto item = new QTreeWidgetItem(m_pageExplorer);
setupPage(item, page);
} }
// Appearance. // Appearance.
{ {
auto page = new AppearancePage(this); auto page = new AppearancePage(this);
m_pageLayout->addWidget(page); auto item = addPage(page);
auto item = new QTreeWidgetItem(m_pageExplorer); // Theme.
setupPage(item, page); {
auto subPage = new ThemePage(this);
addSubPage(subPage, item);
}
} }
// Editor. // Editor.
{ {
auto page = new EditorPage(this); auto page = new EditorPage(this);
m_pageLayout->addWidget(page); auto item = addPage(page);
auto item = new QTreeWidgetItem(m_pageExplorer);
setupPage(item, page);
// Text Editor. // Text Editor.
{ {
auto subPage = new TextEditorPage(this); auto subPage = new TextEditorPage(this);
m_pageLayout->addWidget(subPage); addSubPage(subPage, item);
auto subItem = new QTreeWidgetItem(item);
setupPage(subItem, subPage);
} }
// Markdown Editor. // Markdown Editor.
{ {
auto subPage = new MarkdownEditorPage(this); auto subPage = new MarkdownEditorPage(this);
m_pageLayout->addWidget(subPage); addSubPage(subPage, item);
auto subItem = new QTreeWidgetItem(item);
setupPage(subItem, subPage);
} }
} }
@ -191,3 +183,21 @@ void SettingsDialog::forEachPage(const std::function<void(SettingsPage *)> &p_fu
p_func(page); 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;
}

View File

@ -44,6 +44,10 @@ namespace vnotex
void forEachPage(const std::function<void(SettingsPage *)> &p_func); void forEachPage(const std::function<void(SettingsPage *)> &p_func);
QTreeWidgetItem *addPage(SettingsPage *p_page);
QTreeWidgetItem *addSubPage(SettingsPage *p_page, QTreeWidgetItem *p_parentItem);
QLineEdit *m_searchEdit = nullptr; QLineEdit *m_searchEdit = nullptr;
QTreeWidget *m_pageExplorer = nullptr; QTreeWidget *m_pageExplorer = nullptr;

View File

@ -0,0 +1,32 @@
#include "themepage.h"
#include <QComboBox>
#include <QVBoxLayout>
#include <widgets/widgetsfactory.h>
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");
}

View File

@ -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

View File

@ -37,6 +37,7 @@
#include <utils/textutils.h> #include <utils/textutils.h>
#include <core/exception.h> #include <core/exception.h>
#include <core/markdowneditorconfig.h> #include <core/markdowneditorconfig.h>
#include <core/texteditorconfig.h>
#include <core/configmgr.h> #include <core/configmgr.h>
#include <core/editorconfig.h> #include <core/editorconfig.h>
@ -48,9 +49,13 @@ using namespace vnotex;
// We set the property of the clipboard to mark that we are requesting a rich paste. // We set the property of the clipboard to mark that we are requesting a rich paste.
static const char *c_clipboardPropertyMark = "RichPaste"; 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_name(p_name),
m_level(p_level), m_level(p_level),
m_sectionNumber(p_sectionNumber),
m_blockNumber(p_blockNumber) m_blockNumber(p_blockNumber)
{ {
} }
@ -71,17 +76,25 @@ MarkdownEditor::MarkdownEditor(const MarkdownEditorConfig &p_config,
this, &MarkdownEditor::handleContextMenuEvent); this, &MarkdownEditor::handleContextMenuEvent);
connect(getHighlighter(), &vte::PegMarkdownHighlighter::headersUpdated, connect(getHighlighter(), &vte::PegMarkdownHighlighter::headersUpdated,
this, [this](const QVector<vte::peg::ElementRegion> &p_headerRegions) { this, &MarkdownEditor::updateHeadings);
// TODO: insert heading sequence.
updateHeadings(p_headerRegions);
});
m_headingTimer = new QTimer(this); m_headingTimer = new QTimer(this);
m_headingTimer->setInterval(500); m_headingTimer->setInterval(500);
m_headingTimer->setSingleShot(true);
connect(m_headingTimer, &QTimer::timeout, connect(m_headingTimer, &QTimer::timeout,
this, &MarkdownEditor::currentHeadingChanged); this, &MarkdownEditor::currentHeadingChanged);
connect(m_textEdit, &vte::VTextEdit::cursorLineChanged, connect(m_textEdit, &vte::VTextEdit::cursorLineChanged,
m_headingTimer, QOverload<>::of(&QTimer::start)); 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() MarkdownEditor::~MarkdownEditor()
@ -719,13 +732,29 @@ int MarkdownEditor::getCurrentHeadingIndex() const
void MarkdownEditor::updateHeadings(const QVector<vte::peg::ElementRegion> &p_headerRegions) void MarkdownEditor::updateHeadings(const QVector<vte::peg::ElementRegion> &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<Heading> headings; QVector<Heading> headings;
headings.reserve(p_headerRegions.size()); headings.reserve(p_headerRegions.size());
// Assume that each block contains only one line. // Assume that each block contains only one line.
// Only support # syntax for now. // Only support # syntax for now.
auto doc = document();
QRegExp headerReg(vte::MarkdownUtils::c_headerRegExp); QRegExp headerReg(vte::MarkdownUtils::c_headerRegExp);
for (auto const &reg : p_headerRegions) { for (auto const &reg : p_headerRegions) {
auto block = doc->findBlock(reg.m_startPos); auto block = doc->findBlock(reg.m_startPos);
@ -738,14 +767,22 @@ void MarkdownEditor::updateHeadings(const QVector<vte::peg::ElementRegion> &p_he
} }
if (headerReg.exactMatch(block.text())) { if (headerReg.exactMatch(block.text())) {
int level = headerReg.cap(1).length(); const int level = headerReg.cap(1).length();
Heading heading(headerReg.cap(2).trimmed(), level, block.blockNumber()); Heading heading(headerReg.cap(2).trimmed(),
level,
headerReg.cap(3),
block.blockNumber());
headings.append(heading); headings.append(heading);
} }
} }
OutlineProvider::makePerfectHeadings(headings, m_headings); 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 headingsChanged();
emit currentHeadingChanged(); emit currentHeadingChanged();
@ -1045,3 +1082,121 @@ void MarkdownEditor::fetchImagesToLocalAndReplace(QString &p_text)
proDlg.setValue(regs.size()); proDlg.setValue(regs.size());
} }
static void increaseSectionNumber(QVector<int> &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<int> &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<Heading> &p_headings)
{
QVector<int> 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();
}
}

View File

@ -31,12 +31,18 @@ namespace vnotex
{ {
Heading() = default; 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; QString m_name;
int m_level = -1; int m_level = -1;
// 1.2. .
QString m_sectionNumber;
int m_blockNumber = -1; int m_blockNumber = -1;
}; };
@ -84,6 +90,10 @@ namespace vnotex
void scrollToHeading(int p_idx); void scrollToHeading(int p_idx);
void overrideSectionNumber(OverrideState p_state);
void updateFromConfig(bool p_initialized = true);
public slots: public slots:
void handleHtmlToMarkdownData(quint64 p_id, TimeStamp p_timeStamp, const QString &p_text); void handleHtmlToMarkdownData(quint64 p_id, TimeStamp p_timeStamp, const QString &p_text);
@ -148,6 +158,8 @@ namespace vnotex
QString getRelativeLink(const QString &p_path); QString getRelativeLink(const QString &p_path);
// Update section number.
// Update headings outline.
void updateHeadings(const QVector<vte::peg::ElementRegion> &p_headerRegions); void updateHeadings(const QVector<vte::peg::ElementRegion> &p_headerRegions);
int getHeadingIndexByBlockNumber(int p_blockNumber) const; int getHeadingIndexByBlockNumber(int p_blockNumber) const;
@ -156,6 +168,9 @@ namespace vnotex
void fetchImagesToLocalAndReplace(QString &p_text); void fetchImagesToLocalAndReplace(QString &p_text);
// Return true if there is change.
bool updateSectionNumber(const QVector<Heading> &p_headings);
static QString generateImageFileNameToInsertAs(const QString &p_title, const QString &p_suffix); static QString generateImageFileNameToInsertAs(const QString &p_title, const QString &p_suffix);
const MarkdownEditorConfig &m_config; const MarkdownEditorConfig &m_config;
@ -168,6 +183,13 @@ namespace vnotex
TimeStamp m_timeStamp = 0; TimeStamp m_timeStamp = 0;
QTimer *m_headingTimer = nullptr; 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;
}; };
} }

View File

@ -292,6 +292,7 @@ void MainWindow::closeEvent(QCloseEvent *p_event)
isExit = true; isExit = true;
#endif #endif
bool needShowMessage = false;
if(!isExit && toTray == -1){ if(!isExit && toTray == -1){
int ret = MessageBoxHelper::questionYesNo(MessageBoxHelper::Question, int ret = MessageBoxHelper::questionYesNo(MessageBoxHelper::Question,
tr("Do you want to minimize %1 to system tray " tr("Do you want to minimize %1 to system tray "
@ -301,6 +302,7 @@ void MainWindow::closeEvent(QCloseEvent *p_event)
this); this);
if (ret == QMessageBox::Yes) { if (ret == QMessageBox::Yes) {
ConfigMgr::getInst().getSessionConfig().setMinimizeToSystemTray(true); ConfigMgr::getInst().getSessionConfig().setMinimizeToSystemTray(true);
needShowMessage = true;
} else if (ret == QMessageBox::No) { } else if (ret == QMessageBox::No) {
ConfigMgr::getInst().getSessionConfig().setMinimizeToSystemTray(false); ConfigMgr::getInst().getSessionConfig().setMinimizeToSystemTray(false);
isExit = true; isExit = true;
@ -330,6 +332,9 @@ void MainWindow::closeEvent(QCloseEvent *p_event)
} else { } else {
hide(); hide();
p_event->ignore(); p_event->ignore();
if (needShowMessage) {
m_trayIcon->showMessage(ConfigMgr::c_appName, tr("%1 is still running here.").arg(ConfigMgr::c_appName));
}
} }
} }

View File

@ -186,9 +186,7 @@ void MarkdownViewWindow::handleEditorConfigChange()
auto config = createMarkdownEditorConfig(markdownEditorConfig); auto config = createMarkdownEditorConfig(markdownEditorConfig);
m_editor->setConfig(config); m_editor->setConfig(config);
if (markdownEditorConfig.getTextEditorConfig().getZoomDelta() != 0) { m_editor->updateFromConfig();
m_editor->zoom(markdownEditorConfig.getTextEditorConfig().getZoomDelta());
}
} }
updateWebViewerConfig(markdownEditorConfig); updateWebViewerConfig(markdownEditorConfig);
@ -269,6 +267,8 @@ void MarkdownViewWindow::setupToolBar()
toolBar->addSeparator(); toolBar->addSeparator();
addAction(toolBar, ViewWindowToolBarHelper::SectionNumber);
addAction(toolBar, ViewWindowToolBarHelper::TypeHeading); addAction(toolBar, ViewWindowToolBarHelper::TypeHeading);
addAction(toolBar, ViewWindowToolBarHelper::TypeBold); addAction(toolBar, ViewWindowToolBarHelper::TypeBold);
addAction(toolBar, ViewWindowToolBarHelper::TypeItalic); addAction(toolBar, ViewWindowToolBarHelper::TypeItalic);
@ -306,10 +306,6 @@ void MarkdownViewWindow::setupTextEditor()
this); this);
m_splitter->insertWidget(0, m_editor); m_splitter->insertWidget(0, m_editor);
if (markdownEditorConfig.getTextEditorConfig().getZoomDelta() != 0) {
m_editor->zoom(markdownEditorConfig.getTextEditorConfig().getZoomDelta());
}
TextViewWindowHelper::connectEditor(this); TextViewWindowHelper::connectEditor(this);
// Status widget. // 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() void MarkdownViewWindow::detachFromBufferInternal()
{ {
auto buffer = getBuffer(); auto buffer = getBuffer();

View File

@ -49,6 +49,8 @@ namespace vnotex
void handleTypeAction(TypeAction p_action) Q_DECL_OVERRIDE; 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 handleFindTextChanged(const QString &p_text, FindOptions p_options) Q_DECL_OVERRIDE;
void handleFindNext(const QString &p_text, FindOptions p_options) Q_DECL_OVERRIDE; void handleFindNext(const QString &p_text, FindOptions p_options) Q_DECL_OVERRIDE;

View File

@ -38,9 +38,7 @@ void TextViewWindow::setupUI()
m_editor = new TextEditor(config, this); m_editor = new TextEditor(config, this);
setCentralWidget(m_editor); setCentralWidget(m_editor);
if (textEditorConfig.getZoomDelta() != 0) { updateEditorFromConfig();
m_editor->zoom(textEditorConfig.getZoomDelta());
}
} }
TextViewWindowHelper::connectEditor(this); TextViewWindowHelper::connectEditor(this);
@ -135,9 +133,7 @@ void TextViewWindow::handleEditorConfigChange()
auto config = createTextEditorConfig(textEditorConfig); auto config = createTextEditorConfig(textEditorConfig);
m_editor->setConfig(config); m_editor->setConfig(config);
if (textEditorConfig.getZoomDelta() != 0) { updateEditorFromConfig();
m_editor->zoom(textEditorConfig.getZoomDelta());
}
} }
} }
@ -209,3 +205,12 @@ void TextViewWindow::handleFindAndReplaceWidgetClosed()
{ {
TextViewWindowHelper::handleFindAndReplaceWidgetClosed(this); 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());
}
}

View File

@ -64,6 +64,8 @@ namespace vnotex
// call this function to tell us now the latest buffer revision. // call this function to tell us now the latest buffer revision.
void setBufferRevisionAfterInvalidation(int p_bufferRevision); void setBufferRevisionAfterInvalidation(int p_bufferRevision);
void updateEditorFromConfig();
static QSharedPointer<vte::TextEditorConfig> createTextEditorConfig(const TextEditorConfig &p_config); static QSharedPointer<vte::TextEditorConfig> createTextEditorConfig(const TextEditorConfig &p_config);
// Managed by QObject. // Managed by QObject.

View File

@ -408,6 +408,23 @@ QAction *ViewWindow::addAction(QToolBar *p_toolBar, ViewWindowToolBarHelper::Act
}); });
break; 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<QToolButton *>(p_toolBar->widgetForAction(act));
Q_ASSERT(toolBtn);
connect(toolBtn->menu(), &QMenu::triggered,
this, [this](QAction *p_act) {
OverrideState state = static_cast<OverrideState>(p_act->data().toInt());
handleSectionNumberOverride(state);
});
break;
}
} }
return act; return act;
@ -586,6 +603,12 @@ void ViewWindow::handleTypeAction(TypeAction p_action)
Q_ASSERT(false); Q_ASSERT(false);
} }
void ViewWindow::handleSectionNumberOverride(OverrideState p_state)
{
Q_UNUSED(p_state);
Q_ASSERT(false);
}
ViewWindow::TypeAction ViewWindow::toolBarActionToTypeAction(ViewWindowToolBarHelper::Action p_action) ViewWindow::TypeAction ViewWindow::toolBarActionToTypeAction(ViewWindowToolBarHelper::Action p_action)
{ {
Q_ASSERT(p_action >= ViewWindowToolBarHelper::Action::TypeBold Q_ASSERT(p_action >= ViewWindowToolBarHelper::Action::TypeBold

View File

@ -137,6 +137,8 @@ namespace vnotex
// Handle all kinds of type action. // Handle all kinds of type action.
virtual void handleTypeAction(TypeAction p_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 handleFindTextChanged(const QString &p_text, FindOptions p_options);
virtual void handleFindNext(const QString &p_text, FindOptions p_options); virtual void handleFindNext(const QString &p_text, FindOptions p_options);

View File

@ -8,6 +8,7 @@
#include <QToolButton> #include <QToolButton>
#include <QMenu> #include <QMenu>
#include <QDebug> #include <QDebug>
#include <QActionGroup>
#include "toolbarhelper.h" #include "toolbarhelper.h"
#include <utils/iconutils.h> #include <utils/iconutils.h>
@ -20,6 +21,7 @@
#include "propertydefs.h" #include "propertydefs.h"
#include "outlinepopup.h" #include "outlinepopup.h"
#include "viewwindow.h" #include "viewwindow.h"
#include <core/global.h>
using namespace vnotex; using namespace vnotex;
@ -306,6 +308,39 @@ QAction *ViewWindowToolBarHelper::addAction(QToolBar *p_tb, Action p_action)
break; break;
} }
case Action::SectionNumber:
{
act = p_tb->addAction(ToolBarHelper::generateIcon("section_number_editor.svg"),
ViewWindow::tr("Section Number"));
auto toolBtn = dynamic_cast<QToolButton *>(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: default:
Q_ASSERT(false); Q_ASSERT(false);
break; break;

View File

@ -39,7 +39,8 @@ namespace vnotex
Attachment, Attachment,
Outline, Outline,
FindAndReplace FindAndReplace,
SectionNumber
}; };
static QAction *addAction(QToolBar *p_tb, Action p_action); static QAction *addAction(QToolBar *p_tb, Action p_action);

View File

@ -20,6 +20,7 @@ SOURCES += \
$$PWD/dialogs/settings/settingspage.cpp \ $$PWD/dialogs/settings/settingspage.cpp \
$$PWD/dialogs/settings/settingsdialog.cpp \ $$PWD/dialogs/settings/settingsdialog.cpp \
$$PWD/dialogs/settings/texteditorpage.cpp \ $$PWD/dialogs/settings/texteditorpage.cpp \
$$PWD/dialogs/settings/themepage.cpp \
$$PWD/dragdropareaindicator.cpp \ $$PWD/dragdropareaindicator.cpp \
$$PWD/editors/editormarkdownvieweradapter.cpp \ $$PWD/editors/editormarkdownvieweradapter.cpp \
$$PWD/editors/markdowneditor.cpp \ $$PWD/editors/markdowneditor.cpp \
@ -100,6 +101,7 @@ HEADERS += \
$$PWD/dialogs/settings/settingspage.h \ $$PWD/dialogs/settings/settingspage.h \
$$PWD/dialogs/settings/settingsdialog.h \ $$PWD/dialogs/settings/settingsdialog.h \
$$PWD/dialogs/settings/texteditorpage.h \ $$PWD/dialogs/settings/texteditorpage.h \
$$PWD/dialogs/settings/themepage.h \
$$PWD/dragdropareaindicator.h \ $$PWD/dragdropareaindicator.h \
$$PWD/editors/editormarkdownvieweradapter.h \ $$PWD/editors/editormarkdownvieweradapter.h \
$$PWD/editors/markdowneditor.h \ $$PWD/editors/markdowneditor.h \