support note template

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

Template supports magic word.
This commit is contained in:
Le Tan 2017-11-04 09:00:31 +08:00
parent 2186716655
commit 78a86cddc0
10 changed files with 288 additions and 35 deletions

View File

@ -4,38 +4,81 @@
#include "vdirectory.h" #include "vdirectory.h"
#include "vlineedit.h" #include "vlineedit.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "utils/vmetawordmanager.h"
extern VConfigManager *g_config; extern VConfigManager *g_config;
VNewFileDialog::VNewFileDialog(const QString &title, const QString &info, extern VMetaWordManager *g_mwMgr;
const QString &defaultName, VDirectory *directory,
QWidget *parent) QString VNewFileDialog::s_lastTemplateFile;
: QDialog(parent), title(title), info(info),
defaultName(defaultName), m_directory(directory)
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_nameEdit, &VLineEdit::textChanged, this, &VNewFileDialog::handleInputChanged);
connect(m_templateCB, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, &VNewFileDialog::handleCurrentTemplateChanged);
handleInputChanged(); handleInputChanged();
tryToSelectLastTemplate();
} }
void VNewFileDialog::setupUI() void VNewFileDialog::setupUI(const QString &p_title,
const QString &p_info,
const QString &p_defaultName)
{ {
QLabel *infoLabel = NULL; QLabel *infoLabel = NULL;
if (!info.isEmpty()) { if (!p_info.isEmpty()) {
infoLabel = new QLabel(info); infoLabel = new QLabel(p_info);
} }
// Name. // Name.
QLabel *nameLabel = new QLabel(tr("Note &name:")); m_nameEdit = new VLineEdit(p_defaultName);
m_nameEdit = new VLineEdit(defaultName);
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
m_nameEdit); m_nameEdit);
m_nameEdit->setValidator(validator); m_nameEdit->setValidator(validator);
int dotIndex = defaultName.lastIndexOf('.'); int dotIndex = p_defaultName.lastIndexOf('.');
m_nameEdit->setSelection(0, (dotIndex == -1) ? defaultName.size() : dotIndex); m_nameEdit->setSelection(0, (dotIndex == -1) ? p_defaultName.size() : dotIndex);
nameLabel->setBuddy(m_nameEdit);
// 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. // InsertTitle.
m_insertTitleCB = new QCheckBox(tr("Insert note name as title (for Markdown only)")); m_insertTitleCB = new QCheckBox(tr("Insert note name as title (for Markdown only)"));
@ -47,8 +90,9 @@ void VNewFileDialog::setupUI()
}); });
QFormLayout *topLayout = new QFormLayout(); QFormLayout *topLayout = new QFormLayout();
topLayout->addRow(nameLabel, m_nameEdit); topLayout->addRow(tr("Note &name:"), m_nameEdit);
topLayout->addWidget(m_insertTitleCB); topLayout->addWidget(m_insertTitleCB);
topLayout->addRow(tr("Template:"), templateLayout);
m_nameEdit->setMinimumWidth(m_insertTitleCB->sizeHint().width()); m_nameEdit->setMinimumWidth(m_insertTitleCB->sizeHint().width());
@ -58,9 +102,16 @@ void VNewFileDialog::setupUI()
// Ok is the default button. // Ok is the default button.
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 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); 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(); QVBoxLayout *mainLayout = new QVBoxLayout();
if (infoLabel) { if (infoLabel) {
mainLayout->addWidget(infoLabel); mainLayout->addWidget(infoLabel);
@ -72,13 +123,13 @@ void VNewFileDialog::setupUI()
mainLayout->setSizeConstraint(QLayout::SetFixedSize); mainLayout->setSizeConstraint(QLayout::SetFixedSize);
setLayout(mainLayout); setLayout(mainLayout);
setWindowTitle(title); setWindowTitle(p_title);
} }
void VNewFileDialog::handleInputChanged() void VNewFileDialog::handleInputChanged()
{ {
bool showWarnLabel = false; bool showWarnLabel = false;
QString name = m_nameEdit->getEvaluatedText(); const QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty(); bool nameOk = !name.isEmpty();
if (nameOk) { if (nameOk) {
// Check if the name conflicts with existing note name. // Check if the name conflicts with existing note name.
@ -113,6 +164,10 @@ void VNewFileDialog::handleInputChanged()
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok); QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
okBtn->setEnabled(nameOk); okBtn->setEnabled(nameOk);
if (nameOk) {
updateTemplates(VUtils::docTypeFromName(name));
}
} }
QString VNewFileDialog::getNameInput() const QString VNewFileDialog::getNameInput() const
@ -122,5 +177,103 @@ QString VNewFileDialog::getNameInput() const
bool VNewFileDialog::getInsertTitleInput() 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();
}
} }

View File

@ -3,31 +3,60 @@
#include <QDialog> #include <QDialog>
#include "vconstants.h"
class QLabel; class QLabel;
class VLineEdit; class VLineEdit;
class QDialogButtonBox; class QDialogButtonBox;
class QCheckBox; class QCheckBox;
class VDirectory; class VDirectory;
class QComboBox;
class QTextEdit;
class VNewFileDialog : public QDialog class VNewFileDialog : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
VNewFileDialog(const QString &title, const QString &info, VNewFileDialog(const QString &p_title,
const QString &defaultName, VDirectory *directory, const QString &p_info,
QWidget *parent = 0); const QString &p_defaultName,
VDirectory *p_directory,
QWidget *p_parent = 0);
QString getNameInput() const; QString getNameInput() const;
bool getInsertTitleInput() 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: private slots:
void handleInputChanged(); void handleInputChanged();
void handleCurrentTemplateChanged(int p_idx);
private: 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; VLineEdit *m_nameEdit;
QComboBox *m_templateCB;
// Used for template preview.
QTextEdit *m_templateEdit;
QCheckBox *m_insertTitleCB; QCheckBox *m_insertTitleCB;
QPushButton *okBtn; QPushButton *okBtn;
@ -35,9 +64,14 @@ private:
QLabel *m_warnLabel; QLabel *m_warnLabel;
QString title; // Template content.
QString info; QString m_template;
QString defaultName;
// Doc type of current template.
DocType m_currentTemplateType;
// Last chosen template file.
static QString s_lastTemplateFile;
VDirectory *m_directory; VDirectory *m_directory;
}; };

View File

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

After

Width:  |  Height:  |  Size: 840 B

View File

@ -515,6 +515,10 @@ void VUtils::sleepWait(int p_milliseconds)
DocType VUtils::docTypeFromName(const QString &p_name) DocType VUtils::docTypeFromName(const QString &p_name)
{ {
if (p_name.isEmpty()) {
return DocType::Unknown;
}
const QHash<int, QList<QString>> &suffixes = g_config->getDocSuffixes(); const QHash<int, QList<QString>> &suffixes = g_config->getDocSuffixes();
QString suf = QFileInfo(p_name).suffix().toLower(); QString suf = QFileInfo(p_name).suffix().toLower();

View File

@ -32,6 +32,8 @@ const QString VConfigManager::c_styleConfigFolder = QString("styles");
const QString VConfigManager::c_codeBlockStyleConfigFolder = QString("codeblock_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_defaultCssFile = QString(":/resources/styles/default.css");
const QString VConfigManager::c_defaultCodeBlockCssFile = QString(":/utils/highlightjs/styles/vnote.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; return getStyleConfigFolder() + QDir::separator() + c_codeBlockStyleConfigFolder;
} }
QString VConfigManager::getTemplateConfigFolder() const
{
return getConfigFolder() + QDir::separator() + c_templateConfigFolder;
}
QVector<QString> VConfigManager::getCssStyles() const QVector<QString> VConfigManager::getCssStyles() const
{ {
QVector<QString> res; QVector<QString> res;
@ -761,6 +768,28 @@ QVector<QString> VConfigManager::getCssStyles() const
return res; 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> VConfigManager::getCodeBlockCssStyles() const
{ {
QVector<QString> res; QVector<QString> res;

View File

@ -349,9 +349,15 @@ public:
// Get the folder c_styleConfigFolder in the config folder. // Get the folder c_styleConfigFolder in the config folder.
QString getStyleConfigFolder() const; QString getStyleConfigFolder() const;
// Get the folder c_templateConfigFolder in the config folder.
QString getTemplateConfigFolder() const;
// Read all available css files in c_styleConfigFolder. // Read all available css files in c_styleConfigFolder.
QVector<QString> getCssStyles() const; 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. // Get the folder c_codeBlockStyleConfigFolder in the config folder.
QString getCodeBlockStyleConfigFolder() const; QString getCodeBlockStyleConfigFolder() const;
@ -724,6 +730,9 @@ private:
// The folder name of code block style files. // The folder name of code block style files.
static const QString c_codeBlockStyleConfigFolder; static const QString c_codeBlockStyleConfigFolder;
// The folder name of template files.
static const QString c_templateConfigFolder;
// Default CSS file in resource system. // Default CSS file in resource system.
static const QString c_defaultCssFile; static const QString c_defaultCssFile;

View File

@ -344,19 +344,32 @@ void VFileList::newFile()
return; return;
} }
// Write title if needed. // Whether need to move the cursor to the end.
bool contentInserted = false; bool moveCursorEnd = false;
// Content needed to insert into the new file, title/template.
QString insertContent;
if (dialog.getInsertTitleInput() && file->getDocType() == DocType::Markdown) { 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()) { if (!file->open()) {
qWarning() << "fail to open newly-created note" << file->getName(); qWarning() << "fail to open newly-created note" << file->getName();
} else { } else {
Q_ASSERT(file->getContent().isEmpty()); Q_ASSERT(file->getContent().isEmpty());
QString content = QString("# %1\n").arg(QFileInfo(file->getName()).completeBaseName()); file->setContent(insertContent);
file->setContent(content);
if (!file->save()) { if (!file->save()) {
qWarning() << "fail to write to newly-created note" << file->getName(); qWarning() << "fail to write to newly-created note" << file->getName();
} else { } else {
contentInserted = true; if (dialog.getInsertTitleInput()) {
moveCursorEnd = true;
}
} }
file->close(); file->close();
@ -373,7 +386,7 @@ void VFileList::newFile()
emit fileCreated(file, OpenFileMode::Edit, true); emit fileCreated(file, OpenFileMode::Edit, true);
// Move cursor down if content has been inserted. // Move cursor down if content has been inserted.
if (contentInserted) { if (moveCursorEnd) {
const VMdTab *tab = dynamic_cast<VMdTab *>(editArea->getCurrentTab()); const VMdTab *tab = dynamic_cast<VMdTab *>(editArea->getCurrentTab());
if (tab) { if (tab) {
VMdEditor *edit = tab->getEditor(); VMdEditor *edit = tab->getEditor();

View File

@ -5,9 +5,9 @@
#include "utils/vmetawordmanager.h" #include "utils/vmetawordmanager.h"
extern VMetaWordManager *g_mwMgr; extern VMetaWordManager *g_mwMgr;
VLineEdit::VLineEdit(QWidget *p_parent) VLineEdit::VLineEdit(QWidget *p_parent)
: QLineEdit(p_parent) : QLineEdit(p_parent)
{ {
@ -42,7 +42,7 @@ void VLineEdit::init()
this, &VLineEdit::handleTextChanged); this, &VLineEdit::handleTextChanged);
} }
const QString VLineEdit::getEvaluatedText() const const QString &VLineEdit::getEvaluatedText() const
{ {
return m_evaluatedText; return m_evaluatedText;
} }

View File

@ -14,7 +14,7 @@ public:
VLineEdit(const QString &p_contents, QWidget *p_parent = Q_NULLPTR); VLineEdit(const QString &p_contents, QWidget *p_parent = Q_NULLPTR);
// Return the evaluated text. // Return the evaluated text.
const QString getEvaluatedText() const; const QString &getEvaluatedText() const;
private slots: private slots:
void handleTextChanged(const QString &p_text); void handleTextChanged(const QString &p_text);

View File

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