Browse Source

support note template

VNote will scan files in the template folder in the config folder as
template.

Template supports magic word.
Le Tan 8 years ago
parent
commit
78a86cddc0

+ 172 - 19
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<void(QComboBox::*)(int)>(&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();
+    }
 }

+ 41 - 7
src/dialog/vnewfiledialog.h

@@ -3,31 +3,60 @@
 
 #include <QDialog>
 
+#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;
 };

+ 10 - 0
src/resources/icons/manage_template.svg

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
+<path d="M437.334,144H256.006l-42.668-48H74.666C51.197,96,32,115.198,32,138.667v234.666C32,396.802,51.197,416,74.666,416h362.668
+	C460.803,416,480,396.802,480,373.333V186.667C480,163.198,460.803,144,437.334,144z M448,373.333
+	c0,5.782-4.885,10.667-10.666,10.667H74.666C68.884,384,64,379.115,64,373.333V176h373.334c5.781,0,10.666,4.885,10.666,10.667
+	V373.333z"/>
+</svg>

+ 4 - 0
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<int, QList<QString>> &suffixes = g_config->getDocSuffixes();
 
     QString suf = QFileInfo(p_name).suffix().toLower();

+ 29 - 0
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<QString> VConfigManager::getCssStyles() const
 {
     QVector<QString> res;
@@ -761,6 +768,28 @@ QVector<QString> VConfigManager::getCssStyles() const
     return res;
 }
 
+QVector<QString> VConfigManager::getNoteTemplates(DocType p_type) const
+{
+    QVector<QString> 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<QString> VConfigManager::getCodeBlockCssStyles() const
 {
     QVector<QString> res;

+ 9 - 0
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<QString> getCssStyles() const;
 
+    // Read all available templates files in c_templateConfigFolder.
+    QVector<QString> 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;
 

+ 19 - 6
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<VMdTab *>(editArea->getCurrentTab());
             if (tab) {
                 VMdEditor *edit = tab->getEditor();

+ 2 - 2
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;
 }

+ 1 - 1
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);

+ 1 - 0
src/vnote.qrc

@@ -134,5 +134,6 @@
         <file>resources/icons/heading_sequence.svg</file>
         <file>resources/icons/link.svg</file>
         <file>resources/icons/code_block.svg</file>
+        <file>resources/icons/manage_template.svg</file>
     </qresource>
 </RCC>