add recycle bin to each notebook

This commit is contained in:
Le Tan 2017-09-14 20:28:18 +08:00
parent 4626673925
commit 35fa0a46f6
25 changed files with 355 additions and 45 deletions

View File

@ -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 <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle).arg(m_path));
.arg(g_config->c_dataTextStyle).arg(m_notebook->getPath()));
} else {
m_warningLabel->setText(tr("<span style=\"%1\">WARNING</span>: "
"VNote may delete <b>ANY</b> files in directory <span style=\"%2\">%3</span>! "
"VNote will try to delete all the root folders within this notebook one by one. "
"VNote may delete <b>ANY</b> files in directory <span style=\"%2\">%3</span> "
"and directory <span style=\"%2\">%4</span>!<br>"
"VNote will try to delete all the root folders within this notebook one by one.<br>"
"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()));
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g display="inline">
<title>Layer 1</title>
<path id="svg_6" fill-opacity="0" d="m85.75,73.84658l43.19437,360.59092l241.12296,0l43.05767,-361.68444l-327.375,1.09352z" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="30" fill="#000000" stroke="#000000"/>
</g>
<g display="inline">
<title>Layer 1 copy</title>
</g>
</svg>

After

Width:  |  Height:  |  Size: 478 B

View File

@ -0,0 +1,10 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g display="inline">
<title>Layer 1</title>
<path stroke="#000000" fill="#000000" stroke-width="30" stroke-dasharray="null" stroke-linejoin="null" stroke-linecap="null" d="m85.750002,73.846584l43.194363,360.590916l241.122963,0l43.057672,-361.684444l-327.374998,1.093528z" fill-opacity="0" id="svg_6"/>
</g>
<g display="inline">
<title>Layer 1 copy</title>
<path id="svg_1" d="m278.734806,211.081489l-25.954577,-4.596678l9.562217,-4.993052c5.259196,-2.746226 10.027709,-5.404751 10.596662,-5.907837c1.413302,-1.249639 -16.187262,-28.447523 -18.394639,-28.425008c-0.970333,0.011001 -7.731781,10.542547 -15.025414,23.405944c-7.346415,12.956287 -14.657734,22.823491 -16.392375,22.122467c-20.530561,-8.296065 -37.281946,-16.689857 -37.281946,-18.681204c0,-1.360192 6.441626,-13.493725 14.314808,-26.963432l14.314815,-24.490333l39.029834,0l39.029834,0l10.187868,16.112893l10.187953,16.112893l10.628269,-5.452618c7.367742,-3.779934 10.17318,-4.322708 9.144661,-1.769575c-9.05562,22.481975 -22.568035,48.696355 -24.969931,48.442064c-1.662917,-0.175921 -14.703002,-2.388389 -28.978031,-4.916514l-0.000008,-0.000008zm-121.04853,100.301702c-9.877208,-17.009348 -18.729478,-32.934982 -19.671623,-35.390403c-0.966907,-2.519616 1.850192,-11.523045 6.467384,-20.669546l8.180422,-16.205438l-8.362438,-4.939858c-11.12868,-6.573858 -6.992066,-8.598561 22.529168,-11.027127l23.331407,-1.919317l9.151668,24.180839c5.033491,13.299451 9.795749,25.898384 10.582909,27.997793c0.820922,2.189446 -3.107919,0.732693 -9.21387,-3.416409l-10.645019,-7.233539l-7.43104,14.344659c-4.086999,7.889694 -7.488988,15.223935 -7.559854,16.298312c-0.071034,1.074377 12.472696,2.303847 27.874729,2.732066l28.003626,0.778551l1.827516,20.484288c1.005176,11.266424 1.619826,20.634873 1.366026,20.818805c-0.253785,0.183748 -13.513894,1.179924 -29.466873,2.213472l-29.005467,1.879108l-17.958669,-30.926279l0,0.000023zm110.540841,30.053665c-16.956491,-25.592907 -17.251221,-23.689252 7.951467,-51.353217l16.178415,-17.758401l-1.718289,12.977292l-1.718366,12.9773l18.129745,0c9.97134,0 18.129837,-0.797417 18.129837,-1.771913c0,-0.974657 -5.532483,-11.921252 -12.294352,-24.32608c-6.761869,-12.404828 -12.268679,-23.594376 -12.237409,-24.86568c0.070206,-2.850622 35.51503,-26.389649 37.20008,-24.704623c0.667669,0.667677 7.245506,12.559246 14.617434,26.425987l13.403679,25.21188l-18.035023,29.09066c-9.91941,15.999665 -19.449767,30.651136 -21.178467,32.558624c-1.728899,1.907488 -11.395904,4.238849 -21.482319,5.180634c-19.252488,1.797909 -18.728389,1.364868 -21.551245,17.805095c-0.493994,2.877139 -6.254444,-3.65119 -15.395187,-17.447572l0,0.000015z" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="50" fill="black"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

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

View File

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

View File

@ -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.
// ![image title]( http://github.com/tamlok/vnote.jpg "alt \" text" )
// Captured texts (need to be trimmed):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -447,9 +447,10 @@ void VDirectoryTree::deleteDirectory()
tr("Are you sure to delete folder <span style=\"%1\">%2</span>?")
.arg(g_config->c_dataTextStyle).arg(curDir->getName()),
tr("<span style=\"%1\">WARNING</span>: "
"VNote will delete the whole directory (<b>ANY</b> files) "
"VNote will delete the whole directory "
"<span style=\"%2\">%3</span>."
"<br>It may be UNRECOVERABLE!")
"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(curDir->fetchPath()),
QMessageBox::Ok | QMessageBox::Cancel,
QMessageBox::Ok, this, MessageBoxType::Danger);

View File

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

View File

@ -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 <span style=\"%1\">%2</span>?")
.arg(g_config->c_dataTextStyle).arg(fileName),
tr("<span style=\"%1\">WARNING</span>: The files (including images) "
"deleted may be UNRECOVERABLE!")
tr("<span style=\"%1\">WARNING</span>: "
"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.<br>"
"The operation is IRREVERSIBLE!")
.arg(g_config->c_warningTextStyle),
QMessageBox::Ok | QMessageBox::Cancel,
QMessageBox::Ok, this, MessageBoxType::Danger);

View File

@ -34,6 +34,7 @@ class VTabIndicator;
class VSingleInstanceGuard;
class QTimer;
class QSystemTrayIcon;
class QShortcut;
class VMainWindow : public QMainWindow
{

View File

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

View File

@ -121,5 +121,7 @@
<file>resources/docs/markdown_guide_en.md</file>
<file>resources/docs/markdown_guide_zh.md</file>
<file>utils/highlightjs/highlightjs-line-numbers.min.js</file>
<file>resources/icons/recycle_bin.svg</file>
<file>resources/icons/empty_recycle_bin.svg</file>
</qresource>
</RCC>

View File

@ -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<VDirectory *> 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);
}
}

View File

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

View File

@ -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<QListWidgetItem *> 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<QListWidgetItem *> 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 "
"<span style=\"%1\">%2</span>?")
.arg(g_config->c_dataTextStyle)
.arg(notebook->getName()),
tr("<span style=\"%1\">WARNING</span>: "
"VNote will delete all the files in directory "
"<span style=\"%2\">%3</span>."
"<br>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 "
"<span style=\"%1\">%2</span>!")
.arg(g_config->c_dataTextStyle)
.arg(notebook->getName());
} else {
info = tr("Fail to empty recycle bin of notebook "
"<span style=\"%1\">%2</span>!")
.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);

View File

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