mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
refactor VFileList
- Refine note deletion logics; - Refine note copy/paste logics; - Refine note sorting logics;
This commit is contained in:
parent
4668bd581a
commit
58e7cdca4b
@ -7,55 +7,52 @@
|
||||
|
||||
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();
|
||||
setupUI();
|
||||
|
||||
m_checkBox->setChecked(p_checked);
|
||||
m_fileLabel->setText(p_file);
|
||||
if (!p_tip.isEmpty()) {
|
||||
m_fileLabel->setToolTip(p_tip);
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmItemWidget(bool p_checked, const QString &p_file, QWidget *p_parent = NULL)
|
||||
: QWidget(p_parent)
|
||||
{
|
||||
setupUI();
|
||||
void ConfirmItemWidget::setupUI()
|
||||
{
|
||||
m_checkBox = new QCheckBox;
|
||||
connect(m_checkBox, &QCheckBox::stateChanged,
|
||||
this, &ConfirmItemWidget::checkStateChanged);
|
||||
|
||||
m_checkBox->setChecked(p_checked);
|
||||
m_fileLabel->setText(p_file);
|
||||
}
|
||||
m_fileLabel = new QLabel;
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
mainLayout->addWidget(m_checkBox);
|
||||
mainLayout->addWidget(m_fileLabel);
|
||||
mainLayout->addStretch();
|
||||
mainLayout->setContentsMargins(3, 0, 0, 0);
|
||||
|
||||
bool isChecked() const
|
||||
{
|
||||
return m_checkBox->isChecked();
|
||||
}
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
QString getFile() const
|
||||
{
|
||||
return m_fileLabel->text();
|
||||
}
|
||||
bool ConfirmItemWidget::isChecked() const
|
||||
{
|
||||
return m_checkBox->isChecked();
|
||||
}
|
||||
|
||||
private:
|
||||
void setupUI()
|
||||
{
|
||||
m_checkBox = new QCheckBox;
|
||||
m_fileLabel = new QLabel;
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
mainLayout->addWidget(m_checkBox);
|
||||
mainLayout->addWidget(m_fileLabel);
|
||||
mainLayout->addStretch();
|
||||
mainLayout->setContentsMargins(3, 0, 0, 0);
|
||||
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
QCheckBox *m_checkBox;
|
||||
QLabel *m_fileLabel;
|
||||
};
|
||||
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));
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
// 
|
||||
// Captured texts (need to be trimmed):
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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()) {
|
||||
|
@ -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());
|
||||
QDesktopServices::openUrl(url);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
dir->deleteFile(p_file);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
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);
|
||||
}
|
||||
if (copyFile(p_destDir, fileName, srcFile, isCut)) {
|
||||
nrPasted++;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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(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;
|
||||
}
|
||||
}
|
||||
|
||||
// Rename it to xxx_copy.md.
|
||||
fileName = VUtils::generateCopiedFileName(file->fetchBasePath(), fileName);
|
||||
} else {
|
||||
VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
|
||||
// Rename it to xxx_copy.md if needed.
|
||||
fileName = VUtils::generateCopiedFileName(p_destDir->fetchPath(), fileName);
|
||||
}
|
||||
|
||||
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(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]),
|
||||
msg,
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Ok,
|
||||
this);
|
||||
}
|
||||
|
||||
if (destFile) {
|
||||
++nrPasted;
|
||||
emit fileUpdated(destFile);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
qDebug() << "copy" << nrPasted << "files";
|
||||
if (nrPasted > 0) {
|
||||
g_mainWin->showStatusMessage(tr("%1 %2 pasted")
|
||||
.arg(nrPasted)
|
||||
.arg(nrPasted > 1 ? tr("notes") : tr("note")));
|
||||
}
|
||||
|
||||
// DocType is not allowed to change.
|
||||
Q_ASSERT(p_file->getDocType() == VUtils::docTypeFromName(destPath));
|
||||
|
||||
VNoteFile *destFile = VDirectory::copyFile(p_destDir, p_destName, p_file, p_cut);
|
||||
updateFileList();
|
||||
if (destFile) {
|
||||
emit fileUpdated(destFile);
|
||||
}
|
||||
return destFile != NULL;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
unusedImages.clear();
|
||||
if (dialog.exec()) {
|
||||
unusedImages = dialog.getConfirmedFiles();
|
||||
items = dialog.getConfirmedItems();
|
||||
g_config->setConfirmImagesCleanUp(dialog.getAskAgainEnabled());
|
||||
} else {
|
||||
unusedImages.clear();
|
||||
|
||||
for (auto const & item : items) {
|
||||
unusedImages.push_back(item.m_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user