diff --git a/src/dialog/vdeletenotebookdialog.cpp b/src/dialog/vdeletenotebookdialog.cpp
index 555ff4b7..ffab9fce 100644
--- a/src/dialog/vdeletenotebookdialog.cpp
+++ b/src/dialog/vdeletenotebookdialog.cpp
@@ -4,11 +4,12 @@
extern VConfigManager *g_config;
-VDeleteNotebookDialog::VDeleteNotebookDialog(const QString &p_title, const QString &p_name,
- const QString &p_path, QWidget *p_parent)
- : QDialog(p_parent), m_path(p_path)
+VDeleteNotebookDialog::VDeleteNotebookDialog(const QString &p_title,
+ const VNotebook *p_notebook,
+ QWidget *p_parent)
+ : QDialog(p_parent), m_notebook(p_notebook)
{
- setupUI(p_title, p_name);
+ setupUI(p_title, m_notebook->getName());
}
void VDeleteNotebookDialog::setupUI(const QString &p_title, const QString &p_name)
@@ -20,7 +21,7 @@ void VDeleteNotebookDialog::setupUI(const QString &p_title, const QString &p_nam
m_deleteCheck = new QCheckBox(tr("Delete files from disk"), this);
m_deleteCheck->setChecked(false);
- m_deleteCheck->setToolTip(tr("When checked, VNote will delete all the files within this notebook from disk"));
+ m_deleteCheck->setToolTip(tr("When checked, VNote will delete all files (including Recycle Bin) of this notebook from disk"));
connect(m_deleteCheck, &QCheckBox::stateChanged,
this, &VDeleteNotebookDialog::deleteCheckChanged);
@@ -109,12 +110,16 @@ void VDeleteNotebookDialog::deleteCheckChanged(int p_state)
{
if (!p_state) {
m_warningLabel->setText(tr("VNote won't delete files in directory %2.")
- .arg(g_config->c_dataTextStyle).arg(m_path));
+ .arg(g_config->c_dataTextStyle).arg(m_notebook->getPath()));
} else {
m_warningLabel->setText(tr("WARNING: "
- "VNote may delete ANY files in directory %3! "
- "VNote will try to delete all the root folders within this notebook one by one. "
+ "VNote may delete ANY files in directory %3 "
+ "and directory %4!
"
+ "VNote will try to delete all the root folders within this notebook one by one.
"
"It may be UNRECOVERABLE!")
- .arg(g_config->c_warningTextStyle).arg(g_config->c_dataTextStyle).arg(m_path));
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(m_notebook->getPath())
+ .arg(m_notebook->getRecycleBinFolderPath()));
}
}
diff --git a/src/dialog/vdeletenotebookdialog.h b/src/dialog/vdeletenotebookdialog.h
index 34850689..d88baac8 100644
--- a/src/dialog/vdeletenotebookdialog.h
+++ b/src/dialog/vdeletenotebookdialog.h
@@ -9,12 +9,14 @@ class QLineEdit;
class QString;
class QCheckBox;
class QDialogButtonBox;
+class VNotebook;
class VDeleteNotebookDialog : public QDialog
{
Q_OBJECT
public:
- VDeleteNotebookDialog(const QString &p_title, const QString &p_name, const QString &p_path,
+ VDeleteNotebookDialog(const QString &p_title,
+ const VNotebook *p_notebook,
QWidget *p_parent = 0);
// Whether delete files from disk.
@@ -27,7 +29,7 @@ private:
void setupUI(const QString &p_title, const QString &p_name);
QPixmap standardIcon(QMessageBox::Icon p_icon);
- QString m_path;
+ const VNotebook *m_notebook;
QLabel *m_warningLabel;
QCheckBox *m_deleteCheck;
QDialogButtonBox *m_btnBox;
diff --git a/src/dialog/vdirinfodialog.cpp b/src/dialog/vdirinfodialog.cpp
index b384021e..e29c94b7 100644
--- a/src/dialog/vdirinfodialog.cpp
+++ b/src/dialog/vdirinfodialog.cpp
@@ -72,7 +72,8 @@ void VDirInfoDialog::handleInputChanged()
if (nameOk && name != m_directory->getName()) {
// Check if the name conflicts with existing directory name.
// Case-insensitive when creating note.
- if (m_parentDirectory->findSubDirectory(name, false)) {
+ const VDirectory *directory = m_parentDirectory->findSubDirectory(name, false);
+ if (directory && directory != m_directory) {
nameOk = false;
showWarnLabel = true;
QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
diff --git a/src/dialog/vfileinfodialog.cpp b/src/dialog/vfileinfodialog.cpp
index 94e95019..1413a12f 100644
--- a/src/dialog/vfileinfodialog.cpp
+++ b/src/dialog/vfileinfodialog.cpp
@@ -89,7 +89,8 @@ void VFileInfoDialog::handleInputChanged()
if (nameOk && name != m_file->getName()) {
// Check if the name conflicts with existing note name.
// Case-insensitive when creating note.
- if (m_directory->findFile(name, false)) {
+ const VFile *file = m_directory->findFile(name, false);
+ if (file && file != m_file) {
nameOk = false;
showWarnLabel = true;
QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
diff --git a/src/dialog/vnotebookinfodialog.cpp b/src/dialog/vnotebookinfodialog.cpp
index 15f650c4..7383856f 100644
--- a/src/dialog/vnotebookinfodialog.cpp
+++ b/src/dialog/vnotebookinfodialog.cpp
@@ -42,9 +42,11 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
"(empty to use global configuration)"));
QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit);
m_imageFolderEdit->setValidator(validator);
- QLabel *imageFolderLabel = new QLabel(tr("&Image folder:"));
- imageFolderLabel->setBuddy(m_imageFolderEdit);
- imageFolderLabel->setToolTip(m_imageFolderEdit->toolTip());
+
+ // 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"));
// Created time.
QString createdTimeStr = const_cast(m_notebook)->getCreatedTimeUtc().toLocalTime()
@@ -54,7 +56,8 @@ void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info)
QFormLayout *topLayout = new QFormLayout();
topLayout->addRow(tr("Notebook &name:"), m_nameEdit);
topLayout->addRow(tr("Notebook &root folder:"), m_pathEdit);
- topLayout->addRow(imageFolderLabel, m_imageFolderEdit);
+ topLayout->addRow(tr("&Image folder:"), m_imageFolderEdit);
+ topLayout->addRow(tr("Recycle bin folder:"), recycleBinFolderEdit);
topLayout->addRow(tr("Created time:"), createdTimeLabel);
// Warning label.
@@ -100,7 +103,7 @@ void VNotebookInfoDialog::handleInputChanged()
}
}
- if (idx < m_notebooks.size()) {
+ if (idx < m_notebooks.size() && m_notebooks[idx] != m_notebook) {
nameOk = false;
showWarnLabel = true;
QString nameConflictText = tr("WARNING: Name (case-insensitive) already exists. "
diff --git a/src/resources/icons/empty_recycle_bin.svg b/src/resources/icons/empty_recycle_bin.svg
new file mode 100644
index 00000000..a2200617
--- /dev/null
+++ b/src/resources/icons/empty_recycle_bin.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/resources/icons/recycle_bin.svg b/src/resources/icons/recycle_bin.svg
new file mode 100644
index 00000000..89b5feec
--- /dev/null
+++ b/src/resources/icons/recycle_bin.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini
index 329f24dd..b531d149 100644
--- a/src/resources/vnote.ini
+++ b/src/resources/vnote.ini
@@ -107,6 +107,9 @@ markdownit_opt_breaks=false
; Auto-convert URL-like text to links
markdownit_opt_linkify=true
+; Default name of the recycle bin of notebook
+recycle_bin_folder=_v_recycle_bin
+
[session]
tools_dock_checked=true
diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp
index c7485d56..1f311009 100644
--- a/src/utils/vutils.cpp
+++ b/src/utils/vutils.cpp
@@ -24,6 +24,7 @@
#include "vfile.h"
#include "vnote.h"
+#include "vnotebook.h"
extern VConfigManager *g_config;
@@ -718,3 +719,100 @@ QString VUtils::getShortcutText(const QString &p_keySeq)
{
return QKeySequence(p_keySeq).toString(QKeySequence::NativeText);
}
+
+static QString getRecycleBinSubFolderToUse(const VNotebook *p_notebook)
+{
+ QString folderPath = p_notebook->getRecycleBinFolderPath();
+ QDir dir(folderPath);
+ return QDir::cleanPath(dir.absoluteFilePath(QDateTime::currentDateTime().toString("yyyyMMdd")));
+}
+
+bool VUtils::deleteDirectory(const VNotebook *p_notebook,
+ const QString &p_path,
+ bool p_skipRecycleBin)
+{
+ if (p_skipRecycleBin) {
+ QDir dir(p_path);
+ return dir.removeRecursively();
+ } else {
+ // Move it to the recycle bin folder.
+ QString binPath = getRecycleBinSubFolderToUse(p_notebook);
+ QDir binDir(binPath);
+ if (!binDir.exists()) {
+ binDir.mkpath(binPath);
+ if (!binDir.exists()) {
+ return false;
+ }
+ }
+
+ QString destName = getFileNameWithSequence(binPath,
+ directoryNameFromPath(p_path));
+
+ qDebug() << "try to move" << p_path << "to" << binPath << "as" << destName;
+ if (!binDir.rename(p_path, binDir.filePath(destName))) {
+ qWarning() << "fail to move directory" << p_path << "to" << binDir.filePath(destName);
+ return false;
+ }
+
+ return true;
+ }
+}
+
+bool VUtils::emptyDirectory(const VNotebook *p_notebook,
+ const QString &p_path,
+ bool p_skipRecycleBin)
+{
+ QDir dir(p_path);
+ if (!dir.exists()) {
+ return true;
+ }
+
+ QFileInfoList nodes = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden
+ | QDir::NoSymLinks | QDir::NoDotAndDotDot);
+ for (int i = 0; i < nodes.size(); ++i) {
+ const QFileInfo &fileInfo = nodes.at(i);
+ if (fileInfo.isDir()) {
+ if (!deleteDirectory(p_notebook, fileInfo.absoluteFilePath(), p_skipRecycleBin)) {
+ return false;
+ }
+ } else {
+ Q_ASSERT(fileInfo.isFile());
+ if (!deleteFile(p_notebook, fileInfo.absoluteFilePath(), p_skipRecycleBin)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool VUtils::deleteFile(const VNotebook *p_notebook,
+ const QString &p_path,
+ bool p_skipRecycleBin)
+{
+ if (p_skipRecycleBin) {
+ QFile file(p_path);
+ return file.remove();
+ } else {
+ // Move it to the recycle bin folder.
+ QString binPath = getRecycleBinSubFolderToUse(p_notebook);
+ QDir binDir(binPath);
+ if (!binDir.exists()) {
+ binDir.mkpath(binPath);
+ if (!binDir.exists()) {
+ return false;
+ }
+ }
+
+ QString destName = getFileNameWithSequence(binPath,
+ fileNameFromPath(p_path));
+
+ qDebug() << "try to move" << p_path << "to" << binPath << "as" << destName;
+ if (!binDir.rename(p_path, binDir.filePath(destName))) {
+ qWarning() << "fail to move file" << p_path << "to" << binDir.filePath(destName);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/utils/vutils.h b/src/utils/vutils.h
index 84cbdc0a..7f106f89 100644
--- a/src/utils/vutils.h
+++ b/src/utils/vutils.h
@@ -12,6 +12,7 @@
class QKeyEvent;
class VFile;
+class VNotebook;
#if !defined(V_ASSERT)
#define V_ASSERT(cond) ((!(cond)) ? qt_assert(#cond, __FILE__, __LINE__) : qt_noop())
@@ -124,6 +125,27 @@ public:
// Returns the shortcut text.
static QString getShortcutText(const QString &p_keySeq);
+ // Delete directory recursively specified by @p_path.
+ // Will just move the directory to the recycle bin of @p_notebook if
+ // @p_skipRecycleBin is false.
+ static bool deleteDirectory(const VNotebook *p_notebook,
+ const QString &p_path,
+ bool p_skipRecycleBin = false);
+
+ // Empty all files in directory recursively specified by @p_path.
+ // Will just move files to the recycle bin of @p_notebook if
+ // @p_skipRecycleBin is false.
+ static bool emptyDirectory(const VNotebook *p_notebook,
+ const QString &p_path,
+ bool p_skipRecycleBin = false);
+
+ // Delete file specified by @p_path.
+ // Will just move the file to the recycle bin of @p_notebook if
+ // @p_skipRecycleBin is false.
+ static bool deleteFile(const VNotebook *p_notebook,
+ const QString &p_path,
+ bool p_skipRecycleBin = false);
+
// Regular expression for image link.
// 
// Captured texts (need to be trimmed):
diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp
index 0b67331b..fad62c66 100644
--- a/src/vconfigmanager.cpp
+++ b/src/vconfigmanager.cpp
@@ -197,6 +197,9 @@ void VConfigManager::initialize()
m_markdownitOptLinkify = getConfigFromSettings("global",
"markdownit_opt_linkify").toBool();
+
+ m_recycleBinFolder = getConfigFromSettings("global",
+ "recycle_bin_folder").toString();
}
void VConfigManager::readPredefinedColorsFromSettings()
diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h
index 78921ea3..30ec89f6 100644
--- a/src/vconfigmanager.h
+++ b/src/vconfigmanager.h
@@ -264,6 +264,8 @@ public:
MarkdownitOption getMarkdownitOption() const;
void setMarkdownitOption(const MarkdownitOption &p_opt);
+ const QString &getRecycleBinFolder() const;
+
// Return the configured key sequence of @p_operation.
// Return empty if there is no corresponding config.
QString getShortcutKeySequence(const QString &p_operation) const;
@@ -531,6 +533,9 @@ private:
// Auto-convert URL-like text to links.
bool m_markdownitOptLinkify;
+ // Default name of the recycle bin folder of notebook.
+ QString m_recycleBinFolder;
+
// The name of the config file in each directory, obsolete.
// Use c_dirConfigFile instead.
static const QString c_obsoleteDirConfigFile;
@@ -1377,4 +1382,9 @@ inline void VConfigManager::setMarkdownitOption(const MarkdownitOption &p_opt)
}
}
+inline const QString &VConfigManager::getRecycleBinFolder() const
+{
+ return m_recycleBinFolder;
+}
+
#endif // VCONFIGMANAGER_H
diff --git a/src/vconstants.h b/src/vconstants.h
index 61eafaf7..44da5975 100644
--- a/src/vconstants.h
+++ b/src/vconstants.h
@@ -30,6 +30,7 @@ namespace DirConfig
static const QString c_subDirectories = "sub_directories";
static const QString c_files = "files";
static const QString c_imageFolder = "image_folder";
+ static const QString c_recycleBinFolder = "recycle_bin_folder";
static const QString c_name = "name";
static const QString c_createdTime = "created_time";
static const QString c_modifiedTime = "modified_time";
diff --git a/src/vdirectory.cpp b/src/vdirectory.cpp
index 514411d9..0f6628ff 100644
--- a/src/vdirectory.cpp
+++ b/src/vdirectory.cpp
@@ -412,20 +412,21 @@ VDirectory *VDirectory::addSubDirectory(const QString &p_name, int p_index)
return dir;
}
-void VDirectory::deleteSubDirectory(VDirectory *p_subDir)
+void VDirectory::deleteSubDirectory(VDirectory *p_subDir, bool p_skipRecycleBin)
{
+ Q_ASSERT(p_subDir->getNotebook() == m_notebook);
+
QString dirPath = p_subDir->fetchPath();
p_subDir->close();
removeSubDirectory(p_subDir);
- // Delete the entire directory
- QDir dir(dirPath);
- if (!dir.removeRecursively()) {
+ // Delete the entire directory.
+ if (!VUtils::deleteDirectory(m_notebook, dirPath, p_skipRecycleBin)) {
qWarning() << "fail to remove directory" << dirPath << "recursively";
} else {
- qDebug() << "deleted" << dirPath << "from disk";
+ qDebug() << "deleted" << dirPath << (p_skipRecycleBin ? "from disk" : "to recycle bin");
}
delete p_subDir;
diff --git a/src/vdirectory.h b/src/vdirectory.h
index e0c40ba1..a10fab58 100644
--- a/src/vdirectory.h
+++ b/src/vdirectory.h
@@ -35,7 +35,8 @@ public:
VFile *createFile(const QString &p_name);
- void deleteSubDirectory(VDirectory *p_subDir);
+ // Remove and delete subdirectory @p_subDir.
+ void deleteSubDirectory(VDirectory *p_subDir, bool p_skipRecycleBin = false);
// Remove the file in the config and m_files without deleting it in the disk.
// It won't change the parent of @p_file to enable it find its path.
diff --git a/src/vdirectorytree.cpp b/src/vdirectorytree.cpp
index ba2daa21..6f51f7f7 100644
--- a/src/vdirectorytree.cpp
+++ b/src/vdirectorytree.cpp
@@ -447,9 +447,10 @@ void VDirectoryTree::deleteDirectory()
tr("Are you sure to delete folder %2?")
.arg(g_config->c_dataTextStyle).arg(curDir->getName()),
tr("WARNING: "
- "VNote will delete the whole directory (ANY files) "
+ "VNote will delete the whole directory "
"%3."
- "
It may be UNRECOVERABLE!")
+ "You could find deleted files in the recycle bin "
+ "of this notebook.
The operation is IRREVERSIBLE!")
.arg(g_config->c_warningTextStyle).arg(g_config->c_dataTextStyle).arg(curDir->fetchPath()),
QMessageBox::Ok | QMessageBox::Cancel,
QMessageBox::Ok, this, MessageBoxType::Danger);
diff --git a/src/vfile.cpp b/src/vfile.cpp
index 292fe7d8..e3aacb80 100644
--- a/src/vfile.cpp
+++ b/src/vfile.cpp
@@ -64,8 +64,7 @@ void VFile::deleteDiskFile()
// Delete the file
QString filePath = fetchPath();
- QFile file(filePath);
- if (file.remove()) {
+ if (VUtils::deleteFile(getNotebook(), filePath, false)) {
qDebug() << "deleted" << filePath;
} else {
qWarning() << "fail to delete" << filePath;
@@ -96,8 +95,7 @@ void VFile::deleteLocalImages()
ImageLink::LocalRelativeInternal);
int deleted = 0;
for (int i = 0; i < images.size(); ++i) {
- QFile file(images[i].m_path);
- if (file.remove()) {
+ if (VUtils::deleteFile(getNotebook(), images[i].m_path, false)) {
++deleted;
}
}
diff --git a/src/vfilelist.cpp b/src/vfilelist.cpp
index 1f4816de..03806f8c 100644
--- a/src/vfilelist.cpp
+++ b/src/vfilelist.cpp
@@ -394,8 +394,12 @@ void VFileList::deleteFile(VFile *p_file)
int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
tr("Are you sure to delete note %2?")
.arg(g_config->c_dataTextStyle).arg(fileName),
- tr("WARNING: The files (including images) "
- "deleted may be UNRECOVERABLE!")
+ tr("WARNING: "
+ "VNote will delete the note as well as all "
+ "its images and attachments managed by VNote. "
+ "You could find deleted files in the recycle "
+ "bin of this notebook.
"
+ "The operation is IRREVERSIBLE!")
.arg(g_config->c_warningTextStyle),
QMessageBox::Ok | QMessageBox::Cancel,
QMessageBox::Ok, this, MessageBoxType::Danger);
diff --git a/src/vmainwindow.h b/src/vmainwindow.h
index f940b1b6..0f2476d2 100644
--- a/src/vmainwindow.h
+++ b/src/vmainwindow.h
@@ -34,6 +34,7 @@ class VTabIndicator;
class VSingleInstanceGuard;
class QTimer;
class QSystemTrayIcon;
+class QShortcut;
class VMainWindow : public QMainWindow
{
diff --git a/src/vmdedit.cpp b/src/vmdedit.cpp
index 262e0966..76d986f3 100644
--- a/src/vmdedit.cpp
+++ b/src/vmdedit.cpp
@@ -255,7 +255,7 @@ void VMdEdit::clearUnusedImages()
// This inserted image is no longer in the file.
if (j == images.size()) {
- if (!QFile(link.m_path).remove()) {
+ if (!VUtils::deleteFile(m_file->getNotebook(), link.m_path, false)) {
qWarning() << "fail to delete unused inserted image" << link.m_path;
} else {
qDebug() << "delete unused inserted image" << link.m_path;
@@ -280,7 +280,7 @@ void VMdEdit::clearUnusedImages()
// Original local relative image is no longer in the file.
if (j == images.size()) {
- if (!QFile(link.m_path).remove()) {
+ if (!VUtils::deleteFile(m_file->getNotebook(), link.m_path, false)) {
qWarning() << "fail to delete unused original image" << link.m_path;
} else {
qDebug() << "delete unused original image" << link.m_path;
diff --git a/src/vnote.qrc b/src/vnote.qrc
index cc1266ba..1a9e9539 100644
--- a/src/vnote.qrc
+++ b/src/vnote.qrc
@@ -121,5 +121,7 @@
resources/docs/markdown_guide_en.md
resources/docs/markdown_guide_zh.md
utils/highlightjs/highlightjs-line-numbers.min.js
+ resources/icons/recycle_bin.svg
+ resources/icons/empty_recycle_bin.svg
diff --git a/src/vnotebook.cpp b/src/vnotebook.cpp
index c5aeb972..3d5d4a0e 100644
--- a/src/vnotebook.cpp
+++ b/src/vnotebook.cpp
@@ -12,6 +12,7 @@ VNotebook::VNotebook(const QString &name, const QString &path, QObject *parent)
: QObject(parent), m_name(name)
{
m_path = QDir::cleanPath(path);
+ m_recycleBinFolder = g_config->getRecycleBinFolder();
m_rootDir = new VDirectory(this,
VUtils::directoryNameFromPath(path),
NULL,
@@ -37,6 +38,12 @@ bool VNotebook::readConfig()
m_imageFolder = it.value().toString();
}
+ // [recycle_bin_folder] section.
+ it = configJson.find(DirConfig::c_recycleBinFolder);
+ if (it != configJson.end()) {
+ m_recycleBinFolder = it.value().toString();
+ }
+
return true;
}
@@ -47,6 +54,9 @@ QJsonObject VNotebook::toConfigJsonNotebook() const
// [image_folder] section.
json[DirConfig::c_imageFolder] = m_imageFolder;
+ // [recycle_bin_folder] section.
+ json[DirConfig::c_recycleBinFolder] = m_recycleBinFolder;
+
return json;
}
@@ -69,9 +79,9 @@ bool VNotebook::writeToConfig() const
return VConfigManager::writeDirectoryConfig(m_path, toConfigJson());
}
-bool VNotebook::writeConfig() const
+bool VNotebook::writeConfigNotebook() const
{
- QJsonObject json = toConfigJson();
+ QJsonObject nbJson = toConfigJsonNotebook();
QJsonObject configJson = VConfigManager::readDirectoryConfig(m_path);
if (configJson.isEmpty()) {
@@ -79,10 +89,11 @@ bool VNotebook::writeConfig() const
return false;
}
- json[DirConfig::c_subDirectories] = configJson[DirConfig::c_subDirectories];
- json[DirConfig::c_files] = configJson[DirConfig::c_files];
+ for (auto it = nbJson.begin(); it != nbJson.end(); ++it) {
+ configJson[it.key()] = it.value();
+ }
- return VConfigManager::writeDirectoryConfig(m_path, json);
+ return VConfigManager::writeDirectoryConfig(m_path, configJson);
}
const QString &VNotebook::getName() const
@@ -102,6 +113,16 @@ void VNotebook::close()
bool VNotebook::open()
{
+ QString recycleBinPath = getRecycleBinFolderPath();
+ if (!QFileInfo::exists(recycleBinPath)) {
+ QDir dir(m_path);
+ if (!dir.mkpath(recycleBinPath)) {
+ qWarning() << "fail to create recycle bin folder" << recycleBinPath
+ << "for notebook" << m_name;
+ return false;
+ }
+ }
+
return m_rootDir->open();
}
@@ -148,10 +169,20 @@ bool VNotebook::deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles)
goto exit;
}
+ // Delete sub directories.
VDirectory *rootDir = p_notebook->getRootDir();
QVector subdirs = rootDir->getSubDirs();
for (auto dir : subdirs) {
- rootDir->deleteSubDirectory(dir);
+ // Skip recycle bin.
+ rootDir->deleteSubDirectory(dir, true);
+ }
+
+ // Delete the recycle bin.
+ QDir recycleDir(p_notebook->getRecycleBinFolderPath());
+ if (!recycleDir.removeRecursively()) {
+ qWarning() << "fail to delete notebook recycle bin folder"
+ << p_notebook->getRecycleBinFolderPath();
+ ret = false;
}
// Delete the config file.
@@ -255,3 +286,13 @@ QDateTime VNotebook::getCreatedTimeUtc()
return m_rootDir->getCreatedTimeUtc();
}
+
+QString VNotebook::getRecycleBinFolderPath() const
+{
+ QFileInfo fi(m_recycleBinFolder);
+ if (fi.isAbsolute()) {
+ return m_recycleBinFolder;
+ } else {
+ return QDir(m_path).filePath(m_recycleBinFolder);
+ }
+}
diff --git a/src/vnotebook.h b/src/vnotebook.h
index 3bc0501a..1348cea6 100644
--- a/src/vnotebook.h
+++ b/src/vnotebook.h
@@ -13,6 +13,7 @@ class VNotebook : public QObject
Q_OBJECT
public:
VNotebook(const QString &name, const QString &path, QObject *parent = 0);
+
~VNotebook();
// Open the root directory to load contents
@@ -54,15 +55,20 @@ public:
// Return m_imageFolder.
const QString &getImageFolderConfig() const;
+ // Return m_recycleBinFolder.
+ const QString &getRecycleBinFolder() const;
+
+ // Get the recycle folder path for this notebook to use.
+ QString getRecycleBinFolderPath() const;
+
void setImageFolder(const QString &p_imageFolder);
// Read configurations (excluding "sub_directories" and "files" section)
// from root directory config file.
bool readConfig();
- // Write configurations (excluding "sub_directories" and "files" section)
- // to root directory config file.
- bool writeConfig() const;
+ // Write configurations only related to notebook to root directory config file.
+ bool writeConfigNotebook() const;
// Return only the info of notebook part in json.
QJsonObject toConfigJsonNotebook() const;
@@ -88,6 +94,10 @@ private:
// Otherwise, VNote will use the global configured folder.
QString m_imageFolder;
+ // Folder name to store deleted files.
+ // Could be relative or absolute.
+ QString m_recycleBinFolder;
+
// Parent is NULL for root directory
VDirectory *m_rootDir;
};
@@ -97,4 +107,9 @@ inline VDirectory *VNotebook::getRootDir() const
return m_rootDir;
}
+inline const QString &VNotebook::getRecycleBinFolder() const
+{
+ return m_recycleBinFolder;
+}
+
#endif // VNOTEBOOK_H
diff --git a/src/vnotebookselector.cpp b/src/vnotebookselector.cpp
index b1c67437..46abe1eb 100644
--- a/src/vnotebookselector.cpp
+++ b/src/vnotebookselector.cpp
@@ -81,6 +81,78 @@ void VNotebookSelector::initActions()
QUrl url = QUrl::fromLocalFile(notebook->getPath());
QDesktopServices::openUrl(url);
});
+
+ m_recycleBinAct = new QAction(QIcon(":/resources/icons/recycle_bin.svg"),
+ tr("&Recycle Bin"), this);
+ m_recycleBinAct->setToolTip(tr("Open the recycle bin of this notebook"));
+ connect(m_recycleBinAct, &QAction::triggered,
+ this, [this]() {
+ QList items = this->m_listWidget->selectedItems();
+ if (items.isEmpty()) {
+ return;
+ }
+
+ Q_ASSERT(items.size() == 1);
+ QListWidgetItem *item = items[0];
+ int index = this->indexOfListItem(item);
+ VNotebook *notebook = this->getNotebookFromComboIndex(index);
+ QUrl url = QUrl::fromLocalFile(notebook->getRecycleBinFolderPath());
+ QDesktopServices::openUrl(url);
+ });
+
+ m_emptyRecycleBinAct = new QAction(QIcon(":/resources/icons/empty_recycle_bin.svg"),
+ tr("&Empty Recycle Bin"), this);
+ m_emptyRecycleBinAct->setToolTip(tr("Empty the recycle bin of this notebook"));
+ connect(m_emptyRecycleBinAct, &QAction::triggered,
+ this, [this]() {
+ QList items = this->m_listWidget->selectedItems();
+ if (items.isEmpty()) {
+ return;
+ }
+
+ Q_ASSERT(items.size() == 1);
+ QListWidgetItem *item = items[0];
+ int index = this->indexOfListItem(item);
+ VNotebook *notebook = this->getNotebookFromComboIndex(index);
+ QString binPath = notebook->getRecycleBinFolderPath();
+
+ int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+ tr("Are you sure to empty recycle bin of notebook "
+ "%2?")
+ .arg(g_config->c_dataTextStyle)
+ .arg(notebook->getName()),
+ tr("WARNING: "
+ "VNote will delete all the files in directory "
+ "%3."
+ "
It may be UNRECOVERABLE!")
+ .arg(g_config->c_warningTextStyle)
+ .arg(g_config->c_dataTextStyle)
+ .arg(binPath),
+ QMessageBox::Ok | QMessageBox::Cancel,
+ QMessageBox::Ok, this, MessageBoxType::Danger);
+ if (ret == QMessageBox::Ok) {
+ QString info;
+ if (VUtils::emptyDirectory(notebook, binPath, true)) {
+ info = tr("Successfully emptied recycle bin of notebook "
+ "%2!")
+ .arg(g_config->c_dataTextStyle)
+ .arg(notebook->getName());
+ } else {
+ info = tr("Fail to empty recycle bin of notebook "
+ "%2!")
+ .arg(g_config->c_dataTextStyle)
+ .arg(notebook->getName());
+ }
+
+ VUtils::showMessage(QMessageBox::Information,
+ tr("Information"),
+ info,
+ "",
+ QMessageBox::Ok,
+ QMessageBox::Ok,
+ this);
+ }
+ });
}
void VNotebookSelector::updateComboBox()
@@ -244,7 +316,7 @@ void VNotebookSelector::deleteNotebook()
VNotebook *notebook = getNotebookFromComboIndex(index);
Q_ASSERT(notebook);
- VDeleteNotebookDialog dialog(tr("Delete Notebook"), notebook->getName(), notebook->getPath(), this);
+ VDeleteNotebookDialog dialog(tr("Delete Notebook"), notebook, this);
if (dialog.exec() == QDialog::Accepted) {
bool deleteFiles = dialog.getDeleteFiles();
m_editArea->closeFile(notebook, true);
@@ -323,7 +395,7 @@ void VNotebookSelector::editNotebookInfo()
if (imageFolder != notebook->getImageFolderConfig()) {
updated = true;
notebook->setImageFolder(imageFolder);
- notebook->writeConfig();
+ notebook->writeConfigNotebook();
}
if (updated) {
@@ -369,6 +441,10 @@ void VNotebookSelector::requestPopupListContextMenu(QPoint p_pos)
QMenu menu(this);
menu.setToolTipsVisible(true);
menu.addAction(m_deleteNotebookAct);
+ menu.addSeparator();
+ menu.addAction(m_recycleBinAct);
+ menu.addAction(m_emptyRecycleBinAct);
+ menu.addSeparator();
menu.addAction(m_openLocationAct);
menu.addAction(m_notebookInfoAct);
diff --git a/src/vnotebookselector.h b/src/vnotebookselector.h
index eddcfa7c..dab19d7e 100644
--- a/src/vnotebookselector.h
+++ b/src/vnotebookselector.h
@@ -90,6 +90,8 @@ private:
QAction *m_deleteNotebookAct;
QAction *m_notebookInfoAct;
QAction *m_openLocationAct;
+ QAction *m_recycleBinAct;
+ QAction *m_emptyRecycleBinAct;
// We will add several special action item in the combobox. This is the start index
// of the real notebook items related to m_notebooks.