From 78a86cddc01d9f458cf7ce78d865eb1c99c6a3e2 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Sat, 4 Nov 2017 09:00:31 +0800 Subject: [PATCH] support note template VNote will scan files in the template folder in the config folder as template. Template supports magic word. --- src/dialog/vnewfiledialog.cpp | 191 +++++++++++++++++++++--- src/dialog/vnewfiledialog.h | 48 +++++- src/resources/icons/manage_template.svg | 10 ++ src/utils/vutils.cpp | 4 + src/vconfigmanager.cpp | 29 ++++ src/vconfigmanager.h | 9 ++ src/vfilelist.cpp | 25 +++- src/vlineedit.cpp | 4 +- src/vlineedit.h | 2 +- src/vnote.qrc | 1 + 10 files changed, 288 insertions(+), 35 deletions(-) create mode 100644 src/resources/icons/manage_template.svg diff --git a/src/dialog/vnewfiledialog.cpp b/src/dialog/vnewfiledialog.cpp index 730728cc..b7cddaee 100644 --- a/src/dialog/vnewfiledialog.cpp +++ b/src/dialog/vnewfiledialog.cpp @@ -4,38 +4,81 @@ #include "vdirectory.h" #include "vlineedit.h" #include "utils/vutils.h" +#include "utils/vmetawordmanager.h" extern VConfigManager *g_config; -VNewFileDialog::VNewFileDialog(const QString &title, const QString &info, - const QString &defaultName, VDirectory *directory, - QWidget *parent) - : QDialog(parent), title(title), info(info), - defaultName(defaultName), m_directory(directory) +extern VMetaWordManager *g_mwMgr; + +QString VNewFileDialog::s_lastTemplateFile; + + +VNewFileDialog::VNewFileDialog(const QString &p_title, + const QString &p_info, + const QString &p_defaultName, + VDirectory *p_directory, + QWidget *p_parent) + : QDialog(p_parent), + m_currentTemplateType(DocType::Unknown), + m_directory(p_directory) { - setupUI(); + setupUI(p_title, p_info, p_defaultName); connect(m_nameEdit, &VLineEdit::textChanged, this, &VNewFileDialog::handleInputChanged); + connect(m_templateCB, static_cast(&QComboBox::currentIndexChanged), + this, &VNewFileDialog::handleCurrentTemplateChanged); + handleInputChanged(); + + tryToSelectLastTemplate(); } -void VNewFileDialog::setupUI() +void VNewFileDialog::setupUI(const QString &p_title, + const QString &p_info, + const QString &p_defaultName) { QLabel *infoLabel = NULL; - if (!info.isEmpty()) { - infoLabel = new QLabel(info); + if (!p_info.isEmpty()) { + infoLabel = new QLabel(p_info); } // Name. - QLabel *nameLabel = new QLabel(tr("Note &name:")); - m_nameEdit = new VLineEdit(defaultName); + m_nameEdit = new VLineEdit(p_defaultName); QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_nameEdit); m_nameEdit->setValidator(validator); - int dotIndex = defaultName.lastIndexOf('.'); - m_nameEdit->setSelection(0, (dotIndex == -1) ? defaultName.size() : dotIndex); - nameLabel->setBuddy(m_nameEdit); + int dotIndex = p_defaultName.lastIndexOf('.'); + m_nameEdit->setSelection(0, (dotIndex == -1) ? p_defaultName.size() : dotIndex); + + // Template. + m_templateCB = new QComboBox(); + m_templateCB->setToolTip(tr("Choose a template (magic word supported)")); + m_templateCB->setSizeAdjustPolicy(QComboBox::AdjustToContents); + + QPushButton *templateBtn = new QPushButton(QIcon(":/resources/icons/manage_template.svg"), + ""); + templateBtn->setToolTip(tr("Manage Templates")); + templateBtn->setProperty("FlatBtn", true); + connect(templateBtn, &QPushButton::clicked, + this, [this]() { + QUrl url = QUrl::fromLocalFile(g_config->getTemplateConfigFolder()); + QDesktopServices::openUrl(url); + }); + + QHBoxLayout *tempBtnLayout = new QHBoxLayout(); + tempBtnLayout->addWidget(m_templateCB); + tempBtnLayout->addWidget(templateBtn); + tempBtnLayout->addStretch(); + + m_templateEdit = new QTextEdit(); + m_templateEdit->setReadOnly(true); + + QVBoxLayout *templateLayout = new QVBoxLayout(); + templateLayout->addLayout(tempBtnLayout); + templateLayout->addWidget(m_templateEdit); + + m_templateEdit->hide(); // InsertTitle. m_insertTitleCB = new QCheckBox(tr("Insert note name as title (for Markdown only)")); @@ -47,8 +90,9 @@ void VNewFileDialog::setupUI() }); QFormLayout *topLayout = new QFormLayout(); - topLayout->addRow(nameLabel, m_nameEdit); + topLayout->addRow(tr("Note &name:"), m_nameEdit); topLayout->addWidget(m_insertTitleCB); + topLayout->addRow(tr("Template:"), templateLayout); m_nameEdit->setMinimumWidth(m_insertTitleCB->sizeHint().width()); @@ -58,9 +102,16 @@ void VNewFileDialog::setupUI() // Ok is the default button. m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(m_btnBox, &QDialogButtonBox::accepted, + this, [this]() { + s_lastTemplateFile = m_templateCB->currentData().toString(); + QDialog::accept(); + }); connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok); + m_templateCB->setMaximumWidth(okBtn->sizeHint().width() * 4); + QVBoxLayout *mainLayout = new QVBoxLayout(); if (infoLabel) { mainLayout->addWidget(infoLabel); @@ -72,13 +123,13 @@ void VNewFileDialog::setupUI() mainLayout->setSizeConstraint(QLayout::SetFixedSize); setLayout(mainLayout); - setWindowTitle(title); + setWindowTitle(p_title); } void VNewFileDialog::handleInputChanged() { bool showWarnLabel = false; - QString name = m_nameEdit->getEvaluatedText(); + const QString name = m_nameEdit->getEvaluatedText(); bool nameOk = !name.isEmpty(); if (nameOk) { // Check if the name conflicts with existing note name. @@ -113,6 +164,10 @@ void VNewFileDialog::handleInputChanged() QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok); okBtn->setEnabled(nameOk); + + if (nameOk) { + updateTemplates(VUtils::docTypeFromName(name)); + } } QString VNewFileDialog::getNameInput() const @@ -122,5 +177,103 @@ QString VNewFileDialog::getNameInput() const bool VNewFileDialog::getInsertTitleInput() const { - return m_insertTitleCB->isChecked(); + return m_insertTitleCB->isEnabled() && m_insertTitleCB->isChecked(); +} + +void VNewFileDialog::updateTemplates(DocType p_type) +{ + if (m_currentTemplateType == p_type) { + return; + } + + m_currentTemplateType = p_type; + + // Clear the combo. + m_templateCB->clear(); + + // Add None item. + m_templateCB->addItem(tr("None"), "None"); + + if (m_currentTemplateType == DocType::Unknown) { + return; + } + + int idx = 1; + auto templates = g_config->getNoteTemplates(m_currentTemplateType); + for (auto const & tp : templates) { + m_templateCB->addItem(tp, tp); + m_templateCB->setItemData(idx++, tp, Qt::ToolTipRole); + } +} + +void VNewFileDialog::handleCurrentTemplateChanged(int p_idx) +{ + if (p_idx == -1) { + m_templateEdit->hide(); + enableInsertTitleCB(false); + return; + } + + QString file = m_templateCB->itemData(p_idx).toString(); + if (file == "None") { + m_templateEdit->hide(); + enableInsertTitleCB(false); + return; + } + + // Read the template file. + QString filePath = QDir(g_config->getTemplateConfigFolder()).filePath(file); + m_template = VUtils::readFileFromDisk(filePath); + DocType type = VUtils::docTypeFromName(file); + switch (type) { + case DocType::Html: + m_templateEdit->setHtml(m_template); + break; + + case DocType::Markdown: + m_templateEdit->setPlainText(m_template); + break; + + default: + m_templateEdit->setPlainText(m_template); + break; + } + + m_templateEdit->show(); + enableInsertTitleCB(true); +} + +void VNewFileDialog::enableInsertTitleCB(bool p_hasTemplate) +{ + m_insertTitleCB->setEnabled(!p_hasTemplate + && VUtils::docTypeFromName(m_nameEdit->getEvaluatedText()) + == DocType::Markdown); +} + +bool VNewFileDialog::isTemplateUsed() const +{ + QString file = m_templateCB->currentData().toString(); + return !(file.isEmpty() || file == "None"); +} + +QString VNewFileDialog::getTemplate() const +{ + return g_mwMgr->evaluate(m_template); +} + +void VNewFileDialog::tryToSelectLastTemplate() +{ + Q_ASSERT(m_templateCB->count() > 0 + && m_templateCB->itemData(0).toString() == "None"); + if (s_lastTemplateFile.isEmpty() || s_lastTemplateFile == "None") { + m_templateCB->setCurrentIndex(0); + return; + } + + int idx = m_templateCB->findData(s_lastTemplateFile); + if (idx != -1) { + m_templateCB->setCurrentIndex(idx); + } else { + s_lastTemplateFile.clear(); + } } diff --git a/src/dialog/vnewfiledialog.h b/src/dialog/vnewfiledialog.h index 496e90df..729416dd 100644 --- a/src/dialog/vnewfiledialog.h +++ b/src/dialog/vnewfiledialog.h @@ -3,31 +3,60 @@ #include +#include "vconstants.h" + class QLabel; class VLineEdit; class QDialogButtonBox; class QCheckBox; class VDirectory; +class QComboBox; +class QTextEdit; class VNewFileDialog : public QDialog { Q_OBJECT public: - VNewFileDialog(const QString &title, const QString &info, - const QString &defaultName, VDirectory *directory, - QWidget *parent = 0); + VNewFileDialog(const QString &p_title, + const QString &p_info, + const QString &p_defaultName, + VDirectory *p_directory, + QWidget *p_parent = 0); QString getNameInput() const; bool getInsertTitleInput() const; + // Whether user choose a note template. + bool isTemplateUsed() const; + + // Get the template content (after magic words evaluated) user chose. + QString getTemplate() const; + private slots: void handleInputChanged(); + void handleCurrentTemplateChanged(int p_idx); + private: - void setupUI(); + void setupUI(const QString &p_title, + const QString &p_info, + const QString &p_defaultName); + + // Update the templates according to @p_type. + void updateTemplates(DocType p_type); + + void enableInsertTitleCB(bool p_hasTemplate); + + void tryToSelectLastTemplate(); VLineEdit *m_nameEdit; + + QComboBox *m_templateCB; + + // Used for template preview. + QTextEdit *m_templateEdit; + QCheckBox *m_insertTitleCB; QPushButton *okBtn; @@ -35,9 +64,14 @@ private: QLabel *m_warnLabel; - QString title; - QString info; - QString defaultName; + // Template content. + QString m_template; + + // Doc type of current template. + DocType m_currentTemplateType; + + // Last chosen template file. + static QString s_lastTemplateFile; VDirectory *m_directory; }; diff --git a/src/resources/icons/manage_template.svg b/src/resources/icons/manage_template.svg new file mode 100644 index 00000000..19545aa6 --- /dev/null +++ b/src/resources/icons/manage_template.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index 92dab9b6..bfce455f 100644 --- a/src/utils/vutils.cpp +++ b/src/utils/vutils.cpp @@ -515,6 +515,10 @@ void VUtils::sleepWait(int p_milliseconds) DocType VUtils::docTypeFromName(const QString &p_name) { + if (p_name.isEmpty()) { + return DocType::Unknown; + } + const QHash> &suffixes = g_config->getDocSuffixes(); QString suf = QFileInfo(p_name).suffix().toLower(); diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp index 9cb03520..e4ae3047 100644 --- a/src/vconfigmanager.cpp +++ b/src/vconfigmanager.cpp @@ -32,6 +32,8 @@ const QString VConfigManager::c_styleConfigFolder = QString("styles"); const QString VConfigManager::c_codeBlockStyleConfigFolder = QString("codeblock_styles"); +const QString VConfigManager::c_templateConfigFolder = QString("templates"); + const QString VConfigManager::c_defaultCssFile = QString(":/resources/styles/default.css"); const QString VConfigManager::c_defaultCodeBlockCssFile = QString(":/utils/highlightjs/styles/vnote.css"); @@ -740,6 +742,11 @@ QString VConfigManager::getCodeBlockStyleConfigFolder() const return getStyleConfigFolder() + QDir::separator() + c_codeBlockStyleConfigFolder; } +QString VConfigManager::getTemplateConfigFolder() const +{ + return getConfigFolder() + QDir::separator() + c_templateConfigFolder; +} + QVector VConfigManager::getCssStyles() const { QVector res; @@ -761,6 +768,28 @@ QVector VConfigManager::getCssStyles() const return res; } +QVector VConfigManager::getNoteTemplates(DocType p_type) const +{ + QVector res; + QDir dir(getTemplateConfigFolder()); + if (!dir.exists()) { + dir.mkpath(getTemplateConfigFolder()); + return res; + } + + dir.setFilter(QDir::Files | QDir::NoSymLinks); + QStringList files = dir.entryList(); + res.reserve(files.size()); + for (auto const &item : files) { + if (p_type == DocType::Unknown + || p_type == VUtils::docTypeFromName(item)) { + res.push_back(item); + } + } + + return res; +} + QVector VConfigManager::getCodeBlockCssStyles() const { QVector res; diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index c2a01c0c..15bdcab5 100644 --- a/src/vconfigmanager.h +++ b/src/vconfigmanager.h @@ -349,9 +349,15 @@ public: // Get the folder c_styleConfigFolder in the config folder. QString getStyleConfigFolder() const; + // Get the folder c_templateConfigFolder in the config folder. + QString getTemplateConfigFolder() const; + // Read all available css files in c_styleConfigFolder. QVector getCssStyles() const; + // Read all available templates files in c_templateConfigFolder. + QVector getNoteTemplates(DocType p_type = DocType::Unknown) const; + // Get the folder c_codeBlockStyleConfigFolder in the config folder. QString getCodeBlockStyleConfigFolder() const; @@ -724,6 +730,9 @@ private: // The folder name of code block style files. static const QString c_codeBlockStyleConfigFolder; + // The folder name of template files. + static const QString c_templateConfigFolder; + // Default CSS file in resource system. static const QString c_defaultCssFile; diff --git a/src/vfilelist.cpp b/src/vfilelist.cpp index fed5d1ed..37fb66b6 100644 --- a/src/vfilelist.cpp +++ b/src/vfilelist.cpp @@ -344,19 +344,32 @@ void VFileList::newFile() return; } - // Write title if needed. - bool contentInserted = false; + // Whether need to move the cursor to the end. + bool moveCursorEnd = false; + // Content needed to insert into the new file, title/template. + QString insertContent; if (dialog.getInsertTitleInput() && file->getDocType() == DocType::Markdown) { + // Insert title. + insertContent = QString("# %1\n").arg(QFileInfo(file->getName()).completeBaseName()); + } + + if (dialog.isTemplateUsed()) { + Q_ASSERT(insertContent.isEmpty()); + insertContent = dialog.getTemplate(); + } + + if (!insertContent.isEmpty()) { if (!file->open()) { qWarning() << "fail to open newly-created note" << file->getName(); } else { Q_ASSERT(file->getContent().isEmpty()); - QString content = QString("# %1\n").arg(QFileInfo(file->getName()).completeBaseName()); - file->setContent(content); + file->setContent(insertContent); if (!file->save()) { qWarning() << "fail to write to newly-created note" << file->getName(); } else { - contentInserted = true; + if (dialog.getInsertTitleInput()) { + moveCursorEnd = true; + } } file->close(); @@ -373,7 +386,7 @@ void VFileList::newFile() emit fileCreated(file, OpenFileMode::Edit, true); // Move cursor down if content has been inserted. - if (contentInserted) { + if (moveCursorEnd) { const VMdTab *tab = dynamic_cast(editArea->getCurrentTab()); if (tab) { VMdEditor *edit = tab->getEditor(); diff --git a/src/vlineedit.cpp b/src/vlineedit.cpp index ea716ce0..e7b65368 100644 --- a/src/vlineedit.cpp +++ b/src/vlineedit.cpp @@ -5,9 +5,9 @@ #include "utils/vmetawordmanager.h" - extern VMetaWordManager *g_mwMgr; + VLineEdit::VLineEdit(QWidget *p_parent) : QLineEdit(p_parent) { @@ -42,7 +42,7 @@ void VLineEdit::init() this, &VLineEdit::handleTextChanged); } -const QString VLineEdit::getEvaluatedText() const +const QString &VLineEdit::getEvaluatedText() const { return m_evaluatedText; } diff --git a/src/vlineedit.h b/src/vlineedit.h index 81a8a86c..67934f9e 100644 --- a/src/vlineedit.h +++ b/src/vlineedit.h @@ -14,7 +14,7 @@ public: VLineEdit(const QString &p_contents, QWidget *p_parent = Q_NULLPTR); // Return the evaluated text. - const QString getEvaluatedText() const; + const QString &getEvaluatedText() const; private slots: void handleTextChanged(const QString &p_text); diff --git a/src/vnote.qrc b/src/vnote.qrc index 80d83209..155fbef1 100644 --- a/src/vnote.qrc +++ b/src/vnote.qrc @@ -134,5 +134,6 @@ resources/icons/heading_sequence.svg resources/icons/link.svg resources/icons/code_block.svg + resources/icons/manage_template.svg