support attachments
Support attachments to internal note file. - Add/Delete/Clear/Sort; - Support custom attachment folder for each notebook (read-only); - Support renaming attachment;
@ -56,8 +56,14 @@ private:
|
||||
VConfirmDeletionDialog::VConfirmDeletionDialog(const QString &p_title,
|
||||
const QString &p_info,
|
||||
const QVector<QString> &p_files,
|
||||
bool p_enableAskAgain,
|
||||
bool p_askAgainEnabled,
|
||||
bool p_enablePreview,
|
||||
QWidget *p_parent)
|
||||
: QDialog(p_parent)
|
||||
: QDialog(p_parent),
|
||||
m_enableAskAgain(p_enableAskAgain),
|
||||
m_askAgainEnabled(p_askAgainEnabled),
|
||||
m_enablePreview(p_enablePreview)
|
||||
{
|
||||
setupUI(p_title, p_info);
|
||||
|
||||
@ -66,33 +72,44 @@ VConfirmDeletionDialog::VConfirmDeletionDialog(const QString &p_title,
|
||||
|
||||
void VConfirmDeletionDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
{
|
||||
QLabel *infoLabel = new QLabel(p_info);
|
||||
QLabel *infoLabel = NULL;
|
||||
if (!p_info.isEmpty()) {
|
||||
infoLabel = new QLabel(p_info);
|
||||
infoLabel->setWordWrap(true);
|
||||
}
|
||||
|
||||
m_listWidget = new QListWidget();
|
||||
connect(m_listWidget, &QListWidget::currentRowChanged,
|
||||
this, &VConfirmDeletionDialog::currentFileChanged);
|
||||
|
||||
m_previewer = new QLabel();
|
||||
|
||||
m_askAgainCB = new QCheckBox(tr("Just delete them and do not ask for confirmation again"));
|
||||
m_askAgainCB->setChecked(!g_config->getConfirmImagesCleanUp());
|
||||
m_askAgainCB = new QCheckBox(tr("Do not ask for confirmation again"));
|
||||
m_askAgainCB->setChecked(!m_askAgainEnabled);
|
||||
m_askAgainCB->setVisible(m_enableAskAgain);
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted,
|
||||
this, [this]() {
|
||||
g_config->setConfirmImagesCleanUp(!m_askAgainCB->isChecked());
|
||||
QDialog::accept();
|
||||
});
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
m_btnBox->button(QDialogButtonBox::Ok)->setStyleSheet(g_config->c_dangerBtnStyle);
|
||||
|
||||
QHBoxLayout *midLayout = new QHBoxLayout;
|
||||
midLayout->addWidget(m_listWidget);
|
||||
midLayout->addStretch();
|
||||
midLayout->addWidget(m_previewer);
|
||||
midLayout->addStretch();
|
||||
if (m_enablePreview) {
|
||||
midLayout->addStretch();
|
||||
midLayout->addWidget(m_previewer);
|
||||
midLayout->addStretch();
|
||||
} else {
|
||||
midLayout->addWidget(m_previewer);
|
||||
m_previewer->setVisible(false);
|
||||
}
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(infoLabel);
|
||||
if (infoLabel) {
|
||||
mainLayout->addWidget(infoLabel);
|
||||
}
|
||||
|
||||
mainLayout->addWidget(m_askAgainCB);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
mainLayout->addLayout(midLayout);
|
||||
@ -137,10 +154,15 @@ void VConfirmDeletionDialog::initFileItems(const QVector<QString> &p_files)
|
||||
m_listWidget->setCurrentRow(0);
|
||||
}
|
||||
|
||||
bool VConfirmDeletionDialog::getAskAgainEnabled() const
|
||||
{
|
||||
return !m_askAgainCB->isChecked();
|
||||
}
|
||||
|
||||
void VConfirmDeletionDialog::currentFileChanged(int p_row)
|
||||
{
|
||||
bool succeed = false;
|
||||
if (p_row > -1) {
|
||||
if (p_row > -1 && m_enablePreview) {
|
||||
ConfirmItemWidget *widget = getItemWidget(m_listWidget->item(p_row));
|
||||
if (widget) {
|
||||
QPixmap image(widget->getFile());
|
||||
|
@ -19,10 +19,15 @@ public:
|
||||
VConfirmDeletionDialog(const QString &p_title,
|
||||
const QString &p_info,
|
||||
const QVector<QString> &p_files,
|
||||
bool p_enableAskAgain,
|
||||
bool p_askAgainEnabled,
|
||||
bool p_enablePreview,
|
||||
QWidget *p_parent = 0);
|
||||
|
||||
QVector<QString> getConfirmedFiles() const;
|
||||
|
||||
bool getAskAgainEnabled() const;
|
||||
|
||||
private slots:
|
||||
void currentFileChanged(int p_row);
|
||||
|
||||
@ -37,6 +42,12 @@ private:
|
||||
QLabel *m_previewer;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
QCheckBox *m_askAgainCB;
|
||||
|
||||
bool m_enableAskAgain;
|
||||
// Init value if m_enableAskAgain is true.
|
||||
bool m_askAgainEnabled;
|
||||
|
||||
bool m_enablePreview;
|
||||
};
|
||||
|
||||
#endif // VCONFIRMDELETIONDIALOG_H
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "vdirinfodialog.h"
|
||||
#include "vdirectory.h"
|
||||
#include "vconfigmanager.h"
|
||||
#include "utils/vutils.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
@ -31,8 +32,7 @@ void VDirInfoDialog::setupUI()
|
||||
nameEdit->selectAll();
|
||||
|
||||
// Created time.
|
||||
QString createdTimeStr = m_directory->getCreatedTimeUtc().toLocalTime()
|
||||
.toString(Qt::DefaultLocaleLongDate);
|
||||
QString createdTimeStr = VUtils::displayDateTime(m_directory->getCreatedTimeUtc().toLocalTime());
|
||||
QLabel *createdTimeLabel = new QLabel(createdTimeStr);
|
||||
|
||||
QFormLayout *topLayout = new QFormLayout();
|
||||
|
@ -40,19 +40,24 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
// Select without suffix.
|
||||
nameEdit->setSelection(baseStart, baseLength);
|
||||
|
||||
// Attachment folder.
|
||||
QLineEdit *attachmentFolderEdit = new QLineEdit(m_file->getAttachmentFolder());
|
||||
attachmentFolderEdit->setPlaceholderText(tr("Will be assigned when adding attachments"));
|
||||
attachmentFolderEdit->setToolTip(tr("The folder to hold attachments of this note"));
|
||||
attachmentFolderEdit->setReadOnly(true);
|
||||
|
||||
// Created time.
|
||||
QString createdTimeStr = m_file->getCreatedTimeUtc().toLocalTime()
|
||||
.toString(Qt::DefaultLocaleLongDate);
|
||||
QString createdTimeStr = VUtils::displayDateTime(m_file->getCreatedTimeUtc().toLocalTime());
|
||||
QLabel *createdTimeLabel = new QLabel(createdTimeStr);
|
||||
|
||||
// Modified time.
|
||||
createdTimeStr = m_file->getModifiedTimeUtc().toLocalTime()
|
||||
.toString(Qt::DefaultLocaleLongDate);
|
||||
createdTimeStr = VUtils::displayDateTime(m_file->getModifiedTimeUtc().toLocalTime());
|
||||
QLabel *modifiedTimeLabel = new QLabel(createdTimeStr);
|
||||
modifiedTimeLabel->setToolTip(tr("Last modified time within VNote"));
|
||||
|
||||
QFormLayout *topLayout = new QFormLayout();
|
||||
topLayout->addRow(tr("Note &name:"), nameEdit);
|
||||
topLayout->addRow(tr("Attachment folder:"), attachmentFolderEdit);
|
||||
topLayout->addRow(tr("Created time:"), createdTimeLabel);
|
||||
topLayout->addRow(tr("Modified time:"), modifiedTimeLabel);
|
||||
|
||||
|
@ -12,11 +12,11 @@ VNewNotebookDialog::VNewNotebookDialog(const QString &title, const QString &info
|
||||
const QVector<VNotebook *> &p_notebooks,
|
||||
QWidget *parent)
|
||||
: QDialog(parent),
|
||||
title(title), info(info), defaultName(defaultName), defaultPath(defaultPath),
|
||||
defaultName(defaultName), defaultPath(defaultPath),
|
||||
m_importNotebook(false), m_manualPath(false), m_manualName(false),
|
||||
m_notebooks(p_notebooks)
|
||||
{
|
||||
setupUI();
|
||||
setupUI(title, info);
|
||||
|
||||
connect(nameEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
|
||||
connect(pathEdit, &QLineEdit::textChanged, this, &VNewNotebookDialog::handleInputChanged);
|
||||
@ -25,11 +25,11 @@ VNewNotebookDialog::VNewNotebookDialog(const QString &title, const QString &info
|
||||
handleInputChanged();
|
||||
}
|
||||
|
||||
void VNewNotebookDialog::setupUI()
|
||||
void VNewNotebookDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
{
|
||||
QLabel *infoLabel = NULL;
|
||||
if (!info.isEmpty()) {
|
||||
infoLabel = new QLabel(info);
|
||||
if (!p_info.isEmpty()) {
|
||||
infoLabel = new QLabel(p_info);
|
||||
infoLabel->setWordWrap(true);
|
||||
}
|
||||
|
||||
@ -44,16 +44,26 @@ void VNewNotebookDialog::setupUI()
|
||||
|
||||
QLabel *imageFolderLabel = new QLabel(tr("&Image folder:"));
|
||||
m_imageFolderEdit = new QLineEdit();
|
||||
imageFolderLabel->setBuddy(m_imageFolderEdit);
|
||||
m_imageFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
|
||||
.arg(g_config->getImageFolder()));
|
||||
imageFolderLabel->setBuddy(m_imageFolderEdit);
|
||||
QString imageFolderTip = tr("Set the name of the folder for all the notes of this notebook to store images "
|
||||
"(empty to use global configuration)");
|
||||
m_imageFolderEdit->setToolTip(imageFolderTip);
|
||||
imageFolderLabel->setToolTip(imageFolderTip);
|
||||
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);
|
||||
m_imageFolderEdit->setValidator(validator);
|
||||
|
||||
QLabel *attachmentFolderLabel = new QLabel(tr("&Attachment folder:"));
|
||||
m_attachmentFolderEdit = new QLineEdit();
|
||||
attachmentFolderLabel->setBuddy(m_attachmentFolderEdit);
|
||||
m_attachmentFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
|
||||
.arg(g_config->getAttachmentFolder()));
|
||||
m_attachmentFolderEdit->setToolTip(tr("Set the name of the folder to hold attachments of all the notes in this notebook "
|
||||
"(empty to use global configuration, read-only once created)"));
|
||||
attachmentFolderLabel->setToolTip(m_attachmentFolderEdit->toolTip());
|
||||
validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_attachmentFolderEdit);
|
||||
m_attachmentFolderEdit->setValidator(validator);
|
||||
|
||||
QGridLayout *topLayout = new QGridLayout();
|
||||
topLayout->addWidget(nameLabel, 0, 0);
|
||||
topLayout->addWidget(nameEdit, 0, 1, 1, 2);
|
||||
@ -62,6 +72,8 @@ void VNewNotebookDialog::setupUI()
|
||||
topLayout->addWidget(browseBtn, 1, 2);
|
||||
topLayout->addWidget(imageFolderLabel, 2, 0);
|
||||
topLayout->addWidget(m_imageFolderEdit, 2, 1);
|
||||
topLayout->addWidget(attachmentFolderLabel, 3, 0);
|
||||
topLayout->addWidget(m_attachmentFolderEdit, 3, 1);
|
||||
|
||||
// Warning label.
|
||||
m_warnLabel = new QLabel();
|
||||
@ -87,7 +99,7 @@ void VNewNotebookDialog::setupUI()
|
||||
// Will set the parent of above widgets properly.
|
||||
setLayout(mainLayout);
|
||||
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setWindowTitle(title);
|
||||
setWindowTitle(p_title);
|
||||
}
|
||||
|
||||
QString VNewNotebookDialog::getNameInput() const
|
||||
@ -111,6 +123,15 @@ QString VNewNotebookDialog::getImageFolder() const
|
||||
}
|
||||
}
|
||||
|
||||
QString VNewNotebookDialog::getAttachmentFolder() const
|
||||
{
|
||||
if (m_attachmentFolderEdit->isEnabled()) {
|
||||
return m_attachmentFolderEdit->text();
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
void VNewNotebookDialog::handleBrowseBtnClicked()
|
||||
{
|
||||
static QString defaultPath;
|
||||
@ -251,6 +272,7 @@ void VNewNotebookDialog::handleInputChanged()
|
||||
m_warnLabel->setVisible(showWarnLabel);
|
||||
m_importNotebook = configExist;
|
||||
m_imageFolderEdit->setEnabled(!m_importNotebook);
|
||||
m_attachmentFolderEdit->setEnabled(!m_importNotebook);
|
||||
|
||||
QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setEnabled(nameOk && pathOk);
|
||||
|
@ -29,6 +29,10 @@ public:
|
||||
// Empty string indicates using global config.
|
||||
QString getImageFolder() const;
|
||||
|
||||
// Get the custom attachment folder for this notebook.
|
||||
// Empty string indicates using global config.
|
||||
QString getAttachmentFolder() const;
|
||||
|
||||
private slots:
|
||||
void handleBrowseBtnClicked();
|
||||
|
||||
@ -39,7 +43,7 @@ protected:
|
||||
void showEvent(QShowEvent *event) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
void setupUI(const QString &p_title, const QString &p_info);
|
||||
|
||||
// Should be called before enableOkButton() when path changed.
|
||||
void checkRootFolder(const QString &p_path);
|
||||
@ -53,10 +57,9 @@ private:
|
||||
QPushButton *browseBtn;
|
||||
QLabel *m_warnLabel;
|
||||
QLineEdit *m_imageFolderEdit;
|
||||
QLineEdit *m_attachmentFolderEdit;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
|
||||
QString title;
|
||||
QString info;
|
||||
QString defaultName;
|
||||
QString defaultPath;
|
||||
|
||||
|
@ -35,28 +35,37 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
m_pathEdit = new QLineEdit(m_notebook->getPath());
|
||||
m_pathEdit->setReadOnly(true);
|
||||
|
||||
// Image folder.
|
||||
m_imageFolderEdit = new QLineEdit(m_notebook->getImageFolderConfig());
|
||||
m_imageFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
|
||||
.arg(g_config->getImageFolder()));
|
||||
m_imageFolderEdit->setToolTip(tr("Set the name of the folder for all the notes of this notebook to store images "
|
||||
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);
|
||||
m_imageFolderEdit->setValidator(validator);
|
||||
|
||||
// Attachment folder.
|
||||
Q_ASSERT(!m_notebook->getAttachmentFolder().isEmpty());
|
||||
m_attachmentFolderEdit = new QLineEdit(m_notebook->getAttachmentFolder());
|
||||
m_attachmentFolderEdit->setPlaceholderText(tr("Use global configuration (%1)")
|
||||
.arg(g_config->getAttachmentFolder()));
|
||||
m_attachmentFolderEdit->setToolTip(tr("The folder to hold attachments of all the notes in this notebook"));
|
||||
m_attachmentFolderEdit->setReadOnly(true);
|
||||
|
||||
// Recycle bin folder.
|
||||
QLineEdit *recycleBinFolderEdit = new QLineEdit(m_notebook->getRecycleBinFolder());
|
||||
recycleBinFolderEdit->setReadOnly(true);
|
||||
recycleBinFolderEdit->setToolTip(tr("The folder to hold deleted files from within VNote"));
|
||||
recycleBinFolderEdit->setToolTip(tr("The folder to hold deleted files from within VNote of all the notes in this notebook"));
|
||||
|
||||
// Created time.
|
||||
QString createdTimeStr = const_cast<VNotebook *>(m_notebook)->getCreatedTimeUtc().toLocalTime()
|
||||
.toString(Qt::DefaultLocaleLongDate);
|
||||
QString createdTimeStr = VUtils::displayDateTime(const_cast<VNotebook *>(m_notebook)->getCreatedTimeUtc().toLocalTime());
|
||||
QLabel *createdTimeLabel = new QLabel(createdTimeStr);
|
||||
|
||||
QFormLayout *topLayout = new QFormLayout();
|
||||
topLayout->addRow(tr("Notebook &name:"), m_nameEdit);
|
||||
topLayout->addRow(tr("Notebook &root folder:"), m_pathEdit);
|
||||
topLayout->addRow(tr("&Image folder:"), m_imageFolderEdit);
|
||||
topLayout->addRow(tr("Attachment folder:"), m_attachmentFolderEdit);
|
||||
topLayout->addRow(tr("Recycle bin folder:"), recycleBinFolderEdit);
|
||||
topLayout->addRow(tr("Created time:"), createdTimeLabel);
|
||||
|
||||
|
@ -41,6 +41,8 @@ private:
|
||||
QLineEdit *m_nameEdit;
|
||||
QLineEdit *m_pathEdit;
|
||||
QLineEdit *m_imageFolderEdit;
|
||||
// Read-only.
|
||||
QLineEdit *m_attachmentFolderEdit;
|
||||
QLabel *m_warnLabel;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
const QVector<VNotebook *> &m_notebooks;
|
||||
|
@ -295,7 +295,7 @@ VNoteManagementTab::VNoteManagementTab(QWidget *p_parent)
|
||||
// Note.
|
||||
// Image folder.
|
||||
m_customImageFolder = new QCheckBox(tr("Custom image folder"), this);
|
||||
m_customImageFolder->setToolTip(tr("Set the global name of the image folder to store images "
|
||||
m_customImageFolder->setToolTip(tr("Set the global name of the image folder to hold images "
|
||||
"of notes (restart VNote to make it work)"));
|
||||
connect(m_customImageFolder, &QCheckBox::stateChanged,
|
||||
this, &VNoteManagementTab::customImageFolderChanged);
|
||||
@ -310,14 +310,32 @@ VNoteManagementTab::VNoteManagementTab(QWidget *p_parent)
|
||||
imageFolderLayout->addWidget(m_customImageFolder);
|
||||
imageFolderLayout->addWidget(m_imageFolderEdit);
|
||||
|
||||
// Attachment folder.
|
||||
m_customAttachmentFolder = new QCheckBox(tr("Custom attachment folder"), this);
|
||||
m_customAttachmentFolder->setToolTip(tr("Set the global name of the attachment folder to hold attachments "
|
||||
"of notes (restart VNote to make it work)"));
|
||||
connect(m_customAttachmentFolder, &QCheckBox::stateChanged,
|
||||
this, &VNoteManagementTab::customAttachmentFolderChanged);
|
||||
|
||||
m_attachmentFolderEdit = new QLineEdit(this);
|
||||
m_attachmentFolderEdit->setPlaceholderText(tr("Name of the attachment folder"));
|
||||
m_attachmentFolderEdit->setToolTip(m_customAttachmentFolder->toolTip());
|
||||
validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), this);
|
||||
m_attachmentFolderEdit->setValidator(validator);
|
||||
|
||||
QHBoxLayout *attachmentFolderLayout = new QHBoxLayout();
|
||||
attachmentFolderLayout->addWidget(m_customAttachmentFolder);
|
||||
attachmentFolderLayout->addWidget(m_attachmentFolderEdit);
|
||||
|
||||
QFormLayout *noteLayout = new QFormLayout();
|
||||
noteLayout->addRow(imageFolderLayout);
|
||||
noteLayout->addRow(attachmentFolderLayout);
|
||||
m_noteBox->setLayout(noteLayout);
|
||||
|
||||
// External File.
|
||||
// Image folder.
|
||||
m_customImageFolderExt = new QCheckBox(tr("Custom image folder"), this);
|
||||
m_customImageFolderExt->setToolTip(tr("Set the path of the global image folder to store images "
|
||||
m_customImageFolderExt->setToolTip(tr("Set the path of the global image folder to hold images "
|
||||
"of external files (restart VNote to make it work).\nYou "
|
||||
"could use both absolute or relative path here. If "
|
||||
"absolute path is used, VNote will not manage\nthose images, "
|
||||
@ -350,6 +368,10 @@ bool VNoteManagementTab::loadConfiguration()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadAttachmentFolder()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadImageFolderExt()) {
|
||||
return false;
|
||||
}
|
||||
@ -363,6 +385,10 @@ bool VNoteManagementTab::saveConfiguration()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!saveAttachmentFolder()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!saveImageFolderExt()) {
|
||||
return false;
|
||||
}
|
||||
@ -403,6 +429,39 @@ void VNoteManagementTab::customImageFolderChanged(int p_state)
|
||||
}
|
||||
}
|
||||
|
||||
bool VNoteManagementTab::loadAttachmentFolder()
|
||||
{
|
||||
bool isCustom = g_config->isCustomAttachmentFolder();
|
||||
|
||||
m_customAttachmentFolder->setChecked(isCustom);
|
||||
m_attachmentFolderEdit->setText(g_config->getAttachmentFolder());
|
||||
m_attachmentFolderEdit->setEnabled(isCustom);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VNoteManagementTab::saveAttachmentFolder()
|
||||
{
|
||||
if (m_customAttachmentFolder->isChecked()) {
|
||||
g_config->setAttachmentFolder(m_attachmentFolderEdit->text());
|
||||
} else {
|
||||
g_config->setAttachmentFolder("");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VNoteManagementTab::customAttachmentFolderChanged(int p_state)
|
||||
{
|
||||
if (p_state == Qt::Checked) {
|
||||
m_attachmentFolderEdit->setEnabled(true);
|
||||
m_attachmentFolderEdit->selectAll();
|
||||
m_attachmentFolderEdit->setFocus();
|
||||
} else {
|
||||
m_attachmentFolderEdit->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool VNoteManagementTab::loadImageFolderExt()
|
||||
{
|
||||
bool isCustom = g_config->isCustomImageFolderExt();
|
||||
|
@ -69,9 +69,14 @@ public:
|
||||
QCheckBox *m_customImageFolderExt;
|
||||
QLineEdit *m_imageFolderEditExt;
|
||||
|
||||
// Attachment folder.
|
||||
QCheckBox *m_customAttachmentFolder;
|
||||
QLineEdit *m_attachmentFolderEdit;
|
||||
|
||||
private slots:
|
||||
void customImageFolderChanged(int p_state);
|
||||
void customImageFolderExtChanged(int p_state);
|
||||
void customAttachmentFolderChanged(int p_state);
|
||||
|
||||
private:
|
||||
bool loadImageFolder();
|
||||
@ -79,6 +84,9 @@ private:
|
||||
|
||||
bool loadImageFolderExt();
|
||||
bool saveImageFolderExt();
|
||||
|
||||
bool loadAttachmentFolder();
|
||||
bool saveAttachmentFolder();
|
||||
};
|
||||
|
||||
class VMarkdownTab : public QWidget
|
||||
|
205
src/dialog/vsortdialog.cpp
Normal file
@ -0,0 +1,205 @@
|
||||
#include "vsortdialog.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
VSortDialog::VSortDialog(const QString &p_title,
|
||||
const QString &p_info,
|
||||
QWidget *p_parent)
|
||||
: QDialog(p_parent)
|
||||
{
|
||||
setupUI(p_title, p_info);
|
||||
}
|
||||
|
||||
void VSortDialog::setupUI(const QString &p_title, const QString &p_info)
|
||||
{
|
||||
QLabel *infoLabel = NULL;
|
||||
if (!p_info.isEmpty()) {
|
||||
infoLabel = new QLabel(p_info);
|
||||
infoLabel->setWordWrap(true);
|
||||
}
|
||||
|
||||
m_treeWidget = new QTreeWidget();
|
||||
m_treeWidget->setRootIsDecorated(false);
|
||||
m_treeWidget->setSelectionMode(QAbstractItemView::ContiguousSelection);
|
||||
m_treeWidget->setDragDropMode(QAbstractItemView::InternalMove);
|
||||
|
||||
// Buttons for top/up/down/bottom.
|
||||
m_topBtn = new QPushButton(tr("&Top"));
|
||||
m_topBtn->setToolTip(tr("Move selected items to top"));
|
||||
connect(m_topBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
this->handleMoveOperation(MoveOperation::Top);
|
||||
});
|
||||
|
||||
m_upBtn = new QPushButton(tr("&Up"));
|
||||
m_upBtn->setToolTip(tr("Move selected items up"));
|
||||
connect(m_upBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
this->handleMoveOperation(MoveOperation::Up);
|
||||
});
|
||||
|
||||
m_downBtn = new QPushButton(tr("&Down"));
|
||||
m_downBtn->setToolTip(tr("Move selected items down"));
|
||||
connect(m_downBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
this->handleMoveOperation(MoveOperation::Down);
|
||||
});
|
||||
|
||||
m_bottomBtn = new QPushButton(tr("&Bottom"));
|
||||
m_bottomBtn->setToolTip(tr("Move selected items to bottom"));
|
||||
connect(m_bottomBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
this->handleMoveOperation(MoveOperation::Bottom);
|
||||
});
|
||||
|
||||
QVBoxLayout *btnLayout = new QVBoxLayout;
|
||||
btnLayout->addWidget(m_topBtn);
|
||||
btnLayout->addWidget(m_upBtn);
|
||||
btnLayout->addWidget(m_downBtn);
|
||||
btnLayout->addWidget(m_bottomBtn);
|
||||
btnLayout->addStretch();
|
||||
|
||||
QHBoxLayout *midLayout = new QHBoxLayout;
|
||||
midLayout->addWidget(m_treeWidget);
|
||||
midLayout->addLayout(btnLayout);
|
||||
|
||||
// Ok is the default button.
|
||||
m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
if (infoLabel) {
|
||||
mainLayout->addWidget(infoLabel);
|
||||
}
|
||||
|
||||
mainLayout->addLayout(midLayout);
|
||||
mainLayout->addWidget(m_btnBox);
|
||||
|
||||
setLayout(mainLayout);
|
||||
setWindowTitle(p_title);
|
||||
}
|
||||
|
||||
void VSortDialog::treeUpdated()
|
||||
{
|
||||
// We just need single level.
|
||||
int cnt = m_treeWidget->topLevelItemCount();
|
||||
for (int i = 0; i < cnt; ++i) {
|
||||
QTreeWidgetItem *item = m_treeWidget->topLevelItem(i);
|
||||
item->setFlags(item->flags() & ~Qt::ItemIsDropEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
void VSortDialog::handleMoveOperation(MoveOperation p_op)
|
||||
{
|
||||
const QList<QTreeWidgetItem *> selectedItems = m_treeWidget->selectedItems();
|
||||
if (selectedItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int first = m_treeWidget->topLevelItemCount();
|
||||
int last = -1;
|
||||
for (auto const & it : selectedItems) {
|
||||
int idx = m_treeWidget->indexOfTopLevelItem(it);
|
||||
Q_ASSERT(idx > -1);
|
||||
if (idx < first) {
|
||||
first = idx;
|
||||
}
|
||||
|
||||
if (idx > last) {
|
||||
last = idx;
|
||||
}
|
||||
}
|
||||
|
||||
Q_ASSERT(first <= last && (last - first + 1) == selectedItems.size());
|
||||
QTreeWidgetItem *firstItem = NULL;
|
||||
|
||||
switch (p_op) {
|
||||
case MoveOperation::Top:
|
||||
if (first == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
m_treeWidget->clearSelection();
|
||||
|
||||
// Insert item[last] to index 0 repeatedly.
|
||||
for (int i = last - first; i >= 0; --i) {
|
||||
QTreeWidgetItem *item = m_treeWidget->takeTopLevelItem(last);
|
||||
Q_ASSERT(item);
|
||||
m_treeWidget->insertTopLevelItem(0, item);
|
||||
item->setSelected(true);
|
||||
}
|
||||
|
||||
firstItem = m_treeWidget->topLevelItem(0);
|
||||
|
||||
break;
|
||||
|
||||
case MoveOperation::Up:
|
||||
if (first == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
m_treeWidget->clearSelection();
|
||||
|
||||
// Insert item[last] to index (first -1) repeatedly.
|
||||
for (int i = last - first; i >= 0; --i) {
|
||||
QTreeWidgetItem *item = m_treeWidget->takeTopLevelItem(last);
|
||||
Q_ASSERT(item);
|
||||
m_treeWidget->insertTopLevelItem(first - 1, item);
|
||||
item->setSelected(true);
|
||||
}
|
||||
|
||||
firstItem = m_treeWidget->topLevelItem(first - 1);
|
||||
|
||||
break;
|
||||
|
||||
case MoveOperation::Down:
|
||||
if (last == m_treeWidget->topLevelItemCount() - 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
m_treeWidget->clearSelection();
|
||||
|
||||
// Insert item[first] to index (last) repeatedly.
|
||||
for (int i = last - first; i >= 0; --i) {
|
||||
QTreeWidgetItem *item = m_treeWidget->takeTopLevelItem(first);
|
||||
Q_ASSERT(item);
|
||||
m_treeWidget->insertTopLevelItem(last + 1, item);
|
||||
item->setSelected(true);
|
||||
|
||||
if (!firstItem) {
|
||||
firstItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case MoveOperation::Bottom:
|
||||
if (last == m_treeWidget->topLevelItemCount() - 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
m_treeWidget->clearSelection();
|
||||
|
||||
// Insert item[first] to the last of the tree repeatedly.
|
||||
for (int i = last - first; i >= 0; --i) {
|
||||
QTreeWidgetItem *item = m_treeWidget->takeTopLevelItem(first);
|
||||
Q_ASSERT(item);
|
||||
m_treeWidget->addTopLevelItem(item);
|
||||
item->setSelected(true);
|
||||
|
||||
if (!firstItem) {
|
||||
firstItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstItem) {
|
||||
m_treeWidget->scrollToItem(firstItem);
|
||||
}
|
||||
}
|
46
src/dialog/vsortdialog.h
Normal file
@ -0,0 +1,46 @@
|
||||
#ifndef VSORTDIALOG_H
|
||||
#define VSORTDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QVector>
|
||||
|
||||
class QPushButton;
|
||||
class QDialogButtonBox;
|
||||
class QTreeWidget;
|
||||
|
||||
class VSortDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VSortDialog(const QString &p_title,
|
||||
const QString &p_info,
|
||||
QWidget *p_parent = 0);
|
||||
|
||||
QTreeWidget *getTreeWidget() const;
|
||||
|
||||
// Called after updating the m_treeWidget.
|
||||
void treeUpdated();
|
||||
|
||||
private:
|
||||
enum MoveOperation { Top, Up, Down, Bottom };
|
||||
|
||||
private slots:
|
||||
void handleMoveOperation(MoveOperation p_op);
|
||||
|
||||
private:
|
||||
void setupUI(const QString &p_title, const QString &p_info);
|
||||
|
||||
QTreeWidget *m_treeWidget;
|
||||
QPushButton *m_topBtn;
|
||||
QPushButton *m_upBtn;
|
||||
QPushButton *m_downBtn;
|
||||
QPushButton *m_bottomBtn;
|
||||
QDialogButtonBox *m_btnBox;
|
||||
};
|
||||
|
||||
inline QTreeWidget *VSortDialog::getTreeWidget() const
|
||||
{
|
||||
return m_treeWidget;
|
||||
}
|
||||
|
||||
#endif // VSORTDIALOG_H
|
7
src/resources/icons/add_attachment.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<polygon points="448,224 288,224 288,64 224,64 224,224 64,224 64,288 224,288 224,448 288,448 288,288 448,288 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 597 B |
15
src/resources/icons/attachment.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
|
||||
<g id="Icon_3_">
|
||||
<g>
|
||||
<path d="M341.334,128v234.666C341.334,409.604,302.938,448,256,448c-46.937,0-85.333-38.396-85.333-85.334V117.334
|
||||
C170.667,87.469,194.135,64,224,64c29.864,0,53.333,23.469,53.333,53.334v245.333c0,11.729-9.605,21.333-21.334,21.333
|
||||
c-11.729,0-21.333-9.604-21.333-21.333V160h-32v202.667C202.667,392.531,226.135,416,256,416
|
||||
c29.865,0,53.334-23.469,53.334-53.333V117.334C309.334,70.401,270.938,32,224,32c-46.938,0-85.334,38.401-85.334,85.334v245.332
|
||||
C138.667,427.729,190.938,480,256,480c65.062,0,117.334-52.271,117.334-117.334V128H341.334z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
14
src/resources/icons/attachment_full.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<title>Layer 1</title>
|
||||
<g id="Icon_3_">
|
||||
<g id="svg_1">
|
||||
<path d="m341.334,128l0,234.666c0,46.938 -38.396,85.334 -85.334,85.334c-46.937,0 -85.333,-38.396 -85.333,-85.334l0,-245.332c0,-29.865 23.468,-53.334 53.333,-53.334c29.864,0 53.333,23.469 53.333,53.334l0,245.333c0,11.729 -9.605,21.333 -21.334,21.333c-11.729,0 -21.333,-9.604 -21.333,-21.333l0,-202.667l-32,0l0,202.667c0.001,29.864 23.469,53.333 53.334,53.333c29.865,0 53.334,-23.469 53.334,-53.333l0,-245.333c0,-46.933 -38.396,-85.334 -85.334,-85.334c-46.938,0 -85.334,38.401 -85.334,85.334l0,245.332c0.001,65.063 52.272,117.334 117.334,117.334c65.062,0 117.334,-52.271 117.334,-117.334l0,-234.666l-32,0z" id="svg_2"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<title>Layer 2</title>
|
||||
<circle stroke="#000000" fill="#15ae67" stroke-width="5" stroke-opacity="0" cx="435.5" cy="75.50001" r="70.05334" id="svg_3"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1001 B |
10
src/resources/icons/clear_attachment.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<path style="fill:#C9302C" d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4
|
||||
L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1
|
||||
c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1
|
||||
c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 995 B |
10
src/resources/icons/delete_attachment.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<path d="M341,128V99c0-19.1-14.5-35-34.5-35H205.4C185.5,64,171,79.9,171,99v29H80v32h9.2c0,0,5.4,0.6,8.2,3.4c2.8,2.8,3.9,9,3.9,9
|
||||
l19,241.7c1.5,29.4,1.5,33.9,36,33.9h199.4c34.5,0,34.5-4.4,36-33.8l19-241.6c0,0,1.1-6.3,3.9-9.1c2.8-2.8,8.2-3.4,8.2-3.4h9.2v-32
|
||||
h-91V128z M192,99c0-9.6,7.8-15,17.7-15h91.7c9.9,0,18.6,5.5,18.6,15v29H192V99z M183.5,384l-10.3-192h20.3L204,384H183.5z
|
||||
M267.1,384h-22V192h22V384z M328.7,384h-20.4l10.5-192h20.3L328.7,384z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 934 B |
10
src/resources/icons/locate_attachment.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
|
||||
<path d="M437.334,144H256.006l-42.668-48H74.666C51.197,96,32,115.198,32,138.667v234.666C32,396.802,51.197,416,74.666,416h362.668
|
||||
C460.803,416,480,396.802,480,373.333V186.667C480,163.198,460.803,144,437.334,144z M448,373.333
|
||||
c0,5.782-4.885,10.667-10.666,10.667H74.666C68.884,384,64,379.115,64,373.333V176h373.334c5.781,0,10.666,4.885,10.666,10.667
|
||||
V373.333z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 840 B |
12
src/resources/icons/sort.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<g>
|
||||
<rect x="80" y="352" width="64" height="64"/>
|
||||
<rect x="176" y="288" width="64" height="128"/>
|
||||
<rect x="272" y="192" width="64" height="224"/>
|
||||
<rect x="368" y="96" width="64" height="320"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 698 B |
@ -47,6 +47,9 @@ image_folder=_v_images
|
||||
; Image folder name for the external files
|
||||
external_image_folder=_v_images
|
||||
|
||||
; Attachment folder name for the notes
|
||||
attachment_folder=_v_attachments
|
||||
|
||||
; Enable trailing space highlight
|
||||
enable_trailing_space_highlight=true
|
||||
|
||||
|
@ -75,7 +75,9 @@ SOURCES += main.cpp\
|
||||
vtextblockdata.cpp \
|
||||
utils/vpreviewutils.cpp \
|
||||
dialog/vconfirmdeletiondialog.cpp \
|
||||
vnotefile.cpp
|
||||
vnotefile.cpp \
|
||||
vattachmentlist.cpp \
|
||||
dialog/vsortdialog.cpp
|
||||
|
||||
HEADERS += vmainwindow.h \
|
||||
vdirectorytree.h \
|
||||
@ -138,7 +140,9 @@ HEADERS += vmainwindow.h \
|
||||
vtextblockdata.h \
|
||||
utils/vpreviewutils.h \
|
||||
dialog/vconfirmdeletiondialog.h \
|
||||
vnotefile.h
|
||||
vnotefile.h \
|
||||
vattachmentlist.h \
|
||||
dialog/vsortdialog.h
|
||||
|
||||
RESOURCES += \
|
||||
vnote.qrc \
|
||||
|
@ -123,14 +123,13 @@ QString VUtils::generateImageFileName(const QString &path, const QString &title,
|
||||
baseName = baseName + '_' + QString::number(QDateTime::currentDateTime().toTime_t());
|
||||
baseName = baseName + '_' + QString::number(qrand());
|
||||
|
||||
QDir dir(path);
|
||||
QString imageName = baseName + "." + format.toLower();
|
||||
QString filePath = QDir(path).filePath(imageName);
|
||||
int index = 1;
|
||||
|
||||
while (QFileInfo::exists(filePath)) {
|
||||
while (fileExists(dir, imageName, true)) {
|
||||
imageName = QString("%1_%2.%3").arg(baseName).arg(index++)
|
||||
.arg(format.toLower());
|
||||
filePath = QDir(path).filePath(imageName);
|
||||
}
|
||||
|
||||
return imageName;
|
||||
@ -191,6 +190,7 @@ QVector<ImageLink> VUtils::fetchImagesFromMarkdownFile(VFile *p_file,
|
||||
QString linkText = text.mid(reg.m_startPos, reg.m_endPos - reg.m_startPos);
|
||||
bool matched = regExp.exactMatch(linkText);
|
||||
Q_ASSERT(matched);
|
||||
Q_UNUSED(matched);
|
||||
QString imageUrl = regExp.capturedTexts()[2].trimmed();
|
||||
|
||||
ImageLink link;
|
||||
@ -605,11 +605,25 @@ QString VUtils::getFileNameWithSequence(const QString &p_directory,
|
||||
if (!suffix.isEmpty()) {
|
||||
fileName = fileName + "." + suffix;
|
||||
}
|
||||
} while (dir.exists(fileName));
|
||||
} while (fileExists(dir, fileName, true));
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
QString VUtils::getRandomFileName(const QString &p_directory)
|
||||
{
|
||||
Q_ASSERT(!p_directory.isEmpty());
|
||||
|
||||
QString name;
|
||||
QDir dir(p_directory);
|
||||
do {
|
||||
name = QString::number(QDateTime::currentDateTimeUtc().toTime_t());
|
||||
name = name + '_' + QString::number(qrand());
|
||||
} while (fileExists(dir, name, true));
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
bool VUtils::checkPathLegal(const QString &p_path)
|
||||
{
|
||||
// Ensure every part of the p_path is a valid file name until we come to
|
||||
@ -869,3 +883,28 @@ QVector<VElementRegion> VUtils::fetchImageRegionsUsingParser(const QString &p_co
|
||||
|
||||
return regs;
|
||||
}
|
||||
|
||||
QString VUtils::displayDateTime(const QDateTime &p_dateTime)
|
||||
{
|
||||
QString res = p_dateTime.date().toString(Qt::DefaultLocaleLongDate);
|
||||
res += " " + p_dateTime.time().toString();
|
||||
return res;
|
||||
}
|
||||
|
||||
bool VUtils::fileExists(const QDir &p_dir, const QString &p_name, bool p_forceCaseInsensitive)
|
||||
{
|
||||
if (!p_forceCaseInsensitive) {
|
||||
return p_dir.exists(p_name);
|
||||
}
|
||||
|
||||
QString name = p_name.toLower();
|
||||
QStringList names = p_dir.entryList(QDir::Dirs | QDir::Files | QDir::Hidden
|
||||
| QDir::NoSymLinks | QDir::NoDotAndDotDot);
|
||||
foreach (const QString &str, names) {
|
||||
if (str.toLower() == name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <QPair>
|
||||
#include <QMessageBox>
|
||||
#include <QUrl>
|
||||
#include <QDir>
|
||||
#include "vconfigmanager.h"
|
||||
#include "vconstants.h"
|
||||
|
||||
@ -104,6 +105,9 @@ public:
|
||||
static QString getFileNameWithSequence(const QString &p_directory,
|
||||
const QString &p_baseFileName);
|
||||
|
||||
// Get an available random file name in @p_directory.
|
||||
static QString getRandomFileName(const QString &p_directory);
|
||||
|
||||
// Try to check if @p_path is legal.
|
||||
static bool checkPathLegal(const QString &p_path);
|
||||
|
||||
@ -152,6 +156,12 @@ public:
|
||||
static bool deleteFile(const QString &p_path,
|
||||
bool p_skipRecycleBin = false);
|
||||
|
||||
static QString displayDateTime(const QDateTime &p_dateTime);
|
||||
|
||||
// Check if file @p_name exists in @p_dir.
|
||||
// @p_forceCaseInsensitive: if true, will check the name ignoring the case.
|
||||
static bool fileExists(const QDir &p_dir, const QString &p_name, bool p_forceCaseInsensitive = false);
|
||||
|
||||
// Regular expression for image link.
|
||||
// 
|
||||
// Captured texts (need to be trimmed):
|
||||
|
404
src/vattachmentlist.cpp
Normal file
@ -0,0 +1,404 @@
|
||||
#include "vattachmentlist.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
#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;
|
||||
|
||||
VAttachmentList::VAttachmentList(QWidget *p_parent)
|
||||
: QWidget(p_parent), m_file(NULL)
|
||||
{
|
||||
setupUI();
|
||||
|
||||
initActions();
|
||||
|
||||
updateContent();
|
||||
}
|
||||
|
||||
void VAttachmentList::setupUI()
|
||||
{
|
||||
m_addBtn = new QPushButton(QIcon(":/resources/icons/add_attachment.svg"), "");
|
||||
m_addBtn->setToolTip(tr("Add"));
|
||||
m_addBtn->setProperty("FlatBtn", true);
|
||||
connect(m_addBtn, &QPushButton::clicked,
|
||||
this, &VAttachmentList::addAttachment);
|
||||
|
||||
m_clearBtn = new QPushButton(QIcon(":/resources/icons/clear_attachment.svg"), "");
|
||||
m_clearBtn->setToolTip(tr("Clear"));
|
||||
m_clearBtn->setProperty("FlatBtn", true);
|
||||
connect(m_clearBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
if (m_file && m_attachmentList->count() > 0) {
|
||||
int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
|
||||
tr("Are you sure to clear attachments of note "
|
||||
"<span style=\"%1\">%2</span>?")
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(m_file->getName()),
|
||||
tr("<span style=\"%1\">WARNING</span>: "
|
||||
"VNote will delete all the files in directory "
|
||||
"<span style=\"%2\">%3</span>."
|
||||
"You could find deleted files in the recycle bin "
|
||||
"of this notebook.<br>The operation is IRREVERSIBLE!")
|
||||
.arg(g_config->c_warningTextStyle)
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(m_file->fetchAttachmentFolderPath()),
|
||||
QMessageBox::Ok | QMessageBox::Cancel,
|
||||
QMessageBox::Ok,
|
||||
g_vnote->getMainWindow(),
|
||||
MessageBoxType::Danger);
|
||||
if (ret == QMessageBox::Ok) {
|
||||
if (!m_file->deleteAttachments()) {
|
||||
VUtils::showMessage(QMessageBox::Warning,
|
||||
tr("Warning"),
|
||||
tr("Fail to clear attachments of note <span style=\"%1\">%2</span>.")
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(m_file->getName()),
|
||||
tr("Please maintain the configureation file manually."),
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Ok,
|
||||
g_vnote->getMainWindow());
|
||||
}
|
||||
|
||||
m_attachmentList->clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
m_locateBtn = new QPushButton(QIcon(":/resources/icons/locate_attachment.svg"), "");
|
||||
m_locateBtn->setToolTip(tr("Open Folder"));
|
||||
m_locateBtn->setProperty("FlatBtn", true);
|
||||
connect(m_locateBtn, &QPushButton::clicked,
|
||||
this, [this]() {
|
||||
if (m_file && !m_file->getAttachmentFolder().isEmpty()) {
|
||||
QUrl url = QUrl::fromLocalFile(m_file->fetchAttachmentFolderPath());
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
});
|
||||
|
||||
m_numLabel = new QLabel();
|
||||
|
||||
QHBoxLayout *btnLayout = new QHBoxLayout;
|
||||
btnLayout->addWidget(m_addBtn);
|
||||
btnLayout->addWidget(m_clearBtn);
|
||||
btnLayout->addWidget(m_locateBtn);
|
||||
btnLayout->addStretch();
|
||||
btnLayout->addWidget(m_numLabel);
|
||||
|
||||
m_attachmentList = new QListWidget;
|
||||
m_attachmentList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_attachmentList->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
m_attachmentList->setEditTriggers(QAbstractItemView::SelectedClicked);
|
||||
connect(m_attachmentList, &QListWidget::customContextMenuRequested,
|
||||
this, &VAttachmentList::handleContextMenuRequested);
|
||||
connect(m_attachmentList, &QListWidget::itemActivated,
|
||||
this, &VAttachmentList::handleItemActivated);
|
||||
connect(m_attachmentList->itemDelegate(), &QAbstractItemDelegate::commitData,
|
||||
this, &VAttachmentList::handleListItemCommitData);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
mainLayout->addLayout(btnLayout);
|
||||
mainLayout->addWidget(m_attachmentList);
|
||||
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
void VAttachmentList::initActions()
|
||||
{
|
||||
m_openAct = new QAction(tr("&Open"), this);
|
||||
m_openAct->setToolTip(tr("Open current attachment file"));
|
||||
connect(m_openAct, &QAction::triggered,
|
||||
this, [this]() {
|
||||
QListWidgetItem *item = m_attachmentList->currentItem();
|
||||
handleItemActivated(item);
|
||||
});
|
||||
|
||||
m_deleteAct = new QAction(QIcon(":/resources/icons/delete_attachment.svg"),
|
||||
tr("&Delete"),
|
||||
this);
|
||||
m_deleteAct->setToolTip(tr("Delete selected attachments"));
|
||||
connect(m_deleteAct, &QAction::triggered,
|
||||
this, &VAttachmentList::deleteSelectedItems);
|
||||
|
||||
m_sortAct = new QAction(QIcon(":/resources/icons/sort.svg"),
|
||||
tr("&Sort"),
|
||||
this);
|
||||
m_sortAct->setToolTip(tr("Sort attachments manually"));
|
||||
connect(m_sortAct, &QAction::triggered,
|
||||
this, &VAttachmentList::sortItems);
|
||||
}
|
||||
|
||||
void VAttachmentList::setFile(VNoteFile *p_file)
|
||||
{
|
||||
m_file = p_file;
|
||||
updateContent();
|
||||
}
|
||||
|
||||
void VAttachmentList::updateContent()
|
||||
{
|
||||
bool enableAdd = true, enableDelete = true, enableClear = true, enableLocate = true;
|
||||
m_attachmentList->clear();
|
||||
|
||||
if (!m_file) {
|
||||
enableAdd = enableDelete = enableClear = enableLocate = false;
|
||||
} else {
|
||||
QString folder = m_file->getAttachmentFolder();
|
||||
const QVector<VAttachment> &attas = m_file->getAttachments();
|
||||
|
||||
if (folder.isEmpty()) {
|
||||
Q_ASSERT(attas.isEmpty());
|
||||
enableDelete = enableClear = enableLocate = false;
|
||||
} else if (attas.isEmpty()) {
|
||||
enableDelete = enableClear = false;
|
||||
} else {
|
||||
fillAttachmentList(attas);
|
||||
}
|
||||
}
|
||||
|
||||
m_addBtn->setEnabled(enableAdd);
|
||||
m_clearBtn->setEnabled(enableClear);
|
||||
m_locateBtn->setEnabled(enableLocate);
|
||||
|
||||
int cnt = m_attachmentList->count();
|
||||
if (cnt > 0) {
|
||||
m_numLabel->setText(tr("%1 %2").arg(cnt).arg(cnt > 1 ? tr("Files") : tr("File")));
|
||||
} else {
|
||||
m_numLabel->setText("");
|
||||
}
|
||||
}
|
||||
|
||||
void VAttachmentList::fillAttachmentList(const QVector<VAttachment> &p_attachments)
|
||||
{
|
||||
Q_ASSERT(m_attachmentList->count() == 0);
|
||||
for (int i = 0; i < p_attachments.size(); ++i) {
|
||||
const VAttachment &atta = p_attachments[i];
|
||||
QListWidgetItem *item = new QListWidgetItem(atta.m_name);
|
||||
item->setFlags(item->flags() | Qt::ItemIsEditable);
|
||||
item->setData(Qt::UserRole, atta.m_name);
|
||||
|
||||
m_attachmentList->addItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
void VAttachmentList::addAttachment()
|
||||
{
|
||||
if (!m_file) {
|
||||
return;
|
||||
}
|
||||
|
||||
static QString lastPath = QDir::homePath();
|
||||
QStringList files = QFileDialog::getOpenFileNames(g_vnote->getMainWindow(),
|
||||
tr("Select Files As Attachments"),
|
||||
lastPath);
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update lastPath
|
||||
lastPath = QFileInfo(files[0]).path();
|
||||
|
||||
int addedFiles = 0;
|
||||
for (int i = 0; i < files.size(); ++i) {
|
||||
if (!m_file->addAttachment(files[i])) {
|
||||
VUtils::showMessage(QMessageBox::Warning,
|
||||
tr("Warning"),
|
||||
tr("Fail to add attachment %1 for note <span style=\"%2\">%3</span>.")
|
||||
.arg(files[i])
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(m_file->getName()),
|
||||
"",
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Ok,
|
||||
g_vnote->getMainWindow());
|
||||
} else {
|
||||
++addedFiles;
|
||||
}
|
||||
}
|
||||
|
||||
updateContent();
|
||||
|
||||
if (addedFiles > 0) {
|
||||
g_vnote->getMainWindow()->showStatusMessage(tr("Added %1 %2 as attachments")
|
||||
.arg(addedFiles)
|
||||
.arg(addedFiles > 1 ? tr("files") : tr("file")));
|
||||
}
|
||||
}
|
||||
|
||||
void VAttachmentList::handleContextMenuRequested(QPoint p_pos)
|
||||
{
|
||||
// @p_pos is the position in the coordinate of VAttachmentList, no m_attachmentList.
|
||||
QListWidgetItem *item = m_attachmentList->itemAt(m_attachmentList->mapFromParent(p_pos));
|
||||
QMenu menu(this);
|
||||
menu.setToolTipsVisible(true);
|
||||
|
||||
if (!m_file) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item) {
|
||||
if (!item->isSelected()) {
|
||||
m_attachmentList->setCurrentItem(item, QItemSelectionModel::ClearAndSelect);
|
||||
}
|
||||
|
||||
if (m_attachmentList->selectedItems().size() == 1) {
|
||||
menu.addAction(m_openAct);
|
||||
}
|
||||
|
||||
menu.addAction(m_deleteAct);
|
||||
}
|
||||
|
||||
m_attachmentList->update();
|
||||
|
||||
if (m_file->getAttachments().size() > 1) {
|
||||
if (!menu.actions().isEmpty()) {
|
||||
menu.addSeparator();
|
||||
}
|
||||
|
||||
menu.addAction(m_sortAct);
|
||||
}
|
||||
|
||||
if (!menu.actions().isEmpty()) {
|
||||
menu.exec(mapToGlobal(p_pos));
|
||||
}
|
||||
}
|
||||
|
||||
void VAttachmentList::handleItemActivated(QListWidgetItem *p_item)
|
||||
{
|
||||
if (p_item) {
|
||||
Q_ASSERT(m_file);
|
||||
|
||||
QString name = p_item->text();
|
||||
QString folderPath = m_file->fetchAttachmentFolderPath();
|
||||
QUrl url = QUrl::fromLocalFile(QDir(folderPath).filePath(name));
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
void VAttachmentList::deleteSelectedItems()
|
||||
{
|
||||
QVector<QString> names;
|
||||
const QList<QListWidgetItem *> selectedItems = m_attachmentList->selectedItems();
|
||||
|
||||
if (selectedItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto const & item : selectedItems) {
|
||||
names.push_back(item->text());
|
||||
}
|
||||
|
||||
QString info = tr("Are you sure to delete these attachments of note "
|
||||
"<span style=\"%1\">%2</span>? "
|
||||
"You could find deleted files in the recycle "
|
||||
"bin of this notebook.<br>"
|
||||
"Click \"Cancel\" to leave them untouched.")
|
||||
.arg(g_config->c_dataTextStyle).arg(m_file->getName());
|
||||
VConfirmDeletionDialog dialog(tr("Confirm Deleting Attachments"),
|
||||
info,
|
||||
names,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
g_vnote->getMainWindow());
|
||||
if (dialog.exec()) {
|
||||
names = dialog.getConfirmedFiles();
|
||||
|
||||
if (!m_file->deleteAttachments(names)) {
|
||||
VUtils::showMessage(QMessageBox::Warning,
|
||||
tr("Warning"),
|
||||
tr("Fail to delete attachments of note <span style=\"%1\">%2</span>.")
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(m_file->getName()),
|
||||
tr("Please maintain the configureation file manually."),
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Ok,
|
||||
g_vnote->getMainWindow());
|
||||
}
|
||||
|
||||
updateContent();
|
||||
}
|
||||
}
|
||||
|
||||
void VAttachmentList::sortItems()
|
||||
{
|
||||
const QVector<VAttachment> &attas = m_file->getAttachments();
|
||||
if (attas.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
VSortDialog dialog(tr("Sort Attachments"),
|
||||
tr("Sort attachments in the configuration file."),
|
||||
g_vnote->getMainWindow());
|
||||
QTreeWidget *tree = dialog.getTreeWidget();
|
||||
tree->clear();
|
||||
tree->setColumnCount(1);
|
||||
tree->header()->setStretchLastSection(true);
|
||||
QStringList headers;
|
||||
headers << tr("Name");
|
||||
tree->setHeaderLabels(headers);
|
||||
|
||||
for (int i = 0; i < attas.size(); ++i) {
|
||||
QTreeWidgetItem *item = new QTreeWidgetItem(tree, QStringList(attas[i].m_name));
|
||||
|
||||
item->setData(0, Qt::UserRole, i);
|
||||
}
|
||||
|
||||
dialog.treeUpdated();
|
||||
|
||||
if (dialog.exec()) {
|
||||
int cnt = tree->topLevelItemCount();
|
||||
Q_ASSERT(cnt == attas.size());
|
||||
QVector<int> sortedIdx(cnt, -1);
|
||||
for (int i = 0; i < cnt; ++i) {
|
||||
QTreeWidgetItem *item = tree->topLevelItem(i);
|
||||
Q_ASSERT(item);
|
||||
sortedIdx[i] = item->data(0, Qt::UserRole).toInt();
|
||||
}
|
||||
|
||||
m_file->sortAttachments(sortedIdx);
|
||||
}
|
||||
}
|
||||
|
||||
void VAttachmentList::handleListItemCommitData(QWidget *p_itemEdit)
|
||||
{
|
||||
QString text = reinterpret_cast<QLineEdit *>(p_itemEdit)->text();
|
||||
QListWidgetItem *item = m_attachmentList->currentItem();
|
||||
Q_ASSERT(item && item->text() == text);
|
||||
|
||||
QString oldText = item->data(Qt::UserRole).toString();
|
||||
|
||||
if (oldText == text) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(oldText.toLower() == text.toLower())
|
||||
&& m_file->findAttachment(text, false) > -1) {
|
||||
// Name conflict.
|
||||
// Recover to old name.
|
||||
item->setText(oldText);
|
||||
} else {
|
||||
if (!m_file->renameAttachment(oldText, text)) {
|
||||
VUtils::showMessage(QMessageBox::Information,
|
||||
tr("Rename Attachment"),
|
||||
tr("Fail to rename attachment <span style=\"%1\">%2</span>.")
|
||||
.arg(g_config->c_dataTextStyle)
|
||||
.arg(oldText),
|
||||
"",
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Ok,
|
||||
this);
|
||||
// Recover to old name.
|
||||
item->setText(oldText);
|
||||
} else {
|
||||
// Change the data.
|
||||
item->setData(Qt::UserRole, text);
|
||||
}
|
||||
}
|
||||
}
|
60
src/vattachmentlist.h
Normal file
@ -0,0 +1,60 @@
|
||||
#ifndef VATTACHMENTLIST_H
|
||||
#define VATTACHMENTLIST_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QVector>
|
||||
#include "vnotefile.h"
|
||||
|
||||
class QPushButton;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
class QLabel;
|
||||
class VNoteFile;
|
||||
class QAction;
|
||||
|
||||
class VAttachmentList : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VAttachmentList(QWidget *p_parent = 0);
|
||||
|
||||
void setFile(VNoteFile *p_file);
|
||||
|
||||
private slots:
|
||||
void addAttachment();
|
||||
|
||||
void handleContextMenuRequested(QPoint p_pos);
|
||||
|
||||
void handleItemActivated(QListWidgetItem *p_item);
|
||||
|
||||
void deleteSelectedItems();
|
||||
|
||||
void sortItems();
|
||||
|
||||
void handleListItemCommitData(QWidget *p_itemEdit);
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
void initActions();
|
||||
|
||||
// Update attachment info of m_file.
|
||||
void updateContent();
|
||||
|
||||
void fillAttachmentList(const QVector<VAttachment> &p_attachments);
|
||||
|
||||
QPushButton *m_addBtn;
|
||||
QPushButton *m_clearBtn;
|
||||
QPushButton *m_locateBtn;
|
||||
QLabel *m_numLabel;
|
||||
|
||||
QListWidget *m_attachmentList;
|
||||
|
||||
QAction *m_openAct;
|
||||
QAction *m_deleteAct;
|
||||
QAction *m_sortAct;
|
||||
|
||||
VNoteFile *m_file;
|
||||
};
|
||||
|
||||
#endif // VATTACHMENTLIST_H
|
@ -1,60 +1,44 @@
|
||||
#include "vbuttonwithwidget.h"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QRect>
|
||||
#include <QMenu>
|
||||
|
||||
VButtonWithWidget::VButtonWithWidget(QWidget *p_parent)
|
||||
: QPushButton(p_parent), m_popupWidget(NULL)
|
||||
VButtonWithWidget::VButtonWithWidget(QWidget *p_widget,
|
||||
QWidget *p_parent)
|
||||
: QPushButton(p_parent), m_popupWidget(p_widget)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
VButtonWithWidget::VButtonWithWidget(const QString &p_text, QWidget *p_parent)
|
||||
: QPushButton(p_text, p_parent), m_popupWidget(NULL)
|
||||
VButtonWithWidget::VButtonWithWidget(const QString &p_text,
|
||||
QWidget *p_widget,
|
||||
QWidget *p_parent)
|
||||
: QPushButton(p_text, p_parent), m_popupWidget(p_widget)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
VButtonWithWidget::VButtonWithWidget(const QIcon &p_icon,
|
||||
const QString &p_text,
|
||||
QWidget *p_widget,
|
||||
QWidget *p_parent)
|
||||
: QPushButton(p_icon, p_text, p_parent), m_popupWidget(NULL)
|
||||
: QPushButton(p_icon, p_text, p_parent), m_popupWidget(p_widget)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
VButtonWithWidget::~VButtonWithWidget()
|
||||
{
|
||||
if (m_popupWidget) {
|
||||
delete m_popupWidget;
|
||||
}
|
||||
}
|
||||
|
||||
void VButtonWithWidget::init()
|
||||
{
|
||||
connect(this, &QPushButton::clicked,
|
||||
this, &VButtonWithWidget::showPopupWidget);
|
||||
}
|
||||
m_popupWidget->setParent(this);
|
||||
|
||||
void VButtonWithWidget::setPopupWidget(QWidget *p_widget)
|
||||
{
|
||||
if (m_popupWidget) {
|
||||
delete m_popupWidget;
|
||||
}
|
||||
QMenu *menu = new QMenu(this);
|
||||
VButtonWidgetAction *act = new VButtonWidgetAction(m_popupWidget, menu);
|
||||
menu->addAction(act);
|
||||
connect(menu, &QMenu::aboutToShow,
|
||||
this, [this]() {
|
||||
emit popupWidgetAboutToShow(m_popupWidget);
|
||||
});
|
||||
|
||||
m_popupWidget = p_widget;
|
||||
if (m_popupWidget) {
|
||||
m_popupWidget->hide();
|
||||
m_popupWidget->setParent(NULL);
|
||||
|
||||
Qt::WindowFlags flags = Qt::Popup;
|
||||
m_popupWidget->setWindowFlags(flags);
|
||||
m_popupWidget->setWindowModality(Qt::NonModal);
|
||||
|
||||
// Let popup widget to hide itself if focus lost.
|
||||
m_popupWidget->installEventFilter(this);
|
||||
}
|
||||
setMenu(menu);
|
||||
}
|
||||
|
||||
QWidget *VButtonWithWidget::getPopupWidget() const
|
||||
@ -64,35 +48,5 @@ QWidget *VButtonWithWidget::getPopupWidget() const
|
||||
|
||||
void VButtonWithWidget::showPopupWidget()
|
||||
{
|
||||
if (m_popupWidget->isVisible()) {
|
||||
m_popupWidget->hide();
|
||||
} else {
|
||||
emit popupWidgetAboutToShow(m_popupWidget);
|
||||
|
||||
// Calculate the position of the popup widget.
|
||||
QPoint btnPos = mapToGlobal(QPoint(0, 0));
|
||||
int btnWidth = width();
|
||||
|
||||
int popupWidth = btnWidth * 10;
|
||||
int popupHeight = height() * 10;
|
||||
int popupX = btnPos.x() + btnWidth - popupWidth;
|
||||
int popupY = btnPos.y() - popupHeight - 10;
|
||||
|
||||
m_popupWidget->setGeometry(popupX, popupY, popupWidth, popupHeight);
|
||||
m_popupWidget->show();
|
||||
}
|
||||
}
|
||||
|
||||
bool VButtonWithWidget::eventFilter(QObject *p_obj, QEvent *p_event)
|
||||
{
|
||||
if (p_event->type() == QEvent::MouseButtonRelease) {
|
||||
QMouseEvent *eve = dynamic_cast<QMouseEvent *>(p_event);
|
||||
QPoint clickPos = eve->pos();
|
||||
const QRect &rect = m_popupWidget->rect();
|
||||
if (!rect.contains(clickPos)) {
|
||||
m_popupWidget->hide();
|
||||
}
|
||||
}
|
||||
|
||||
return QPushButton::eventFilter(p_obj, p_event);
|
||||
showMenu();
|
||||
}
|
||||
|
@ -4,35 +4,53 @@
|
||||
#include <QPushButton>
|
||||
#include <QString>
|
||||
#include <QIcon>
|
||||
#include <QWidgetAction>
|
||||
|
||||
class VButtonWidgetAction : public QWidgetAction
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VButtonWidgetAction(QWidget *p_widget, QWidget *p_parent)
|
||||
: QWidgetAction(p_parent), m_widget(p_widget)
|
||||
{
|
||||
}
|
||||
|
||||
QWidget *createWidget(QWidget *p_parent)
|
||||
{
|
||||
m_widget->setParent(p_parent);
|
||||
return m_widget;
|
||||
}
|
||||
|
||||
private:
|
||||
QWidget *m_widget;
|
||||
};
|
||||
|
||||
// A QPushButton with popup widget.
|
||||
class VButtonWithWidget : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VButtonWithWidget(QWidget *p_parent = Q_NULLPTR);
|
||||
VButtonWithWidget(const QString &p_text, QWidget *p_parent = Q_NULLPTR);
|
||||
VButtonWithWidget(QWidget *p_widget,
|
||||
QWidget *p_parent = Q_NULLPTR);
|
||||
|
||||
VButtonWithWidget(const QString &p_text,
|
||||
QWidget *p_widget,
|
||||
QWidget *p_parent = Q_NULLPTR);
|
||||
|
||||
VButtonWithWidget(const QIcon &p_icon,
|
||||
const QString &p_text,
|
||||
QWidget *p_widget,
|
||||
QWidget *p_parent = Q_NULLPTR);
|
||||
~VButtonWithWidget();
|
||||
|
||||
// Set the widget which will transfer the ownership to VButtonWithWidget.
|
||||
void setPopupWidget(QWidget *p_widget);
|
||||
|
||||
QWidget *getPopupWidget() const;
|
||||
|
||||
// Show the popup widget.
|
||||
void showPopupWidget();
|
||||
|
||||
signals:
|
||||
// Emit when popup widget is about to show.
|
||||
void popupWidgetAboutToShow(QWidget *p_widget);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
// Show the popup widget.
|
||||
void showPopupWidget();
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
|
@ -136,6 +136,13 @@ void VConfigManager::initialize()
|
||||
m_imageFolderExt = getConfigFromSettings("global",
|
||||
"external_image_folder").toString();
|
||||
|
||||
m_attachmentFolder = getConfigFromSettings("global",
|
||||
"attachment_folder").toString();
|
||||
if (m_attachmentFolder.isEmpty()) {
|
||||
// Reset the default folder.
|
||||
m_attachmentFolder = resetDefaultConfig("global", "attachment_folder").toString();
|
||||
}
|
||||
|
||||
m_enableTrailingSpaceHighlight = getConfigFromSettings("global",
|
||||
"enable_trailing_space_highlight").toBool();
|
||||
|
||||
@ -230,7 +237,7 @@ void VConfigManager::readNotebookFromSettings(QVector<VNotebook *> &p_notebooks,
|
||||
QString name = userSettings->value("name").toString();
|
||||
QString path = userSettings->value("path").toString();
|
||||
VNotebook *notebook = new VNotebook(name, path, parent);
|
||||
notebook->readConfig();
|
||||
notebook->readConfigNotebook();
|
||||
p_notebooks.append(notebook);
|
||||
}
|
||||
userSettings->endArray();
|
||||
|
@ -213,6 +213,11 @@ public:
|
||||
void setImageFolderExt(const QString &p_folder);
|
||||
bool isCustomImageFolderExt() const;
|
||||
|
||||
const QString &getAttachmentFolder() const;
|
||||
// Empty string to reset the default folder.
|
||||
void setAttachmentFolder(const QString &p_folder);
|
||||
bool isCustomAttachmentFolder() const;
|
||||
|
||||
bool getEnableTrailingSpaceHighlight() const;
|
||||
void setEnableTrailingSapceHighlight(bool p_enabled);
|
||||
|
||||
@ -462,6 +467,10 @@ private:
|
||||
// Each file can specify its custom folder.
|
||||
QString m_imageFolderExt;
|
||||
|
||||
// Global default folder name to store attachments of all the notes.
|
||||
// Each notebook can specify its custom folder.
|
||||
QString m_attachmentFolder;
|
||||
|
||||
// Enable trailing-space highlight.
|
||||
bool m_enableTrailingSpaceHighlight;
|
||||
|
||||
@ -1140,6 +1149,32 @@ inline bool VConfigManager::isCustomImageFolderExt() const
|
||||
return m_imageFolderExt != getDefaultConfig("global", "external_image_folder").toString();
|
||||
}
|
||||
|
||||
inline const QString &VConfigManager::getAttachmentFolder() const
|
||||
{
|
||||
return m_attachmentFolder;
|
||||
}
|
||||
|
||||
inline void VConfigManager::setAttachmentFolder(const QString &p_folder)
|
||||
{
|
||||
if (p_folder.isEmpty()) {
|
||||
// Reset the default folder.
|
||||
m_attachmentFolder = resetDefaultConfig("global", "attachment_folder").toString();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_attachmentFolder == p_folder) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_attachmentFolder = p_folder;
|
||||
setConfigToSettings("global", "attachment_folder", m_attachmentFolder);
|
||||
}
|
||||
|
||||
inline bool VConfigManager::isCustomAttachmentFolder() const
|
||||
{
|
||||
return m_attachmentFolder != getDefaultConfig("global", "attachment_folder").toString();
|
||||
}
|
||||
|
||||
inline bool VConfigManager::getEnableTrailingSpaceHighlight() const
|
||||
{
|
||||
return m_enableTrailingSpaceHighlight;
|
||||
|
@ -29,7 +29,9 @@ namespace DirConfig
|
||||
static const QString c_version = "version";
|
||||
static const QString c_subDirectories = "sub_directories";
|
||||
static const QString c_files = "files";
|
||||
static const QString c_attachments = "attachments";
|
||||
static const QString c_imageFolder = "image_folder";
|
||||
static const QString c_attachmentFolder = "attachment_folder";
|
||||
static const QString c_recycleBinFolder = "recycle_bin_folder";
|
||||
static const QString c_name = "name";
|
||||
static const QString c_createdTime = "created_time";
|
||||
|
@ -163,6 +163,13 @@ bool VDirectory::writeToConfig() const
|
||||
return writeToConfig(json);
|
||||
}
|
||||
|
||||
bool VDirectory::updateFileConfig(const VNoteFile *p_file)
|
||||
{
|
||||
Q_ASSERT(m_opened);
|
||||
Q_UNUSED(p_file);
|
||||
return writeToConfig();
|
||||
}
|
||||
|
||||
bool VDirectory::writeToConfig(const QJsonObject &p_json) const
|
||||
{
|
||||
return VConfigManager::writeDirectoryConfig(fetchPath(), p_json);
|
||||
|
@ -82,6 +82,9 @@ public:
|
||||
QString getNotebookName() const;
|
||||
bool isExpanded() const;
|
||||
void setExpanded(bool p_expanded);
|
||||
|
||||
// Reorder files in m_files by index.
|
||||
// Move [@p_first, @p_last] to @p_destStart.
|
||||
void reorderFiles(int p_first, int p_last, int p_destStart);
|
||||
|
||||
// Serialize current instance to json.
|
||||
@ -97,6 +100,9 @@ public:
|
||||
// notebook.
|
||||
bool writeToConfig() const;
|
||||
|
||||
// Write the config of @p_file to config file.
|
||||
bool updateFileConfig(const VNoteFile *p_file);
|
||||
|
||||
// Try to load file given relative path @p_filePath.
|
||||
VNoteFile *tryLoadFile(QStringList &p_filePath);
|
||||
|
||||
|
@ -27,6 +27,8 @@
|
||||
#include "dialog/vorphanfileinfodialog.h"
|
||||
#include "vsingleinstanceguard.h"
|
||||
#include "vnotefile.h"
|
||||
#include "vbuttonwithwidget.h"
|
||||
#include "vattachmentlist.h"
|
||||
|
||||
extern VConfigManager *g_config;
|
||||
|
||||
@ -124,7 +126,12 @@ void VMainWindow::setupUI()
|
||||
connect(notebookSelector, &VNotebookSelector::notebookUpdated,
|
||||
editArea, &VEditArea::handleNotebookUpdated);
|
||||
connect(notebookSelector, &VNotebookSelector::notebookCreated,
|
||||
directoryTree, &VDirectoryTree::newRootDirectory);
|
||||
directoryTree, [this](const QString &p_name, bool p_import) {
|
||||
Q_UNUSED(p_name);
|
||||
if (!p_import) {
|
||||
directoryTree->newRootDirectory();
|
||||
}
|
||||
});
|
||||
|
||||
connect(fileList, &VFileList::fileClicked,
|
||||
editArea, &VEditArea::openFile);
|
||||
@ -203,6 +210,7 @@ void VMainWindow::initToolBar()
|
||||
initFileToolBar(iconSize);
|
||||
initViewToolBar(iconSize);
|
||||
initEditToolBar(iconSize);
|
||||
initNoteToolBar(iconSize);
|
||||
}
|
||||
|
||||
void VMainWindow::initViewToolBar(QSize p_iconSize)
|
||||
@ -319,6 +327,38 @@ void VMainWindow::initEditToolBar(QSize p_iconSize)
|
||||
setActionsEnabled(m_editToolBar, false);
|
||||
}
|
||||
|
||||
void VMainWindow::initNoteToolBar(QSize p_iconSize)
|
||||
{
|
||||
QToolBar *noteToolBar = addToolBar(tr("Note Toolbar"));
|
||||
noteToolBar->setObjectName("NoteToolBar");
|
||||
noteToolBar->setMovable(false);
|
||||
if (p_iconSize.isValid()) {
|
||||
noteToolBar->setIconSize(p_iconSize);
|
||||
}
|
||||
|
||||
noteToolBar->addSeparator();
|
||||
|
||||
// Attachment.
|
||||
m_attachmentList = new VAttachmentList(this);
|
||||
m_attachmentBtn = new VButtonWithWidget(QIcon(":/resources/icons/attachment.svg"),
|
||||
"",
|
||||
m_attachmentList,
|
||||
this);
|
||||
m_attachmentBtn->setToolTip(tr("Attachments"));
|
||||
m_attachmentBtn->setStatusTip(tr("Manage current note's attachments"));
|
||||
m_attachmentBtn->setProperty("CornerBtn", true);
|
||||
m_attachmentBtn->setFocusPolicy(Qt::NoFocus);
|
||||
|
||||
connect(m_attachmentBtn, &VButtonWithWidget::popupWidgetAboutToShow,
|
||||
this, [this]() {
|
||||
m_attachmentList->setFile(dynamic_cast<VNoteFile *>(m_curFile.data()));
|
||||
});
|
||||
|
||||
m_attachmentBtn->setEnabled(false);
|
||||
|
||||
noteToolBar->addWidget(m_attachmentBtn);
|
||||
}
|
||||
|
||||
void VMainWindow::initFileToolBar(QSize p_iconSize)
|
||||
{
|
||||
QToolBar *fileToolBar = addToolBar(tr("Note"));
|
||||
@ -1511,6 +1551,14 @@ void VMainWindow::updateActionStateFromTabStatusChange(const VFile *p_file,
|
||||
deleteNoteAct->setEnabled(p_file && p_file->getType() == FileType::Note);
|
||||
noteInfoAct->setEnabled(p_file && !systemFile);
|
||||
|
||||
m_attachmentBtn->setEnabled(p_file && p_file->getType() == FileType::Note);
|
||||
if (m_attachmentBtn->isEnabled()
|
||||
&& !dynamic_cast<const VNoteFile *>(p_file)->getAttachments().isEmpty()) {
|
||||
m_attachmentBtn->setIcon(QIcon(":/resources/icons/attachment_full.svg"));
|
||||
} else {
|
||||
m_attachmentBtn->setIcon(QIcon(":/resources/icons/attachment.svg"));
|
||||
}
|
||||
|
||||
m_insertImageAct->setEnabled(p_file && p_editMode);
|
||||
|
||||
setActionsEnabled(m_editToolBar, p_file && p_editMode);
|
||||
|
@ -35,6 +35,8 @@ class VSingleInstanceGuard;
|
||||
class QTimer;
|
||||
class QSystemTrayIcon;
|
||||
class QShortcut;
|
||||
class VButtonWithWidget;
|
||||
class VAttachmentList;
|
||||
|
||||
class VMainWindow : public QMainWindow
|
||||
{
|
||||
@ -65,6 +67,9 @@ public:
|
||||
// Try to open @p_filePath as internal note.
|
||||
bool tryOpenInternalFile(const QString &p_filePath);
|
||||
|
||||
// Show a temporary message in status bar.
|
||||
void showStatusMessage(const QString &p_msg);
|
||||
|
||||
private slots:
|
||||
void importNoteFromFile();
|
||||
void viewSettings();
|
||||
@ -107,9 +112,6 @@ private slots:
|
||||
void printNote();
|
||||
void exportAsPDF();
|
||||
|
||||
// Show a temporary message in status bar.
|
||||
void showStatusMessage(const QString &p_msg);
|
||||
|
||||
// Handle Vim status updated.
|
||||
void handleVimStatusUpdated(const VVim *p_vim);
|
||||
|
||||
@ -140,6 +142,8 @@ private:
|
||||
void initFileToolBar(QSize p_iconSize = QSize());
|
||||
void initViewToolBar(QSize p_iconSize = QSize());
|
||||
|
||||
void initNoteToolBar(QSize p_iconSize = QSize());
|
||||
|
||||
// Init the Edit toolbar.
|
||||
void initEditToolBar(QSize p_iconSize = QSize());
|
||||
|
||||
@ -249,6 +253,12 @@ private:
|
||||
// Edit Toolbar.
|
||||
QToolBar *m_editToolBar;
|
||||
|
||||
// Attachment button.
|
||||
VButtonWithWidget *m_attachmentBtn;
|
||||
|
||||
// Attachment list.
|
||||
VAttachmentList *m_attachmentList;
|
||||
|
||||
QVector<QPixmap> predefinedColorPixmaps;
|
||||
|
||||
// Single instance guard.
|
||||
|
@ -291,9 +291,13 @@ void VMdEdit::clearUnusedImages()
|
||||
VConfirmDeletionDialog dialog(tr("Confirm Cleaning Up Unused Images"),
|
||||
info,
|
||||
unusedImages,
|
||||
true,
|
||||
g_config->getConfirmImagesCleanUp(),
|
||||
true,
|
||||
this);
|
||||
if (dialog.exec()) {
|
||||
unusedImages = dialog.getConfirmedFiles();
|
||||
g_config->setConfirmImagesCleanUp(dialog.getAskAgainEnabled());
|
||||
} else {
|
||||
unusedImages.clear();
|
||||
}
|
||||
|
@ -123,5 +123,12 @@
|
||||
<file>utils/highlightjs/highlightjs-line-numbers.min.js</file>
|
||||
<file>resources/icons/recycle_bin.svg</file>
|
||||
<file>resources/icons/empty_recycle_bin.svg</file>
|
||||
<file>resources/icons/attachment.svg</file>
|
||||
<file>resources/icons/attachment_full.svg</file>
|
||||
<file>resources/icons/add_attachment.svg</file>
|
||||
<file>resources/icons/clear_attachment.svg</file>
|
||||
<file>resources/icons/locate_attachment.svg</file>
|
||||
<file>resources/icons/delete_attachment.svg</file>
|
||||
<file>resources/icons/sort.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@ -24,7 +24,7 @@ VNotebook::~VNotebook()
|
||||
delete m_rootDir;
|
||||
}
|
||||
|
||||
bool VNotebook::readConfig()
|
||||
bool VNotebook::readConfigNotebook()
|
||||
{
|
||||
QJsonObject configJson = VConfigManager::readDirectoryConfig(m_path);
|
||||
if (configJson.isEmpty()) {
|
||||
@ -44,6 +44,20 @@ bool VNotebook::readConfig()
|
||||
m_recycleBinFolder = it.value().toString();
|
||||
}
|
||||
|
||||
// [attachment_folder] section.
|
||||
// SHOULD be processed at last.
|
||||
it = configJson.find(DirConfig::c_attachmentFolder);
|
||||
if (it != configJson.end()) {
|
||||
m_attachmentFolder = it.value().toString();
|
||||
}
|
||||
|
||||
// We do not allow empty attachment folder.
|
||||
if (m_attachmentFolder.isEmpty()) {
|
||||
m_attachmentFolder = g_config->getAttachmentFolder();
|
||||
Q_ASSERT(!m_attachmentFolder.isEmpty());
|
||||
writeConfigNotebook();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -54,6 +68,9 @@ QJsonObject VNotebook::toConfigJsonNotebook() const
|
||||
// [image_folder] section.
|
||||
json[DirConfig::c_imageFolder] = m_imageFolder;
|
||||
|
||||
// [attachment_folder] section.
|
||||
json[DirConfig::c_attachmentFolder] = m_attachmentFolder;
|
||||
|
||||
// [recycle_bin_folder] section.
|
||||
json[DirConfig::c_recycleBinFolder] = m_recycleBinFolder;
|
||||
|
||||
@ -126,8 +143,11 @@ bool VNotebook::open()
|
||||
return m_rootDir->open();
|
||||
}
|
||||
|
||||
VNotebook *VNotebook::createNotebook(const QString &p_name, const QString &p_path,
|
||||
bool p_import, const QString &p_imageFolder,
|
||||
VNotebook *VNotebook::createNotebook(const QString &p_name,
|
||||
const QString &p_path,
|
||||
bool p_import,
|
||||
const QString &p_imageFolder,
|
||||
const QString &p_attachmentFolder,
|
||||
QObject *p_parent)
|
||||
{
|
||||
VNotebook *nb = new VNotebook(p_name, p_path, p_parent);
|
||||
@ -136,10 +156,18 @@ VNotebook *VNotebook::createNotebook(const QString &p_name, const QString &p_pat
|
||||
// its image folder.
|
||||
nb->setImageFolder(p_imageFolder);
|
||||
|
||||
// If @p_attachmentFolder is empty, use global configured folder.
|
||||
QString attachmentFolder = p_attachmentFolder;
|
||||
if (attachmentFolder.isEmpty()) {
|
||||
attachmentFolder = g_config->getAttachmentFolder();
|
||||
}
|
||||
|
||||
nb->setAttachmentFolder(attachmentFolder);
|
||||
|
||||
// Check if there alread exists a config file.
|
||||
if (p_import && VConfigManager::directoryConfigExist(p_path)) {
|
||||
qDebug() << "import existing notebook";
|
||||
nb->readConfig();
|
||||
nb->readConfigNotebook();
|
||||
return nb;
|
||||
}
|
||||
|
||||
@ -271,6 +299,16 @@ const QString &VNotebook::getImageFolderConfig() const
|
||||
return m_imageFolder;
|
||||
}
|
||||
|
||||
const QString &VNotebook::getAttachmentFolder() const
|
||||
{
|
||||
return m_attachmentFolder;
|
||||
}
|
||||
|
||||
void VNotebook::setAttachmentFolder(const QString &p_attachmentFolder)
|
||||
{
|
||||
m_attachmentFolder = p_attachmentFolder;
|
||||
}
|
||||
|
||||
bool VNotebook::isOpened() const
|
||||
{
|
||||
return m_rootDir->isOpened();
|
||||
|
@ -43,8 +43,11 @@ public:
|
||||
|
||||
void rename(const QString &p_name);
|
||||
|
||||
static VNotebook *createNotebook(const QString &p_name, const QString &p_path,
|
||||
bool p_import, const QString &p_imageFolder,
|
||||
static VNotebook *createNotebook(const QString &p_name,
|
||||
const QString &p_path,
|
||||
bool p_import,
|
||||
const QString &p_imageFolder,
|
||||
const QString &p_attachmentFolder,
|
||||
QObject *p_parent = 0);
|
||||
|
||||
static bool deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles);
|
||||
@ -56,6 +59,11 @@ public:
|
||||
// Return m_imageFolder.
|
||||
const QString &getImageFolderConfig() const;
|
||||
|
||||
// Different from image folder. We could not change the attachment folder
|
||||
// of a notebook once it has been created.
|
||||
// Get the attachment folder for this notebook to use.
|
||||
const QString &getAttachmentFolder() const;
|
||||
|
||||
// Return m_recycleBinFolder.
|
||||
const QString &getRecycleBinFolder() const;
|
||||
|
||||
@ -64,9 +72,10 @@ public:
|
||||
|
||||
void setImageFolder(const QString &p_imageFolder);
|
||||
|
||||
// Read configurations (excluding "sub_directories" and "files" section)
|
||||
// from root directory config file.
|
||||
bool readConfig();
|
||||
void setAttachmentFolder(const QString &p_attachmentFolder);
|
||||
|
||||
// Read configurations (only notebook part) directly from root directory config file.
|
||||
bool readConfigNotebook();
|
||||
|
||||
// Write configurations only related to notebook to root directory config file.
|
||||
bool writeConfigNotebook() const;
|
||||
@ -95,6 +104,10 @@ private:
|
||||
// Otherwise, VNote will use the global configured folder.
|
||||
QString m_imageFolder;
|
||||
|
||||
// Folder name to store attachments.
|
||||
// Should not be empty and changed once a notebook is created.
|
||||
QString m_attachmentFolder;
|
||||
|
||||
// Folder name to store deleted files.
|
||||
// Could be relative or absolute.
|
||||
QString m_recycleBinFolder;
|
||||
|
@ -271,9 +271,10 @@ bool VNotebookSelector::newNotebook()
|
||||
createNotebook(dialog.getNameInput(),
|
||||
dialog.getPathInput(),
|
||||
dialog.isImportExistingNotebook(),
|
||||
dialog.getImageFolder());
|
||||
dialog.getImageFolder(),
|
||||
dialog.getAttachmentFolder());
|
||||
|
||||
emit notebookCreated();
|
||||
emit notebookCreated(dialog.getNameInput(), dialog.isImportExistingNotebook());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -283,10 +284,12 @@ bool VNotebookSelector::newNotebook()
|
||||
void VNotebookSelector::createNotebook(const QString &p_name,
|
||||
const QString &p_path,
|
||||
bool p_import,
|
||||
const QString &p_imageFolder)
|
||||
const QString &p_imageFolder,
|
||||
const QString &p_attachmentFolder)
|
||||
{
|
||||
VNotebook *nb = VNotebook::createNotebook(p_name, p_path, p_import,
|
||||
p_imageFolder, m_vnote);
|
||||
p_imageFolder, p_attachmentFolder,
|
||||
m_vnote);
|
||||
if (!nb) {
|
||||
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
|
||||
tr("Fail to create notebook "
|
||||
@ -383,6 +386,7 @@ void VNotebookSelector::editNotebookInfo()
|
||||
m_notebooks, this);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
bool updated = false;
|
||||
bool configUpdated = false;
|
||||
QString name = dialog.getName();
|
||||
if (name != curName) {
|
||||
updated = true;
|
||||
@ -393,8 +397,12 @@ void VNotebookSelector::editNotebookInfo()
|
||||
|
||||
QString imageFolder = dialog.getImageFolder();
|
||||
if (imageFolder != notebook->getImageFolderConfig()) {
|
||||
updated = true;
|
||||
configUpdated = true;
|
||||
notebook->setImageFolder(imageFolder);
|
||||
}
|
||||
|
||||
if (configUpdated) {
|
||||
updated = true;
|
||||
notebook->writeConfigNotebook();
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ signals:
|
||||
void notebookUpdated(const VNotebook *p_notebook);
|
||||
|
||||
// Emit after creating a new notebook.
|
||||
void notebookCreated();
|
||||
void notebookCreated(const QString &p_name, bool p_import);
|
||||
|
||||
public slots:
|
||||
bool newNotebook();
|
||||
@ -62,8 +62,10 @@ private:
|
||||
|
||||
// If @p_import is true, we will use the existing config file.
|
||||
// If @p_imageFolder is empty, we will use the global one.
|
||||
// If @p_attachmentFolder is empty, we will use the global one.
|
||||
void createNotebook(const QString &p_name, const QString &p_path,
|
||||
bool p_import, const QString &p_imageFolder);
|
||||
bool p_import, const QString &p_imageFolder,
|
||||
const QString &p_attachmentFolder);
|
||||
|
||||
void deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles);
|
||||
void addNotebookItem(const QString &p_name);
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include <QDebug>
|
||||
#include <QTextEdit>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QDebug>
|
||||
|
||||
#include "utils/vutils.h"
|
||||
@ -14,8 +16,12 @@ VNoteFile::VNoteFile(VDirectory *p_directory,
|
||||
FileType p_type,
|
||||
bool p_modifiable,
|
||||
QDateTime p_createdTimeUtc,
|
||||
QDateTime p_modifiedTimeUtc)
|
||||
: VFile(p_directory, p_name, p_type, p_modifiable, p_createdTimeUtc, p_modifiedTimeUtc)
|
||||
QDateTime p_modifiedTimeUtc,
|
||||
const QString &p_attachmentFolder,
|
||||
const QVector<VAttachment> &p_attachments)
|
||||
: VFile(p_directory, p_name, p_type, p_modifiable, p_createdTimeUtc, p_modifiedTimeUtc),
|
||||
m_attachmentFolder(p_attachmentFolder),
|
||||
m_attachments(p_attachments)
|
||||
{
|
||||
}
|
||||
|
||||
@ -124,6 +130,14 @@ VNoteFile *VNoteFile::fromJson(VDirectory *p_directory,
|
||||
FileType p_type,
|
||||
bool p_modifiable)
|
||||
{
|
||||
// Attachments.
|
||||
QJsonArray attachmentJson = p_json[DirConfig::c_attachments].toArray();
|
||||
QVector<VAttachment> attachments;
|
||||
for (int i = 0; i < attachmentJson.size(); ++i) {
|
||||
QJsonObject attachmentItem = attachmentJson[i].toObject();
|
||||
attachments.push_back(VAttachment(attachmentItem[DirConfig::c_name].toString()));
|
||||
}
|
||||
|
||||
return new VNoteFile(p_directory,
|
||||
p_json[DirConfig::c_name].toString(),
|
||||
p_type,
|
||||
@ -131,7 +145,9 @@ VNoteFile *VNoteFile::fromJson(VDirectory *p_directory,
|
||||
QDateTime::fromString(p_json[DirConfig::c_createdTime].toString(),
|
||||
Qt::ISODate),
|
||||
QDateTime::fromString(p_json[DirConfig::c_modifiedTime].toString(),
|
||||
Qt::ISODate));
|
||||
Qt::ISODate),
|
||||
p_json[DirConfig::c_attachmentFolder].toString(),
|
||||
attachments);
|
||||
}
|
||||
|
||||
QJsonObject VNoteFile::toConfigJson() const
|
||||
@ -140,6 +156,18 @@ QJsonObject VNoteFile::toConfigJson() const
|
||||
item[DirConfig::c_name] = m_name;
|
||||
item[DirConfig::c_createdTime] = m_createdTimeUtc.toString(Qt::ISODate);
|
||||
item[DirConfig::c_modifiedTime] = m_modifiedTimeUtc.toString(Qt::ISODate);
|
||||
item[DirConfig::c_attachmentFolder] = m_attachmentFolder;
|
||||
|
||||
// Attachments.
|
||||
QJsonArray attachmentJson;
|
||||
for (int i = 0; i < m_attachments.size(); ++i) {
|
||||
const VAttachment &item = m_attachments[i];
|
||||
QJsonObject attachmentItem;
|
||||
attachmentItem[DirConfig::c_name] = item.m_name;
|
||||
attachmentJson.append(attachmentItem);
|
||||
}
|
||||
|
||||
item[DirConfig::c_attachments] = attachmentJson;
|
||||
|
||||
return item;
|
||||
}
|
||||
@ -185,3 +213,150 @@ void VNoteFile::deleteInternalImages()
|
||||
qDebug() << "delete" << deleted << "images for" << m_name << fetchPath();
|
||||
}
|
||||
|
||||
bool VNoteFile::addAttachment(const QString &p_file)
|
||||
{
|
||||
if (p_file.isEmpty() || !QFileInfo::exists(p_file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString folderPath = fetchAttachmentFolderPath();
|
||||
QString name = VUtils::fileNameFromPath(p_file);
|
||||
Q_ASSERT(!name.isEmpty());
|
||||
name = VUtils::getFileNameWithSequence(folderPath, name);
|
||||
QString destPath = QDir(folderPath).filePath(name);
|
||||
if (!VUtils::copyFile(p_file, destPath, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_attachments.push_back(VAttachment(name));
|
||||
|
||||
if (!getDirectory()->updateFileConfig(this)) {
|
||||
qWarning() << "fail to update config of file" << m_name
|
||||
<< "in directory" << fetchBasePath();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString VNoteFile::fetchAttachmentFolderPath()
|
||||
{
|
||||
QString folderPath = QDir(fetchBasePath()).filePath(getNotebook()->getAttachmentFolder());
|
||||
if (m_attachmentFolder.isEmpty()) {
|
||||
m_attachmentFolder = VUtils::getRandomFileName(folderPath);
|
||||
}
|
||||
|
||||
folderPath = QDir(folderPath).filePath(m_attachmentFolder);
|
||||
if (!QFileInfo::exists(folderPath)) {
|
||||
QDir dir;
|
||||
if (!dir.mkpath(folderPath)) {
|
||||
qWarning() << "fail to create attachment folder of notebook" << m_name << folderPath;
|
||||
}
|
||||
}
|
||||
|
||||
return folderPath;
|
||||
}
|
||||
|
||||
bool VNoteFile::deleteAttachments()
|
||||
{
|
||||
if (m_attachments.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QVector<QString> attas;
|
||||
for (int i = 0; i < m_attachments.size(); ++i) {
|
||||
attas.push_back(m_attachments[i].m_name);
|
||||
}
|
||||
|
||||
return deleteAttachments(attas);
|
||||
}
|
||||
|
||||
bool VNoteFile::deleteAttachments(const QVector<QString> &p_names)
|
||||
{
|
||||
if (p_names.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QDir dir(fetchAttachmentFolderPath());
|
||||
bool ret = true;
|
||||
for (int i = 0; i < p_names.size(); ++i) {
|
||||
int idx = findAttachment(p_names[i]);
|
||||
if (idx == -1) {
|
||||
ret = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
m_attachments.remove(idx);
|
||||
if (!VUtils::deleteFile(getNotebook(), dir.filePath(p_names[i]), false)) {
|
||||
ret = false;
|
||||
qWarning() << "fail to delete attachment" << p_names[i]
|
||||
<< "for note" << m_name;
|
||||
}
|
||||
}
|
||||
|
||||
if (!getDirectory()->updateFileConfig(this)) {
|
||||
qWarning() << "fail to update config of file" << m_name
|
||||
<< "in directory" << fetchBasePath();
|
||||
ret = false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int VNoteFile::findAttachment(const QString &p_name, bool p_caseSensitive)
|
||||
{
|
||||
const QString name = p_caseSensitive ? p_name : p_name.toLower();
|
||||
for (int i = 0; i < m_attachments.size(); ++i) {
|
||||
QString attaName = p_caseSensitive ? m_attachments[i].m_name
|
||||
: m_attachments[i].m_name.toLower();
|
||||
if (name == attaName) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void VNoteFile::sortAttachments(QVector<int> p_sortedIdx)
|
||||
{
|
||||
V_ASSERT(m_opened);
|
||||
V_ASSERT(p_sortedIdx.size() == m_attachments.size());
|
||||
|
||||
auto oriFiles = m_attachments;
|
||||
|
||||
for (int i = 0; i < p_sortedIdx.size(); ++i) {
|
||||
m_attachments[i] = oriFiles[p_sortedIdx[i]];
|
||||
}
|
||||
|
||||
if (!getDirectory()->updateFileConfig(this)) {
|
||||
qWarning() << "fail to reorder files in config" << p_sortedIdx;
|
||||
m_attachments = oriFiles;
|
||||
}
|
||||
}
|
||||
|
||||
bool VNoteFile::renameAttachment(const QString &p_oldName, const QString &p_newName)
|
||||
{
|
||||
int idx = findAttachment(p_oldName);
|
||||
if (idx == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir dir(fetchAttachmentFolderPath());
|
||||
if (!dir.rename(p_oldName, p_newName)) {
|
||||
qWarning() << "fail to rename attachment file" << p_oldName << p_newName;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_attachments[idx].m_name = p_newName;
|
||||
|
||||
if (!getDirectory()->updateFileConfig(this)) {
|
||||
qWarning() << "fail to rename attachment in config" << p_oldName << p_newName;
|
||||
|
||||
m_attachments[idx].m_name = p_oldName;
|
||||
dir.rename(p_newName, p_oldName);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1,11 +1,30 @@
|
||||
#ifndef VNOTEFILE_H
|
||||
#define VNOTEFILE_H
|
||||
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
|
||||
#include "vfile.h"
|
||||
|
||||
class VDirectory;
|
||||
class VNotebook;
|
||||
|
||||
// Structure for a note attachment.
|
||||
struct VAttachment
|
||||
{
|
||||
VAttachment()
|
||||
{
|
||||
}
|
||||
|
||||
VAttachment(const QString &p_name)
|
||||
: m_name(p_name)
|
||||
{
|
||||
}
|
||||
|
||||
// File name of the attachment.
|
||||
QString m_name;
|
||||
};
|
||||
|
||||
class VNoteFile : public VFile
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -15,7 +34,9 @@ public:
|
||||
FileType p_type,
|
||||
bool p_modifiable,
|
||||
QDateTime p_createdTimeUtc,
|
||||
QDateTime p_modifiedTimeUtc);
|
||||
QDateTime p_modifiedTimeUtc,
|
||||
const QString &p_attachmentFolder = "",
|
||||
const QVector<VAttachment> &p_attachments = QVector<VAttachment>());
|
||||
|
||||
QString fetchPath() const Q_DECL_OVERRIDE;
|
||||
|
||||
@ -58,9 +79,52 @@ public:
|
||||
// Delete this file in disk as well as all its images/attachments.
|
||||
bool deleteFile();
|
||||
|
||||
const QString &getAttachmentFolder() const;
|
||||
|
||||
const QVector<VAttachment> &getAttachments() const;
|
||||
|
||||
// Add @p_file as an attachment to this note.
|
||||
bool addAttachment(const QString &p_file);
|
||||
|
||||
// Fetch attachment folder path.
|
||||
// Will create it if it does not exist.
|
||||
QString fetchAttachmentFolderPath();
|
||||
|
||||
// Delete all the attachments.
|
||||
bool deleteAttachments();
|
||||
|
||||
// Delete attachments specified by @p_names.
|
||||
bool deleteAttachments(const QVector<QString> &p_names);
|
||||
|
||||
// Reorder attachments in m_attachments by index.
|
||||
void sortAttachments(QVector<int> p_sortedIdx);
|
||||
|
||||
// Return the index of @p_name in m_attachments.
|
||||
// -1 if not found.
|
||||
int findAttachment(const QString &p_name, bool p_caseSensitive = true);
|
||||
|
||||
bool renameAttachment(const QString &p_oldName, const QString &p_newName);
|
||||
|
||||
private:
|
||||
// Delete internal images of this file.
|
||||
void deleteInternalImages();
|
||||
|
||||
// Folder under the attachment folder of the notebook.
|
||||
// Store all the attachments of current file.
|
||||
QString m_attachmentFolder;
|
||||
|
||||
// Attachments.
|
||||
QVector<VAttachment> m_attachments;
|
||||
};
|
||||
|
||||
inline const QString &VNoteFile::getAttachmentFolder() const
|
||||
{
|
||||
return m_attachmentFolder;
|
||||
}
|
||||
|
||||
inline const QVector<VAttachment> &VNoteFile::getAttachments() const
|
||||
{
|
||||
return m_attachments;
|
||||
}
|
||||
|
||||
#endif // VNOTEFILE_H
|
||||
|
@ -100,35 +100,35 @@ void VVimIndicator::setupUI()
|
||||
|
||||
m_modeLabel = new QLabel(this);
|
||||
|
||||
m_regBtn = new VButtonWithWidget(QIcon(":/resources/icons/arrow_dropup.svg"),
|
||||
"\"",
|
||||
this);
|
||||
m_regBtn->setToolTip(tr("Registers"));
|
||||
m_regBtn->setProperty("StatusBtn", true);
|
||||
m_regBtn->setFocusPolicy(Qt::NoFocus);
|
||||
QTreeWidget *regTree = new QTreeWidget(this);
|
||||
regTree->setColumnCount(2);
|
||||
regTree->header()->setStretchLastSection(true);
|
||||
QStringList headers;
|
||||
headers << tr("Register") << tr("Value");
|
||||
regTree->setHeaderLabels(headers);
|
||||
m_regBtn->setPopupWidget(regTree);
|
||||
|
||||
m_regBtn = new VButtonWithWidget("\"",
|
||||
regTree,
|
||||
this);
|
||||
m_regBtn->setToolTip(tr("Registers"));
|
||||
m_regBtn->setProperty("StatusBtn", true);
|
||||
m_regBtn->setFocusPolicy(Qt::NoFocus);
|
||||
connect(m_regBtn, &VButtonWithWidget::popupWidgetAboutToShow,
|
||||
this, &VVimIndicator::updateRegistersTree);
|
||||
|
||||
m_markBtn = new VButtonWithWidget(QIcon(":/resources/icons/arrow_dropup.svg"),
|
||||
"[]",
|
||||
this);
|
||||
m_markBtn->setToolTip(tr("Marks"));
|
||||
m_markBtn->setProperty("StatusBtn", true);
|
||||
m_markBtn->setFocusPolicy(Qt::NoFocus);
|
||||
QTreeWidget *markTree = new QTreeWidget(this);
|
||||
markTree->setColumnCount(4);
|
||||
markTree->header()->setStretchLastSection(true);
|
||||
headers.clear();
|
||||
headers << tr("Mark") << tr("Line") << tr("Column") << tr("Text");
|
||||
markTree->setHeaderLabels(headers);
|
||||
m_markBtn->setPopupWidget(markTree);
|
||||
|
||||
m_markBtn = new VButtonWithWidget("[]",
|
||||
markTree,
|
||||
this);
|
||||
m_markBtn->setToolTip(tr("Marks"));
|
||||
m_markBtn->setProperty("StatusBtn", true);
|
||||
m_markBtn->setFocusPolicy(Qt::NoFocus);
|
||||
connect(m_markBtn, &VButtonWithWidget::popupWidgetAboutToShow,
|
||||
this, &VVimIndicator::updateMarksTree);
|
||||
|
||||
|