diff --git a/src/dialog/vdirinfodialog.cpp b/src/dialog/vdirinfodialog.cpp
index 63a9d477..e67c98a3 100644
--- a/src/dialog/vdirinfodialog.cpp
+++ b/src/dialog/vdirinfodialog.cpp
@@ -2,6 +2,7 @@
#include "vdirinfodialog.h"
#include "vdirectory.h"
#include "vconfigmanager.h"
+#include "vlineedit.h"
#include "utils/vutils.h"
extern VConfigManager *g_config;
@@ -16,7 +17,7 @@ VDirInfoDialog::VDirInfoDialog(const QString &title,
{
setupUI();
- connect(nameEdit, &QLineEdit::textChanged, this, &VDirInfoDialog::handleInputChanged);
+ connect(m_nameEdit, &QLineEdit::textChanged, this, &VDirInfoDialog::handleInputChanged);
handleInputChanged();
}
@@ -28,15 +29,18 @@ void VDirInfoDialog::setupUI()
infoLabel = new QLabel(info);
}
- nameEdit = new QLineEdit(m_directory->getName());
- nameEdit->selectAll();
+ m_nameEdit = new VLineEdit(m_directory->getName());
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
+ m_nameEdit);
+ m_nameEdit->setValidator(validator);
+ m_nameEdit->selectAll();
// Created time.
QString createdTimeStr = VUtils::displayDateTime(m_directory->getCreatedTimeUtc().toLocalTime());
QLabel *createdTimeLabel = new QLabel(createdTimeStr);
QFormLayout *topLayout = new QFormLayout();
- topLayout->addRow(tr("Folder &name:"), nameEdit);
+ topLayout->addRow(tr("Folder &name:"), m_nameEdit);
topLayout->addRow(tr("Created time:"), createdTimeLabel);
m_warnLabel = new QLabel();
@@ -49,7 +53,7 @@ void VDirInfoDialog::setupUI()
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
- nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
+ m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
QVBoxLayout *mainLayout = new QVBoxLayout();
if (infoLabel) {
@@ -67,19 +71,35 @@ void VDirInfoDialog::setupUI()
void VDirInfoDialog::handleInputChanged()
{
bool showWarnLabel = false;
- QString name = nameEdit->text();
+ QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty();
if (nameOk && name != m_directory->getName()) {
// Check if the name conflicts with existing directory name.
// Case-insensitive when creating note.
const VDirectory *directory = m_parentDirectory->findSubDirectory(name, false);
+ QString warnText;
if (directory && directory != m_directory) {
nameOk = false;
+ warnText = tr("WARNING: "
+ "Name (case-insensitive) %3 already exists. "
+ "Please choose another name.")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ } else if (!VUtils::checkFileNameLegal(name)) {
+ // Check if evaluated name contains illegal characters.
+ nameOk = false;
+ warnText = tr("WARNING: "
+ "Name %3 contains illegal characters "
+ "(after magic word evaluation).")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ }
+
+ if (!nameOk) {
showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
- "Please choose another name.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ m_warnLabel->setText(warnText);
}
}
@@ -91,5 +111,5 @@ void VDirInfoDialog::handleInputChanged()
QString VDirInfoDialog::getNameInput() const
{
- return nameEdit->text();
+ return m_nameEdit->getEvaluatedText();
}
diff --git a/src/dialog/vdirinfodialog.h b/src/dialog/vdirinfodialog.h
index 558334df..d23a0211 100644
--- a/src/dialog/vdirinfodialog.h
+++ b/src/dialog/vdirinfodialog.h
@@ -4,7 +4,7 @@
#include
class QLabel;
-class QLineEdit;
+class VLineEdit;
class QDialogButtonBox;
class QString;
class VDirectory;
@@ -27,7 +27,7 @@ private slots:
private:
void setupUI();
- QLineEdit *nameEdit;
+ VLineEdit *m_nameEdit;
QLabel *m_warnLabel;
QDialogButtonBox *m_btnBox;
diff --git a/src/dialog/vfileinfodialog.cpp b/src/dialog/vfileinfodialog.cpp
index 40373a87..4fd4a8a8 100644
--- a/src/dialog/vfileinfodialog.cpp
+++ b/src/dialog/vfileinfodialog.cpp
@@ -4,6 +4,7 @@
#include "vnotefile.h"
#include "vconfigmanager.h"
#include "utils/vutils.h"
+#include "vlineedit.h"
extern VConfigManager *g_config;
@@ -16,7 +17,7 @@ VFileInfoDialog::VFileInfoDialog(const QString &title,
{
setupUI(title, info);
- connect(nameEdit, &QLineEdit::textChanged, this, &VFileInfoDialog::handleInputChanged);
+ connect(m_nameEdit, &QLineEdit::textChanged, this, &VFileInfoDialog::handleInputChanged);
handleInputChanged();
}
@@ -30,7 +31,10 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
// File name.
QString name = m_file->getName();
- nameEdit = new QLineEdit(name);
+ m_nameEdit = new VLineEdit(name);
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
+ m_nameEdit);
+ m_nameEdit->setValidator(validator);
int baseStart = 0, baseLength = name.size();
int dotIdx = name.lastIndexOf('.');
if (dotIdx != -1) {
@@ -38,7 +42,7 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
}
// Select without suffix.
- nameEdit->setSelection(baseStart, baseLength);
+ m_nameEdit->setSelection(baseStart, baseLength);
// Attachment folder.
QLineEdit *attachmentFolderEdit = new QLineEdit(m_file->getAttachmentFolder());
@@ -56,7 +60,7 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
modifiedTimeLabel->setToolTip(tr("Last modified time within VNote"));
QFormLayout *topLayout = new QFormLayout();
- topLayout->addRow(tr("Note &name:"), nameEdit);
+ topLayout->addRow(tr("Note &name:"), m_nameEdit);
topLayout->addRow(tr("Attachment folder:"), attachmentFolderEdit);
topLayout->addRow(tr("Created time:"), createdTimeLabel);
topLayout->addRow(tr("Modified time:"), modifiedTimeLabel);
@@ -71,7 +75,7 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
- nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
+ m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
QVBoxLayout *mainLayout = new QVBoxLayout();
if (infoLabel) {
@@ -90,28 +94,43 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
void VFileInfoDialog::handleInputChanged()
{
bool showWarnLabel = false;
- QString name = nameEdit->text();
+ QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty();
if (nameOk && name != m_file->getName()) {
// Check if the name conflicts with existing note name.
// Case-insensitive when creating note.
const VNoteFile *file = m_directory->findFile(name, false);
+ QString warnText;
if (file && file != m_file) {
nameOk = false;
- showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
- "Please choose another name.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ warnText = tr("WARNING: "
+ "Name (case-insensitive) %3 already exists. "
+ "Please choose another name.")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
} else if (m_file->getDocType() != DocType::Unknown
&& VUtils::docTypeFromName(name) != m_file->getDocType()) {
// Check if the name change the doc type.
nameOk = false;
+ warnText = tr("WARNING: "
+ "Changing type of the note is not supported. "
+ "Please use the same suffix as the old one.")
+ .arg(g_config->c_warningTextStyle);
+ } else if (!VUtils::checkFileNameLegal(name)) {
+ // Check if evaluated name contains illegal characters.
+ nameOk = false;
+ warnText = tr("WARNING: "
+ "Name %3 contains illegal characters "
+ "(after magic word evaluation).")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ }
+
+ if (!nameOk) {
showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Changing type of the note is not supported. "
- "Please use the same suffix as the old one.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ m_warnLabel->setText(warnText);
}
}
@@ -123,5 +142,5 @@ void VFileInfoDialog::handleInputChanged()
QString VFileInfoDialog::getNameInput() const
{
- return nameEdit->text();
+ return m_nameEdit->getEvaluatedText();
}
diff --git a/src/dialog/vfileinfodialog.h b/src/dialog/vfileinfodialog.h
index 939cfcdf..015705f0 100644
--- a/src/dialog/vfileinfodialog.h
+++ b/src/dialog/vfileinfodialog.h
@@ -4,7 +4,7 @@
#include
class QLabel;
-class QLineEdit;
+class VLineEdit;
class QDialogButtonBox;
class QString;
class VDirectory;
@@ -29,7 +29,7 @@ private slots:
private:
void setupUI(const QString &p_title, const QString &p_info);
- QLineEdit *nameEdit;
+ VLineEdit *m_nameEdit;
QLabel *m_warnLabel;
QDialogButtonBox *m_btnBox;
diff --git a/src/dialog/vinsertimagedialog.cpp b/src/dialog/vinsertimagedialog.cpp
index 9b0b090e..ff0a724f 100644
--- a/src/dialog/vinsertimagedialog.cpp
+++ b/src/dialog/vinsertimagedialog.cpp
@@ -3,6 +3,7 @@
#include
#include "vinsertimagedialog.h"
#include "utils/vutils.h"
+#include "vlineedit.h"
VInsertImageDialog::VInsertImageDialog(const QString &title, const QString &defaultImageTitle,
const QString &defaultPath, QWidget *parent)
@@ -11,9 +12,12 @@ VInsertImageDialog::VInsertImageDialog(const QString &title, const QString &defa
{
setupUI();
- connect(imageTitleEdit, &QLineEdit::textChanged, this, &VInsertImageDialog::enableOkButton);
- connect(pathEdit, &QLineEdit::textChanged, this, &VInsertImageDialog::enableOkButton);
- connect(browseBtn, &QPushButton::clicked, this, &VInsertImageDialog::handleBrowseBtnClicked);
+ connect(m_imageTitleEdit, &QLineEdit::textChanged,
+ this, &VInsertImageDialog::enableOkButton);
+ connect(pathEdit, &QLineEdit::textChanged,
+ this, &VInsertImageDialog::enableOkButton);
+ connect(browseBtn, &QPushButton::clicked,
+ this, &VInsertImageDialog::handleBrowseBtnClicked);
enableOkButton();
}
@@ -34,19 +38,19 @@ void VInsertImageDialog::setupUI()
browseBtn = new QPushButton(tr("&Browse"));
imageTitleLabel = new QLabel(tr("&Image title:"));
- imageTitleEdit = new QLineEdit(defaultImageTitle);
- imageTitleEdit->selectAll();
- imageTitleLabel->setBuddy(imageTitleEdit);
- QRegExp regExp("[\\w\\(\\)@#%\\*\\-\\+=\\?<>\\,\\.\\s]+");
- QValidator *validator = new QRegExpValidator(regExp, this);
- imageTitleEdit->setValidator(validator);
+ m_imageTitleEdit = new VLineEdit(defaultImageTitle);
+ m_imageTitleEdit->selectAll();
+ imageTitleLabel->setBuddy(m_imageTitleEdit);
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_imageTitleRegExp),
+ m_imageTitleEdit);
+ m_imageTitleEdit->setValidator(validator);
QGridLayout *topLayout = new QGridLayout();
topLayout->addWidget(pathLabel, 0, 0);
topLayout->addWidget(pathEdit, 0, 1);
topLayout->addWidget(browseBtn, 0, 2);
topLayout->addWidget(imageTitleLabel, 1, 0);
- topLayout->addWidget(imageTitleEdit, 1, 1, 1, 2);
+ topLayout->addWidget(m_imageTitleEdit, 1, 1, 1, 2);
topLayout->setColumnStretch(0, 0);
topLayout->setColumnStretch(1, 1);
topLayout->setColumnStretch(2, 0);
@@ -67,22 +71,25 @@ void VInsertImageDialog::setupUI()
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
setWindowTitle(title);
- imageTitleEdit->setFocus();
+ m_imageTitleEdit->setFocus();
}
void VInsertImageDialog::enableOkButton()
{
- bool enabled = true;
- if (imageTitleEdit->text().isEmpty() || !image) {
- enabled = false;
+ QString title = m_imageTitleEdit->getEvaluatedText();
+ bool titleOk = !title.isEmpty();
+ if (titleOk) {
+ QRegExp reg(VUtils::c_imageTitleRegExp);
+ titleOk = reg.exactMatch(title);
}
+
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
- okBtn->setEnabled(enabled);
+ okBtn->setEnabled(titleOk);
}
QString VInsertImageDialog::getImageTitleInput() const
{
- return imageTitleEdit->text();
+ return m_imageTitleEdit->getEvaluatedText();
}
QString VInsertImageDialog::getPathInput() const
diff --git a/src/dialog/vinsertimagedialog.h b/src/dialog/vinsertimagedialog.h
index b16b7cb8..670f50d1 100644
--- a/src/dialog/vinsertimagedialog.h
+++ b/src/dialog/vinsertimagedialog.h
@@ -8,6 +8,7 @@
class QLabel;
class QLineEdit;
+class VLineEdit;
class QPushButton;
class QDialogButtonBox;
@@ -37,7 +38,7 @@ private:
void setupUI();
QLabel *imageTitleLabel;
- QLineEdit *imageTitleEdit;
+ VLineEdit *m_imageTitleEdit;
QLabel *pathLabel;
QLineEdit *pathEdit;
QPushButton *browseBtn;
diff --git a/src/dialog/vnewdirdialog.cpp b/src/dialog/vnewdirdialog.cpp
index 7a8bff59..24ad8ccc 100644
--- a/src/dialog/vnewdirdialog.cpp
+++ b/src/dialog/vnewdirdialog.cpp
@@ -2,6 +2,8 @@
#include "vnewdirdialog.h"
#include "vdirectory.h"
#include "vconfigmanager.h"
+#include "vlineedit.h"
+#include "utils/vutils.h"
extern VConfigManager *g_config;
@@ -15,7 +17,7 @@ VNewDirDialog::VNewDirDialog(const QString &title,
{
setupUI();
- connect(nameEdit, &QLineEdit::textChanged, this, &VNewDirDialog::handleInputChanged);
+ connect(m_nameEdit, &QLineEdit::textChanged, this, &VNewDirDialog::handleInputChanged);
handleInputChanged();
}
@@ -29,9 +31,12 @@ void VNewDirDialog::setupUI()
}
QLabel *nameLabel = new QLabel(tr("Folder &name:"));
- nameEdit = new QLineEdit(defaultName);
- nameEdit->selectAll();
- nameLabel->setBuddy(nameEdit);
+ m_nameEdit = new VLineEdit(defaultName);
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
+ m_nameEdit);
+ m_nameEdit->setValidator(validator);
+ m_nameEdit->selectAll();
+ nameLabel->setBuddy(m_nameEdit);
m_warnLabel = new QLabel();
m_warnLabel->setWordWrap(true);
@@ -44,10 +49,10 @@ void VNewDirDialog::setupUI()
QHBoxLayout *topLayout = new QHBoxLayout();
topLayout->addWidget(nameLabel);
- topLayout->addWidget(nameEdit);
+ topLayout->addWidget(m_nameEdit);
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
- nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
+ m_nameEdit->setMinimumWidth(okBtn->sizeHint().width() * 3);
QVBoxLayout *mainLayout = new QVBoxLayout();
if (infoLabel) {
@@ -64,18 +69,34 @@ void VNewDirDialog::setupUI()
void VNewDirDialog::handleInputChanged()
{
bool showWarnLabel = false;
- QString name = nameEdit->text();
+ QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty();
if (nameOk) {
// Check if the name conflicts with existing directory name.
// Case-insensitive when creating folder.
+ QString warnText;
if (m_directory->findSubDirectory(name, false)) {
nameOk = false;
+ warnText = tr("WARNING: "
+ "Name (case-insensitive) %3 already exists. "
+ "Please choose another name.")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ } else if (!VUtils::checkFileNameLegal(name)) {
+ // Check if evaluated name contains illegal characters.
+ nameOk = false;
+ warnText = tr("WARNING: "
+ "Name %3 contains illegal characters "
+ "(after magic word evaluation).")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ }
+
+ if (!nameOk) {
showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
- "Please choose another name.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ m_warnLabel->setText(warnText);
}
}
@@ -87,5 +108,5 @@ void VNewDirDialog::handleInputChanged()
QString VNewDirDialog::getNameInput() const
{
- return nameEdit->text();
+ return m_nameEdit->getEvaluatedText();
}
diff --git a/src/dialog/vnewdirdialog.h b/src/dialog/vnewdirdialog.h
index 30ca9727..8594c594 100644
--- a/src/dialog/vnewdirdialog.h
+++ b/src/dialog/vnewdirdialog.h
@@ -4,7 +4,7 @@
#include
class QLabel;
-class QLineEdit;
+class VLineEdit;
class QDialogButtonBox;
class QString;
class VDirectory;
@@ -27,7 +27,7 @@ private slots:
private:
void setupUI();
- QLineEdit *nameEdit;
+ VLineEdit *m_nameEdit;
QDialogButtonBox *m_btnBox;
QLabel *m_warnLabel;
diff --git a/src/dialog/vnewfiledialog.cpp b/src/dialog/vnewfiledialog.cpp
index 6ef321ff..730728cc 100644
--- a/src/dialog/vnewfiledialog.cpp
+++ b/src/dialog/vnewfiledialog.cpp
@@ -2,6 +2,8 @@
#include "vnewfiledialog.h"
#include "vconfigmanager.h"
#include "vdirectory.h"
+#include "vlineedit.h"
+#include "utils/vutils.h"
extern VConfigManager *g_config;
@@ -13,7 +15,7 @@ VNewFileDialog::VNewFileDialog(const QString &title, const QString &info,
{
setupUI();
- connect(nameEdit, &QLineEdit::textChanged, this, &VNewFileDialog::handleInputChanged);
+ connect(m_nameEdit, &VLineEdit::textChanged, this, &VNewFileDialog::handleInputChanged);
handleInputChanged();
}
@@ -27,10 +29,13 @@ void VNewFileDialog::setupUI()
// Name.
QLabel *nameLabel = new QLabel(tr("Note &name:"));
- nameEdit = new QLineEdit(defaultName);
+ m_nameEdit = new VLineEdit(defaultName);
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
+ m_nameEdit);
+ m_nameEdit->setValidator(validator);
int dotIndex = defaultName.lastIndexOf('.');
- nameEdit->setSelection(0, (dotIndex == -1) ? defaultName.size() : dotIndex);
- nameLabel->setBuddy(nameEdit);
+ m_nameEdit->setSelection(0, (dotIndex == -1) ? defaultName.size() : dotIndex);
+ nameLabel->setBuddy(m_nameEdit);
// InsertTitle.
m_insertTitleCB = new QCheckBox(tr("Insert note name as title (for Markdown only)"));
@@ -42,10 +47,10 @@ void VNewFileDialog::setupUI()
});
QFormLayout *topLayout = new QFormLayout();
- topLayout->addRow(nameLabel, nameEdit);
+ topLayout->addRow(nameLabel, m_nameEdit);
topLayout->addWidget(m_insertTitleCB);
- nameEdit->setMinimumWidth(m_insertTitleCB->sizeHint().width());
+ m_nameEdit->setMinimumWidth(m_insertTitleCB->sizeHint().width());
m_warnLabel = new QLabel();
m_warnLabel->setWordWrap(true);
@@ -73,18 +78,34 @@ void VNewFileDialog::setupUI()
void VNewFileDialog::handleInputChanged()
{
bool showWarnLabel = false;
- QString name = nameEdit->text();
+ QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty();
if (nameOk) {
// Check if the name conflicts with existing note name.
// Case-insensitive when creating note.
+ QString warnText;
if (m_directory->findFile(name, false)) {
nameOk = false;
+ warnText = tr("WARNING: "
+ "Name (case-insensitive) %3 already exists. "
+ "Please choose another name.")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ } else if (!VUtils::checkFileNameLegal(name)) {
+ // Check if evaluated name contains illegal characters.
+ nameOk = false;
+ warnText = tr("WARNING: "
+ "Name %3 contains illegal characters "
+ "(after magic word evaluation).")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ }
+
+ if (!nameOk) {
showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
- "Please choose another name.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ m_warnLabel->setText(warnText);
}
}
@@ -96,7 +117,7 @@ void VNewFileDialog::handleInputChanged()
QString VNewFileDialog::getNameInput() const
{
- return nameEdit->text();
+ return m_nameEdit->getEvaluatedText();
}
bool VNewFileDialog::getInsertTitleInput() const
diff --git a/src/dialog/vnewfiledialog.h b/src/dialog/vnewfiledialog.h
index 2eeb8370..496e90df 100644
--- a/src/dialog/vnewfiledialog.h
+++ b/src/dialog/vnewfiledialog.h
@@ -4,7 +4,7 @@
#include
class QLabel;
-class QLineEdit;
+class VLineEdit;
class QDialogButtonBox;
class QCheckBox;
class VDirectory;
@@ -27,7 +27,7 @@ private slots:
private:
void setupUI();
- QLineEdit *nameEdit;
+ VLineEdit *m_nameEdit;
QCheckBox *m_insertTitleCB;
QPushButton *okBtn;
diff --git a/src/dialog/vnewnotebookdialog.cpp b/src/dialog/vnewnotebookdialog.cpp
index b9856c11..f211bdc0 100644
--- a/src/dialog/vnewnotebookdialog.cpp
+++ b/src/dialog/vnewnotebookdialog.cpp
@@ -4,6 +4,7 @@
#include "vconfigmanager.h"
#include "utils/vutils.h"
#include "vnotebook.h"
+#include "vlineedit.h"
extern VConfigManager *g_config;
@@ -18,7 +19,7 @@ VNewNotebookDialog::VNewNotebookDialog(const QString &title, const QString &info
{
setupUI(title, info);
- connect(nameEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
+ connect(m_nameEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
connect(pathEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
connect(browseBtn, &QPushButton::clicked, this, &VNewNotebookDialog::handleBrowseBtnClicked);
@@ -34,8 +35,11 @@ void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
}
QLabel *nameLabel = new QLabel(tr("Notebook &name:"));
- nameEdit = new QLineEdit(defaultName);
- nameLabel->setBuddy(nameEdit);
+ m_nameEdit = new VLineEdit(defaultName);
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
+ m_nameEdit);
+ m_nameEdit->setValidator(validator);
+ nameLabel->setBuddy(m_nameEdit);
QLabel *pathLabel = new QLabel(tr("Notebook &root folder:"));
pathEdit = new QLineEdit(defaultPath);
@@ -50,7 +54,7 @@ void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
m_imageFolderEdit->setToolTip(tr("Set the name of the folder to hold images of all the notes in this notebook "
"(empty to use global configuration)"));
imageFolderLabel->setToolTip(m_imageFolderEdit->toolTip());
- QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
+ validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
m_imageFolderEdit->setValidator(validator);
QLabel *attachmentFolderLabel = new QLabel(tr("&Attachment folder:"));
@@ -66,7 +70,7 @@ void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
QGridLayout *topLayout = new QGridLayout();
topLayout->addWidget(nameLabel, 0, 0);
- topLayout->addWidget(nameEdit, 0, 1, 1, 2);
+ topLayout->addWidget(m_nameEdit, 0, 1, 1, 2);
topLayout->addWidget(pathLabel, 1, 0);
topLayout->addWidget(pathEdit, 1, 1);
topLayout->addWidget(browseBtn, 1, 2);
@@ -104,7 +108,7 @@ void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
QString VNewNotebookDialog::getNameInput() const
{
- return nameEdit->text();
+ return m_nameEdit->getEvaluatedText();
}
QString VNewNotebookDialog::getPathInput() const
@@ -161,7 +165,7 @@ bool VNewNotebookDialog::isImportExistingNotebook() const
void VNewNotebookDialog::showEvent(QShowEvent *event)
{
- nameEdit->setFocus();
+ m_nameEdit->setFocus();
QDialog::showEvent(event);
}
@@ -183,7 +187,7 @@ void VNewNotebookDialog::handleInputChanged()
m_manualPath = true;
}
- if (nameEdit->isModified()) {
+ if (m_nameEdit->isModified()) {
m_manualName = true;
}
@@ -248,7 +252,7 @@ void VNewNotebookDialog::handleInputChanged()
}
}
- QString name = nameEdit->text();
+ QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty();
if (pathOk && nameOk) {
// Check if the name conflicts with existing notebook name.
@@ -260,13 +264,29 @@ void VNewNotebookDialog::handleInputChanged()
}
}
+ QString warnText;
if (idx < m_notebooks.size()) {
nameOk = false;
+ warnText = tr("WARNING: "
+ "Name (case-insensitive) %3 already exists. "
+ "Please choose another name.")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ } else if (!VUtils::checkFileNameLegal(name)) {
+ // Check if evaluated name contains illegal characters.
+ nameOk = false;
+ warnText = tr("WARNING: "
+ "Name %3 contains illegal characters "
+ "(after magic word evaluation).")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ }
+
+ if (!nameOk) {
showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
- "Please choose another name.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ m_warnLabel->setText(warnText);
}
}
@@ -281,6 +301,8 @@ void VNewNotebookDialog::handleInputChanged()
bool VNewNotebookDialog::autoComplete()
{
+ QString nameText = m_nameEdit->getEvaluatedText();
+
if (m_manualPath) {
if (m_manualName) {
return false;
@@ -290,8 +312,8 @@ bool VNewNotebookDialog::autoComplete()
QString pathText = pathEdit->text();
if (!pathText.isEmpty()) {
QString autoName = VUtils::directoryNameFromPath(pathText);
- if (autoName != nameEdit->text()) {
- nameEdit->setText(autoName);
+ if (autoName != nameText) {
+ m_nameEdit->setText(autoName);
return true;
}
}
@@ -307,7 +329,6 @@ bool VNewNotebookDialog::autoComplete()
}
bool ret = false;
- QString nameText = nameEdit->text();
if (nameText.isEmpty()) {
if (m_manualName) {
return false;
@@ -316,7 +337,7 @@ bool VNewNotebookDialog::autoComplete()
// Get a folder name under vnoteFolder and set it as the name of the notebook.
QString name = "vnotebook";
name = VUtils::getDirNameWithSequence(vnoteFolder, name);
- nameEdit->setText(name);
+ m_nameEdit->setText(name);
ret = true;
} else {
// Use the name as the folder name under vnoteFolder.
diff --git a/src/dialog/vnewnotebookdialog.h b/src/dialog/vnewnotebookdialog.h
index d1f38105..87d0d9b8 100644
--- a/src/dialog/vnewnotebookdialog.h
+++ b/src/dialog/vnewnotebookdialog.h
@@ -6,6 +6,7 @@
class QLabel;
class QLineEdit;
+class VLineEdit;
class QPushButton;
class QDialogButtonBox;
class VNotebook;
@@ -52,7 +53,7 @@ private:
// Returns true if name or path is modified.
bool autoComplete();
- QLineEdit *nameEdit;
+ VLineEdit *m_nameEdit;
QLineEdit *pathEdit;
QPushButton *browseBtn;
QLabel *m_warnLabel;
diff --git a/src/dialog/vnotebookinfodialog.cpp b/src/dialog/vnotebookinfodialog.cpp
index d52eec1d..f7e41eb2 100644
--- a/src/dialog/vnotebookinfodialog.cpp
+++ b/src/dialog/vnotebookinfodialog.cpp
@@ -3,6 +3,7 @@
#include "vnotebook.h"
#include "utils/vutils.h"
#include "vconfigmanager.h"
+#include "vlineedit.h"
extern VConfigManager *g_config;
@@ -29,7 +30,10 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
infoLabel = new QLabel(p_info);
}
- m_nameEdit = new QLineEdit(m_notebook->getName());
+ m_nameEdit = new VLineEdit(m_notebook->getName());
+ QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp),
+ m_nameEdit);
+ m_nameEdit->setValidator(validator);
m_nameEdit->selectAll();
m_pathEdit = new QLineEdit(m_notebook->getPath());
@@ -41,7 +45,7 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
.arg(g_config->getImageFolder()));
m_imageFolderEdit->setToolTip(tr("Set the name of the folder to hold images of all the notes in this notebook "
"(empty to use global configuration)"));
- QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
+ validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
m_imageFolderEdit->setValidator(validator);
// Attachment folder.
@@ -98,7 +102,7 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
void VNotebookInfoDialog::handleInputChanged()
{
- QString name = m_nameEdit->text();
+ QString name = m_nameEdit->getEvaluatedText();
bool nameOk = !name.isEmpty();
bool showWarnLabel = false;
@@ -112,13 +116,29 @@ void VNotebookInfoDialog::handleInputChanged()
}
}
+ QString warnText;
if (idx < m_notebooks.size() && m_notebooks[idx] != m_notebook) {
nameOk = false;
+ warnText = tr("WARNING: "
+ "Name (case-insensitive) %3 already exists. "
+ "Please choose another name.")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ } else if (!VUtils::checkFileNameLegal(name)) {
+ // Check if evaluated name contains illegal characters.
+ nameOk = false;
+ warnText = tr("WARNING: "
+ "Name %3 contains illegal characters "
+ "(after magic word evaluation).")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(name);
+ }
+
+ if (!nameOk) {
showWarnLabel = true;
- QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
- "Please choose another name.")
- .arg(g_config->c_warningTextStyle);
- m_warnLabel->setText(nameConflictText);
+ m_warnLabel->setText(warnText);
}
}
@@ -130,7 +150,7 @@ void VNotebookInfoDialog::handleInputChanged()
QString VNotebookInfoDialog::getName() const
{
- return m_nameEdit->text();
+ return m_nameEdit->getEvaluatedText();
}
QString VNotebookInfoDialog::getImageFolder() const
diff --git a/src/dialog/vnotebookinfodialog.h b/src/dialog/vnotebookinfodialog.h
index 68d02fbd..d1b4c234 100644
--- a/src/dialog/vnotebookinfodialog.h
+++ b/src/dialog/vnotebookinfodialog.h
@@ -6,6 +6,7 @@
class QLabel;
class QLineEdit;
+class VLineEdit;
class QDialogButtonBox;
class QString;
class VNotebook;
@@ -38,7 +39,7 @@ private:
const VNotebook *m_notebook;
- QLineEdit *m_nameEdit;
+ VLineEdit *m_nameEdit;
QLineEdit *m_pathEdit;
QLineEdit *m_imageFolderEdit;
// Read-only.
diff --git a/src/dialog/vorphanfileinfodialog.cpp b/src/dialog/vorphanfileinfodialog.cpp
index 579865d6..aa9a48af 100644
--- a/src/dialog/vorphanfileinfodialog.cpp
+++ b/src/dialog/vorphanfileinfodialog.cpp
@@ -25,7 +25,6 @@ void VOrphanFileInfoDialog::setupUI()
QLabel *fileLabel = new QLabel(m_file->fetchPath());
topLayout->addRow(tr("File:"), fileLabel);
- QLabel *imageFolderLabel = new QLabel(tr("Image folder:"));
m_imageFolderEdit = new QLineEdit(m_file->getImageFolder());
m_imageFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
.arg(g_config->getImageFolderExt()));
@@ -33,9 +32,8 @@ void VOrphanFileInfoDialog::setupUI()
"of this file.\nIf absolute path is used, "
"VNote will not manage those images."
"(empty to use global configuration)");
- imageFolderLabel->setToolTip(imgFolderTip);
m_imageFolderEdit->setToolTip(imgFolderTip);
- topLayout->addRow(imageFolderLabel, m_imageFolderEdit);
+ topLayout->addRow(tr("&Image folder:"), m_imageFolderEdit);
// Ok is the default button.
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
diff --git a/src/src.pro b/src/src.pro
index f575f9d1..8831b063 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -78,7 +78,9 @@ SOURCES += main.cpp\
vattachmentlist.cpp \
dialog/vsortdialog.cpp \
vfilesessioninfo.cpp \
- vtableofcontent.cpp
+ vtableofcontent.cpp \
+ utils/vmetawordmanager.cpp \
+ vlineedit.cpp
HEADERS += vmainwindow.h \
vdirectorytree.h \
@@ -144,7 +146,9 @@ HEADERS += vmainwindow.h \
vattachmentlist.h \
dialog/vsortdialog.h \
vfilesessioninfo.h \
- vtableofcontent.h
+ vtableofcontent.h \
+ utils/vmetawordmanager.h \
+ vlineedit.h
RESOURCES += \
vnote.qrc \
diff --git a/src/utils/vmetawordmanager.cpp b/src/utils/vmetawordmanager.cpp
new file mode 100644
index 00000000..d32efb2f
--- /dev/null
+++ b/src/utils/vmetawordmanager.cpp
@@ -0,0 +1,593 @@
+#include "vmetawordmanager.h"
+
+#include
+#include
+#include
+#include
+
+#include "vconfigmanager.h"
+
+extern VConfigManager *g_config;
+
+
+// Used as the function template for some date/time related meta words.
+static QString formattedDateTime(const VMetaWord *p_metaWord,
+ const QString &p_format)
+{
+ return p_metaWord->getManager()->getDateTime().toString(p_format);
+}
+
+static QString allMetaWordsInfo(const VMetaWord *p_metaWord)
+{
+ QString msg = QObject::tr("All magic words:");
+
+ const VMetaWordManager *mgr = p_metaWord->getManager();
+ QList keys = mgr->getAllMetaWords().keys();
+ keys.sort(Qt::CaseInsensitive);
+
+ for (auto const & key : keys) {
+ const VMetaWord *word = mgr->findMetaWord(key);
+ Q_ASSERT(word);
+ msg += QString("\n%1:\t%2").arg(word->getWord()).arg(word->getDefinition());
+ }
+
+ QWidget *focusWid = QApplication::focusWidget();
+ if (focusWid) {
+ QPoint pos = focusWid->mapToGlobal(QPoint(0, focusWid->height()));
+ QToolTip::showText(pos, msg, focusWid);
+ }
+
+ // Just return the same word.
+ return QString("%1help%1").arg(VMetaWordManager::c_delimiter);
+}
+
+const QChar VMetaWordManager::c_delimiter = '%';
+
+VMetaWordManager::VMetaWordManager(QObject *p_parent)
+ : QObject(p_parent)
+{
+}
+
+void VMetaWordManager::init()
+{
+ using namespace std::placeholders;
+
+ // %d%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "d",
+ tr("the day as number without a leading zero (`1` to `31`)"),
+ std::bind(formattedDateTime, _1, "d"));
+
+ // %dd%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "dd",
+ tr("the day as number with a leading zero (`01` to `31`)"),
+ std::bind(formattedDateTime, _1, "dd"));
+
+ // %ddd%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "ddd",
+ tr("the abbreviated localized day name (e.g. `Mon` to `Sun`)"),
+ std::bind(formattedDateTime, _1, "ddd"));
+
+ // %dddd%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "dddd",
+ tr("the long localized day name (e.g. `Monday` to `Sunday`)"),
+ std::bind(formattedDateTime, _1, "dddd"));
+
+ // %M%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "M",
+ tr("the month as number without a leading zero (`1` to `12`)"),
+ std::bind(formattedDateTime, _1, "M"));
+
+ // %MM%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "MM",
+ tr("the month as number with a leading zero (`01` to `12`)"),
+ std::bind(formattedDateTime, _1, "MM"));
+
+ // %MMM%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "MMM",
+ tr("the abbreviated localized month name (e.g. `Jan` to `Dec`)"),
+ std::bind(formattedDateTime, _1, "MMM"));
+
+ // %MMMM%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "MMMM",
+ tr("the long localized month name (e.g. `January` to `December`"),
+ std::bind(formattedDateTime, _1, "MMMM"));
+
+ // %yy%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "yy",
+ tr("the year as two digit number (`00` to `99`)"),
+ std::bind(formattedDateTime, _1, "yy"));
+
+ // %yyyy%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "yyyy",
+ tr("the year as four digit number"),
+ std::bind(formattedDateTime, _1, "yyyy"));
+
+ // %h%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "h",
+ tr("the hour without a leading zero (`0` to `23` or `1` to `12` if AM/PM display"),
+ std::bind(formattedDateTime, _1, "h"));
+
+ // %hh%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "hh",
+ tr("the hour with a leading zero (`00` to `23` or `01` to `12` if AM/PM display"),
+ std::bind(formattedDateTime, _1, "hh"));
+
+ // %H%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "H",
+ tr("the hour without a leading zero (`0` to `23` even with AM/PM display"),
+ std::bind(formattedDateTime, _1, "H"));
+
+ // %HH%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "HH",
+ tr("the hour with a leading zero (`00` to `23` even with AM/PM display"),
+ std::bind(formattedDateTime, _1, "HH"));
+
+ // %m%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "m",
+ tr("the minute without a leading zero (`0` to `59`)"),
+ std::bind(formattedDateTime, _1, "m"));
+
+ // %mm%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "mm",
+ tr("the minute with a leading zero (`00` to `59`)"),
+ std::bind(formattedDateTime, _1, "mm"));
+
+ // %s%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "s",
+ tr("the second without a leading zero (`0` to `59`)"),
+ std::bind(formattedDateTime, _1, "s"));
+
+ // %ss%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "ss",
+ tr("the second with a leading zero (`00` to `59`)"),
+ std::bind(formattedDateTime, _1, "ss"));
+
+ // %z%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "z",
+ tr("the milliseconds without leading zeroes (`0` to `999`)"),
+ std::bind(formattedDateTime, _1, "z"));
+
+ // %zzz%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "zzz",
+ tr("the milliseconds with leading zeroes (`000` to `999`)"),
+ std::bind(formattedDateTime, _1, "zzz"));
+
+ // %AP%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "AP",
+ tr("use AM/PM display (`AM` or `PM`)"),
+ std::bind(formattedDateTime, _1, "AP"));
+
+ // %A%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "A",
+ tr("use AM/PM display (`AM` or `PM`)"),
+ std::bind(formattedDateTime, _1, "A"));
+
+ // %ap%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "ap",
+ tr("use am/pm display (`am` or `pm`)"),
+ std::bind(formattedDateTime, _1, "ap"));
+
+ // %a%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "a",
+ tr("use am/pm display (`am` or `pm`)"),
+ std::bind(formattedDateTime, _1, "a"));
+
+ // %t%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "t",
+ tr("the timezone (e.g. `CEST`)"),
+ std::bind(formattedDateTime, _1, "t"));
+
+ // %random%.
+ addMetaWord(MetaWordType::FunctionBased,
+ "random",
+ tr("a random number"),
+ [](const VMetaWord *) {
+ return QString::number(qrand());
+ });
+
+ // %random_d%.
+ addMetaWord(MetaWordType::Dynamic,
+ "random_d",
+ tr("dynamic version of `random`"),
+ [](const VMetaWord *) {
+ return QString::number(qrand());
+ });
+
+ // %date%.
+ addMetaWord(MetaWordType::Compound,
+ "date",
+ QString("%1yyyy%1-%1MM%1-%1dd%1").arg(c_delimiter));
+
+ // %da%.
+ addMetaWord(MetaWordType::Compound,
+ "da",
+ QString("%1yyyy%1%1MM%1%1dd%1").arg(c_delimiter));
+
+ // %time%.
+ addMetaWord(MetaWordType::Compound,
+ "time",
+ QString("%1hh%1:%1mm%1:%1ss%1").arg(c_delimiter));
+
+ // %datetime%.
+ addMetaWord(MetaWordType::Compound,
+ "datetime",
+ QString("%1date%1 %1time%1").arg(c_delimiter));
+
+ // Custom meta words.
+ initCustomMetaWords();
+
+ // %help% to print all metaword info.
+ addMetaWord(MetaWordType::FunctionBased,
+ "help",
+ tr("information about all defined magic words"),
+ allMetaWordsInfo);
+}
+
+void VMetaWordManager::initCustomMetaWords()
+{
+ QVector words = g_config->getCustomMagicWords();
+ for (auto const & item : words) {
+ addMetaWord(MetaWordType::Compound,
+ item.m_name,
+ item.m_definition);
+ }
+}
+
+QString VMetaWordManager::evaluate(const QString &p_text) const
+{
+ if (p_text.isEmpty()) {
+ return p_text;
+ }
+
+ // Update datetime for later parse.
+ const_cast(this)->m_dateTime = QDateTime::currentDateTime();
+
+ // Treat the text as a Compound meta word.
+ const QString tmpWord("vnote_tmp_metaword");
+ Q_ASSERT(!contains(tmpWord));
+ VMetaWord metaWord(this,
+ MetaWordType::Compound,
+ tmpWord,
+ p_text);
+ if (metaWord.isValid()) {
+ return metaWord.evaluate();
+ } else {
+ return p_text;
+ }
+}
+
+bool VMetaWordManager::contains(const QString &p_word) const
+{
+ return m_metaWords.contains(p_word);
+}
+
+const VMetaWord *VMetaWordManager::findMetaWord(const QString &p_word) const
+{
+ auto it = m_metaWords.find(p_word);
+ if (it != m_metaWords.end()) {
+ return &it.value();
+ }
+
+ return NULL;
+}
+
+void VMetaWordManager::addMetaWord(MetaWordType p_type,
+ const QString &p_word,
+ const QString &p_definition,
+ MetaWordFunc p_function)
+{
+ if (p_word.isEmpty() || contains(p_word)) {
+ return;
+ }
+
+ VMetaWord metaWord(this,
+ p_type,
+ p_word,
+ p_definition,
+ p_function);
+
+ if (metaWord.isValid()) {
+ m_metaWords.insert(p_word, metaWord);
+ qDebug() << QString("MetaWord %1%2%1[%3] added")
+ .arg(c_delimiter).arg(p_word).arg(p_definition);
+ }
+}
+
+VMetaWord::VMetaWord(const VMetaWordManager *p_manager,
+ MetaWordType p_type,
+ const QString &p_word,
+ const QString &p_definition,
+ MetaWordFunc p_function)
+ : m_manager(p_manager),
+ m_type(p_type),
+ m_word(p_word),
+ m_definition(p_definition),
+ m_valid(false)
+{
+ m_function = p_function;
+
+ if (checkType(MetaWordType::Simple)
+ || checkType(MetaWordType::Compound)) {
+ Q_ASSERT(!m_function);
+ } else {
+ Q_ASSERT(m_function);
+ }
+
+ checkAndParseDefinition();
+}
+
+bool VMetaWord::checkType(MetaWordType p_type)
+{
+ return m_type == p_type;
+}
+
+void VMetaWord::checkAndParseDefinition()
+{
+ if (m_word.contains(VMetaWordManager::c_delimiter)) {
+ m_valid = false;
+ return;
+ }
+
+ m_valid = true;
+ m_tokens.clear();
+
+ // We do not accept \n and \t in the definition.
+ QRegExp defReg("[\\S ]*");
+ if (!defReg.exactMatch(m_definition)) {
+ m_valid = false;
+ return;
+ }
+
+ if (checkType(MetaWordType::FunctionBased)
+ || checkType(MetaWordType::Dynamic)) {
+ if (!m_function) {
+ m_valid = false;
+ }
+ } else if (checkType(MetaWordType::Compound)) {
+ m_tokens = parseToTokens(m_definition);
+ if (m_tokens.isEmpty()) {
+ m_valid = false;
+ return;
+ }
+
+ for (auto const & to : m_tokens) {
+ if (to.isMetaWord()) {
+ if (!m_manager->contains(to.m_value)) {
+ // Dependency not defined.
+ m_valid = false;
+ break;
+ }
+ }
+ }
+ }
+}
+
+bool VMetaWord::isValid() const
+{
+ return m_valid;
+}
+
+QString VMetaWord::evaluate() const
+{
+ Q_ASSERT(m_valid);
+ qDebug() << "evaluate meta word" << m_word;
+ switch (m_type) {
+ case MetaWordType::Simple:
+ return m_definition;
+
+ case MetaWordType::FunctionBased:
+ case MetaWordType::Dynamic:
+ return m_function(this);
+
+ case MetaWordType::Compound:
+ {
+ QHash cache;
+ return evaluateTokens(m_tokens, cache);
+ }
+
+ default:
+ return "";
+ }
+}
+
+MetaWordType VMetaWord::getType() const
+{
+ return m_type;
+}
+
+const QString &VMetaWord::getWord() const
+{
+ return m_word;
+}
+
+const QString &VMetaWord::getDefinition() const
+{
+ return m_definition;
+}
+
+const VMetaWordManager *VMetaWord::getManager() const
+{
+ return m_manager;
+}
+
+
+QString VMetaWord::toString() const
+{
+ QChar typeChar('U');
+ switch (m_type) {
+ case MetaWordType::Simple:
+ typeChar = 'S';
+ break;
+
+ case MetaWordType::FunctionBased:
+ typeChar = 'F';
+ break;
+
+ case MetaWordType::Dynamic:
+ typeChar = 'D';
+ break;
+
+ case MetaWordType::Compound:
+ typeChar = 'C';
+ break;
+
+ default:
+ break;
+ }
+
+ return QString("%1%2%1[%3]: %4").arg(VMetaWordManager::c_delimiter)
+ .arg(m_word)
+ .arg(typeChar)
+ .arg(m_definition);
+}
+
+QVector VMetaWord::parseToTokens(const QString &p_text)
+{
+ QVector tokens;
+
+ TokenType type = TokenType::Raw;
+ QString value;
+ value.reserve(p_text.size());
+ for (int idx = 0; idx < p_text.size(); ++idx) {
+ const QChar &ch = p_text[idx];
+ if (ch == VMetaWordManager::c_delimiter) {
+ // Check if it is single or double.
+ int next = idx + 1;
+ if (next == p_text.size()
+ || p_text[next] != VMetaWordManager::c_delimiter) {
+ // Single delimiter.
+ if (type == TokenType::Raw) {
+ // End of a raw token, begin of a MetaWord token.
+ if (!value.isEmpty()) {
+ tokens.push_back(Token(type, value));
+ }
+
+ type = TokenType::MetaWord;
+ } else {
+ // End of a MetaWord token, begin of a Raw token.
+ Q_ASSERT(!value.isEmpty());
+
+ tokens.push_back(Token(type, value));
+ type = TokenType::Raw;
+ }
+
+ value.clear();
+ } else {
+ // Double delimiter.
+ // If now is parsing a MetaWord token, treat the first delimiter
+ // as the end of a token.
+ // Otherwise, store one single delimiter in value and skip next char.
+ if (type == TokenType::MetaWord) {
+ Q_ASSERT(!value.isEmpty());
+ tokens.push_back(Token(type, value));
+ type = TokenType::Raw;
+ value.clear();
+ } else {
+ value.push_back(ch);
+ ++idx;
+ }
+ }
+ } else {
+ // Push ch in value.
+ value.push_back(ch);
+ }
+ }
+
+ if (!value.isEmpty()) {
+ if (type == TokenType::Raw) {
+ tokens.push_back(Token(type, value));
+ } else {
+ // An imcomplete metaword token.
+ // Treat it as raw.
+ tokens.push_back(Token(TokenType::Raw, "%" + value));
+ }
+
+ value.clear();
+ }
+
+ return tokens;
+}
+
+QString VMetaWord::evaluateTokens(const QVector &p_tokens,
+ QHash &p_cache) const
+{
+ QString val;
+
+ for (auto const & to : p_tokens) {
+ switch (to.m_type) {
+ case TokenType::Raw:
+ val += to.m_value;
+ break;
+
+ case TokenType::MetaWord:
+ {
+ const VMetaWord *metaWord = m_manager->findMetaWord(to.m_value);
+ if (!metaWord) {
+ // Invalid meta word. Treat it as literal value.
+ val += VMetaWordManager::c_delimiter + to.m_value + VMetaWordManager::c_delimiter;
+ break;
+ }
+
+ QString wordVal;
+ switch (metaWord->getType()) {
+ case MetaWordType::FunctionBased:
+ {
+ auto it = p_cache.find(metaWord->getWord());
+ if (it != p_cache.end()) {
+ // Find it in the cache.
+ wordVal = it.value();
+ } else {
+ // First evaluate this meta word.
+ wordVal = metaWord->evaluate();
+ p_cache.insert(metaWord->getWord(), wordVal);
+ }
+
+ break;
+ }
+
+ case MetaWordType::Compound:
+ wordVal = evaluateTokens(metaWord->m_tokens, p_cache);
+ break;
+
+ default:
+ wordVal = metaWord->evaluate();
+ break;
+ }
+
+ val += wordVal;
+ break;
+ }
+
+ default:
+ Q_ASSERT(false);
+ break;
+ }
+ }
+
+ return val;
+}
diff --git a/src/utils/vmetawordmanager.h b/src/utils/vmetawordmanager.h
new file mode 100644
index 00000000..abfb642a
--- /dev/null
+++ b/src/utils/vmetawordmanager.h
@@ -0,0 +1,202 @@
+#ifndef VMETAWORDMANAGER_H
+#define VMETAWORDMANAGER_H
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+
+enum class MetaWordType
+{
+ // Definition is plain text.
+ Simple = 0,
+
+ // Definition is a function call to get the value.
+ FunctionBased,
+
+ // Like FunctionBased, but should re-evaluate the value for each occurence.
+ Dynamic,
+
+ // Consists of other meta words.
+ Compound,
+
+ Invalid
+};
+
+// We call meta word "magic word" in user interaction.
+struct VMagicWord
+{
+ QString m_name;
+ QString m_definition;
+};
+
+class VMetaWordManager;
+class VMetaWord;
+
+typedef std::function MetaWordFunc;
+
+// A Meta Word is surrounded by %.
+// - Use %% for an escaped %;
+// - Built-in or user-defined;
+// - A meta word could contain other meta words as definition.
+class VMetaWord
+{
+public:
+ VMetaWord(const VMetaWordManager *p_manager,
+ MetaWordType p_type,
+ const QString &p_word,
+ const QString &p_definition,
+ MetaWordFunc p_function = nullptr);
+
+ bool isValid() const;
+
+ QString evaluate() const;
+
+ MetaWordType getType() const;
+
+ const QString &getWord() const;
+
+ const QString &getDefinition() const;
+
+ const VMetaWordManager *getManager() const;
+
+ QString toString() const;
+
+ enum class TokenType
+ {
+ Raw = 0,
+ MetaWord
+ };
+
+ struct Token
+ {
+ Token()
+ : m_type(TokenType::Raw)
+ {
+ }
+
+ Token(VMetaWord::TokenType p_type, const QString &p_value)
+ : m_type(p_type),
+ m_value(p_value)
+ {
+ }
+
+ QString toString() const
+ {
+ return QString("token %1[%2]").arg(m_type == TokenType::Raw
+ ? "Raw" : "MetaWord")
+ .arg(m_value);
+ }
+
+ bool isRaw() const
+ {
+ return m_type == TokenType::Raw;
+ }
+
+ bool isMetaWord() const
+ {
+ return m_type == TokenType::MetaWord;
+ }
+
+ TokenType m_type;
+
+ // For Raw type, m_value is the raw string of this token;
+ // For MetaWord type, m_value is the word of the meta word pointed to by
+ // this token.
+ QString m_value;
+ };
+
+private:
+ // Check if m_type is @p_type.
+ bool checkType(MetaWordType p_type);
+
+ // Parse children word from definition.
+ // Children word MUST be defined in m_manager already.
+ void checkAndParseDefinition();
+
+ // Parse @p_text to a list of tokens.
+ static QVector parseToTokens(const QString &p_text);
+
+ // Used for Compound meta word with cache @p_cache.
+ // @p_cache: value cache for FunctionBased Token.
+ // For a meta word occuring more than once, we should evaluate it once for
+ // FunctionBased meta word.
+ QString evaluateTokens(const QVector &p_tokens,
+ QHash &p_cache) const;
+
+ const VMetaWordManager *m_manager;
+
+ MetaWordType m_type;
+
+ // Word could contains spaces but no %.
+ QString m_word;
+
+ // For Simple/Compound meta word, this contains the definition;
+ // For FunctionBased/Dynamic meta word, this makes no sense and is used
+ // for description.
+ QString m_definition;
+
+ // For FunctionBased and Dynamic meta word.
+ MetaWordFunc m_function;
+
+ bool m_valid;
+
+ // Tokens used for Compound meta word.
+ QVector m_tokens;
+};
+
+
+// Manager of meta word.
+class VMetaWordManager : public QObject
+{
+ Q_OBJECT
+public:
+ explicit VMetaWordManager(QObject *p_parent = nullptr);
+
+ void init();
+
+ // Expand meta words in @p_text and return the expanded text.
+ QString evaluate(const QString &p_text) const;
+
+ const VMetaWord *findMetaWord(const QString &p_word) const;
+
+ bool contains(const QString &p_word) const;
+
+ const QDateTime &getDateTime() const;
+
+ const QHash &getAllMetaWords() const;
+
+ // % by default.
+ static const QChar c_delimiter;
+
+private:
+ void addMetaWord(MetaWordType p_type,
+ const QString &p_word,
+ const QString &p_definition,
+ MetaWordFunc p_function = nullptr);
+
+ void initCustomMetaWords();
+
+ // Map using word as key.
+ QHash m_metaWords;
+
+ // Used for data/time related evaluate.
+ // Will be updated before each evaluation.
+ QDateTime m_dateTime;
+};
+
+inline const QDateTime &VMetaWordManager::getDateTime() const
+{
+ return m_dateTime;
+}
+
+inline const QHash &VMetaWordManager::getAllMetaWords() const
+{
+ return m_metaWords;
+}
+
+#endif // VMETAWORDMANAGER_H
diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp
index 047bff19..95f11136 100644
--- a/src/utils/vutils.cpp
+++ b/src/utils/vutils.cpp
@@ -33,6 +33,8 @@ QVector> VUtils::s_availableLanguages;
const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(([^\\)\"]+)\\s*(\"(\\\\.|[^\"\\)])*\")?\\s*\\)");
+const QString VUtils::c_imageTitleRegExp = QString("[\\w\\(\\)@#%\\*\\-\\+=\\?<>\\,\\.\\s]+");
+
const QString VUtils::c_fileNameRegExp = QString("[^\\\\/:\\*\\?\"<>\\|]*");
const QString VUtils::c_fencedCodeBlockStartRegExp = QString("^(\\s*)```([^`\\s]*)\\s*[^`]*$");
@@ -725,6 +727,16 @@ bool VUtils::checkPathLegal(const QString &p_path)
return ret;
}
+bool VUtils::checkFileNameLegal(const QString &p_name)
+{
+ if (p_name.isEmpty()) {
+ return false;
+ }
+
+ QRegExp exp(c_fileNameRegExp);
+ return exp.exactMatch(p_name);
+}
+
bool VUtils::equalPath(const QString &p_patha, const QString &p_pathb)
{
QString a = QDir::cleanPath(p_patha);
diff --git a/src/utils/vutils.h b/src/utils/vutils.h
index de748473..e21a580f 100644
--- a/src/utils/vutils.h
+++ b/src/utils/vutils.h
@@ -177,6 +177,9 @@ public:
// Try to check if @p_path is legal.
static bool checkPathLegal(const QString &p_path);
+ // Check if file/folder name is legal.
+ static bool checkFileNameLegal(const QString &p_name);
+
// Returns true if @p_patha and @p_pathb points to the same file/directory.
static bool equalPath(const QString &p_patha, const QString &p_pathb);
@@ -248,6 +251,9 @@ public:
// 4. Unused;
static const QString c_imageLinkRegExp;
+ // Regular expression for image title.
+ static const QString c_imageTitleRegExp;
+
// Regular expression for file/directory name.
// Forbidden char: \/:*?"<>|
static const QString c_fileNameRegExp;
diff --git a/src/vattachmentlist.cpp b/src/vattachmentlist.cpp
index 240fa899..8c44044b 100644
--- a/src/vattachmentlist.cpp
+++ b/src/vattachmentlist.cpp
@@ -5,13 +5,12 @@
#include "vconfigmanager.h"
#include "utils/vutils.h"
#include "vbuttonwithwidget.h"
-#include "vnote.h"
#include "vmainwindow.h"
#include "dialog/vconfirmdeletiondialog.h"
#include "dialog/vsortdialog.h"
extern VConfigManager *g_config;
-extern VNote *g_vnote;
+extern VMainWindow *g_mainWin;
VAttachmentList::VAttachmentList(QWidget *p_parent)
: QWidget(p_parent), m_file(NULL)
@@ -53,7 +52,7 @@ void VAttachmentList::setupUI()
.arg(m_file->fetchAttachmentFolderPath()),
QMessageBox::Ok | QMessageBox::Cancel,
QMessageBox::Ok,
- g_vnote->getMainWindow(),
+ g_mainWin,
MessageBoxType::Danger);
if (ret == QMessageBox::Ok) {
if (!m_file->deleteAttachments()) {
@@ -66,7 +65,7 @@ void VAttachmentList::setupUI()
"maintain the configuration file manually."),
QMessageBox::Ok,
QMessageBox::Ok,
- g_vnote->getMainWindow());
+ g_mainWin);
}
m_attachmentList->clear();
@@ -204,7 +203,7 @@ void VAttachmentList::addAttachment()
}
static QString lastPath = QDir::homePath();
- QStringList files = QFileDialog::getOpenFileNames(g_vnote->getMainWindow(),
+ QStringList files = QFileDialog::getOpenFileNames(g_mainWin,
tr("Select Files As Attachments"),
lastPath);
if (files.isEmpty()) {
@@ -236,16 +235,16 @@ void VAttachmentList::addAttachments(const QStringList &p_files)
"",
QMessageBox::Ok,
QMessageBox::Ok,
- g_vnote->getMainWindow());
+ g_mainWin);
} else {
++addedFiles;
}
}
if (addedFiles > 0) {
- g_vnote->getMainWindow()->showStatusMessage(tr("%1 %2 added as attachments")
- .arg(addedFiles)
- .arg(addedFiles > 1 ? tr("files") : tr("file")));
+ g_mainWin->showStatusMessage(tr("%1 %2 added as attachments")
+ .arg(addedFiles)
+ .arg(addedFiles > 1 ? tr("files") : tr("file")));
}
}
@@ -330,7 +329,7 @@ void VAttachmentList::deleteSelectedItems()
false,
false,
false,
- g_vnote->getMainWindow());
+ g_mainWin);
if (dialog.exec()) {
items = dialog.getConfirmedItems();
@@ -349,7 +348,7 @@ void VAttachmentList::deleteSelectedItems()
"maintain the configuration file manually."),
QMessageBox::Ok,
QMessageBox::Ok,
- g_vnote->getMainWindow());
+ g_mainWin);
}
updateButtonState();
@@ -370,7 +369,7 @@ void VAttachmentList::sortItems()
"in the configuration file.")
.arg(g_config->c_dataTextStyle)
.arg(m_file->getName()),
- g_vnote->getMainWindow());
+ g_mainWin);
QTreeWidget *tree = dialog.getTreeWidget();
tree->clear();
tree->setColumnCount(1);
@@ -624,7 +623,7 @@ void VAttachmentList::checkAttachments()
false,
false,
false,
- g_vnote->getMainWindow());
+ g_mainWin);
if (dialog.exec()) {
items = dialog.getConfirmedItems();
@@ -643,7 +642,7 @@ void VAttachmentList::checkAttachments()
"maintain the configuration file manually."),
QMessageBox::Ok,
QMessageBox::Ok,
- g_vnote->getMainWindow());
+ g_mainWin);
}
updateButtonState();
diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp
index 256e6f2c..66b39882 100644
--- a/src/vconfigmanager.cpp
+++ b/src/vconfigmanager.cpp
@@ -1152,3 +1152,21 @@ void VConfigManager::setLastOpenedFiles(const QVector &p_files
<< "items in [last_opened_files] section";
}
+
+QVector VConfigManager::getCustomMagicWords()
+{
+ QVector words;
+ int size = userSettings->beginReadArray("magic_words");
+ for (int i = 0; i < size; ++i) {
+ userSettings->setArrayIndex(i);
+
+ VMagicWord word;
+ word.m_name = userSettings->value("name").toString();
+ word.m_definition = userSettings->value("definition").toString();
+ words.push_back(word);
+ }
+
+ userSettings->endArray();
+
+ return words;
+}
diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h
index 1d14d43f..7bef0fa7 100644
--- a/src/vconfigmanager.h
+++ b/src/vconfigmanager.h
@@ -12,6 +12,7 @@
#include "vmarkdownconverter.h"
#include "vconstants.h"
#include "vfilesessioninfo.h"
+#include "utils/vmetawordmanager.h"
class QJsonObject;
@@ -326,6 +327,9 @@ public:
// Write last opened files to [last_opened_files] of session.ini.
void setLastOpenedFiles(const QVector &p_files);
+ // Read custom magic words from [magic_words] section.
+ QVector getCustomMagicWords();
+
// Return the configured key sequence of @p_operation.
// Return empty if there is no corresponding config.
QString getShortcutKeySequence(const QString &p_operation) const;
diff --git a/src/veditarea.cpp b/src/veditarea.cpp
index 87ee6255..6100ec14 100644
--- a/src/veditarea.cpp
+++ b/src/veditarea.cpp
@@ -12,9 +12,10 @@
extern VConfigManager *g_config;
extern VNote *g_vnote;
-VEditArea::VEditArea(VNote *vnote, QWidget *parent)
- : QWidget(parent), VNavigationMode(),
- vnote(vnote), curWindowIndex(-1)
+VEditArea::VEditArea(QWidget *parent)
+ : QWidget(parent),
+ VNavigationMode(),
+ curWindowIndex(-1)
{
setupUI();
@@ -66,7 +67,7 @@ void VEditArea::setupUI()
void VEditArea::insertSplitWindow(int idx)
{
- VEditWindow *win = new VEditWindow(vnote, this);
+ VEditWindow *win = new VEditWindow(this);
splitter->insertWidget(idx, win);
connect(win, &VEditWindow::tabStatusUpdated,
this, &VEditArea::handleWindowTabStatusUpdated);
diff --git a/src/veditarea.h b/src/veditarea.h
index dbeb9d8f..6f3f95fa 100644
--- a/src/veditarea.h
+++ b/src/veditarea.h
@@ -14,7 +14,6 @@
#include "veditwindow.h"
#include "vnavigationmode.h"
-class VNote;
class VFile;
class VDirectory;
class VFindReplaceDialog;
@@ -25,7 +24,7 @@ class VEditArea : public QWidget, public VNavigationMode
{
Q_OBJECT
public:
- explicit VEditArea(VNote *vnote, QWidget *parent = 0);
+ explicit VEditArea(QWidget *parent = 0);
// Whether @p_file has been opened in edit area.
bool isFileOpened(const VFile *p_file);
@@ -161,7 +160,6 @@ private:
// Update status of current window.
void updateWindowStatus();
- VNote *vnote;
int curWindowIndex;
// Splitter holding multiple split windows
diff --git a/src/veditwindow.cpp b/src/veditwindow.cpp
index 519c589f..3cd0a281 100644
--- a/src/veditwindow.cpp
+++ b/src/veditwindow.cpp
@@ -2,7 +2,6 @@
#include
#include "veditwindow.h"
#include "vedittab.h"
-#include "vnote.h"
#include "utils/vutils.h"
#include "vorphanfile.h"
#include "vmainwindow.h"
@@ -13,12 +12,14 @@
#include "vfilelist.h"
#include "vconfigmanager.h"
-extern VNote *g_vnote;
extern VConfigManager *g_config;
+extern VMainWindow *g_mainWin;
-VEditWindow::VEditWindow(VNote *vnote, VEditArea *editArea, QWidget *parent)
- : QTabWidget(parent), vnote(vnote), m_editArea(editArea),
- m_curTabWidget(NULL), m_lastTabWidget(NULL)
+VEditWindow::VEditWindow(VEditArea *editArea, QWidget *parent)
+ : QTabWidget(parent),
+ m_editArea(editArea),
+ m_curTabWidget(NULL),
+ m_lastTabWidget(NULL)
{
setAcceptDrops(true);
initTabActions();
@@ -137,9 +138,9 @@ void VEditWindow::initTabActions()
Q_ASSERT(file);
if (file->getType() == FileType::Note) {
VNoteFile *tmpFile = dynamic_cast((VFile *)file);
- g_vnote->getMainWindow()->getFileList()->fileInfo(tmpFile);
+ g_mainWin->getFileList()->fileInfo(tmpFile);
} else if (file->getType() == FileType::Orphan) {
- g_vnote->getMainWindow()->editOrphanFileInfo(file);
+ g_mainWin->editOrphanFileInfo(file);
}
});
@@ -817,7 +818,7 @@ void VEditWindow::handleLocateAct()
VEditTab *editor = getTab(tab);
QPointer file = editor->getFile();
if (file->getType() == FileType::Note) {
- vnote->getMainWindow()->locateFile(file);
+ g_mainWin->locateFile(file);
}
}
@@ -1022,7 +1023,7 @@ void VEditWindow::dropEvent(QDropEvent *p_event)
if (!files.isEmpty()) {
focusWindow();
- g_vnote->getMainWindow()->openFiles(files);
+ g_mainWin->openFiles(files);
}
p_event->acceptProposedAction();
diff --git a/src/veditwindow.h b/src/veditwindow.h
index e0cdd8a8..d7f35ae7 100644
--- a/src/veditwindow.h
+++ b/src/veditwindow.h
@@ -11,7 +11,6 @@
#include "vconstants.h"
#include "vnotefile.h"
-class VNote;
class QPushButton;
class QActionGroup;
class VEditArea;
@@ -20,7 +19,7 @@ class VEditWindow : public QTabWidget
{
Q_OBJECT
public:
- explicit VEditWindow(VNote *vnote, VEditArea *editArea, QWidget *parent = 0);
+ explicit VEditWindow(VEditArea *editArea, QWidget *parent = 0);
int findTabByFile(const VFile *p_file) const;
int openFile(VFile *p_file, OpenFileMode p_mode);
bool closeFile(const VFile *p_file, bool p_forced);
@@ -160,7 +159,6 @@ private:
// Connect the signals of VEditTab to this VEditWindow.
void connectEditTab(const VEditTab *p_tab);
- VNote *vnote;
VEditArea *m_editArea;
// These two members are only used for alternateTab().
diff --git a/src/vlineedit.cpp b/src/vlineedit.cpp
new file mode 100644
index 00000000..7824196c
--- /dev/null
+++ b/src/vlineedit.cpp
@@ -0,0 +1,46 @@
+#include "vlineedit.h"
+
+#include
+#include
+
+#include "utils/vmetawordmanager.h"
+
+
+extern VMetaWordManager *g_mwMgr;
+
+VLineEdit::VLineEdit(QWidget *p_parent)
+ : QLineEdit(p_parent)
+{
+ init();
+}
+
+VLineEdit::VLineEdit(const QString &p_contents, QWidget *p_parent)
+ : QLineEdit(p_contents, p_parent)
+{
+ init();
+}
+
+void VLineEdit::handleTextChanged(const QString &p_text)
+{
+ m_evaluatedText = g_mwMgr->evaluate(p_text);
+ qDebug() << "evaluate text:" << m_evaluatedText;
+
+ if (m_evaluatedText == p_text) {
+ return;
+ }
+
+ // Display tooltip at bottom-left.
+ QPoint pos = mapToGlobal(QPoint(0, height()));
+ QToolTip::showText(pos, m_evaluatedText, this);
+}
+
+void VLineEdit::init()
+{
+ connect(this, &QLineEdit::textChanged,
+ this, &VLineEdit::handleTextChanged);
+}
+
+const QString VLineEdit::getEvaluatedText() const
+{
+ return m_evaluatedText;
+}
diff --git a/src/vlineedit.h b/src/vlineedit.h
new file mode 100644
index 00000000..81a8a86c
--- /dev/null
+++ b/src/vlineedit.h
@@ -0,0 +1,29 @@
+#ifndef VLINEEDIT_H
+#define VLINEEDIT_H
+
+#include
+
+
+// QLineEdit with meta word support.
+class VLineEdit : public QLineEdit
+{
+ Q_OBJECT
+public:
+ explicit VLineEdit(QWidget *p_parent = nullptr);
+
+ VLineEdit(const QString &p_contents, QWidget *p_parent = Q_NULLPTR);
+
+ // Return the evaluated text.
+ const QString getEvaluatedText() const;
+
+private slots:
+ void handleTextChanged(const QString &p_text);
+
+private:
+ void init();
+
+ // We should keep the evaluated text identical with what's displayed.
+ QString m_evaluatedText;
+};
+
+#endif // VLINEEDIT_H
diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp
index 022bea55..f5591724 100644
--- a/src/vmainwindow.cpp
+++ b/src/vmainwindow.cpp
@@ -45,6 +45,8 @@ VMainWindow::VMainWindow(VSingleInstanceGuard *p_guard, QWidget *p_parent)
: QMainWindow(p_parent), m_guard(p_guard),
m_windowOldState(Qt::WindowNoState), m_requestQuit(false)
{
+ qsrand(QDateTime::currentDateTime().toTime_t());
+
setWindowIcon(QIcon(":/resources/icons/vnote.ico"));
vnote = new VNote(this);
g_vnote = vnote;
@@ -110,7 +112,7 @@ void VMainWindow::setupUI()
m_fileList = new VFileList();
m_fileList->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
- editArea = new VEditArea(vnote);
+ editArea = new VEditArea();
editArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_findReplaceDialog = editArea->getFindReplaceDialog();
m_fileList->setEditArea(editArea);
diff --git a/src/vnote.cpp b/src/vnote.cpp
index 720d5725..48d2e6d9 100644
--- a/src/vnote.cpp
+++ b/src/vnote.cpp
@@ -16,6 +16,10 @@
extern VConfigManager *g_config;
+// Meta word manager.
+VMetaWordManager *g_mwMgr;
+
+
QString VNote::s_markdownTemplate;
QString VNote::s_markdownTemplatePDF;
@@ -53,10 +57,15 @@ const QString VNote::c_markdownGuideDocFile_en = ":/resources/docs/markdown_guid
const QString VNote::c_markdownGuideDocFile_zh = ":/resources/docs/markdown_guide_zh.md";
VNote::VNote(QObject *parent)
- : QObject(parent), m_mainWindow(dynamic_cast(parent))
+ : QObject(parent)
{
initTemplate();
+
g_config->getNotebooks(m_notebooks, this);
+
+ m_metaWordMgr.init();
+
+ g_mwMgr = &m_metaWordMgr;
}
void VNote::initPalette(QPalette palette)
diff --git a/src/vnote.h b/src/vnote.h
index 81a9abc9..11f4ebd6 100644
--- a/src/vnote.h
+++ b/src/vnote.h
@@ -12,11 +12,12 @@
#include
#include "vnotebook.h"
#include "vconstants.h"
+#include "utils/vmetawordmanager.h"
-class VMainWindow;
class VOrphanFile;
class VNoteFile;
+
class VNote : public QObject
{
Q_OBJECT
@@ -74,7 +75,6 @@ public:
const QVector > &getPalette() const;
void initPalette(QPalette palette);
QString getColorFromPalette(const QString &p_name) const;
- VMainWindow *getMainWindow() const;
QString getNavigationLabelStyle(const QString &p_str) const;
@@ -106,7 +106,8 @@ private:
// Maintain all the notebooks. Other holder should use QPointer.
QVector m_notebooks;
QVector > m_palette;
- VMainWindow *m_mainWindow;
+
+ VMetaWordManager m_metaWordMgr;
// Hold all external file: Orphan File.
// Need to clean up periodly.
@@ -118,9 +119,4 @@ inline const QVector >& VNote::getPalette() const
return m_palette;
}
-inline VMainWindow *VNote::getMainWindow() const
-{
- return m_mainWindow;
-}
-
#endif // VNOTE_H
diff --git a/src/vpreviewpage.cpp b/src/vpreviewpage.cpp
index 524ae109..d87859d7 100644
--- a/src/vpreviewpage.cpp
+++ b/src/vpreviewpage.cpp
@@ -2,10 +2,9 @@
#include
-#include "vnote.h"
#include "vmainwindow.h"
-extern VNote *g_vnote;
+extern VMainWindow *g_mainWin;
VPreviewPage::VPreviewPage(QWidget *parent) : QWebEnginePage(parent)
{
@@ -21,7 +20,7 @@ bool VPreviewPage::acceptNavigationRequest(const QUrl &p_url,
if (p_url.isLocalFile()) {
QString filePath = p_url.toLocalFile();
- if (g_vnote->getMainWindow()->tryOpenInternalFile(filePath)) {
+ if (g_mainWin->tryOpenInternalFile(filePath)) {
qDebug() << "internal notes jump" << filePath;
return false;
}