support meta word

- Add VLineEdit as a QLineEdit with meta data support;
- support custom magic words through [magic_words];
- add %help% for all magic words information;
This commit is contained in:
Le Tan 2017-10-17 19:47:57 +08:00
parent f1f6980921
commit 787c61a5af
33 changed files with 1201 additions and 154 deletions

View File

@ -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("<span style=\"%1\">WARNING</span>: "
"Name (case-insensitive) <span style=\"%2\">%3</span> 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("<span style=\"%1\">WARNING</span>: "
"Name <span style=\"%2\">%3</span> 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("<span style=\"%1\">WARNING</span>: 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();
}

View File

@ -4,7 +4,7 @@
#include <QDialog>
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;

View File

@ -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("<span style=\"%1\">WARNING</span>: Name (case-insensitive) already exists. "
"Please choose another name.")
.arg(g_config->c_warningTextStyle);
m_warnLabel->setText(nameConflictText);
warnText = tr("<span style=\"%1\">WARNING</span>: "
"Name (case-insensitive) <span style=\"%2\">%3</span> 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("<span style=\"%1\">WARNING</span>: "
"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("<span style=\"%1\">WARNING</span>: "
"Name <span style=\"%2\">%3</span> 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("<span style=\"%1\">WARNING</span>: 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();
}

View File

@ -4,7 +4,7 @@
#include <QDialog>
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;

View File

@ -3,6 +3,7 @@
#include <QRegExp>
#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

View File

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

View File

@ -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("<span style=\"%1\">WARNING</span>: "
"Name (case-insensitive) <span style=\"%2\">%3</span> 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("<span style=\"%1\">WARNING</span>: "
"Name <span style=\"%2\">%3</span> 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("<span style=\"%1\">WARNING</span>: 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();
}

View File

@ -4,7 +4,7 @@
#include <QDialog>
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;

View File

@ -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("<span style=\"%1\">WARNING</span>: "
"Name (case-insensitive) <span style=\"%2\">%3</span> 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("<span style=\"%1\">WARNING</span>: "
"Name <span style=\"%2\">%3</span> 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("<span style=\"%1\">WARNING</span>: 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

View File

@ -4,7 +4,7 @@
#include <QDialog>
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;

View File

@ -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("<span style=\"%1\">WARNING</span>: "
"Name (case-insensitive) <span style=\"%2\">%3</span> 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("<span style=\"%1\">WARNING</span>: "
"Name <span style=\"%2\">%3</span> 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("<span style=\"%1\">WARNING</span>: 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.

View File

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

View File

@ -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("<span style=\"%1\">WARNING</span>: "
"Name (case-insensitive) <span style=\"%2\">%3</span> 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("<span style=\"%1\">WARNING</span>: "
"Name <span style=\"%2\">%3</span> 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("<span style=\"%1\">WARNING</span>: 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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,593 @@
#include "vmetawordmanager.h"
#include <QDebug>
#include <QWidget>
#include <QApplication>
#include <QToolTip>
#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<QString> 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<VMagicWord> 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<VMetaWordManager *>(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<QString, QString> 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::Token> VMetaWord::parseToTokens(const QString &p_text)
{
QVector<Token> 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<VMetaWord::Token> &p_tokens,
QHash<QString, QString> &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;
}

View File

@ -0,0 +1,202 @@
#ifndef VMETAWORDMANAGER_H
#define VMETAWORDMANAGER_H
#include <functional>
#include <QObject>
#include <QString>
#include <QVector>
#include <QHash>
#include <QDateTime>
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<QString(const VMetaWord *)> 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<VMetaWord::Token> 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<VMetaWord::Token> &p_tokens,
QHash<QString, QString> &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<Token> 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<QString, VMetaWord> &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<QString, VMetaWord> 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<QString, VMetaWord> &VMetaWordManager::getAllMetaWords() const
{
return m_metaWords;
}
#endif // VMETAWORDMANAGER_H

View File

@ -33,6 +33,8 @@ QVector<QPair<QString, QString>> 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);

View File

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

View File

@ -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();

View File

@ -1152,3 +1152,21 @@ void VConfigManager::setLastOpenedFiles(const QVector<VFileSessionInfo> &p_files
<< "items in [last_opened_files] section";
}
QVector<VMagicWord> VConfigManager::getCustomMagicWords()
{
QVector<VMagicWord> 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;
}

View File

@ -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<VFileSessionInfo> &p_files);
// Read custom magic words from [magic_words] section.
QVector<VMagicWord> getCustomMagicWords();
// Return the configured key sequence of @p_operation.
// Return empty if there is no corresponding config.
QString getShortcutKeySequence(const QString &p_operation) const;

View File

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

View File

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

View File

@ -2,7 +2,6 @@
#include <QtDebug>
#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<VNoteFile *>((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<VFile> 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();

View File

@ -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().

46
src/vlineedit.cpp Normal file
View File

@ -0,0 +1,46 @@
#include "vlineedit.h"
#include <QDebug>
#include <QToolTip>
#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;
}

29
src/vlineedit.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef VLINEEDIT_H
#define VLINEEDIT_H
#include <QLineEdit>
// 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

View File

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

View File

@ -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<VMainWindow *>(parent))
: QObject(parent)
{
initTemplate();
g_config->getNotebooks(m_notebooks, this);
m_metaWordMgr.init();
g_mwMgr = &m_metaWordMgr;
}
void VNote::initPalette(QPalette palette)

View File

@ -12,11 +12,12 @@
#include <QPalette>
#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<QPair<QString, QString> > &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<VNotebook *> m_notebooks;
QVector<QPair<QString, QString> > 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<QPair<QString, QString> >& VNote::getPalette() const
return m_palette;
}
inline VMainWindow *VNote::getMainWindow() const
{
return m_mainWindow;
}
#endif // VNOTE_H

View File

@ -2,10 +2,9 @@
#include <QDesktopServices>
#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;
}