diff --git a/libs/vtextedit b/libs/vtextedit index 893a88cd..b9c3758d 160000 --- a/libs/vtextedit +++ b/libs/vtextedit @@ -1 +1 @@ -Subproject commit 893a88cdd7fdc3957005d109b46c9972a83d6772 +Subproject commit b9c3758d1d34a2281748352902cf1d3452f24bd4 diff --git a/src/core/configmgr.cpp b/src/core/configmgr.cpp index f0684e97..7bbd859f 100644 --- a/src/core/configmgr.cpp +++ b/src/core/configmgr.cpp @@ -381,6 +381,13 @@ QString ConfigMgr::getUserDictsFolder() const return folderPath; } +QString ConfigMgr::getUserTemplateFolder() const +{ + auto folderPath = PathUtils::concatenateFilePath(m_userConfigFolderPath, QStringLiteral("templates")); + QDir().mkpath(folderPath); + return folderPath; +} + QString ConfigMgr::getUserOrAppFile(const QString &p_filePath) const { QFileInfo fi(p_filePath); diff --git a/src/core/configmgr.h b/src/core/configmgr.h index 5c7c9377..a8ba4819 100644 --- a/src/core/configmgr.h +++ b/src/core/configmgr.h @@ -91,6 +91,8 @@ namespace vnotex QString getAppDictsFolder() const; QString getUserDictsFolder() const; + QString getUserTemplateFolder() const; + // If @p_filePath is absolute, just return it. // Otherwise, first try to find it in user folder, then in app folder. QString getUserOrAppFile(const QString &p_filePath) const; diff --git a/src/core/core.pri b/src/core/core.pri index 3590aea2..85112ee8 100644 --- a/src/core/core.pri +++ b/src/core/core.pri @@ -23,6 +23,7 @@ SOURCES += \ $$PWD/markdowneditorconfig.cpp \ $$PWD/quickaccesshelper.cpp \ $$PWD/singleinstanceguard.cpp \ + $$PWD/templatemgr.cpp \ $$PWD/texteditorconfig.cpp \ $$PWD/vnotex.cpp \ $$PWD/thememgr.cpp \ @@ -51,6 +52,7 @@ HEADERS += \ $$PWD/quickaccesshelper.h \ $$PWD/singleinstanceguard.h \ $$PWD/iconfig.h \ + $$PWD/templatemgr.h \ $$PWD/texteditorconfig.h \ $$PWD/vnotex.h \ $$PWD/thememgr.h \ diff --git a/src/core/mainconfig.cpp b/src/core/mainconfig.cpp index 8a705a1a..3bfcabc3 100644 --- a/src/core/mainconfig.cpp +++ b/src/core/mainconfig.cpp @@ -117,6 +117,4 @@ QString MainConfig::getVersion(const QJsonObject &p_jobj) void MainConfig::doVersionSpecificOverride() { // In a new version, we may want to change one value by force. - m_editorConfig->m_toolBarIconSize = 16; - m_editorConfig->writeToSettings(); } diff --git a/src/core/notebook/notebook.cpp b/src/core/notebook/notebook.cpp index 0511de6a..f88436ac 100644 --- a/src/core/notebook/notebook.cpp +++ b/src/core/notebook/notebook.cpp @@ -175,9 +175,12 @@ QSharedPointer Notebook::getRecycleBinNode() const return nullptr; } -QSharedPointer Notebook::newNode(Node *p_parent, Node::Flags p_flags, const QString &p_name) +QSharedPointer Notebook::newNode(Node *p_parent, + Node::Flags p_flags, + const QString &p_name, + const QString &p_content) { - return m_configMgr->newNode(p_parent, p_flags, p_name); + return m_configMgr->newNode(p_parent, p_flags, p_name, p_content); } const QDateTime &Notebook::getCreatedTimeUtc() const diff --git a/src/core/notebook/notebook.h b/src/core/notebook/notebook.h index 4307b225..94b46d85 100644 --- a/src/core/notebook/notebook.h +++ b/src/core/notebook/notebook.h @@ -67,7 +67,8 @@ namespace vnotex QSharedPointer newNode(Node *p_parent, Node::Flags p_flags, - const QString &p_name); + const QString &p_name, + const QString &p_content = QString()); // Add @p_name under @p_parent to add as a new node @p_type. QSharedPointer addAsNode(Node *p_parent, diff --git a/src/core/notebookconfigmgr/inotebookconfigmgr.h b/src/core/notebookconfigmgr/inotebookconfigmgr.h index 858a0b60..199fe4d0 100644 --- a/src/core/notebookconfigmgr/inotebookconfigmgr.h +++ b/src/core/notebookconfigmgr/inotebookconfigmgr.h @@ -45,7 +45,8 @@ namespace vnotex virtual QSharedPointer newNode(Node *p_parent, Node::Flags p_flags, - const QString &p_name) = 0; + const QString &p_name, + const QString &p_content) = 0; virtual QSharedPointer addAsNode(Node *p_parent, Node::Flags p_flags, diff --git a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp index c94d4eaf..8d506ca3 100644 --- a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp +++ b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp @@ -273,7 +273,7 @@ void VXNotebookConfigMgr::createRecycleBinNode(const QSharedPointer &p_roo { Q_ASSERT(p_root->isRoot()); - auto node = newNode(p_root.data(), Node::Flag::Container, c_recycleBinFolderName); + auto node = newNode(p_root.data(), Node::Flag::Container, c_recycleBinFolderName, ""); node->setUse(Node::Use::RecycleBin); markNodeReadOnly(node.data()); } @@ -376,7 +376,8 @@ void VXNotebookConfigMgr::loadFolderNode(Node *p_node, const NodeConfig &p_confi QSharedPointer VXNotebookConfigMgr::newNode(Node *p_parent, Node::Flags p_flags, - const QString &p_name) + const QString &p_name, + const QString &p_content) { Q_ASSERT(p_parent && p_parent->isContainer() && !p_name.isEmpty()); @@ -384,7 +385,7 @@ QSharedPointer VXNotebookConfigMgr::newNode(Node *p_parent, if (p_flags & Node::Flag::Content) { Q_ASSERT(!(p_flags & Node::Flag::Container)); - node = newFileNode(p_parent, p_name, true, NodeParameters()); + node = newFileNode(p_parent, p_name, p_content, true, NodeParameters()); } else { node = newFolderNode(p_parent, p_name, true, NodeParameters()); } @@ -403,7 +404,7 @@ QSharedPointer VXNotebookConfigMgr::addAsNode(Node *p_parent, QSharedPointer node; if (p_flags & Node::Flag::Content) { Q_ASSERT(!(p_flags & Node::Flag::Container)); - node = newFileNode(p_parent, p_name, false, p_paras); + node = newFileNode(p_parent, p_name, "", false, p_paras); } else { node = newFolderNode(p_parent, p_name, false, p_paras); } @@ -430,6 +431,7 @@ QSharedPointer VXNotebookConfigMgr::copyAsNode(Node *p_parent, QSharedPointer VXNotebookConfigMgr::newFileNode(Node *p_parent, const QString &p_name, + const QString &p_content, bool p_create, const NodeParameters &p_paras) { @@ -447,7 +449,7 @@ QSharedPointer VXNotebookConfigMgr::newFileNode(Node *p_parent, // Write empty file. if (p_create) { - getBackend()->writeFile(node->fetchPath(), QString()); + getBackend()->writeFile(node->fetchPath(), p_content); node->setExists(true); } else { node->setExists(getBackend()->existsFile(node->fetchPath())); diff --git a/src/core/notebookconfigmgr/vxnotebookconfigmgr.h b/src/core/notebookconfigmgr/vxnotebookconfigmgr.h index a30a0fdd..d1958a46 100644 --- a/src/core/notebookconfigmgr/vxnotebookconfigmgr.h +++ b/src/core/notebookconfigmgr/vxnotebookconfigmgr.h @@ -41,7 +41,8 @@ namespace vnotex QSharedPointer newNode(Node *p_parent, Node::Flags p_flags, - const QString &p_name) Q_DECL_OVERRIDE; + const QString &p_name, + const QString &p_content) Q_DECL_OVERRIDE; QSharedPointer addAsNode(Node *p_parent, Node::Flags p_flags, @@ -154,6 +155,7 @@ namespace vnotex QSharedPointer newFileNode(Node *p_parent, const QString &p_name, + const QString &p_content, bool p_create, const NodeParameters &p_paras); diff --git a/src/core/templatemgr.cpp b/src/core/templatemgr.cpp new file mode 100644 index 00000000..2b618b44 --- /dev/null +++ b/src/core/templatemgr.cpp @@ -0,0 +1,24 @@ +#include "templatemgr.h" + +#include + +#include "configmgr.h" + +using namespace vnotex; + +QString TemplateMgr::getTemplateFolder() const +{ + return ConfigMgr::getInst().getUserTemplateFolder(); +} + +QStringList TemplateMgr::getTemplates() const +{ + QDir dir(getTemplateFolder()); + dir.setFilter(QDir::Files | QDir::NoSymLinks); + return dir.entryList(); +} + +QString TemplateMgr::getTemplateFilePath(const QString &p_name) const +{ + return QDir(getTemplateFolder()).filePath(p_name); +} diff --git a/src/core/templatemgr.h b/src/core/templatemgr.h new file mode 100644 index 00000000..aa1fd770 --- /dev/null +++ b/src/core/templatemgr.h @@ -0,0 +1,32 @@ +#ifndef TEMPLATEMGR_H +#define TEMPLATEMGR_H + +#include +#include + +#include "noncopyable.h" + +namespace vnotex +{ + class TemplateMgr : public QObject, private Noncopyable + { + Q_OBJECT + public: + static TemplateMgr &getInst() + { + static TemplateMgr inst; + return inst; + } + + QString getTemplateFolder() const; + + QStringList getTemplates() const; + + QString getTemplateFilePath(const QString &p_name) const; + + private: + TemplateMgr() = default; + }; +} + +#endif // TEMPLATEMGR_H diff --git a/src/widgets/dialogs/newnotedialog.cpp b/src/widgets/dialogs/newnotedialog.cpp index 8eecbd29..e0311381 100644 --- a/src/widgets/dialogs/newnotedialog.cpp +++ b/src/widgets/dialogs/newnotedialog.cpp @@ -1,6 +1,11 @@ #include "newnotedialog.h" -#include +#include +#include +#include +#include +#include +#include #include "notebook/notebook.h" #include "notebook/node.h" @@ -10,9 +15,12 @@ #include "exception.h" #include "nodeinfowidget.h" #include +#include using namespace vnotex; +QString NewNoteDialog::s_lastTemplate; + NewNoteDialog::NewNoteDialog(Node *p_node, QWidget *p_parent) : ScrollDialog(p_parent) { @@ -29,6 +37,30 @@ void NewNoteDialog::setupUI(const Node *p_node) setupNodeInfoWidget(p_node, this); setCentralWidget(m_infoWidget); + auto infoLayout = m_infoWidget->getMainLayout(); + + { + auto templateLayout = new QHBoxLayout(); + templateLayout->setContentsMargins(0, 0, 0, 0); + infoLayout->addRow(tr("Template:"), templateLayout); + + setupTemplateComboBox(m_infoWidget); + templateLayout->addWidget(m_templateComboBox); + + templateLayout->addStretch(); + + auto manageBtn = new QPushButton(tr("Manage"), m_infoWidget); + templateLayout->addWidget(manageBtn); + connect(manageBtn, &QPushButton::clicked, + this, []() { + WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(TemplateMgr::getInst().getTemplateFolder())); + }); + + m_templateTextEdit = WidgetsFactory::createPlainTextConsole(m_infoWidget); + infoLayout->addRow("", m_templateTextEdit); + m_templateTextEdit->hide(); + } + setDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); setButtonEnabled(QDialogButtonBox::Ok, false); @@ -73,6 +105,8 @@ bool NewNoteDialog::validateNameInput(QString &p_msg) void NewNoteDialog::acceptedButtonClicked() { + s_lastTemplate = m_templateComboBox->currentData().toString(); + if (newNote()) { accept(); } @@ -85,7 +119,10 @@ bool NewNoteDialog::newNote() Notebook *notebook = const_cast(m_infoWidget->getNotebook()); Node *parentNode = const_cast(m_infoWidget->getParentNode()); try { - m_newNode = notebook->newNode(parentNode, Node::Flag::Content, m_infoWidget->getName()); + m_newNode = notebook->newNode(parentNode, + Node::Flag::Content, + m_infoWidget->getName(), + getTemplateContent()); } catch (Exception &p_e) { QString msg = tr("Failed to create note under (%1) in (%2) (%3).").arg(parentNode->getName(), notebook->getName(), @@ -118,3 +155,60 @@ void NewNoteDialog::initDefaultValues(const Node *p_node) validateInputs(); } } + +void NewNoteDialog::setupTemplateComboBox(QWidget *p_parent) +{ + m_templateComboBox = WidgetsFactory::createComboBox(p_parent); + + // None. + m_templateComboBox->addItem(tr("None"), ""); + + int idx = 1; + auto templates = TemplateMgr::getInst().getTemplates(); + for (const auto &temp : templates) { + m_templateComboBox->addItem(temp, temp); + m_templateComboBox->setItemData(idx++, temp, Qt::ToolTipRole); + } + + if (!s_lastTemplate.isEmpty()) { + // Restore. + int idx = m_templateComboBox->findData(s_lastTemplate); + if (idx != -1) { + m_templateComboBox->setCurrentIndex(idx); + } else { + s_lastTemplate.clear(); + } + } + + connect(m_templateComboBox, QOverload::of(&QComboBox::currentIndexChanged), + this, [this]() { + m_templateContent.clear(); + m_templateTextEdit->clear(); + + auto temp = m_templateComboBox->currentData().toString(); + if (temp.isEmpty()) { + m_templateTextEdit->hide(); + return; + } + + const auto filePath = TemplateMgr::getInst().getTemplateFilePath(temp); + try { + m_templateContent = FileUtils::readTextFile(filePath); + m_templateTextEdit->setPlainText(m_templateContent); + m_templateTextEdit->show(); + } catch (Exception &p_e) { + m_templateTextEdit->hide(); + + QString msg = tr("Failed to load template (%1) (%2).") + .arg(filePath, p_e.what()); + qCritical() << msg; + setInformationText(msg, ScrollDialog::InformationLevel::Error); + } + }); +} + +QString NewNoteDialog::getTemplateContent() const +{ + // TODO: parse snippets of the template. + return m_templateContent; +} diff --git a/src/widgets/dialogs/newnotedialog.h b/src/widgets/dialogs/newnotedialog.h index b5c8cdf3..d5d78477 100644 --- a/src/widgets/dialogs/newnotedialog.h +++ b/src/widgets/dialogs/newnotedialog.h @@ -3,6 +3,9 @@ #include "scrolldialog.h" +class QComboBox; +class QPlainTextEdit; + namespace vnotex { class Notebook; @@ -29,15 +32,27 @@ namespace vnotex void setupNodeInfoWidget(const Node *p_node, QWidget *p_parent); + void setupTemplateComboBox(QWidget *p_parent); + bool validateNameInput(QString &p_msg); bool newNote(); void initDefaultValues(const Node *p_node); + QString getTemplateContent() const; + NodeInfoWidget *m_infoWidget = nullptr; + QComboBox *m_templateComboBox = nullptr; + + QPlainTextEdit *m_templateTextEdit = nullptr; + + QString m_templateContent; + QSharedPointer m_newNode; + + static QString s_lastTemplate; }; } // ns vnotex diff --git a/src/widgets/dialogs/nodeinfowidget.cpp b/src/widgets/dialogs/nodeinfowidget.cpp index 4f5726fa..0d2c4b62 100644 --- a/src/widgets/dialogs/nodeinfowidget.cpp +++ b/src/widgets/dialogs/nodeinfowidget.cpp @@ -1,6 +1,9 @@ #include "nodeinfowidget.h" -#include +#include +#include +#include +#include #include "notebook/notebook.h" #include "../widgetsfactory.h" @@ -174,3 +177,8 @@ void NodeInfoWidget::setupFileTypeComboBox(QWidget *p_parent) m_nameLineEdit->setFocus(); }); } + +QFormLayout *NodeInfoWidget::getMainLayout() const +{ + return m_mainLayout; +} diff --git a/src/widgets/dialogs/nodeinfowidget.h b/src/widgets/dialogs/nodeinfowidget.h index 89f8d38e..3dbbf7cf 100644 --- a/src/widgets/dialogs/nodeinfowidget.h +++ b/src/widgets/dialogs/nodeinfowidget.h @@ -35,6 +35,9 @@ namespace vnotex const Node *getParentNode() const; + // Allow upper level to add more widgets to the layout. + QFormLayout *getMainLayout() const; + signals: void inputEdited();