refactor VFileList

- Refine note deletion logics;
- Refine note copy/paste logics;
- Refine note sorting logics;
This commit is contained in:
Le Tan 2017-09-29 19:56:38 +08:00
parent 4668bd581a
commit 58e7cdca4b
17 changed files with 1150 additions and 500 deletions

View File

@ -7,38 +7,28 @@
extern VConfigManager *g_config;
class ConfirmItemWidget : public QWidget
ConfirmItemWidget::ConfirmItemWidget(bool p_checked,
const QString &p_file,
const QString &p_tip,
int p_index,
QWidget *p_parent)
: QWidget(p_parent), m_index(p_index)
{
public:
explicit ConfirmItemWidget(QWidget *p_parent = NULL)
: QWidget(p_parent)
{
setupUI();
}
ConfirmItemWidget(bool p_checked, const QString &p_file, QWidget *p_parent = NULL)
: QWidget(p_parent)
{
setupUI();
m_checkBox->setChecked(p_checked);
m_fileLabel->setText(p_file);
if (!p_tip.isEmpty()) {
m_fileLabel->setToolTip(p_tip);
}
}
bool isChecked() const
{
return m_checkBox->isChecked();
}
QString getFile() const
{
return m_fileLabel->text();
}
private:
void setupUI()
{
void ConfirmItemWidget::setupUI()
{
m_checkBox = new QCheckBox;
connect(m_checkBox, &QCheckBox::stateChanged,
this, &ConfirmItemWidget::checkStateChanged);
m_fileLabel = new QLabel;
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addWidget(m_checkBox);
@ -47,15 +37,22 @@ private:
mainLayout->setContentsMargins(3, 0, 0, 0);
setLayout(mainLayout);
}
}
QCheckBox *m_checkBox;
QLabel *m_fileLabel;
};
bool ConfirmItemWidget::isChecked() const
{
return m_checkBox->isChecked();
}
int ConfirmItemWidget::getIndex() const
{
return m_index;
}
VConfirmDeletionDialog::VConfirmDeletionDialog(const QString &p_title,
const QString &p_text,
const QString &p_info,
const QVector<QString> &p_files,
const QVector<ConfirmItemInfo> &p_items,
bool p_enableAskAgain,
bool p_askAgainEnabled,
bool p_enablePreview,
@ -63,21 +60,38 @@ VConfirmDeletionDialog::VConfirmDeletionDialog(const QString &p_title,
: QDialog(p_parent),
m_enableAskAgain(p_enableAskAgain),
m_askAgainEnabled(p_askAgainEnabled),
m_enablePreview(p_enablePreview)
m_enablePreview(p_enablePreview),
m_items(p_items)
{
setupUI(p_title, p_info);
setupUI(p_title, p_text, p_info);
initFileItems(p_files);
initItems();
updateCountLabel();
}
void VConfirmDeletionDialog::setupUI(const QString &p_title, const QString &p_info)
void VConfirmDeletionDialog::setupUI(const QString &p_title,
const QString &p_text,
const QString &p_info)
{
QLabel *textLabel = NULL;
if (!p_text.isEmpty()) {
textLabel = new QLabel(p_text);
textLabel->setWordWrap(true);
}
QLabel *infoLabel = NULL;
if (!p_info.isEmpty()) {
infoLabel = new QLabel(p_info);
infoLabel->setWordWrap(true);
}
m_countLabel = new QLabel("Items");
QHBoxLayout *labelLayout = new QHBoxLayout;
labelLayout->addWidget(m_countLabel);
labelLayout->addStretch();
labelLayout->setContentsMargins(0, 0, 0, 0);
m_listWidget = new QListWidget();
connect(m_listWidget, &QListWidget::currentRowChanged,
this, &VConfirmDeletionDialog::currentFileChanged);
@ -106,40 +120,50 @@ void VConfirmDeletionDialog::setupUI(const QString &p_title, const QString &p_in
}
QVBoxLayout *mainLayout = new QVBoxLayout;
if (textLabel) {
mainLayout->addWidget(textLabel);
}
if (infoLabel) {
mainLayout->addWidget(infoLabel);
}
mainLayout->addWidget(m_askAgainCB);
mainLayout->addWidget(m_btnBox);
mainLayout->addLayout(labelLayout);
mainLayout->addLayout(midLayout);
setLayout(mainLayout);
setWindowTitle(p_title);
}
QVector<QString> VConfirmDeletionDialog::getConfirmedFiles() const
QVector<ConfirmItemInfo> VConfirmDeletionDialog::getConfirmedItems() const
{
QVector<QString> files;
QVector<ConfirmItemInfo> confirmedItems;
for (int i = 0; i < m_listWidget->count(); ++i) {
ConfirmItemWidget *widget = getItemWidget(m_listWidget->item(i));
if (widget->isChecked()) {
files.push_back(widget->getFile());
confirmedItems.push_back(m_items[widget->getIndex()]);
}
}
return files;
return confirmedItems;
}
void VConfirmDeletionDialog::initFileItems(const QVector<QString> &p_files)
void VConfirmDeletionDialog::initItems()
{
m_listWidget->clear();
for (int i = 0; i < p_files.size(); ++i) {
for (int i = 0; i < m_items.size(); ++i) {
ConfirmItemWidget *itemWidget = new ConfirmItemWidget(true,
p_files[i],
m_items[i].m_name,
m_items[i].m_tip,
i,
this);
connect(itemWidget, &ConfirmItemWidget::checkStateChanged,
this, &VConfirmDeletionDialog::updateCountLabel);
QListWidgetItem *item = new QListWidgetItem();
QSize size = itemWidget->sizeHint();
size.setHeight(size.height() * 2);
@ -165,7 +189,9 @@ void VConfirmDeletionDialog::currentFileChanged(int p_row)
if (p_row > -1 && m_enablePreview) {
ConfirmItemWidget *widget = getItemWidget(m_listWidget->item(p_row));
if (widget) {
QPixmap image(widget->getFile());
int idx = widget->getIndex();
Q_ASSERT(idx < m_items.size());
QPixmap image(m_items[idx].m_path);
if (!image.isNull()) {
int width = 512 * VUtils::calculateScaleFactor();
QSize previewSize(width, width);
@ -183,3 +209,18 @@ ConfirmItemWidget *VConfirmDeletionDialog::getItemWidget(QListWidgetItem *p_item
QWidget *wid = m_listWidget->itemWidget(p_item);
return dynamic_cast<ConfirmItemWidget *>(wid);
}
void VConfirmDeletionDialog::updateCountLabel()
{
int total = m_listWidget->count();
int checked = 0;
for (int i = 0; i < total; ++i) {
ConfirmItemWidget *widget = getItemWidget(m_listWidget->item(i));
if (widget->isChecked()) {
++checked;
}
}
m_countLabel->setText(tr("%1/%2 Items").arg(checked).arg(total));
}

View File

@ -12,32 +12,87 @@ class QListWidgetItem;
class ConfirmItemWidget;
class QCheckBox;
// Information about a deletion item needed to confirm.
struct ConfirmItemInfo
{
ConfirmItemInfo()
: m_data(NULL)
{
}
ConfirmItemInfo(const QString &p_name,
const QString &p_tip,
const QString &p_path,
void *p_data)
: m_name(p_name), m_tip(p_tip), m_path(p_path), m_data(p_data)
{
}
QString m_name;
QString m_tip;
QString m_path;
void *m_data;
};
class ConfirmItemWidget : public QWidget
{
Q_OBJECT
public:
ConfirmItemWidget(bool p_checked,
const QString &p_file,
const QString &p_tip,
int p_index,
QWidget *p_parent = NULL);
bool isChecked() const;
int getIndex() const;
signals:
// Emit when item's check state changed.
void checkStateChanged(int p_state);
private:
void setupUI();
QCheckBox *m_checkBox;
QLabel *m_fileLabel;
int m_index;
};
class VConfirmDeletionDialog : public QDialog
{
Q_OBJECT
public:
VConfirmDeletionDialog(const QString &p_title,
const QString &p_text,
const QString &p_info,
const QVector<QString> &p_files,
const QVector<ConfirmItemInfo> &p_items,
bool p_enableAskAgain,
bool p_askAgainEnabled,
bool p_enablePreview,
QWidget *p_parent = 0);
QVector<QString> getConfirmedFiles() const;
QVector<ConfirmItemInfo> getConfirmedItems() const;
bool getAskAgainEnabled() const;
private slots:
void currentFileChanged(int p_row);
private:
void setupUI(const QString &p_title, const QString &p_info);
void updateCountLabel();
void initFileItems(const QVector<QString> &p_files);
private:
void setupUI(const QString &p_title,
const QString &p_text,
const QString &p_info);
void initItems();
ConfirmItemWidget *getItemWidget(QListWidgetItem *p_item) const;
QLabel *m_countLabel;
QListWidget *m_listWidget;
QLabel *m_previewer;
QDialogButtonBox *m_btnBox;
@ -48,6 +103,8 @@ private:
bool m_askAgainEnabled;
bool m_enablePreview;
QVector<ConfirmItemInfo> m_items;
};
#endif // VCONFIRMDELETIONDIALOG_H

View File

@ -2,6 +2,33 @@
#include <QtWidgets>
void VTreeWidget::dropEvent(QDropEvent *p_event)
{
QList<QTreeWidgetItem *> dragItems = selectedItems();
int first = -1, last = -1;
QTreeWidgetItem *firstItem = NULL;
for (int i = 0; i < dragItems.size(); ++i) {
int row = indexFromItem(dragItems[i]).row();
if (row > last) {
last = row;
}
if (first == -1 || row < first) {
first = row;
firstItem = dragItems[i];
}
}
Q_ASSERT(firstItem);
QTreeWidget::dropEvent(p_event);
int target = indexFromItem(firstItem).row();
emit rowsMoved(first, last, target);
}
VSortDialog::VSortDialog(const QString &p_title,
const QString &p_info,
QWidget *p_parent)
@ -18,10 +45,28 @@ void VSortDialog::setupUI(const QString &p_title, const QString &p_info)
infoLabel->setWordWrap(true);
}
m_treeWidget = new QTreeWidget();
m_treeWidget = new VTreeWidget();
m_treeWidget->setRootIsDecorated(false);
m_treeWidget->setSelectionMode(QAbstractItemView::ContiguousSelection);
m_treeWidget->setDragDropMode(QAbstractItemView::InternalMove);
connect(m_treeWidget, &VTreeWidget::rowsMoved,
this, [this](int p_first, int p_last, int p_row) {
Q_UNUSED(p_first);
Q_UNUSED(p_last);
QTreeWidgetItem *item = m_treeWidget->topLevelItem(p_row);
if (item) {
m_treeWidget->setCurrentItem(item);
// Select all items back.
int cnt = p_last - p_first + 1;
for (int i = 0; i < cnt; ++i) {
QTreeWidgetItem *it = m_treeWidget->topLevelItem(p_row + i);
if (it) {
it->setSelected(true);
}
}
}
});
// Buttons for top/up/down/bottom.
m_topBtn = new QPushButton(tr("&Top"));
@ -82,6 +127,11 @@ void VSortDialog::setupUI(const QString &p_title, const QString &p_info)
void VSortDialog::treeUpdated()
{
int cols = m_treeWidget->columnCount();
for (int i = 0; i < cols; ++i) {
m_treeWidget->resizeColumnToContents(i);
}
// We just need single level.
int cnt = m_treeWidget->topLevelItemCount();
for (int i = 0; i < cnt; ++i) {
@ -200,6 +250,20 @@ void VSortDialog::handleMoveOperation(MoveOperation p_op)
}
if (firstItem) {
m_treeWidget->setCurrentItem(firstItem);
m_treeWidget->scrollToItem(firstItem);
}
}
QVector<QVariant> VSortDialog::getSortedData() const
{
int cnt = m_treeWidget->topLevelItemCount();
QVector<QVariant> data(cnt);
for (int i = 0; i < cnt; ++i) {
QTreeWidgetItem *item = m_treeWidget->topLevelItem(i);
Q_ASSERT(item);
data[i] = item->data(0, Qt::UserRole);
}
return data;
}

View File

@ -3,10 +3,32 @@
#include <QDialog>
#include <QVector>
#include <QTreeWidget>
class QPushButton;
class QDialogButtonBox;
class QTreeWidget;
class QDropEvent;
// QTreeWidget won't emit the rowsMoved() signal after drag-and-drop.
// VTreeWidget will emit rowsMoved() signal.
class VTreeWidget : public QTreeWidget
{
Q_OBJECT
public:
explicit VTreeWidget(QWidget *p_parent = 0)
: QTreeWidget(p_parent)
{
}
protected:
void dropEvent(QDropEvent *p_event) Q_DECL_OVERRIDE;
signals:
// Rows [@p_first, @p_last] were moved to @p_row.
void rowsMoved(int p_first, int p_last, int p_row);
};
class VSortDialog : public QDialog
{
@ -21,6 +43,9 @@ public:
// Called after updating the m_treeWidget.
void treeUpdated();
// Get user data of column 0 from sorted items.
QVector<QVariant> getSortedData() const;
private:
enum MoveOperation { Top, Up, Down, Bottom };
@ -30,7 +55,7 @@ private slots:
private:
void setupUI(const QString &p_title, const QString &p_info);
QTreeWidget *m_treeWidget;
VTreeWidget *m_treeWidget;
QPushButton *m_topBtn;
QPushButton *m_upBtn;
QPushButton *m_downBtn;

View File

@ -250,18 +250,28 @@ bool VUtils::makePath(const QString &p_path)
return ret;
}
ClipboardOpType VUtils::opTypeInClipboard()
QJsonObject VUtils::clipboardToJson()
{
QClipboard *clipboard = QApplication::clipboard();
const QMimeData *mimeData = clipboard->mimeData();
QJsonObject obj;
if (mimeData->hasText()) {
QString text = mimeData->text();
QJsonObject clip = QJsonDocument::fromJson(text.toLocal8Bit()).object();
if (clip.contains("operation")) {
return (ClipboardOpType)clip["operation"].toInt();
obj = QJsonDocument::fromJson(text.toUtf8()).object();
qDebug() << "Json object in clipboard" << obj;
}
return obj;
}
ClipboardOpType VUtils::operationInClipboard()
{
QJsonObject obj = clipboardToJson();
if (obj.contains(ClipboardConfig::c_type)) {
return (ClipboardOpType)obj[ClipboardConfig::c_type].toInt();
}
return ClipboardOpType::Invalid;
}
@ -274,6 +284,12 @@ bool VUtils::copyFile(const QString &p_srcFilePath, const QString &p_destFilePat
return true;
}
QDir dir;
if (!dir.mkpath(basePathFromPath(p_destFilePath))) {
qWarning() << "fail to create directory" << basePathFromPath(p_destFilePath);
return false;
}
if (p_isCut) {
QFile file(srcPath);
if (!file.rename(destPath)) {
@ -286,10 +302,10 @@ bool VUtils::copyFile(const QString &p_srcFilePath, const QString &p_destFilePat
return false;
}
}
return true;
}
// Copy @p_srcDirPath to be @p_destDirPath.
bool VUtils::copyDirectory(const QString &p_srcDirPath, const QString &p_destDirPath, bool p_isCut)
{
QString srcPath = QDir::cleanPath(p_srcDirPath);
@ -298,17 +314,24 @@ bool VUtils::copyDirectory(const QString &p_srcDirPath, const QString &p_destDir
return true;
}
// Make a directory
QDir parentDir(VUtils::basePathFromPath(p_destDirPath));
QString dirName = VUtils::fileNameFromPath(p_destDirPath);
if (!parentDir.mkdir(dirName)) {
qWarning() << QString("fail to create target directory %1: already exists").arg(p_destDirPath);
if (QFileInfo::exists(destPath)) {
qWarning() << QString("target directory %1 already exists").arg(destPath);
return false;
}
// Handle sub-dirs recursively and copy files.
QDir srcDir(p_srcDirPath);
QDir destDir(p_destDirPath);
// QDir.rename() could not move directory across drives.
// Make sure target directory exists.
QDir destDir(destPath);
if (!destDir.exists()) {
if (!destDir.mkpath(destPath)) {
qWarning() << QString("fail to create target directory %1").arg(destPath);
return false;
}
}
// Handle directory recursively.
QDir srcDir(srcPath);
Q_ASSERT(srcDir.exists() && destDir.exists());
QFileInfoList nodes = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden
| QDir::NoSymLinks | QDir::NoDotAndDotDot);
@ -327,13 +350,13 @@ bool VUtils::copyDirectory(const QString &p_srcDirPath, const QString &p_destDir
}
}
// Delete the src dir if p_isCut
if (p_isCut) {
if (!srcDir.removeRecursively()) {
qWarning() << "fail to remove directory" << p_srcDirPath;
if (!destDir.rmdir(srcPath)) {
qWarning() << QString("fail to delete source directory %1 after cut").arg(srcPath);
return false;
}
}
return true;
}
@ -364,19 +387,20 @@ QString VUtils::generateCopiedFileName(const QString &p_dirPath, const QString &
suffix = p_fileName.right(p_fileName.size() - dotIdx);
base = p_fileName.left(dotIdx);
}
QDir dir(p_dirPath);
QString name = p_fileName;
QString filePath = dir.filePath(name);
int index = 0;
while (QFile(filePath).exists()) {
while (dir.exists(name)) {
QString seq;
if (index > 0) {
seq = QString::number(index);
}
index++;
name = QString("%1_copy%2%3").arg(base).arg(seq).arg(suffix);
filePath = dir.filePath(name);
}
return name;
}
@ -898,3 +922,16 @@ bool VUtils::fileExists(const QDir &p_dir, const QString &p_name, bool p_forceCa
return false;
}
void VUtils::addErrMsg(QString *p_msg, const QString &p_str)
{
if (!p_msg) {
return;
}
if (p_msg->isEmpty()) {
*p_msg = p_str;
} else {
*p_msg = *p_msg + '\n' + p_str;
}
}

View File

@ -51,7 +51,11 @@ public:
static QRgb QRgbFromString(const QString &str);
static QString generateImageFileName(const QString &path, const QString &title,
const QString &format = "png");
// Given the file name @p_fileName and directory path @p_dirPath, generate
// a file name based on @p_fileName which does not exist in @p_dirPath.
static QString generateCopiedFileName(const QString &p_dirPath, const QString &p_fileName);
static QString generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName);
static void processStyle(QString &style, const QVector<QPair<QString, QString> > &varMap);
@ -76,9 +80,24 @@ public:
// @p_path could be /home/tamlok/abc, /home/tamlok/abc/.
static bool makePath(const QString &p_path);
static ClipboardOpType opTypeInClipboard();
// Return QJsonObject if there is valid Json string in clipboard.
// Return empty object if there is no valid Json string.
static QJsonObject clipboardToJson();
// Get the operation type in system's clipboard.
static ClipboardOpType operationInClipboard();
static ClipboardOpType opTypeInClipboard() { return ClipboardOpType::Invalid; }
// Copy file @p_srcFilePath to @p_destFilePath.
// Will make necessary parent directory along the destination path.
static bool copyFile(const QString &p_srcFilePath, const QString &p_destFilePath, bool p_isCut);
// Copy @p_srcDirPath to be @p_destDirPath.
// @p_destDirPath should not exist.
// Will make necessary parent directory along the destination path.
static bool copyDirectory(const QString &p_srcDirPath, const QString &p_destDirPath, bool p_isCut);
static int showMessage(QMessageBox::Icon p_icon, const QString &p_title, const QString &p_text,
const QString &p_infoText, QMessageBox::StandardButtons p_buttons,
QMessageBox::StandardButton p_defaultBtn, QWidget *p_parent,
@ -164,6 +183,9 @@ public:
// @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);
// Assign @p_str to @p_msg if it is not NULL.
static void addErrMsg(QString *p_msg, const QString &p_str);
// Regular expression for image link.
// ![image title]( http://github.com/tamlok/vnote.jpg "alt \" text" )
// Captured texts (need to be trimmed):

View File

@ -242,7 +242,7 @@ void VAttachmentList::addAttachments(const QStringList &p_files)
}
if (addedFiles > 0) {
g_vnote->getMainWindow()->showStatusMessage(tr("Added %1 %2 as attachments")
g_vnote->getMainWindow()->showStatusMessage(tr("%1 %2 added as attachments")
.arg(addedFiles)
.arg(addedFiles > 1 ? tr("files") : tr("file")));
}
@ -300,7 +300,7 @@ void VAttachmentList::handleItemActivated(QListWidgetItem *p_item)
void VAttachmentList::deleteSelectedItems()
{
QVector<QString> names;
QVector<ConfirmItemInfo> items;
const QList<QListWidgetItem *> selectedItems = m_attachmentList->selectedItems();
if (selectedItems.isEmpty()) {
@ -308,24 +308,35 @@ void VAttachmentList::deleteSelectedItems()
}
for (auto const & item : selectedItems) {
names.push_back(item->text());
items.push_back(ConfirmItemInfo(item->text(),
item->text(),
"",
NULL));
}
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.")
QString text = tr("Are you sure to delete these attachments of note "
"<span style=\"%1\">%2</span>?")
.arg(g_config->c_dataTextStyle).arg(m_file->getName());
QString info = tr("You could find deleted files in the recycle "
"bin of this note.<br>"
"Click \"Cancel\" to leave them untouched.");
VConfirmDeletionDialog dialog(tr("Confirm Deleting Attachments"),
text,
info,
names,
items,
false,
false,
false,
g_vnote->getMainWindow());
if (dialog.exec()) {
names = dialog.getConfirmedFiles();
items = dialog.getConfirmedItems();
QVector<QString> names;
for (auto const & item : items) {
names.push_back(item.m_name);
}
if (!m_file->deleteAttachments(names)) {
VUtils::showMessage(QMessageBox::Warning,
@ -353,7 +364,10 @@ void VAttachmentList::sortItems()
}
VSortDialog dialog(tr("Sort Attachments"),
tr("Sort attachments in the configuration file."),
tr("Sort attachments of note <span style=\"%1\">%2</span> "
"in the configuration file.")
.arg(g_config->c_dataTextStyle)
.arg(m_file->getName()),
g_vnote->getMainWindow());
QTreeWidget *tree = dialog.getTreeWidget();
tree->clear();
@ -372,16 +386,24 @@ void VAttachmentList::sortItems()
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();
QVector<QVariant> data = dialog.getSortedData();
Q_ASSERT(data.size() == attas.size());
QVector<int> sortedIdx(data.size(), -1);
for (int i = 0; i < data.size(); ++i) {
sortedIdx[i] = data[i].toInt();
}
m_file->sortAttachments(sortedIdx);
if (!m_file->sortAttachments(sortedIdx)) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to sort attachments of note <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle)
.arg(m_file->getName()),
"",
QMessageBox::Ok,
QMessageBox::Ok,
this);
}
}
}

View File

@ -11,7 +11,16 @@ enum class DocType { Html = 0, Markdown, List, Container, Unknown };
// Orphan: external file;
enum class FileType { Note, Orphan };
enum class ClipboardOpType { Invalid, CopyFile, CopyDir };
enum class ClipboardOpType { CopyFile, CopyDir, Invalid };
namespace ClipboardConfig
{
static const QString c_type = "type";
static const QString c_magic = "magic";
static const QString c_isCut = "is_cut";
static const QString c_files = "files";
}
enum class OpenFileMode {Read = 0, Edit};
static const qreal c_webZoomFactorMax = 5;

View File

@ -470,24 +470,9 @@ bool VDirectory::removeFile(VNoteFile *p_file)
return false;
}
qDebug() << "note" << p_file->getName() << "removed from folder" << m_name;
return true;
}
void VDirectory::deleteFile(VNoteFile *p_file)
{
removeFile(p_file);
// Delete the file
V_ASSERT(!p_file->isOpened());
V_ASSERT(p_file->parent());
p_file->deleteFile();
delete p_file;
}
bool VDirectory::rename(const QString &p_name)
{
if (m_name == p_name) {
@ -519,126 +504,6 @@ bool VDirectory::rename(const QString &p_name)
return true;
}
VNoteFile *VDirectory::copyFile(VDirectory *p_destDir, const QString &p_destName,
VNoteFile *p_srcFile, bool p_cut)
{
QString srcPath = QDir::cleanPath(p_srcFile->fetchPath());
QString destPath = QDir::cleanPath(QDir(p_destDir->fetchPath()).filePath(p_destName));
if (VUtils::equalPath(srcPath, destPath)) {
return p_srcFile;
}
VDirectory *srcDir = p_srcFile->getDirectory();
DocType docType = p_srcFile->getDocType();
DocType newDocType = VUtils::docTypeFromName(destPath);
QVector<ImageLink> images;
if (docType == DocType::Markdown) {
images = VUtils::fetchImagesFromMarkdownFile(p_srcFile,
ImageLink::LocalRelativeInternal);
}
// Copy the file
if (!VUtils::copyFile(srcPath, destPath, p_cut)) {
return NULL;
}
// Handle VDirectory and VNoteFile
int index = -1;
VNoteFile *destFile = NULL;
if (p_cut) {
// Remove the file from config
srcDir->removeFile(p_srcFile);
p_srcFile->setName(p_destName);
// Add the file to new dir's config
if (p_destDir->addFile(p_srcFile, index)) {
destFile = p_srcFile;
} else {
destFile = NULL;
}
} else {
destFile = p_destDir->addFile(p_destName, -1);
}
if (!destFile) {
return NULL;
}
Q_ASSERT(docType == newDocType);
// We need to copy internal images when it is still markdown.
if (!images.isEmpty()) {
if (newDocType == DocType::Markdown) {
QString parentPath = destFile->fetchBasePath();
int nrPasted = 0;
for (int i = 0; i < images.size(); ++i) {
const ImageLink &link = images[i];
if (!QFileInfo::exists(link.m_path)) {
continue;
}
QString errStr;
bool ret = true;
QString imageFolder = VUtils::directoryNameFromPath(VUtils::basePathFromPath(link.m_path));
QString destImagePath = QDir(parentPath).filePath(imageFolder);
ret = VUtils::makePath(destImagePath);
if (!ret) {
errStr = tr("Fail to create image folder <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle).arg(destImagePath);
} else {
destImagePath = QDir(destImagePath).filePath(VUtils::fileNameFromPath(link.m_path));
// Copy or Cut the images accordingly.
if (VUtils::equalPath(destImagePath, link.m_path)) {
ret = false;
} else {
ret = VUtils::copyFile(link.m_path, destImagePath, p_cut);
}
if (ret) {
qDebug() << (p_cut ? "Cut" : "Copy") << "image"
<< link.m_path << "->" << destImagePath;
nrPasted++;
} else {
errStr = tr("Please check if there already exists a file <span style=\"%1\">%2</span> "
"and then manually copy it and modify the note accordingly.")
.arg(g_config->c_dataTextStyle).arg(destImagePath);
}
}
if (!ret) {
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
tr("Fail to copy image <span style=\"%1\">%2</span> while "
"%5 note <span style=\"%3\">%4</span>.")
.arg(g_config->c_dataTextStyle).arg(link.m_path)
.arg(g_config->c_dataTextStyle).arg(srcPath)
.arg(p_cut ? tr("moving") : tr("copying")),
errStr, QMessageBox::Ok, QMessageBox::Ok, NULL);
}
}
qDebug() << "pasted" << nrPasted << "images";
} else {
// Delete the images.
int deleted = 0;
for (int i = 0; i < images.size(); ++i) {
QFile file(images[i].m_path);
if (file.remove()) {
++deleted;
}
}
qDebug() << "delete" << deleted << "images since it is not Markdown any more for" << srcPath;
}
}
return destFile;
}
// Copy @p_srcDir to be a sub-directory of @p_destDir with name @p_destName.
VDirectory *VDirectory::copyDirectory(VDirectory *p_destDir, const QString &p_destName,
VDirectory *p_srcDir, bool p_cut)
@ -687,36 +552,6 @@ void VDirectory::setExpanded(bool p_expanded)
m_expanded = p_expanded;
}
void VDirectory::reorderFiles(int p_first, int p_last, int p_destStart)
{
V_ASSERT(m_opened);
V_ASSERT(p_first <= p_last);
V_ASSERT(p_last < m_files.size());
V_ASSERT(p_destStart < p_first || p_destStart > p_last);
V_ASSERT(p_destStart >= 0 && p_destStart <= m_files.size());
auto oriFiles = m_files;
// Reorder m_files.
if (p_destStart > p_last) {
int to = p_destStart - 1;
for (int i = p_first; i <= p_last; ++i) {
// Move p_first to p_destStart every time.
m_files.move(p_first, to);
}
} else {
int to = p_destStart;
for (int i = p_first; i <= p_last; ++i) {
m_files.move(i, to++);
}
}
if (!writeToConfig()) {
qWarning() << "fail to reorder files in config" << p_first << p_last << p_destStart;
m_files = oriFiles;
}
}
VNoteFile *VDirectory::tryLoadFile(QStringList &p_filePath)
{
qDebug() << "directory" << m_name << "tryLoadFile()" << p_filePath.join("/");
@ -755,3 +590,24 @@ VNoteFile *VDirectory::tryLoadFile(QStringList &p_filePath)
return file;
}
bool VDirectory::sortFiles(const QVector<int> &p_sortedIdx)
{
V_ASSERT(m_opened);
V_ASSERT(p_sortedIdx.size() == m_files.size());
auto ori = m_files;
for (int i = 0; i < p_sortedIdx.size(); ++i) {
m_files[i] = ori[p_sortedIdx[i]];
}
bool ret = true;
if (!writeToConfig()) {
qWarning() << "fail to reorder files in config" << p_sortedIdx;
m_files = ori;
ret = false;
}
return ret;
}

View File

@ -52,18 +52,12 @@ public:
// Return the VNoteFile if succeed.
VNoteFile *addFile(const QString &p_name, int p_index);
// Delete @p_file both from disk and config, as well as its local images.
void deleteFile(VNoteFile *p_file);
// Add the file in the config and m_files. If @p_index is -1, add it at the end.
bool addFile(VNoteFile *p_file, int p_index);
// Rename current directory to @p_name.
bool rename(const QString &p_name);
// Copy @p_srcFile to @p_destDir, setting new name to @p_destName.
// @p_cut: copy or cut.
// Returns the dest VNoteFile.
static VNoteFile *copyFile(VDirectory *p_destDir, const QString &p_destName,
VNoteFile *p_srcFile, bool p_cut);
static VDirectory *copyDirectory(VDirectory *p_destDir, const QString &p_destName,
VDirectory *p_srcDir, bool p_cut);
@ -83,10 +77,6 @@ public:
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.
// Not including sections belonging to notebook.
QJsonObject toConfigJson() const;
@ -108,6 +98,9 @@ public:
QDateTime getCreatedTimeUtc() const;
// Reorder files in m_files by index.
bool sortFiles(const QVector<int> &p_sortedIdx);
private:
// Get the path of @p_dir recursively
QString fetchPath(const VDirectory *p_dir) const;
@ -121,9 +114,6 @@ private:
// Should only be called with root directory.
void addNotebookConfig(QJsonObject &p_json) const;
// Add the file in the config and m_files. If @p_index is -1, add it at the end.
bool addFile(VNoteFile *p_file, int p_index);
// Add the directory in the config and m_subDirs. If @p_index is -1, add it at the end.
// Return the VDirectory if succeed.
VDirectory *addSubDirectory(const QString &p_name, int p_index);

View File

@ -533,12 +533,12 @@ void VDirectoryTree::reloadFromDisk()
curDir = getVDirectory(curItem);
info = tr("Are you sure to reload folder <span style=\"%1\">%2</span>?")
.arg(g_config->c_dataTextStyle).arg(curDir->getName());
msg = tr("Successfully reloaded folder %1 from disk").arg(curDir->getName());
msg = tr("Folder %1 reloaded from disk").arg(curDir->getName());
} else {
// Reload notebook.
info = tr("Are you sure to reload notebook <span style=\"%1\">%2</span>?")
.arg(g_config->c_dataTextStyle).arg(m_notebook->getName());
msg = tr("Successfully reloaded notebook %1 from disk").arg(m_notebook->getName());
msg = tr("Notebook %1 reloaded from disk").arg(m_notebook->getName());
}
if (g_config->getConfirmReloadFolder()) {

View File

@ -12,9 +12,13 @@
#include "vconfigmanager.h"
#include "vmdedit.h"
#include "vmdtab.h"
#include "dialog/vconfirmdeletiondialog.h"
#include "dialog/vsortdialog.h"
#include "vmainwindow.h"
extern VConfigManager *g_config;
extern VNote *g_vnote;
extern VMainWindow *g_mainWin;
const QString VFileList::c_infoShortcutSequence = "F2";
const QString VFileList::c_copyShortcutSequence = "Ctrl+C";
@ -34,7 +38,6 @@ void VFileList::setupUI()
fileList = new QListWidget(this);
fileList->setContextMenuPolicy(Qt::CustomContextMenu);
fileList->setSelectionMode(QAbstractItemView::ExtendedSelection);
fileList->setDragDropMode(QAbstractItemView::InternalMove);
fileList->setObjectName("FileList");
QVBoxLayout *mainLayout = new QVBoxLayout;
@ -45,8 +48,6 @@ void VFileList::setupUI()
this, &VFileList::contextMenuRequested);
connect(fileList, &QListWidget::itemClicked,
this, &VFileList::handleItemClicked);
connect(fileList->model(), &QAbstractItemModel::rowsMoved,
this, &VFileList::handleRowsMoved);
setLayout(mainLayout);
}
@ -78,7 +79,7 @@ void VFileList::initShortcuts()
pasteShortcut->setContext(Qt::WidgetWithChildrenShortcut);
connect(pasteShortcut, &QShortcut::activated,
this, [this](){
pasteFilesInCurDir();
pasteFilesFromClipboard();
});
}
@ -136,7 +137,7 @@ void VFileList::initActions()
tr("&Delete"), this);
deleteFileAct->setToolTip(tr("Delete selected note"));
connect(deleteFileAct, SIGNAL(triggered(bool)),
this, SLOT(deleteFile()));
this, SLOT(deleteSelectedFiles()));
fileInfoAct = new QAction(QIcon(":/resources/icons/note_info.svg"),
tr("&Info\t%1").arg(VUtils::getShortcutText(c_infoShortcutSequence)), this);
@ -160,12 +161,19 @@ void VFileList::initActions()
tr("&Paste\t%1").arg(VUtils::getShortcutText(c_pasteShortcutSequence)), this);
pasteAct->setToolTip(tr("Paste notes in current folder"));
connect(pasteAct, &QAction::triggered,
this, &VFileList::pasteFilesInCurDir);
this, &VFileList::pasteFilesFromClipboard);
m_openLocationAct = new QAction(tr("&Open Note Location"), this);
m_openLocationAct->setToolTip(tr("Open the folder containing this note in operating system"));
connect(m_openLocationAct, &QAction::triggered,
this, &VFileList::openFileLocation);
m_sortAct = new QAction(QIcon(":/resources/icons/sort.svg"),
tr("&Sort"),
this);
m_sortAct->setToolTip(tr("Sort notes in this folder manually"));
connect(m_sortAct, &QAction::triggered,
this, &VFileList::sortItems);
}
void VFileList::setDirectory(VDirectory *p_directory)
@ -187,7 +195,6 @@ void VFileList::setDirectory(VDirectory *p_directory)
return;
}
qDebug() << "filelist set folder" << m_directory->getName();
updateFileList();
}
@ -215,10 +222,11 @@ void VFileList::fileInfo()
void VFileList::openFileLocation() const
{
QListWidgetItem *curItem = fileList->currentItem();
V_ASSERT(curItem);
QUrl url = QUrl::fromLocalFile(getVFile(curItem)->fetchBasePath());
QList<QListWidgetItem *> items = fileList->selectedItems();
if (items.size() == 1) {
QUrl url = QUrl::fromLocalFile(getVFile(items[0])->fetchBasePath());
QDesktopServices::openUrl(url);
}
}
void VFileList::fileInfo(VNoteFile *p_file)
@ -277,15 +285,26 @@ QListWidgetItem* VFileList::insertFileListItem(VNoteFile *file, bool atFront)
// Qt seems not to update the QListWidget correctly. Manually force it to repaint.
fileList->update();
qDebug() << "VFileList adds" << file->getName();
return item;
}
void VFileList::removeFileListItem(QListWidgetItem *item)
void VFileList::removeFileListItem(VNoteFile *p_file)
{
fileList->setCurrentRow(-1);
fileList->removeItemWidget(item);
if (!p_file) {
return;
}
QListWidgetItem *item = findItem(p_file);
if (!item) {
return;
}
int row = fileList->row(item);
Q_ASSERT(row >= 0);
fileList->takeItem(row);
delete item;
// Qt seems not to update the QListWidget correctly. Manually force it to repaint.
fileList->update();
}
@ -383,18 +402,21 @@ QVector<QListWidgetItem *> VFileList::updateFileListAdded()
}
}
}
qDebug() << ret.size() << "items added";
return ret;
}
// Delete the file related to current item
void VFileList::deleteFile()
void VFileList::deleteSelectedFiles()
{
QList<QListWidgetItem *> items = fileList->selectedItems();
Q_ASSERT(!items.isEmpty());
for (int i = 0; i < items.size(); ++i) {
deleteFile(getVFile(items.at(i)));
QVector<VNoteFile *> files;
for (auto const & item : items) {
files.push_back(getVFile(item));
}
deleteFiles(files);
}
// @p_file may or may not be listed in VFileList
@ -404,30 +426,83 @@ void VFileList::deleteFile(VNoteFile *p_file)
return;
}
VDirectory *dir = p_file->getDirectory();
QString fileName = p_file->getName();
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>: "
"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);
if (ret == QMessageBox::Ok) {
editArea->closeFile(p_file, true);
QVector<VNoteFile *> files(1, p_file);
deleteFiles(files);
}
// Remove the item before deleting it totally, or p_file will be invalid.
QListWidgetItem *item = findItem(p_file);
if (item) {
removeFileListItem(item);
void VFileList::deleteFiles(const QVector<VNoteFile *> &p_files)
{
if (p_files.isEmpty()) {
return;
}
dir->deleteFile(p_file);
QVector<ConfirmItemInfo> items;
for (auto const & file : p_files) {
items.push_back(ConfirmItemInfo(file->getName(),
file->fetchPath(),
file->fetchPath(),
(void *)file));
}
QString text = tr("Are you sure to delete these notes?");
QString info = tr("<span style=\"%1\">WARNING</span>: "
"VNote will delete notes as well as all "
"their images and attachments managed by VNote. "
"You could find deleted files in the recycle "
"bin of these notes.<br>"
"Click \"Cancel\" to leave them untouched.<br>"
"The operation is IRREVERSIBLE!")
.arg(g_config->c_warningTextStyle);
VConfirmDeletionDialog dialog(tr("Confirm Deleting Notes"),
text,
info,
items,
false,
false,
false,
this);
if (dialog.exec()) {
items = dialog.getConfirmedItems();
QVector<VNoteFile *> files;
for (auto const & item : items) {
files.push_back((VNoteFile *)item.m_data);
}
int nrDeleted = 0;
for (auto file : files) {
editArea->closeFile(file, true);
// Remove the item before deleting it totally, or file will be invalid.
removeFileListItem(file);
QString errMsg;
QString fileName = file->getName();
QString filePath = file->fetchPath();
if (!VNoteFile::deleteFile(file, &errMsg)) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to delete note <span style=\"%1\">%2</span>.<br>"
"Please check <span style=\"%1\">%3</span> and manually delete it.")
.arg(g_config->c_dataTextStyle)
.arg(fileName)
.arg(filePath),
errMsg,
QMessageBox::Ok,
QMessageBox::Ok,
this);
} else {
Q_ASSERT(errMsg.isEmpty());
++nrDeleted;
}
}
if (nrDeleted > 0) {
g_mainWin->showStatusMessage(tr("%1 %2 deleted")
.arg(nrDeleted)
.arg(nrDeleted > 1 ? tr("notes") : tr("note")));
}
}
}
@ -456,18 +531,22 @@ void VFileList::contextMenuRequested(QPoint pos)
menu.addAction(newFileAct);
if (fileList->count() > 1) {
menu.addAction(m_sortAct);
}
if (item) {
menu.addAction(deleteFileAct);
menu.addSeparator();
menu.addAction(deleteFileAct);
menu.addAction(copyAct);
menu.addAction(cutAct);
}
if (VUtils::opTypeInClipboard() == ClipboardOpType::CopyFile
&& !m_copiedFiles.isEmpty()) {
if (pasteAvailable()) {
if (!item) {
menu.addSeparator();
}
menu.addAction(pasteAct);
}
@ -517,29 +596,59 @@ void VFileList::handleItemClicked(QListWidgetItem *currentItem)
emit fileClicked(getVFile(currentItem), g_config->getNoteOpenMode());
}
bool VFileList::importFile(const QString &p_srcFilePath)
bool VFileList::importFiles(const QStringList &p_files, QString *p_errMsg)
{
if (p_srcFilePath.isEmpty()) {
return false;
}
Q_ASSERT(m_directory);
// Copy file @name to current directory
QString targetPath = m_directory->fetchPath();
QString srcName = VUtils::fileNameFromPath(p_srcFilePath);
if (srcName.isEmpty()) {
return false;
}
QString targetFilePath = QDir(targetPath).filePath(srcName);
bool ret = VUtils::copyFile(p_srcFilePath, targetFilePath, false);
if (!ret) {
if (p_files.isEmpty()) {
return false;
}
VNoteFile *destFile = m_directory->addFile(srcName, -1);
if (destFile) {
return insertFileListItem(destFile, false);
bool ret = true;
Q_ASSERT(m_directory && m_directory->isOpened());
QString dirPath = m_directory->fetchPath();
QDir dir(dirPath);
int nrImported = 0;
for (int i = 0; i < p_files.size(); ++i) {
const QString &file = p_files[i];
QFileInfo fi(file);
if (!fi.exists() || !fi.isFile()) {
VUtils::addErrMsg(p_errMsg, tr("Skip importing non-exist file %1.")
.arg(file));
ret = false;
continue;
}
return false;
QString name = VUtils::fileNameFromPath(file);
Q_ASSERT(!name.isEmpty());
name = VUtils::getFileNameWithSequence(dirPath, name);
QString targetFilePath = dir.filePath(name);
bool ret = VUtils::copyFile(file, targetFilePath, false);
if (!ret) {
VUtils::addErrMsg(p_errMsg, tr("Fail to copy file %1 as %1.")
.arg(file)
.arg(targetFilePath));
ret = false;
continue;
}
VNoteFile *destFile = m_directory->addFile(name, -1);
if (destFile) {
++nrImported;
qDebug() << "imported" << file << "as" << targetFilePath;
} else {
VUtils::addErrMsg(p_errMsg, tr("Fail to add the note %1 to target folder's configuration.")
.arg(file));
ret = false;
continue;
}
}
qDebug() << "imported" << nrImported << "files";
updateFileList();
return ret;
}
void VFileList::copySelectedFiles(bool p_isCut)
@ -548,19 +657,29 @@ void VFileList::copySelectedFiles(bool p_isCut)
if (items.isEmpty()) {
return;
}
QJsonArray files;
m_copiedFiles.clear();
for (int i = 0; i < items.size(); ++i) {
VNoteFile *file = getVFile(items[i]);
QJsonObject fileJson;
fileJson["notebook"] = file->getNotebookName();
fileJson["path"] = file->fetchPath();
files.append(fileJson);
m_copiedFiles.append(file);
files.append(file->fetchPath());
}
copyFileInfoToClipboard(files, p_isCut);
QJsonObject clip;
clip[ClipboardConfig::c_magic] = getNewMagic();
clip[ClipboardConfig::c_type] = (int)ClipboardOpType::CopyFile;
clip[ClipboardConfig::c_isCut] = p_isCut;
clip[ClipboardConfig::c_files] = files;
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(QJsonDocument(clip).toJson(QJsonDocument::Compact));
qDebug() << "copied files info" << clipboard->text();
int cnt = files.size();
g_mainWin->showStatusMessage(tr("%1 %2 %3")
.arg(cnt)
.arg(cnt > 1 ? tr("notes") : tr("note"))
.arg(p_isCut ? tr("cut") : tr("copied")));
}
void VFileList::cutSelectedFiles()
@ -568,82 +687,123 @@ void VFileList::cutSelectedFiles()
copySelectedFiles(true);
}
void VFileList::copyFileInfoToClipboard(const QJsonArray &p_files, bool p_isCut)
void VFileList::pasteFilesFromClipboard()
{
QJsonObject clip;
clip["operation"] = (int)ClipboardOpType::CopyFile;
clip["is_cut"] = p_isCut;
clip["sources"] = p_files;
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(QJsonDocument(clip).toJson(QJsonDocument::Compact));
}
void VFileList::pasteFilesInCurDir()
{
if (m_copiedFiles.isEmpty()) {
if (!pasteAvailable()) {
return;
}
pasteFiles(m_directory);
QJsonObject obj = VUtils::clipboardToJson();
QJsonArray files = obj[ClipboardConfig::c_files].toArray();
bool isCut = obj[ClipboardConfig::c_isCut].toBool();
QVector<QString> filesToPaste(files.size());
for (int i = 0; i < files.size(); ++i) {
filesToPaste[i] = files[i].toString();
}
pasteFiles(m_directory, filesToPaste, isCut);
}
void VFileList::pasteFiles(VDirectory *p_destDir)
void VFileList::pasteFiles(VDirectory *p_destDir,
const QVector<QString> &p_files,
bool p_isCut)
{
qDebug() << "paste files to" << p_destDir->getName();
QClipboard *clipboard = QApplication::clipboard();
QString text = clipboard->text();
QJsonObject clip = QJsonDocument::fromJson(text.toLocal8Bit()).object();
Q_ASSERT(!clip.isEmpty() && clip["operation"] == (int)ClipboardOpType::CopyFile);
bool isCut = clip["is_cut"].toBool();
if (!p_destDir || p_files.isEmpty()) {
clipboard->clear();
return;
}
int nrPasted = 0;
for (int i = 0; i < m_copiedFiles.size(); ++i) {
QPointer<VNoteFile> srcFile = m_copiedFiles[i];
if (!srcFile) {
for (int i = 0; i < p_files.size(); ++i) {
VNoteFile *file = g_vnote->getInternalFile(p_files[i]);
if (!file) {
qWarning() << "Copied file is not an internal note" << p_files[i];
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to copy note <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle)
.arg(p_files[i]),
tr("VNote could not find this note in any notebook."),
QMessageBox::Ok,
QMessageBox::Ok,
this);
continue;
}
QString fileName = srcFile->getName();
VDirectory *srcDir = srcFile->getDirectory();
if (srcDir == p_destDir && !isCut) {
// Copy and paste in the same directory.
// Rename it to xx_copy.md
fileName = VUtils::generateCopiedFileName(srcDir->fetchPath(), fileName);
QString fileName = file->getName();
if (file->getDirectory() == p_destDir) {
if (p_isCut) {
qDebug() << "skip one note to cut and paste in the same folder" << fileName;
continue;
}
if (copyFile(p_destDir, fileName, srcFile, isCut)) {
nrPasted++;
} else {
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
// Copy and paste in the same folder.
// We do not allow this if the note contains local images.
if (file->getDocType() == DocType::Markdown) {
QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(file,
ImageLink::LocalRelativeInternal);
if (!images.isEmpty()) {
qDebug() << "skip one note with internal images to copy and paste in the same folder"
<< fileName;
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to copy note <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle).arg(srcFile->getName()),
tr("Please check if there already exists a file with the same name in the target folder."),
QMessageBox::Ok, QMessageBox::Ok, this);
.arg(g_config->c_dataTextStyle)
.arg(p_files[i]),
tr("VNote does not allow copy and paste notes with internal images "
"in the same folder."),
QMessageBox::Ok,
QMessageBox::Ok,
this);
continue;
}
}
qDebug() << "pasted" << nrPasted << "files sucessfully";
clipboard->clear();
m_copiedFiles.clear();
}
bool VFileList::copyFile(VDirectory *p_destDir, const QString &p_destName, VNoteFile *p_file, bool p_cut)
{
QString srcPath = QDir::cleanPath(p_file->fetchPath());
QString destPath = QDir::cleanPath(QDir(p_destDir->fetchPath()).filePath(p_destName));
if (VUtils::equalPath(srcPath, destPath)) {
return true;
// Rename it to xxx_copy.md.
fileName = VUtils::generateCopiedFileName(file->fetchBasePath(), fileName);
} else {
// Rename it to xxx_copy.md if needed.
fileName = VUtils::generateCopiedFileName(p_destDir->fetchPath(), fileName);
}
// DocType is not allowed to change.
Q_ASSERT(p_file->getDocType() == VUtils::docTypeFromName(destPath));
QString msg;
VNoteFile *destFile = NULL;
bool ret = VNoteFile::copyFile(p_destDir,
fileName,
file,
p_isCut,
&destFile,
&msg);
if (!ret) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to copy note <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle)
.arg(p_files[i]),
msg,
QMessageBox::Ok,
QMessageBox::Ok,
this);
}
VNoteFile *destFile = VDirectory::copyFile(p_destDir, p_destName, p_file, p_cut);
updateFileList();
if (destFile) {
++nrPasted;
emit fileUpdated(destFile);
}
return destFile != NULL;
}
qDebug() << "copy" << nrPasted << "files";
if (nrPasted > 0) {
g_mainWin->showStatusMessage(tr("%1 %2 pasted")
.arg(nrPasted)
.arg(nrPasted > 1 ? tr("notes") : tr("note")));
}
updateFileList();
clipboard->clear();
getNewMagic();
}
void VFileList::keyPressEvent(QKeyEvent *event)
@ -714,30 +874,6 @@ bool VFileList::locateFile(const VNoteFile *p_file)
return false;
}
void VFileList::handleRowsMoved(const QModelIndex &p_parent, int p_start, int p_end, const QModelIndex &p_destination, int p_row)
{
if (p_parent == p_destination) {
// Items[p_start, p_end] are moved to p_row.
m_directory->reorderFiles(p_start, p_end, p_row);
Q_ASSERT(identicalListWithDirectory());
}
}
bool VFileList::identicalListWithDirectory() const
{
const QVector<VNoteFile *> files = m_directory->getFiles();
int nrItems = fileList->count();
if (nrItems != files.size()) {
return false;
}
for (int i = 0; i < nrItems; ++i) {
if (getVFile(fileList->item(i)) != files.at(i)) {
return false;
}
}
return true;
}
void VFileList::registerNavigation(QChar p_majorKey)
{
m_majorKey = p_majorKey;
@ -825,3 +961,105 @@ QList<QListWidgetItem *> VFileList::getVisibleItems() const
return items;
}
int VFileList::getNewMagic()
{
m_magicForClipboard = (int)QDateTime::currentDateTime().toTime_t();
m_magicForClipboard |= qrand();
return m_magicForClipboard;
}
bool VFileList::checkMagic(int p_magic) const
{
return m_magicForClipboard == p_magic;
}
bool VFileList::pasteAvailable() const
{
QJsonObject obj = VUtils::clipboardToJson();
if (obj.isEmpty()) {
return false;
}
if (!obj.contains(ClipboardConfig::c_type)) {
return false;
}
ClipboardOpType type = (ClipboardOpType)obj[ClipboardConfig::c_type].toInt();
if (type != ClipboardOpType::CopyFile) {
return false;
}
if (!obj.contains(ClipboardConfig::c_magic)
|| !obj.contains(ClipboardConfig::c_isCut)
|| !obj.contains(ClipboardConfig::c_files)) {
return false;
}
int magic = obj[ClipboardConfig::c_magic].toInt();
if (!checkMagic(magic)) {
return false;
}
QJsonArray files = obj[ClipboardConfig::c_files].toArray();
return !files.isEmpty();
}
void VFileList::sortItems()
{
const QVector<VNoteFile *> &files = m_directory->getFiles();
if (files.size() < 2) {
return;
}
VSortDialog dialog(tr("Sort Notes"),
tr("Sort notes in folder <span style=\"%1\">%2</span> "
"in the configuration file.")
.arg(g_config->c_dataTextStyle)
.arg(m_directory->getName()),
this);
QTreeWidget *tree = dialog.getTreeWidget();
tree->clear();
tree->setColumnCount(3);
tree->header()->setStretchLastSection(true);
QStringList headers;
headers << tr("Name") << tr("Created Time") << tr("Modified Time");
tree->setHeaderLabels(headers);
for (int i = 0; i < files.size(); ++i) {
const VNoteFile *file = files[i];
QString createdTime = VUtils::displayDateTime(file->getCreatedTimeUtc().toLocalTime());
QString modifiedTime = VUtils::displayDateTime(file->getModifiedTimeUtc().toLocalTime());
QStringList cols;
cols << file->getName() << createdTime << modifiedTime;
QTreeWidgetItem *item = new QTreeWidgetItem(tree, cols);
item->setData(0, Qt::UserRole, i);
}
dialog.treeUpdated();
if (dialog.exec()) {
QVector<QVariant> data = dialog.getSortedData();
Q_ASSERT(data.size() == files.size());
QVector<int> sortedIdx(data.size(), -1);
for (int i = 0; i < data.size(); ++i) {
sortedIdx[i] = data[i].toInt();
}
qDebug() << "sort files" << sortedIdx;
if (!m_directory->sortFiles(sortedIdx)) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to sort notes in folder <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle)
.arg(m_directory->getName()),
"",
QMessageBox::Ok,
QMessageBox::Ok,
this);
}
updateFileList();
}
}

View File

@ -27,11 +27,23 @@ class VFileList : public QWidget, public VNavigationMode
Q_OBJECT
public:
explicit VFileList(QWidget *parent = 0);
bool importFile(const QString &p_srcFilePath);
// Import external files @p_files to current directory.
// Only copy the files itself.
bool importFiles(const QStringList &p_files, QString *p_errMsg = NULL);
inline void setEditArea(VEditArea *editArea);
// View and edit information of @p_file.
void fileInfo(VNoteFile *p_file);
// Delete file @p_file.
// It is not necessary that @p_file exists in the list.
void deleteFile(VNoteFile *p_file);
// Locate @p_file in the list widget.
bool locateFile(const VNoteFile *p_file);
inline const VDirectory *currentDirectory() const;
// Implementations for VNavigationMode.
@ -40,6 +52,13 @@ public:
void hideNavigation() Q_DECL_OVERRIDE;
bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE;
public slots:
// Set VFileList to display content of @p_directory directory.
void setDirectory(VDirectory *p_directory);
// Create a note.
void newFile();
signals:
void fileClicked(VNoteFile *p_file, OpenFileMode mode = OpenFileMode::Read);
void fileCreated(VNoteFile *p_file, OpenFileMode mode = OpenFileMode::Read);
@ -48,18 +67,33 @@ signals:
private slots:
void contextMenuRequested(QPoint pos);
void handleItemClicked(QListWidgetItem *currentItem);
void fileInfo();
void openFileLocation() const;
// m_copiedFiles will keep the files's VNoteFile.
void copySelectedFiles(bool p_isCut = false);
void cutSelectedFiles();
void pasteFilesInCurDir();
void deleteFile();
void handleRowsMoved(const QModelIndex &p_parent, int p_start, int p_end, const QModelIndex &p_destination, int p_row);
public slots:
void setDirectory(VDirectory *p_directory);
void newFile();
// View and edit information of selected file.
// Valid only when there is only one selected file.
void fileInfo();
// Open the folder containing selected file in system's file browser.
// Valid only when there is only one selected file.
void openFileLocation() const;
// Copy selected files to clipboard.
// Will put a Json string into the clipboard which contains the information
// about copied files.
void copySelectedFiles(bool p_isCut = false);
void cutSelectedFiles();
// Paste files from clipboard.
void pasteFilesFromClipboard();
// Delete selected files.
void deleteSelectedFiles();
// Delete files @p_files.
void deleteFiles(const QVector<VNoteFile *> &p_files);
// Sort files in this list.
void sortItems();
protected:
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
@ -71,11 +105,16 @@ private:
// Init shortcuts.
void initShortcuts();
// Clear and re-fill the list widget according to m_directory.
void updateFileList();
// Insert a new item into the list widget.
// @file: the file represented by the new item.
// @atFront: insert at the front or back of the list widget.
QListWidgetItem *insertFileListItem(VNoteFile *file, bool atFront = false);
void removeFileListItem(QListWidgetItem *item);
// Remove and delete item related to @p_file from list widget.
void removeFileListItem(VNoteFile *p_file);
// Init actions.
void initActions();
@ -83,25 +122,36 @@ private:
// Return the corresponding QListWidgetItem of @p_file.
QListWidgetItem *findItem(const VNoteFile *p_file);
void copyFileInfoToClipboard(const QJsonArray &p_files, bool p_isCut);
void pasteFiles(VDirectory *p_destDir);
bool copyFile(VDirectory *p_destDir, const QString &p_destName, VNoteFile *p_file, bool p_cut);
// Paste files given path by @p_files to destination directory @p_destDir.
void pasteFiles(VDirectory *p_destDir,
const QVector<QString> &p_files,
bool p_isCut);
// New items have been added to direcotry. Update file list accordingly.
QVector<QListWidgetItem *> updateFileListAdded();
inline QPointer<VNoteFile> getVFile(QListWidgetItem *p_item) const;
// Check if the list items match exactly the contents of the directory.
bool identicalListWithDirectory() const;
QList<QListWidgetItem *> getVisibleItems() const;
// Fill the info of @p_item according to @p_file.
void fillItem(QListWidgetItem *p_item, const VNoteFile *p_file);
// Generate new magic to m_magicForClipboard.
int getNewMagic();
// Check if @p_magic equals to m_magicForClipboard.
bool checkMagic(int p_magic) const;
// Check if there are files in clipboard available to paste.
bool pasteAvailable() const;
VEditArea *editArea;
QListWidget *fileList;
QPointer<VDirectory> m_directory;
QVector<QPointer<VNoteFile> > m_copiedFiles;
// Magic number for clipboard operations.
int m_magicForClipboard;
// Actions
QAction *m_openInReadAct;
@ -114,6 +164,7 @@ private:
QAction *cutAct;
QAction *pasteAct;
QAction *m_openLocationAct;
QAction *m_sortAct;
// Navigation Mode.
// Map second key to QListWidgetItem.

View File

@ -1039,7 +1039,7 @@ void VMainWindow::importNoteFromFile()
{
static QString lastPath = QDir::homePath();
QStringList files = QFileDialog::getOpenFileNames(this,
tr("Select Files (HTML or Markdown) To Create Notes"),
tr("Select Files To Create Notes"),
lastPath);
if (files.isEmpty()) {
return;
@ -1048,23 +1048,21 @@ void VMainWindow::importNoteFromFile()
// Update lastPath
lastPath = QFileInfo(files[0]).path();
int failedFiles = 0;
for (int i = 0; i < files.size(); ++i) {
bool ret = fileList->importFile(files[i]);
if (!ret) {
++failedFiles;
QString msg;
if (!fileList->importFiles(files, &msg)) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to create notes for all the files."),
msg,
QMessageBox::Ok,
QMessageBox::Ok,
this);
} else {
int cnt = files.size();
showStatusMessage(tr("%1 %2 created from external files")
.arg(cnt)
.arg(cnt > 1 ? tr("notes") : tr("note")));
}
}
QMessageBox msgBox(QMessageBox::Information, tr("New Notes From Files"),
tr("Created notes: %1 succeed, %2 failed.")
.arg(files.size() - failedFiles).arg(failedFiles),
QMessageBox::Ok, this);
if (failedFiles > 0) {
msgBox.setInformativeText(tr("Fail to create notes from files maybe due to name conflicts."));
}
msgBox.exec();
}
void VMainWindow::changeMarkdownConverter(QAction *action)

View File

@ -286,21 +286,39 @@ void VMdEdit::clearUnusedImages()
if (!unusedImages.isEmpty()) {
if (g_config->getConfirmImagesCleanUp()) {
QString info = tr("Following images seems not to be used in this note anymore. "
"Please confirm the deletion of these images.<br>"
QVector<ConfirmItemInfo> items;
for (auto const & img : unusedImages) {
items.push_back(ConfirmItemInfo(img,
img,
img,
NULL));
}
QString text = tr("Following images seems not to be used in this note anymore. "
"Please confirm the deletion of these images.");
QString info = tr("You could find deleted files in the recycle "
"bin of this note.<br>"
"Click \"Cancel\" to leave them untouched.");
VConfirmDeletionDialog dialog(tr("Confirm Cleaning Up Unused Images"),
text,
info,
unusedImages,
items,
true,
true,
g_config->getConfirmImagesCleanUp(),
true,
this);
if (dialog.exec()) {
unusedImages = dialog.getConfirmedFiles();
g_config->setConfirmImagesCleanUp(dialog.getAskAgainEnabled());
} else {
unusedImages.clear();
if (dialog.exec()) {
items = dialog.getConfirmedItems();
g_config->setConfirmImagesCleanUp(dialog.getAskAgainEnabled());
for (auto const & item : items) {
unusedImages.push_back(item.m_name);
}
}
}

View File

@ -53,9 +53,6 @@ QString VNoteFile::getImageFolderInLink() const
void VNoteFile::setName(const QString &p_name)
{
Q_ASSERT(m_name.isEmpty()
|| (m_docType == VUtils::docTypeFromName(p_name)));
m_name = p_name;
}
@ -80,7 +77,7 @@ bool VNoteFile::rename(const QString &p_name)
m_name = p_name;
// Update parent directory's config file.
if (!dir->writeToConfig()) {
if (!dir->updateFileConfig(this)) {
m_name = oldName;
diskDir.rename(p_name, m_name);
return false;
@ -175,32 +172,41 @@ QJsonObject VNoteFile::toConfigJson() const
return item;
}
bool VNoteFile::deleteFile()
bool VNoteFile::deleteFile(QString *p_errMsg)
{
Q_ASSERT(!m_opened);
Q_ASSERT(parent());
bool ret = false;
bool ret = true;
// Delete local images if it is Markdown.
if (m_docType == DocType::Markdown) {
deleteInternalImages();
if (!deleteInternalImages()) {
ret = false;
VUtils::addErrMsg(p_errMsg, tr("Fail to delete images of this note."));
}
}
// TODO: Delete attachments.
// Delete attachments.
if (!deleteAttachments()) {
ret = false;
VUtils::addErrMsg(p_errMsg, tr("Fail to delete attachments of this note."));
}
// Delete the file.
QString filePath = fetchPath();
if (VUtils::deleteFile(getNotebook(), filePath, false)) {
ret = true;
qDebug() << "deleted" << m_name << filePath;
} else {
ret = false;
VUtils::addErrMsg(p_errMsg, tr("Fail to delete the note file."));
qWarning() << "fail to delete" << m_name << filePath;
}
return ret;
}
void VNoteFile::deleteInternalImages()
bool VNoteFile::deleteInternalImages()
{
Q_ASSERT(parent() && m_docType == DocType::Markdown);
@ -214,6 +220,8 @@ void VNoteFile::deleteInternalImages()
}
qDebug() << "delete" << deleted << "images for" << m_name << fetchPath();
return deleted == images.size();
}
bool VNoteFile::addAttachment(const QString &p_file)
@ -297,10 +305,20 @@ bool VNoteFile::deleteAttachments(const QVector<QString> &p_names)
}
}
// Delete the attachment folder if m_attachments is empty now.
if (m_attachments.isEmpty()) {
dir.cdUp();
if (!dir.rmdir(m_attachmentFolder)) {
ret = false;
qWarning() << "fail to delete attachment folder" << m_attachmentFolder
<< "for note" << m_name;
}
}
if (!getDirectory()->updateFileConfig(this)) {
ret = false;
qWarning() << "fail to update config of file" << m_name
<< "in directory" << fetchBasePath();
ret = false;
}
return ret;
@ -320,21 +338,25 @@ int VNoteFile::findAttachment(const QString &p_name, bool p_caseSensitive)
return -1;
}
void VNoteFile::sortAttachments(QVector<int> p_sortedIdx)
bool VNoteFile::sortAttachments(const QVector<int> &p_sortedIdx)
{
V_ASSERT(m_opened);
V_ASSERT(p_sortedIdx.size() == m_attachments.size());
auto oriFiles = m_attachments;
auto ori = m_attachments;
for (int i = 0; i < p_sortedIdx.size(); ++i) {
m_attachments[i] = oriFiles[p_sortedIdx[i]];
m_attachments[i] = ori[p_sortedIdx[i]];
}
bool ret = true;
if (!getDirectory()->updateFileConfig(this)) {
qWarning() << "fail to reorder files in config" << p_sortedIdx;
m_attachments = oriFiles;
qWarning() << "fail to reorder attachments in config" << p_sortedIdx;
m_attachments = ori;
ret = false;
}
return ret;
}
bool VNoteFile::renameAttachment(const QString &p_oldName, const QString &p_newName)
@ -363,3 +385,172 @@ bool VNoteFile::renameAttachment(const QString &p_oldName, const QString &p_newN
return true;
}
bool VNoteFile::deleteFile(VNoteFile *p_file, QString *p_errMsg)
{
Q_ASSERT(!p_file->isOpened());
bool ret = true;
QString name = p_file->getName();
QString path = p_file->fetchPath();
if (!p_file->deleteFile(p_errMsg)) {
qWarning() << "fail to delete file" << name << path;
ret = false;
}
VDirectory *dir = p_file->getDirectory();
Q_ASSERT(dir);
if (!dir->removeFile(p_file)) {
qWarning() << "fail to remove file from directory" << name << path;
VUtils::addErrMsg(p_errMsg, tr("Fail to remove the note from the folder configuration."));
ret = false;
}
delete p_file;
return ret;
}
bool VNoteFile::copyFile(VDirectory *p_destDir,
const QString &p_destName,
VNoteFile *p_file,
bool p_isCut,
VNoteFile **p_targetFile,
QString *p_errMsg)
{
bool ret = true;
*p_targetFile = NULL;
int nrImageCopied = 0;
bool attachmentFolderCopied = false;
QString srcPath = QDir::cleanPath(p_file->fetchPath());
QString destPath = QDir::cleanPath(QDir(p_destDir->fetchPath()).filePath(p_destName));
if (VUtils::equalPath(srcPath, destPath)) {
*p_targetFile = p_file;
return false;
}
if (!p_destDir->isOpened()) {
VUtils::addErrMsg(p_errMsg, tr("Fail to open target folder."));
return false;
}
QString opStr = p_isCut ? tr("cut") : tr("copy");
VDirectory *srcDir = p_file->getDirectory();
DocType docType = p_file->getDocType();
Q_ASSERT(srcDir->isOpened());
Q_ASSERT(docType == VUtils::docTypeFromName(p_destName));
// Images to be copied.
QVector<ImageLink> images;
if (docType == DocType::Markdown) {
images = VUtils::fetchImagesFromMarkdownFile(p_file,
ImageLink::LocalRelativeInternal);
}
// Attachments to be copied.
QString attaFolder = p_file->getAttachmentFolder();
QString attaFolderPath;
if (!attaFolder.isEmpty()) {
attaFolderPath = p_file->fetchAttachmentFolderPath();
}
// Copy the note file.
if (!VUtils::copyFile(srcPath, destPath, p_isCut)) {
VUtils::addErrMsg(p_errMsg, tr("Fail to %1 the note file.").arg(opStr));
qWarning() << "fail to" << opStr << "the note file" << srcPath << "to" << destPath;
return false;
}
// Add file to VDirectory.
VNoteFile *destFile = NULL;
if (p_isCut) {
srcDir->removeFile(p_file);
p_file->setName(p_destName);
if (p_destDir->addFile(p_file, -1)) {
destFile = p_file;
} else {
destFile = NULL;
}
} else {
destFile = p_destDir->addFile(p_destName, -1);
}
if (!destFile) {
VUtils::addErrMsg(p_errMsg, tr("Fail to add the note to target folder's configuration."));
return false;
}
// Copy images.
QDir parentDir(destFile->fetchBasePath());
for (int i = 0; i < images.size(); ++i) {
const ImageLink &link = images[i];
if (!QFileInfo::exists(link.m_path)) {
VUtils::addErrMsg(p_errMsg, tr("Source image %1 does not exist.")
.arg(link.m_path));
ret = false;
continue;
}
QString imageFolder = VUtils::directoryNameFromPath(VUtils::basePathFromPath(link.m_path));
QString destImagePath = QDir(parentDir.filePath(imageFolder)).filePath(VUtils::fileNameFromPath(link.m_path));
if (VUtils::equalPath(link.m_path, destImagePath)) {
VUtils::addErrMsg(p_errMsg, tr("Skip image with the same source and target path %1.")
.arg(link.m_path));
ret = false;
continue;
}
if (!VUtils::copyFile(link.m_path, destImagePath, p_isCut)) {
VUtils::addErrMsg(p_errMsg, tr("Fail to %1 image %2 to %3. "
"Please manually %1 it and modify the note.")
.arg(opStr).arg(link.m_path).arg(destImagePath));
ret = false;
} else {
++nrImageCopied;
qDebug() << opStr << "image" << link.m_path << "to" << destImagePath;
}
}
// Copy attachment folder.
if (!attaFolderPath.isEmpty()) {
QDir dir(destFile->fetchBasePath());
QString folderPath = dir.filePath(destFile->getNotebook()->getAttachmentFolder());
attaFolder = VUtils::getFileNameWithSequence(folderPath, attaFolder);
folderPath = QDir(folderPath).filePath(attaFolder);
// Copy attaFolderPath to folderPath.
if (!VUtils::copyDirectory(attaFolderPath, folderPath, p_isCut)) {
VUtils::addErrMsg(p_errMsg, tr("Fail to %1 attachments folder %2 to %3. "
"Please manually maintain it.")
.arg(opStr).arg(attaFolderPath).arg(folderPath));
QVector<VAttachment> emptyAttas;
destFile->setAttachments(emptyAttas);
ret = false;
} else {
attachmentFolderCopied = true;
destFile->setAttachmentFolder(attaFolder);
if (!p_isCut) {
destFile->setAttachments(p_file->getAttachments());
}
}
if (!p_destDir->updateFileConfig(destFile)) {
VUtils::addErrMsg(p_errMsg, tr("Fail to update configuration of note %1.")
.arg(destFile->fetchPath()));
ret = false;
}
}
qDebug() << "copyFile:" << p_file << "to" << destFile
<< "copied_images:" << nrImageCopied
<< "copied_attachments:" << attachmentFolderCopied;
*p_targetFile = destFile;
return ret;
}

View File

@ -67,22 +67,17 @@ public:
// Get the relative path related to the notebook.
QString fetchRelativePath() const;
// Create a VNoteFile from @p_json Json object.
static VNoteFile *fromJson(VDirectory *p_directory,
const QJsonObject &p_json,
FileType p_type,
bool p_modifiable);
// Create a Json object from current instance.
QJsonObject toConfigJson() const;
// Delete this file in disk as well as all its images/attachments.
bool deleteFile();
const QString &getAttachmentFolder() const;
void setAttachmentFolder(const QString &p_folder);
const QVector<VAttachment> &getAttachments() const;
void setAttachments(const QVector<VAttachment> &p_attas);
// Add @p_file as an attachment to this note.
bool addAttachment(const QString &p_file);
@ -97,17 +92,43 @@ public:
bool deleteAttachments(const QVector<QString> &p_names);
// Reorder attachments in m_attachments by index.
void sortAttachments(QVector<int> p_sortedIdx);
bool sortAttachments(const 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);
// Rename attachment @p_oldName to @p_newName.
bool renameAttachment(const QString &p_oldName, const QString &p_newName);
// Create a VNoteFile from @p_json Json object.
static VNoteFile *fromJson(VDirectory *p_directory,
const QJsonObject &p_json,
FileType p_type,
bool p_modifiable);
// Delete file @p_file including removing it from parent directory configuration
// and delete the file in disk.
// @p_file: should be a normal file with parent directory.
// @p_errMsg: if not NULL, it will contain error message if this function fails.
static bool deleteFile(VNoteFile *p_file, QString *p_errMsg = NULL);
// Copy file @p_file to @p_destDir with new name @p_destName.
// Returns a file representing the destination file after copy/cut.
static bool copyFile(VDirectory *p_destDir,
const QString &p_destName,
VNoteFile *p_file,
bool p_isCut,
VNoteFile **p_targetFile,
QString *p_errMsg = NULL);
private:
// Delete internal images of this file.
void deleteInternalImages();
// Return true only when all internal images were deleted successfully.
bool deleteInternalImages();
// Delete this file in disk as well as all its images/attachments.
bool deleteFile(QString *p_msg = NULL);
// Folder under the attachment folder of the notebook.
// Store all the attachments of current file.
@ -122,9 +143,19 @@ inline const QString &VNoteFile::getAttachmentFolder() const
return m_attachmentFolder;
}
inline void VNoteFile::setAttachmentFolder(const QString &p_folder)
{
m_attachmentFolder = p_folder;
}
inline const QVector<VAttachment> &VNoteFile::getAttachments() const
{
return m_attachments;
}
inline void VNoteFile::setAttachments(const QVector<VAttachment> &p_attas)
{
m_attachments = p_attas;
}
#endif // VNOTEFILE_H