diff --git a/libs/vtextedit b/libs/vtextedit index 5fe5d4be..69bd5765 160000 --- a/libs/vtextedit +++ b/libs/vtextedit @@ -1 +1 @@ -Subproject commit 5fe5d4be7191de58fd1c2411b1ff97bcbecfaa3d +Subproject commit 69bd57656ccac8cf75502506be0c87d80d86e577 diff --git a/src/core/buffer/filetypehelper.cpp b/src/core/buffer/filetypehelper.cpp index 0a8cdab0..1712cae4 100644 --- a/src/core/buffer/filetypehelper.cpp +++ b/src/core/buffer/filetypehelper.cpp @@ -1,54 +1,123 @@ #include "filetypehelper.h" #include +#include #include +#include "buffer.h" using namespace vnotex; -const FileType FileTypeHelper::s_markdownFileType = "markdown"; +FileTypeHelper::FileTypeHelper() +{ + setupBuiltInTypes(); -const FileType FileTypeHelper::s_textFileType = "text"; + // TODO: read configuration file. -const FileType FileTypeHelper::s_unknownFileType = "unknown"; + setupSuffixTypeMap(); +} -QSharedPointer> FileTypeHelper::s_fileTypeMap; +void FileTypeHelper::setupBuiltInTypes() +{ + { + FileType type; + type.m_type = Type::Markdown; + type.m_displayName = Buffer::tr("Markdown"); + type.m_typeName = QStringLiteral("Markdown"); + type.m_suffixes << QStringLiteral("md") << QStringLiteral("mkd") << QStringLiteral("markdown"); + m_fileTypes.push_back(type); + } -FileType FileTypeHelper::fileType(const QString &p_filePath) + { + FileType type; + type.m_type = Type::Text; + type.m_typeName = QStringLiteral("Text"); + type.m_displayName = Buffer::tr("Text"); + type.m_suffixes << QStringLiteral("txt") << QStringLiteral("text") << QStringLiteral("log"); + m_fileTypes.push_back(type); + } + + { + FileType type; + type.m_type = Type::Others; + type.m_typeName = QStringLiteral("Others"); + type.m_displayName = Buffer::tr("Others"); + m_fileTypes.push_back(type); + } +} + +const FileType &FileTypeHelper::getFileType(const QString &p_filePath) const { Q_ASSERT(!p_filePath.isEmpty()); - if (!s_fileTypeMap) { - init(); - } - QFileInfo fi(p_filePath); auto suffix = fi.suffix().toLower(); - auto it = s_fileTypeMap->find(suffix); - if (it != s_fileTypeMap->end()) { - return it.value(); + auto it = m_suffixTypeMap.find(suffix); + if (it != m_suffixTypeMap.end()) { + return m_fileTypes.at(it.value()); } // Treat all unknown text files as plain text files. if (FileUtils::isText(p_filePath)) { - return s_fileTypeMap->value(QStringLiteral("txt")); + return m_fileTypes[Type::Text]; } - return s_unknownFileType; + return m_fileTypes[Type::Others]; } -#define ADD(x, y) s_fileTypeMap->insert((x), (y)) - -void FileTypeHelper::init() +const FileType &FileTypeHelper::getFileTypeBySuffix(const QString &p_suffix) const { - // TODO: load mapping from configuration file. - s_fileTypeMap.reset(new QMap()); - - ADD(QStringLiteral("md"), s_markdownFileType); - ADD(QStringLiteral("markdown"), s_markdownFileType); - ADD(QStringLiteral("mkd"), s_markdownFileType); - - ADD(QStringLiteral("txt"), s_textFileType); - ADD(QStringLiteral("text"), s_textFileType); - ADD(QStringLiteral("log"), s_textFileType); + auto it = m_suffixTypeMap.find(p_suffix.toLower()); + if (it != m_suffixTypeMap.end()) { + return m_fileTypes.at(it.value()); + } else { + return m_fileTypes[Type::Others]; + } +} + +#define ADD(x, y) m_suffixTypeMap.insert((x), (y)) + +void FileTypeHelper::setupSuffixTypeMap() +{ + for (int i = 0; i < m_fileTypes.size(); ++i) { + for (const auto &suffix : m_fileTypes[i].m_suffixes) { + if (m_suffixTypeMap.contains(suffix)) { + qWarning() << "suffix conflicts detected" << suffix << m_fileTypes[i].m_type; + } + m_suffixTypeMap.insert(suffix, i); + } + } +} + +const QVector &FileTypeHelper::getAllFileTypes() const +{ + return m_fileTypes; +} + +const FileType &FileTypeHelper::getFileType(Type p_type) const +{ + return m_fileTypes[p_type]; +} + +const FileTypeHelper &FileTypeHelper::getInst() +{ + static FileTypeHelper helper; + return helper; +} + +bool FileTypeHelper::checkFileType(const QString &p_filePath, Type p_type) const +{ + return getFileType(p_filePath).m_type == static_cast(p_type); +} + +const FileType &FileTypeHelper::getFileTypeByName(const QString &p_typeName) const +{ + for (const auto &ft : m_fileTypes) { + if (ft.m_typeName == p_typeName) { + return ft; + } + } + + Q_ASSERT(false); + return m_fileTypes[Type::Others]; } diff --git a/src/core/buffer/filetypehelper.h b/src/core/buffer/filetypehelper.h index 25c621a2..1c7647cd 100644 --- a/src/core/buffer/filetypehelper.h +++ b/src/core/buffer/filetypehelper.h @@ -3,30 +3,65 @@ #include #include -#include +#include namespace vnotex { - typedef QString FileType; + class FileType + { + public: + // FileTypeHelper::Type. + int m_type = -1; + + QString m_typeName; + + QString m_displayName; + + QStringList m_suffixes; + + QString preferredSuffix() const + { + return m_suffixes.isEmpty() ? QString() : m_suffixes.first(); + } + }; - // Map file suffix to file type. class FileTypeHelper { public: - FileTypeHelper() = delete; + enum Type + { + Markdown = 0, + Text, + Others + }; - static FileType fileType(const QString &p_filePath); + const FileType &getFileType(const QString &p_filePath) const; - static const FileType s_markdownFileType; + const FileType &getFileType(Type p_type) const; - static const FileType s_textFileType; + const FileType &getFileTypeByName(const QString &p_typeName) const; - static const FileType s_unknownFileType; + const FileType &getFileTypeBySuffix(const QString &p_suffix) const; + + const QVector &getAllFileTypes() const; + + bool checkFileType(const QString &p_filePath, Type p_type) const; + + static const FileTypeHelper &getInst(); private: - static void init(); + FileTypeHelper(); - static QSharedPointer> s_fileTypeMap; + void setupBuiltInTypes(); + + void setupSuffixTypeMap(); + + // Built-in Type could be accessed via enum Type. + QVector m_fileTypes; + + // suffix -> index of m_fileTypes. + // TODO: handle suffix conflicts. + QMap m_suffixTypeMap; }; } // ns vnotex diff --git a/src/core/buffermgr.cpp b/src/core/buffermgr.cpp index f549f4f3..fc687df2 100644 --- a/src/core/buffermgr.cpp +++ b/src/core/buffermgr.cpp @@ -37,13 +37,15 @@ void BufferMgr::initBufferServer() { m_bufferServer.reset(new NameBasedServer); + const auto &helper = FileTypeHelper::getInst(); + // Markdown. auto markdownFactory = QSharedPointer::create(); - m_bufferServer->registerItem(FileTypeHelper::s_markdownFileType, markdownFactory); + m_bufferServer->registerItem(helper.getFileType(FileTypeHelper::Markdown).m_typeName, markdownFactory); // Text. auto textFactory = QSharedPointer::create(); - m_bufferServer->registerItem(FileTypeHelper::s_textFileType, textFactory); + m_bufferServer->registerItem(helper.getFileType(FileTypeHelper::Text).m_typeName, textFactory); } void BufferMgr::open(Node *p_node, const QSharedPointer &p_paras) @@ -59,11 +61,11 @@ void BufferMgr::open(Node *p_node, const QSharedPointer &p_p auto buffer = findBuffer(p_node); if (!buffer) { auto nodePath = p_node->fetchAbsolutePath(); - auto fileType = FileTypeHelper::fileType(nodePath); + auto fileType = FileTypeHelper::getInst().getFileType(nodePath).m_typeName; auto factory = m_bufferServer->getItem(fileType); if (!factory) { // No factory to open this file type. - qInfo() << "File will be opened by system:" << nodePath; + qInfo() << "file will be opened by default program" << nodePath; WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(nodePath)); return; } @@ -102,11 +104,11 @@ void BufferMgr::open(const QString &p_filePath, const QSharedPointergetItem(fileType); if (!factory) { // No factory to open this file type. - qInfo() << "File will be opened by system:" << p_filePath; + qInfo() << "file will be opened by default program" << p_filePath; WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(p_filePath)); return; } diff --git a/src/core/markdowneditorconfig.cpp b/src/core/markdowneditorconfig.cpp index 4ef0fcda..5bbd7665 100644 --- a/src/core/markdowneditorconfig.cpp +++ b/src/core/markdowneditorconfig.cpp @@ -37,6 +37,7 @@ void MarkdownEditorConfig::init(const QJsonObject &p_app, const QJsonObject &p_u m_sectionNumberMode = stringToSectionNumberMode(READSTR(QStringLiteral("section_number"))); m_sectionNumberBaseLevel = READINT(QStringLiteral("section_number_base_level")); + m_sectionNumberStyle = stringToSectionNumberStyle(READSTR(QStringLiteral("section_number_style"))); m_constrainImageWidthEnabled = READBOOL(QStringLiteral("constrain_image_width")); m_constrainInPlacePreviewWidthEnabled = READBOOL(QStringLiteral("constrain_inplace_preview_width")); @@ -61,6 +62,7 @@ QJsonObject MarkdownEditorConfig::toJson() const obj[QStringLiteral("section_number")] = sectionNumberModeToString(m_sectionNumberMode); obj[QStringLiteral("section_number_base_level")] = m_sectionNumberBaseLevel; + obj[QStringLiteral("section_number_style")] = sectionNumberStyleToString(m_sectionNumberStyle); obj[QStringLiteral("constrain_image_width")] = m_constrainImageWidthEnabled; obj[QStringLiteral("constrain_inplace_preview_width")] = m_constrainInPlacePreviewWidthEnabled; @@ -267,6 +269,27 @@ MarkdownEditorConfig::SectionNumberMode MarkdownEditorConfig::stringToSectionNum } } +QString MarkdownEditorConfig::sectionNumberStyleToString(SectionNumberStyle p_style) const +{ + switch (p_style) { + case SectionNumberStyle::DigDotDig: + return QStringLiteral("digdotdig"); + + default: + return QStringLiteral("digdotdigdot"); + } +} + +MarkdownEditorConfig::SectionNumberStyle MarkdownEditorConfig::stringToSectionNumberStyle(const QString &p_str) const +{ + auto style = p_str.toLower(); + if (style == QStringLiteral("digdotdig")) { + return SectionNumberStyle::DigDotDig; + } else { + return SectionNumberStyle::DigDotDigDot; + } +} + MarkdownEditorConfig::SectionNumberMode MarkdownEditorConfig::getSectionNumberMode() const { return m_sectionNumberMode; @@ -286,3 +309,13 @@ void MarkdownEditorConfig::setSectionNumberBaseLevel(int p_level) { updateConfig(m_sectionNumberBaseLevel, p_level, this); } + +MarkdownEditorConfig::SectionNumberStyle MarkdownEditorConfig::getSectionNumberStyle() const +{ + return m_sectionNumberStyle; +} + +void MarkdownEditorConfig::setSectionNumberStyle(SectionNumberStyle p_style) +{ + updateConfig(m_sectionNumberStyle, p_style, this); +} diff --git a/src/core/markdowneditorconfig.h b/src/core/markdowneditorconfig.h index c0473bbf..fcc884fe 100644 --- a/src/core/markdowneditorconfig.h +++ b/src/core/markdowneditorconfig.h @@ -22,6 +22,14 @@ namespace vnotex Edit }; + enum SectionNumberStyle + { + // 1.1. + DigDotDigDot, + // 1.1 + DigDotDig + }; + MarkdownEditorConfig(ConfigMgr *p_mgr, IConfig *p_topConfig, const QSharedPointer &p_textEditorConfig); @@ -58,6 +66,9 @@ namespace vnotex int getSectionNumberBaseLevel() const; void setSectionNumberBaseLevel(int p_level); + SectionNumberStyle getSectionNumberStyle() const; + void setSectionNumberStyle(SectionNumberStyle p_style); + bool getConstrainImageWidthEnabled() const; void setConstrainImageWidthEnabled(bool p_enabled); @@ -88,6 +99,9 @@ namespace vnotex QString sectionNumberModeToString(SectionNumberMode p_mode) const; SectionNumberMode stringToSectionNumberMode(const QString &p_str) const; + QString sectionNumberStyleToString(SectionNumberStyle p_style) const; + SectionNumberStyle stringToSectionNumberStyle(const QString &p_str) const; + QSharedPointer m_textEditorConfig; ViewerResource m_viewerResource; @@ -112,6 +126,9 @@ namespace vnotex // 1 based. int m_sectionNumberBaseLevel = 2; + // Section number style. + SectionNumberStyle m_sectionNumberStyle = SectionNumberStyle::DigDotDigDot; + // Whether enable image width constraint. bool m_constrainImageWidthEnabled = true; diff --git a/src/core/notebookconfigmgr/nodecontentmediautils.cpp b/src/core/notebookconfigmgr/nodecontentmediautils.cpp index 2e28a888..b81b1b41 100644 --- a/src/core/notebookconfigmgr/nodecontentmediautils.cpp +++ b/src/core/notebookconfigmgr/nodecontentmediautils.cpp @@ -23,8 +23,8 @@ void NodeContentMediaUtils::copyMediaFiles(const Node *p_node, const QString &p_destFilePath) { Q_ASSERT(p_node->getType() == Node::Type::File); - auto fileType = FileTypeHelper::fileType(p_node->fetchAbsolutePath()); - if (fileType == QStringLiteral("markdown")) { + const auto &fileType = FileTypeHelper::getInst().getFileType(p_node->fetchAbsolutePath()); + if (fileType.m_type == FileTypeHelper::Markdown) { copyMarkdownMediaFiles(p_node->read(), PathUtils::parentDirPath(p_node->fetchContentPath()), p_backend, @@ -36,8 +36,8 @@ void NodeContentMediaUtils::copyMediaFiles(const QString &p_filePath, INotebookBackend *p_backend, const QString &p_destFilePath) { - auto fileType = FileTypeHelper::fileType(p_filePath); - if (fileType == QStringLiteral("markdown")) { + const auto &fileType = FileTypeHelper::getInst().getFileType(p_filePath); + if (fileType.m_type == FileTypeHelper::Markdown) { copyMarkdownMediaFiles(FileUtils::readTextFile(p_filePath), PathUtils::parentDirPath(p_filePath), p_backend, @@ -112,8 +112,8 @@ void NodeContentMediaUtils::copyMarkdownMediaFiles(const QString &p_content, void NodeContentMediaUtils::removeMediaFiles(const Node *p_node) { Q_ASSERT(p_node->getType() == Node::Type::File); - auto fileType = FileTypeHelper::fileType(p_node->fetchAbsolutePath()); - if (fileType == QStringLiteral("markdown")) { + const auto &fileType = FileTypeHelper::getInst().getFileType(p_node->fetchAbsolutePath()); + if (fileType.m_type == FileTypeHelper::Markdown) { removeMarkdownMediaFiles(p_node); } } @@ -162,8 +162,8 @@ void NodeContentMediaUtils::copyAttachment(Node *p_node, return; } - auto fileType = FileTypeHelper::fileType(p_node->fetchAbsolutePath()); - if (fileType == QStringLiteral("markdown")) { + const auto &fileType = FileTypeHelper::getInst().getFileType(p_node->fetchAbsolutePath()); + if (fileType.m_type == FileTypeHelper::Markdown) { fixMarkdownLinks(srcAttachmentFolderPath, p_backend, p_destFilePath, p_destAttachmentFolderPath); } } diff --git a/src/data/core/icons/type_code_block_editor.svg b/src/data/core/icons/type_code_block_editor.svg index 4a949b10..d00b2e8e 100644 --- a/src/data/core/icons/type_code_block_editor.svg +++ b/src/data/core/icons/type_code_block_editor.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/src/data/core/vnotex.json b/src/data/core/vnotex.json index c285437f..ed7fe38a 100644 --- a/src/data/core/vnotex.json +++ b/src/data/core/vnotex.json @@ -228,8 +228,11 @@ "insert_file_name_as_title" : true, "//comment" : "none/read/edit", "section_number" : "read", - "//comment" : "base level to start section numbering, valid only in edit mode", + "//comment" : "Base level to start section numbering, valid only in edit mode", "section_number_base_level" : 2, + "//comment" : "Style of the section number in edit mode", + "//comment" : "digdotdigdot/digdotdig", + "section_number_style" : "digdotdigdot", "//comment" : "Whether enable image width constraint", "constrain_image_width" : true, "//comment" : "Whether enable in-place preview width constraint", diff --git a/src/data/extra/themes/moonlight/interface.qss b/src/data/extra/themes/moonlight/interface.qss index fa2cb54f..52f0c804 100644 --- a/src/data/extra/themes/moonlight/interface.qss +++ b/src/data/extra/themes/moonlight/interface.qss @@ -1079,3 +1079,18 @@ QSizeGrip { width: 16px; height: 16px; } + +/* ViewWindow */ +vnotex--ViewWindow QToolBar[ViewWindowToolBar="true"] { + background-color: @widgets#viewwindow#toolbar#bg; +} + +/* ViewSplit */ +vnotex--ViewSplit QTabBar::tab:selected { + color: @widgets#viewsplit#tabbar#tab#selected#fg; + background-color: @widgets#viewsplit#tabbar#tab#selected#bg; +} + +vte--VTextEdit { + border: none; +} diff --git a/src/data/extra/themes/moonlight/palette.json b/src/data/extra/themes/moonlight/palette.json index 9e9f83c1..d7857e1c 100644 --- a/src/data/extra/themes/moonlight/palette.json +++ b/src/data/extra/themes/moonlight/palette.json @@ -233,6 +233,14 @@ "active" : { "fg" : "@base#icon#fg" } + }, + "tabbar" : { + "tab" : { + "selected" : { + "fg" : "@base#content#fg", + "bg" : "@base#content#bg" + } + } } }, "qmainwindow" : { @@ -403,8 +411,8 @@ "bg" : "@base#hover#bg" }, "selected" : { - "fg" : "@base#selected#fg", - "bg" : "@base#selected#bg", + "fg" : "@base#content#fg", + "bg" : "@base#content#bg", "border" : "@base#master#bg" } } @@ -584,6 +592,11 @@ "border" : "@widgets#qslider#handle#border", "bg" : "@base#master#alt" } + }, + "viewwindow" : { + "toolbar" : { + "bg" : "@base#content#bg" + } } } } diff --git a/src/data/extra/themes/moonlight/text-editor.theme b/src/data/extra/themes/moonlight/text-editor.theme index a55a3c0e..74d17b76 100644 --- a/src/data/extra/themes/moonlight/text-editor.theme +++ b/src/data/extra/themes/moonlight/text-editor.theme @@ -108,7 +108,7 @@ }, "HRULE" : { "text-color" : "#abb2bf", - "background-color" : "#493134" + "background-color" : "#864046" }, "LIST_BULLET" : { "text-color" : "#e06c75", diff --git a/src/data/extra/web/css/globalstyles.css b/src/data/extra/web/css/globalstyles.css index 9733b67f..8cdc6ee0 100644 --- a/src/data/extra/web/css/globalstyles.css +++ b/src/data/extra/web/css/globalstyles.css @@ -22,27 +22,27 @@ #vx-content.vx-section-number h2::before { counter-increment: section1; - content: counter(section1) " "; + content: counter(section1) ". "; } #vx-content.vx-section-number h3::before { counter-increment: section2; - content: counter(section1) "." counter(section2) " "; + content: counter(section1) "." counter(section2) ". "; } #vx-content.vx-section-number h4::before { counter-increment: section3; - content: counter(section1) "." counter(section2) "." counter(section3) " "; + content: counter(section1) "." counter(section2) "." counter(section3) ". "; } #vx-content.vx-section-number h5::before { counter-increment: section4; - content: counter(section1) "." counter(section2) "." counter(section3) "." counter(section4) " "; + content: counter(section1) "." counter(section2) "." counter(section3) "." counter(section4) ". "; } #vx-content.vx-section-number h6::before { counter-increment: section5; - content: counter(section1) "." counter(section2) "." counter(section3) "." counter(section4) "." counter(section5) " "; + content: counter(section1) "." counter(section2) "." counter(section3) "." counter(section4) "." counter(section5) ". "; } #vx-content.vx-constrain-image-width img { diff --git a/src/data/extra/web/js/markjs.js b/src/data/extra/web/js/markjs.js index 6f288fba..32a3d914 100644 --- a/src/data/extra/web/js/markjs.js +++ b/src/data/extra/web/js/markjs.js @@ -61,7 +61,9 @@ class MarkJs { 'className': this.className, 'caseSensitive': p_options.caseSensitive, 'accuracy': p_options.wholeWordOnly ? 'exactly' : 'partially', - 'done': callbackFunc(this, p_text, p_options) + 'done': callbackFunc(this, p_text, p_options), + // Ignore SVG, or SVG will be corrupted. + 'exclude': ['svg *'] } if (p_options.regularExpression) { diff --git a/src/data/extra/web/js/utils.js b/src/data/extra/web/js/utils.js index d1a15d6c..3c84958f 100644 --- a/src/data/extra/web/js/utils.js +++ b/src/data/extra/web/js/utils.js @@ -111,6 +111,6 @@ class Utils { } static headingSequenceRegExp() { - return /^\d{1,3}(?:\.\d+)*\.? /; + return /^\d{1,3}(?:\.\d+)*\. /; } } diff --git a/src/utils/widgetutils.cpp b/src/utils/widgetutils.cpp index d67bd1f7..43cf40d8 100644 --- a/src/utils/widgetutils.cpp +++ b/src/utils/widgetutils.cpp @@ -22,6 +22,7 @@ #include #include #include +#include using namespace vnotex; @@ -366,3 +367,10 @@ QFormLayout *WidgetUtils::createFormLayout(QWidget *p_parent) return layout; } + +void WidgetUtils::selectBaseName(QLineEdit *p_lineEdit) +{ + auto text = p_lineEdit->text(); + int dotIndex = text.lastIndexOf(QLatin1Char('.')); + p_lineEdit->setSelection(0, (dotIndex == -1) ? text.size() : dotIndex); +} diff --git a/src/utils/widgetutils.h b/src/utils/widgetutils.h index 0fb44165..bd56d254 100644 --- a/src/utils/widgetutils.h +++ b/src/utils/widgetutils.h @@ -18,6 +18,7 @@ class QListView; class QMenu; class QShortcut; class QFormLayout; +class QLineEdit; namespace vnotex { @@ -80,6 +81,9 @@ namespace vnotex static QFormLayout *createFormLayout(QWidget *p_parent = nullptr); + // Select the base name part of the line edit content. + static void selectBaseName(QLineEdit *p_lineEdit); + private: static void resizeToHideScrollBar(QScrollArea *p_scroll, bool p_vertical, bool p_horizontal); }; diff --git a/src/widgets/dialogs/newnotedialog.cpp b/src/widgets/dialogs/newnotedialog.cpp index c9f9725d..e89dac1f 100644 --- a/src/widgets/dialogs/newnotedialog.cpp +++ b/src/widgets/dialogs/newnotedialog.cpp @@ -8,6 +8,7 @@ #include #include "exception.h" #include "nodeinfowidget.h" +#include using namespace vnotex; @@ -110,8 +111,7 @@ void NewNoteDialog::initDefaultValues() { auto lineEdit = m_infoWidget->getNameLineEdit(); lineEdit->setText(c_defaultNoteName); - int dotIndex = c_defaultNoteName.lastIndexOf('.'); - lineEdit->setSelection(0, (dotIndex == -1) ? c_defaultNoteName.size() : dotIndex); + WidgetUtils::selectBaseName(lineEdit); validateInputs(); } diff --git a/src/widgets/dialogs/nodeinfowidget.cpp b/src/widgets/dialogs/nodeinfowidget.cpp index 41ef1bc2..1e452342 100644 --- a/src/widgets/dialogs/nodeinfowidget.cpp +++ b/src/widgets/dialogs/nodeinfowidget.cpp @@ -9,6 +9,7 @@ #include "exception.h" #include "nodelabelwithupbutton.h" #include +#include using namespace vnotex; @@ -16,11 +17,9 @@ NodeInfoWidget::NodeInfoWidget(const Node *p_node, QWidget *p_parent) : QWidget(p_parent), m_mode(Mode::Edit) { - setupUI(p_node->getParent()); + setupUI(p_node->getParent(), p_node->getType()); setNode(p_node); - - setStateAccordingToModeAndNodeType(p_node->getType()); } NodeInfoWidget::NodeInfoWidget(const Node *p_parentNode, @@ -29,31 +28,42 @@ NodeInfoWidget::NodeInfoWidget(const Node *p_parentNode, : QWidget(p_parent), m_mode(Mode::Create) { - setupUI(p_parentNode); - - setStateAccordingToModeAndNodeType(p_typeToCreate); + setupUI(p_parentNode, p_typeToCreate); } -void NodeInfoWidget::setupUI(const Node *p_parentNode) +void NodeInfoWidget::setupUI(const Node *p_parentNode, Node::Type p_newNodeType) { + const bool createMode = m_mode == Mode::Create; + const bool isNote = p_newNodeType == Node::Type::File; + m_mainLayout = WidgetUtils::createFormLayout(this); m_mainLayout->addRow(tr("Notebook:"), new QLabel(p_parentNode->getNotebook()->getName(), this)); m_parentNodeLabel = new NodeLabelWithUpButton(p_parentNode, this); + m_parentNodeLabel->setReadOnly(!createMode); connect(m_parentNodeLabel, &NodeLabelWithUpButton::nodeChanged, this, &NodeInfoWidget::inputEdited); m_mainLayout->addRow(tr("Location:"), m_parentNodeLabel); + if (createMode && isNote) { + setupFileTypeComboBox(this); + m_mainLayout->addRow(tr("File type:"), m_fileTypeComboBox); + } + setupNameLineEdit(this); m_mainLayout->addRow(tr("Name:"), m_nameLineEdit); - m_createdDateTimeLabel = new QLabel(this); - m_mainLayout->addRow(tr("Created time:"), m_createdDateTimeLabel); + if (!createMode) { + m_createdDateTimeLabel = new QLabel(this); + m_mainLayout->addRow(tr("Created time:"), m_createdDateTimeLabel); + } - m_modifiedDateTimeLabel = new QLabel(this); - m_mainLayout->addRow(tr("Modified time:"), m_modifiedDateTimeLabel); + if (!createMode && isNote) { + m_modifiedDateTimeLabel = new QLabel(this); + m_mainLayout->addRow(tr("Modified time:"), m_modifiedDateTimeLabel); + } } void NodeInfoWidget::setupNameLineEdit(QWidget *p_parent) @@ -63,23 +73,30 @@ void NodeInfoWidget::setupNameLineEdit(QWidget *p_parent) m_nameLineEdit); m_nameLineEdit->setValidator(validator); connect(m_nameLineEdit, &QLineEdit::textEdited, - this, &NodeInfoWidget::inputEdited); -} + this, [this]() { + // Choose the correct file type. + if (m_fileTypeComboBox) { + auto inputName = m_nameLineEdit->text(); + QString typeName; + int dotIdx = inputName.lastIndexOf(QLatin1Char('.')); + if (dotIdx != -1) { + auto suffix = inputName.mid(dotIdx + 1); + const auto &fileType = FileTypeHelper::getInst().getFileTypeBySuffix(suffix); + typeName = fileType.m_typeName; + } else { + typeName = FileTypeHelper::getInst().getFileType(FileTypeHelper::Others).m_typeName; + } -void NodeInfoWidget::setStateAccordingToModeAndNodeType(Node::Type p_type) -{ - bool createMode = m_mode == Mode::Create; - bool isNote = p_type == Node::Type::File; + int idx = m_fileTypeComboBox->findData(typeName); + if (idx != -1) { + m_fileTypeComboBoxMuted = true; + m_fileTypeComboBox->setCurrentIndex(idx); + m_fileTypeComboBoxMuted = false; + } + } - m_parentNodeLabel->setReadOnly(!createMode); - - bool visible = !createMode; - m_createdDateTimeLabel->setVisible(visible); - m_mainLayout->labelForField(m_createdDateTimeLabel)->setVisible(visible); - - visible = !createMode && isNote; - m_modifiedDateTimeLabel->setVisible(visible); - m_mainLayout->labelForField(m_modifiedDateTimeLabel)->setVisible(visible); + emit inputEdited(); + }); } QLineEdit *NodeInfoWidget::getNameLineEdit() const @@ -108,6 +125,7 @@ void NodeInfoWidget::setNode(const Node *p_node) return; } + Q_ASSERT(m_mode != Mode::Create); m_node = p_node; if (m_node) { Q_ASSERT(getNotebook() == m_node->getNotebook()); @@ -120,3 +138,39 @@ void NodeInfoWidget::setNode(const Node *p_node) m_modifiedDateTimeLabel->setText(modifiedTime); } } + +void NodeInfoWidget::setupFileTypeComboBox(QWidget *p_parent) +{ + m_fileTypeComboBox = WidgetsFactory::createComboBox(p_parent); + const auto &fileTypes = FileTypeHelper::getInst().getAllFileTypes(); + for (const auto &ft : fileTypes) { + m_fileTypeComboBox->addItem(ft.m_displayName, ft.m_typeName); + } + connect(m_fileTypeComboBox, QOverload::of(&QComboBox::currentIndexChanged), + this, [this]() { + if (m_fileTypeComboBoxMuted) { + return; + } + auto name = m_fileTypeComboBox->currentData().toString(); + const auto &fileType = FileTypeHelper::getInst().getFileTypeByName(name); + const auto suffix = fileType.preferredSuffix(); + if (!suffix.isEmpty()) { + // Change the suffix. + auto inputName = m_nameLineEdit->text(); + QString newName; + int dotIdx = inputName.lastIndexOf(QLatin1Char('.')); + if (dotIdx == -1) { + newName = inputName + QLatin1Char('.') + suffix; + } else if (inputName.mid(dotIdx + 1) != suffix) { + newName = inputName.left(dotIdx + 1) + suffix; + } + + if (!newName.isEmpty()) { + m_nameLineEdit->setText(newName); + } + } + + WidgetUtils::selectBaseName(m_nameLineEdit); + m_nameLineEdit->setFocus(); + }); +} diff --git a/src/widgets/dialogs/nodeinfowidget.h b/src/widgets/dialogs/nodeinfowidget.h index 2a894a54..a9343b8c 100644 --- a/src/widgets/dialogs/nodeinfowidget.h +++ b/src/widgets/dialogs/nodeinfowidget.h @@ -8,6 +8,7 @@ class QLineEdit; class QLabel; class QFormLayout; +class QComboBox; namespace vnotex { @@ -38,18 +39,20 @@ namespace vnotex void inputEdited(); private: - void setupUI(const Node *p_parentNode); + void setupUI(const Node *p_parentNode, Node::Type p_newNodeType); + + void setupFileTypeComboBox(QWidget *p_parent); void setupNameLineEdit(QWidget *p_parent); - void setStateAccordingToModeAndNodeType(Node::Type p_type); - void setNode(const Node *p_node); Mode m_mode; QFormLayout *m_mainLayout = nullptr; + QComboBox *m_fileTypeComboBox = nullptr; + QLineEdit *m_nameLineEdit = nullptr; NodeLabelWithUpButton *m_parentNodeLabel = nullptr; @@ -59,6 +62,8 @@ namespace vnotex QLabel *m_modifiedDateTimeLabel = nullptr; const Node *m_node = nullptr; + + bool m_fileTypeComboBoxMuted = false; }; } // ns vnotex diff --git a/src/widgets/dialogs/nodelabelwithupbutton.cpp b/src/widgets/dialogs/nodelabelwithupbutton.cpp index ee2f611a..c6f73368 100644 --- a/src/widgets/dialogs/nodelabelwithupbutton.cpp +++ b/src/widgets/dialogs/nodelabelwithupbutton.cpp @@ -27,9 +27,9 @@ void NodeLabelWithUpButton::setupUI() auto iconFile = VNoteX::getInst().getThemeMgr().getIconFile("up_parent_node.svg"); m_upButton = new QPushButton(IconUtils::fetchIconWithDisabledState(iconFile), - "", + tr("Up"), this); - m_upButton->setToolTip(tr("Up")); + m_upButton->setToolTip(tr("Create note under an upper level node")); connect(m_upButton, &QPushButton::clicked, this, [this]() { if (!m_node->isRoot()) { diff --git a/src/widgets/dialogs/settings/markdowneditorpage.cpp b/src/widgets/dialogs/settings/markdowneditorpage.cpp index f0c912fa..1148da5c 100644 --- a/src/widgets/dialogs/settings/markdowneditorpage.cpp +++ b/src/widgets/dialogs/settings/markdowneditorpage.cpp @@ -51,6 +51,10 @@ void MarkdownEditorPage::loadInternal() m_sectionNumberComboBox->setCurrentIndex(idx); m_sectionNumberBaseLevelSpinBox->setValue(markdownConfig.getSectionNumberBaseLevel()); + + idx = m_sectionNumberStyleComboBox->findData(static_cast(markdownConfig.getSectionNumberStyle())); + Q_ASSERT(idx != -1); + m_sectionNumberStyleComboBox->setCurrentIndex(idx); } m_constrainImageWidthCheckBox->setChecked(markdownConfig.getConstrainImageWidthEnabled()); @@ -83,6 +87,11 @@ void MarkdownEditorPage::saveInternal() if (m_sectionNumberBaseLevelSpinBox->isEnabled()) { markdownConfig.setSectionNumberBaseLevel(m_sectionNumberBaseLevelSpinBox->value()); } + + if (m_sectionNumberStyleComboBox->isEnabled()) { + auto style = m_sectionNumberStyleComboBox->currentData().toInt(); + markdownConfig.setSectionNumberStyle(static_cast(style)); + } } markdownConfig.setConstrainImageWidthEnabled(m_constrainImageWidthCheckBox->isChecked()); @@ -229,11 +238,9 @@ QGroupBox *MarkdownEditorPage::setupGeneralGroup() 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); @@ -242,13 +249,22 @@ QGroupBox *MarkdownEditorPage::setupGeneralGroup() 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); + + m_sectionNumberStyleComboBox = WidgetsFactory::createComboBox(this); + m_sectionNumberStyleComboBox->setToolTip(tr("Section number style")); + m_sectionNumberStyleComboBox->addItem(tr("1.1."), (int)MarkdownEditorConfig::SectionNumberStyle::DigDotDigDot); + m_sectionNumberStyleComboBox->addItem(tr("1.1"), (int)MarkdownEditorConfig::SectionNumberStyle::DigDotDig); + sectionLayout->addWidget(m_sectionNumberStyleComboBox); + connect(m_sectionNumberStyleComboBox, QOverload::of(&QComboBox::currentIndexChanged), + this, &MarkdownEditorPage::pageIsChanged); + connect(m_sectionNumberComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int p_index) { m_sectionNumberBaseLevelSpinBox->setEnabled(p_index == MarkdownEditorConfig::SectionNumberMode::Edit); + m_sectionNumberStyleComboBox->setEnabled(p_index == MarkdownEditorConfig::SectionNumberMode::Edit); }); const QString label(tr("Section number:")); diff --git a/src/widgets/dialogs/settings/markdowneditorpage.h b/src/widgets/dialogs/settings/markdowneditorpage.h index 65425198..48b8d7d8 100644 --- a/src/widgets/dialogs/settings/markdowneditorpage.h +++ b/src/widgets/dialogs/settings/markdowneditorpage.h @@ -54,6 +54,8 @@ namespace vnotex QComboBox *m_sectionNumberComboBox = nullptr; QSpinBox *m_sectionNumberBaseLevelSpinBox = nullptr; + + QComboBox *m_sectionNumberStyleComboBox = nullptr; }; } diff --git a/src/widgets/editors/markdowneditor.cpp b/src/widgets/editors/markdowneditor.cpp index c339cfe3..71a6a613 100644 --- a/src/widgets/editors/markdowneditor.cpp +++ b/src/widgets/editors/markdowneditor.cpp @@ -755,7 +755,6 @@ void MarkdownEditor::updateHeadings(const QVector &p_he // 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); if (!block.isValid()) { @@ -766,11 +765,11 @@ void MarkdownEditor::updateHeadings(const QVector &p_he qWarning() << "header accross multiple blocks, starting from block" << block.blockNumber() << block.text(); } - if (headerReg.exactMatch(block.text())) { - const int level = headerReg.cap(1).length(); - Heading heading(headerReg.cap(2).trimmed(), - level, - headerReg.cap(3), + auto match = vte::MarkdownUtils::matchHeader(block.text()); + if (match.m_matched) { + Heading heading(match.m_header, + match.m_level, + match.m_sequence, block.blockNumber()); headings.append(heading); } @@ -1097,10 +1096,9 @@ static void increaseSectionNumber(QVector &p_sectionNumber, int p_level, in } } -static QString joinSectionNumberStr(const QVector &p_sectionNumber) +static QString joinSectionNumberStr(const QVector &p_sectionNumber, bool p_endingDot) { QString res; - // TODO: make it configurable? 1.1 or 1.1.? for (auto sec : p_sectionNumber) { if (sec != 0) { if (res.isEmpty()) { @@ -1115,28 +1113,41 @@ static QString joinSectionNumberStr(const QVector &p_sectionNumber) } } - return res; + if (p_endingDot && !res.isEmpty()) { + return res + '.'; + } else { + return res; + } } static bool updateHeadingSectionNumber(QTextCursor &p_cursor, const QTextBlock &p_block, - QRegExp &p_headingReg, - QRegExp &p_prefixReg, - const QString &p_sectionNumber) + const QString &p_sectionNumber, + bool p_endingDot) { if (!p_block.isValid()) { return false; } QString text = p_block.text(); - bool matched = p_headingReg.exactMatch(text); - Q_ASSERT(matched); + auto match = vte::MarkdownUtils::matchHeader(text); + Q_ASSERT(match.m_matched); - matched = p_prefixReg.exactMatch(text); - Q_ASSERT(matched); + bool isSequence = false; + if (!match.m_sequence.isEmpty()) { + // Check if this sequence is the real sequence matching current style. + if (match.m_sequence.endsWith('.')) { + isSequence = p_endingDot; + } else { + isSequence = !p_endingDot; + } + } - int start = p_headingReg.cap(1).length() + 1; - int end = p_prefixReg.cap(1).length(); + int start = match.m_level + 1; + int end = match.m_level + match.m_spacesAfterMarker; + if (isSequence) { + end += match.m_sequence.size() + match.m_spacesAfterSequence; + } Q_ASSERT(start <= end); @@ -1162,20 +1173,18 @@ bool MarkdownEditor::updateSectionNumber(const QVector &p_headings) } bool changed = false; + bool endingDot = m_config.getSectionNumberStyle() == MarkdownEditorConfig::SectionNumberStyle::DigDotDigDot; 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(); + auto sectionStr = m_sectionNumberEnabled ? joinSectionNumberStr(sectionNumber, endingDot) : QString(); if (heading.m_blockNumber > -1 && sectionStr != heading.m_sectionNumber) { if (updateHeadingSectionNumber(cursor, doc->findBlockByNumber(heading.m_blockNumber), - headerReg, - prefixReg, - sectionStr)) { + sectionStr, + endingDot)) { changed = true; } } diff --git a/src/widgets/markdownviewwindow.cpp b/src/widgets/markdownviewwindow.cpp index ad253cc2..34825aa6 100644 --- a/src/widgets/markdownviewwindow.cpp +++ b/src/widgets/markdownviewwindow.cpp @@ -251,7 +251,8 @@ void MarkdownViewWindow::handleBufferChangedInternal() void MarkdownViewWindow::setupToolBar() { - auto toolBar = new QToolBar(this); + auto toolBar = createToolBar(this); + const auto &editorConfig = ConfigMgr::getInst().getEditorConfig(); const int iconSize = editorConfig.getToolBarIconSize(); toolBar->setIconSize(QSize(iconSize, iconSize)); diff --git a/src/widgets/outlineviewer.cpp b/src/widgets/outlineviewer.cpp index 91347f47..37bf20af 100644 --- a/src/widgets/outlineviewer.cpp +++ b/src/widgets/outlineviewer.cpp @@ -51,6 +51,7 @@ void OutlineViewer::setupUI(const QString &p_title) { auto mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); { auto titleBar = setupTitleBar(p_title, this); diff --git a/src/widgets/propertydefs.cpp b/src/widgets/propertydefs.cpp index 0ccfe553..e63902db 100644 --- a/src/widgets/propertydefs.cpp +++ b/src/widgets/propertydefs.cpp @@ -13,3 +13,5 @@ const char *PropertyDefs::s_dialogCentralWidget = "DialogCentralWidget"; const char *PropertyDefs::s_viewSplitCornerWidget = "ViewSplitCornerWidget"; const char *PropertyDefs::s_state = "State"; + +const char *PropertyDefs::s_viewWindowToolBar = "ViewWindowToolBar"; diff --git a/src/widgets/propertydefs.h b/src/widgets/propertydefs.h index 66513ff2..d213e4c5 100644 --- a/src/widgets/propertydefs.h +++ b/src/widgets/propertydefs.h @@ -19,6 +19,8 @@ namespace vnotex static const char *s_viewSplitCornerWidget; + static const char *s_viewWindowToolBar; + // Values: info/warning/error. static const char *s_state; }; diff --git a/src/widgets/textviewwindow.cpp b/src/widgets/textviewwindow.cpp index c882fa13..481e904b 100644 --- a/src/widgets/textviewwindow.cpp +++ b/src/widgets/textviewwindow.cpp @@ -55,7 +55,7 @@ void TextViewWindow::setupUI() void TextViewWindow::setupToolBar() { - auto toolBar = new QToolBar(this); + auto toolBar = createToolBar(this); const auto &editorConfig = ConfigMgr::getInst().getEditorConfig(); const int iconSize = editorConfig.getToolBarIconSize(); toolBar->setIconSize(QSize(iconSize, iconSize)); diff --git a/src/widgets/viewwindow.cpp b/src/widgets/viewwindow.cpp index d59303a6..1b1b533a 100644 --- a/src/widgets/viewwindow.cpp +++ b/src/widgets/viewwindow.cpp @@ -32,6 +32,7 @@ #include "exception.h" #include "findandreplacewidget.h" #include "editors/statuswidget.h" +#include "propertydefs.h" using namespace vnotex; @@ -106,14 +107,17 @@ ViewWindow::~ViewWindow() void ViewWindow::setupUI() { m_mainLayout = new QVBoxLayout(this); + m_mainLayout->setSpacing(0); m_mainLayout->setContentsMargins(0, 0, 0, 0); m_topLayout = new QVBoxLayout(); m_topLayout->setContentsMargins(0, 0, 0, 0); + m_topLayout->setSpacing(0); m_mainLayout->addLayout(m_topLayout, 0); m_bottomLayout = new QVBoxLayout(); m_bottomLayout->setContentsMargins(0, 0, 0, 0); + m_bottomLayout->setSpacing(0); m_mainLayout->addLayout(m_bottomLayout, 0); } @@ -1073,3 +1077,10 @@ void ViewWindow::read(bool p_save) } setFocus(); } + +QToolBar *ViewWindow::createToolBar(QWidget *p_parent) +{ + auto toolBar = new QToolBar(p_parent); + toolBar->setProperty(PropertyDefs::s_viewWindowToolBar, true); + return toolBar; +} diff --git a/src/widgets/viewwindow.h b/src/widgets/viewwindow.h index 15d5caa1..cece9d86 100644 --- a/src/widgets/viewwindow.h +++ b/src/widgets/viewwindow.h @@ -11,6 +11,7 @@ class QVBoxLayout; class QTimer; +class QToolBar; namespace vnotex { @@ -218,6 +219,8 @@ namespace vnotex static ViewWindow::Mode modeFromOpenParameters(const FileOpenParameters &p_paras); + static QToolBar *createToolBar(QWidget *p_parent = nullptr); + // The revision of the buffer of the last sync content. int m_bufferRevision = 0;