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;
|
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:
|
setupUI();
|
||||||
explicit ConfirmItemWidget(QWidget *p_parent = NULL)
|
|
||||||
: QWidget(p_parent)
|
m_checkBox->setChecked(p_checked);
|
||||||
{
|
m_fileLabel->setText(p_file);
|
||||||
setupUI();
|
if (!p_tip.isEmpty()) {
|
||||||
|
m_fileLabel->setToolTip(p_tip);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ConfirmItemWidget(bool p_checked, const QString &p_file, QWidget *p_parent = NULL)
|
void ConfirmItemWidget::setupUI()
|
||||||
: QWidget(p_parent)
|
{
|
||||||
{
|
m_checkBox = new QCheckBox;
|
||||||
setupUI();
|
connect(m_checkBox, &QCheckBox::stateChanged,
|
||||||
|
this, &ConfirmItemWidget::checkStateChanged);
|
||||||
|
|
||||||
m_checkBox->setChecked(p_checked);
|
m_fileLabel = new QLabel;
|
||||||
m_fileLabel->setText(p_file);
|
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||||
}
|
mainLayout->addWidget(m_checkBox);
|
||||||
|
mainLayout->addWidget(m_fileLabel);
|
||||||
|
mainLayout->addStretch();
|
||||||
|
mainLayout->setContentsMargins(3, 0, 0, 0);
|
||||||
|
|
||||||
bool isChecked() const
|
setLayout(mainLayout);
|
||||||
{
|
}
|
||||||
return m_checkBox->isChecked();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString getFile() const
|
bool ConfirmItemWidget::isChecked() const
|
||||||
{
|
{
|
||||||
return m_fileLabel->text();
|
return m_checkBox->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
int ConfirmItemWidget::getIndex() const
|
||||||
void setupUI()
|
{
|
||||||
{
|
return m_index;
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
VConfirmDeletionDialog::VConfirmDeletionDialog(const QString &p_title,
|
VConfirmDeletionDialog::VConfirmDeletionDialog(const QString &p_title,
|
||||||
|
const QString &p_text,
|
||||||
const QString &p_info,
|
const QString &p_info,
|
||||||
const QVector<QString> &p_files,
|
const QVector<ConfirmItemInfo> &p_items,
|
||||||
bool p_enableAskAgain,
|
bool p_enableAskAgain,
|
||||||
bool p_askAgainEnabled,
|
bool p_askAgainEnabled,
|
||||||
bool p_enablePreview,
|
bool p_enablePreview,
|
||||||
@ -63,21 +60,38 @@ VConfirmDeletionDialog::VConfirmDeletionDialog(const QString &p_title,
|
|||||||
: QDialog(p_parent),
|
: QDialog(p_parent),
|
||||||
m_enableAskAgain(p_enableAskAgain),
|
m_enableAskAgain(p_enableAskAgain),
|
||||||
m_askAgainEnabled(p_askAgainEnabled),
|
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;
|
QLabel *infoLabel = NULL;
|
||||||
if (!p_info.isEmpty()) {
|
if (!p_info.isEmpty()) {
|
||||||
infoLabel = new QLabel(p_info);
|
infoLabel = new QLabel(p_info);
|
||||||
infoLabel->setWordWrap(true);
|
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();
|
m_listWidget = new QListWidget();
|
||||||
connect(m_listWidget, &QListWidget::currentRowChanged,
|
connect(m_listWidget, &QListWidget::currentRowChanged,
|
||||||
this, &VConfirmDeletionDialog::currentFileChanged);
|
this, &VConfirmDeletionDialog::currentFileChanged);
|
||||||
@ -106,40 +120,50 @@ void VConfirmDeletionDialog::setupUI(const QString &p_title, const QString &p_in
|
|||||||
}
|
}
|
||||||
|
|
||||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||||
|
if (textLabel) {
|
||||||
|
mainLayout->addWidget(textLabel);
|
||||||
|
}
|
||||||
|
|
||||||
if (infoLabel) {
|
if (infoLabel) {
|
||||||
mainLayout->addWidget(infoLabel);
|
mainLayout->addWidget(infoLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
mainLayout->addWidget(m_askAgainCB);
|
mainLayout->addWidget(m_askAgainCB);
|
||||||
mainLayout->addWidget(m_btnBox);
|
mainLayout->addWidget(m_btnBox);
|
||||||
|
mainLayout->addLayout(labelLayout);
|
||||||
mainLayout->addLayout(midLayout);
|
mainLayout->addLayout(midLayout);
|
||||||
|
|
||||||
setLayout(mainLayout);
|
setLayout(mainLayout);
|
||||||
setWindowTitle(p_title);
|
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) {
|
for (int i = 0; i < m_listWidget->count(); ++i) {
|
||||||
ConfirmItemWidget *widget = getItemWidget(m_listWidget->item(i));
|
ConfirmItemWidget *widget = getItemWidget(m_listWidget->item(i));
|
||||||
if (widget->isChecked()) {
|
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();
|
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,
|
ConfirmItemWidget *itemWidget = new ConfirmItemWidget(true,
|
||||||
p_files[i],
|
m_items[i].m_name,
|
||||||
|
m_items[i].m_tip,
|
||||||
|
i,
|
||||||
this);
|
this);
|
||||||
|
connect(itemWidget, &ConfirmItemWidget::checkStateChanged,
|
||||||
|
this, &VConfirmDeletionDialog::updateCountLabel);
|
||||||
|
|
||||||
QListWidgetItem *item = new QListWidgetItem();
|
QListWidgetItem *item = new QListWidgetItem();
|
||||||
QSize size = itemWidget->sizeHint();
|
QSize size = itemWidget->sizeHint();
|
||||||
size.setHeight(size.height() * 2);
|
size.setHeight(size.height() * 2);
|
||||||
@ -165,7 +189,9 @@ void VConfirmDeletionDialog::currentFileChanged(int p_row)
|
|||||||
if (p_row > -1 && m_enablePreview) {
|
if (p_row > -1 && m_enablePreview) {
|
||||||
ConfirmItemWidget *widget = getItemWidget(m_listWidget->item(p_row));
|
ConfirmItemWidget *widget = getItemWidget(m_listWidget->item(p_row));
|
||||||
if (widget) {
|
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()) {
|
if (!image.isNull()) {
|
||||||
int width = 512 * VUtils::calculateScaleFactor();
|
int width = 512 * VUtils::calculateScaleFactor();
|
||||||
QSize previewSize(width, width);
|
QSize previewSize(width, width);
|
||||||
@ -183,3 +209,18 @@ ConfirmItemWidget *VConfirmDeletionDialog::getItemWidget(QListWidgetItem *p_item
|
|||||||
QWidget *wid = m_listWidget->itemWidget(p_item);
|
QWidget *wid = m_listWidget->itemWidget(p_item);
|
||||||
return dynamic_cast<ConfirmItemWidget *>(wid);
|
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 ConfirmItemWidget;
|
||||||
class QCheckBox;
|
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
|
class VConfirmDeletionDialog : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
VConfirmDeletionDialog(const QString &p_title,
|
VConfirmDeletionDialog(const QString &p_title,
|
||||||
|
const QString &p_text,
|
||||||
const QString &p_info,
|
const QString &p_info,
|
||||||
const QVector<QString> &p_files,
|
const QVector<ConfirmItemInfo> &p_items,
|
||||||
bool p_enableAskAgain,
|
bool p_enableAskAgain,
|
||||||
bool p_askAgainEnabled,
|
bool p_askAgainEnabled,
|
||||||
bool p_enablePreview,
|
bool p_enablePreview,
|
||||||
QWidget *p_parent = 0);
|
QWidget *p_parent = 0);
|
||||||
|
|
||||||
QVector<QString> getConfirmedFiles() const;
|
QVector<ConfirmItemInfo> getConfirmedItems() const;
|
||||||
|
|
||||||
bool getAskAgainEnabled() const;
|
bool getAskAgainEnabled() const;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void currentFileChanged(int p_row);
|
void currentFileChanged(int p_row);
|
||||||
|
|
||||||
private:
|
void updateCountLabel();
|
||||||
void setupUI(const QString &p_title, const QString &p_info);
|
|
||||||
|
|
||||||
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;
|
ConfirmItemWidget *getItemWidget(QListWidgetItem *p_item) const;
|
||||||
|
|
||||||
|
QLabel *m_countLabel;
|
||||||
QListWidget *m_listWidget;
|
QListWidget *m_listWidget;
|
||||||
QLabel *m_previewer;
|
QLabel *m_previewer;
|
||||||
QDialogButtonBox *m_btnBox;
|
QDialogButtonBox *m_btnBox;
|
||||||
@ -48,6 +103,8 @@ private:
|
|||||||
bool m_askAgainEnabled;
|
bool m_askAgainEnabled;
|
||||||
|
|
||||||
bool m_enablePreview;
|
bool m_enablePreview;
|
||||||
|
|
||||||
|
QVector<ConfirmItemInfo> m_items;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VCONFIRMDELETIONDIALOG_H
|
#endif // VCONFIRMDELETIONDIALOG_H
|
||||||
|
@ -2,6 +2,33 @@
|
|||||||
|
|
||||||
#include <QtWidgets>
|
#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,
|
VSortDialog::VSortDialog(const QString &p_title,
|
||||||
const QString &p_info,
|
const QString &p_info,
|
||||||
QWidget *p_parent)
|
QWidget *p_parent)
|
||||||
@ -18,10 +45,28 @@ void VSortDialog::setupUI(const QString &p_title, const QString &p_info)
|
|||||||
infoLabel->setWordWrap(true);
|
infoLabel->setWordWrap(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_treeWidget = new QTreeWidget();
|
m_treeWidget = new VTreeWidget();
|
||||||
m_treeWidget->setRootIsDecorated(false);
|
m_treeWidget->setRootIsDecorated(false);
|
||||||
m_treeWidget->setSelectionMode(QAbstractItemView::ContiguousSelection);
|
m_treeWidget->setSelectionMode(QAbstractItemView::ContiguousSelection);
|
||||||
m_treeWidget->setDragDropMode(QAbstractItemView::InternalMove);
|
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.
|
// Buttons for top/up/down/bottom.
|
||||||
m_topBtn = new QPushButton(tr("&Top"));
|
m_topBtn = new QPushButton(tr("&Top"));
|
||||||
@ -82,6 +127,11 @@ void VSortDialog::setupUI(const QString &p_title, const QString &p_info)
|
|||||||
|
|
||||||
void VSortDialog::treeUpdated()
|
void VSortDialog::treeUpdated()
|
||||||
{
|
{
|
||||||
|
int cols = m_treeWidget->columnCount();
|
||||||
|
for (int i = 0; i < cols; ++i) {
|
||||||
|
m_treeWidget->resizeColumnToContents(i);
|
||||||
|
}
|
||||||
|
|
||||||
// We just need single level.
|
// We just need single level.
|
||||||
int cnt = m_treeWidget->topLevelItemCount();
|
int cnt = m_treeWidget->topLevelItemCount();
|
||||||
for (int i = 0; i < cnt; ++i) {
|
for (int i = 0; i < cnt; ++i) {
|
||||||
@ -200,6 +250,20 @@ void VSortDialog::handleMoveOperation(MoveOperation p_op)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (firstItem) {
|
if (firstItem) {
|
||||||
|
m_treeWidget->setCurrentItem(firstItem);
|
||||||
m_treeWidget->scrollToItem(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 <QDialog>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
#include <QTreeWidget>
|
||||||
|
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
class QDialogButtonBox;
|
class QDialogButtonBox;
|
||||||
class QTreeWidget;
|
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
|
class VSortDialog : public QDialog
|
||||||
{
|
{
|
||||||
@ -21,6 +43,9 @@ public:
|
|||||||
// Called after updating the m_treeWidget.
|
// Called after updating the m_treeWidget.
|
||||||
void treeUpdated();
|
void treeUpdated();
|
||||||
|
|
||||||
|
// Get user data of column 0 from sorted items.
|
||||||
|
QVector<QVariant> getSortedData() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum MoveOperation { Top, Up, Down, Bottom };
|
enum MoveOperation { Top, Up, Down, Bottom };
|
||||||
|
|
||||||
@ -30,7 +55,7 @@ private slots:
|
|||||||
private:
|
private:
|
||||||
void setupUI(const QString &p_title, const QString &p_info);
|
void setupUI(const QString &p_title, const QString &p_info);
|
||||||
|
|
||||||
QTreeWidget *m_treeWidget;
|
VTreeWidget *m_treeWidget;
|
||||||
QPushButton *m_topBtn;
|
QPushButton *m_topBtn;
|
||||||
QPushButton *m_upBtn;
|
QPushButton *m_upBtn;
|
||||||
QPushButton *m_downBtn;
|
QPushButton *m_downBtn;
|
||||||
|
@ -250,18 +250,28 @@ bool VUtils::makePath(const QString &p_path)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClipboardOpType VUtils::opTypeInClipboard()
|
QJsonObject VUtils::clipboardToJson()
|
||||||
{
|
{
|
||||||
QClipboard *clipboard = QApplication::clipboard();
|
QClipboard *clipboard = QApplication::clipboard();
|
||||||
const QMimeData *mimeData = clipboard->mimeData();
|
const QMimeData *mimeData = clipboard->mimeData();
|
||||||
|
|
||||||
|
QJsonObject obj;
|
||||||
if (mimeData->hasText()) {
|
if (mimeData->hasText()) {
|
||||||
QString text = mimeData->text();
|
QString text = mimeData->text();
|
||||||
QJsonObject clip = QJsonDocument::fromJson(text.toLocal8Bit()).object();
|
obj = QJsonDocument::fromJson(text.toUtf8()).object();
|
||||||
if (clip.contains("operation")) {
|
qDebug() << "Json object in clipboard" << obj;
|
||||||
return (ClipboardOpType)clip["operation"].toInt();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipboardOpType VUtils::operationInClipboard()
|
||||||
|
{
|
||||||
|
QJsonObject obj = clipboardToJson();
|
||||||
|
if (obj.contains(ClipboardConfig::c_type)) {
|
||||||
|
return (ClipboardOpType)obj[ClipboardConfig::c_type].toInt();
|
||||||
|
}
|
||||||
|
|
||||||
return ClipboardOpType::Invalid;
|
return ClipboardOpType::Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,6 +284,12 @@ bool VUtils::copyFile(const QString &p_srcFilePath, const QString &p_destFilePat
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QDir dir;
|
||||||
|
if (!dir.mkpath(basePathFromPath(p_destFilePath))) {
|
||||||
|
qWarning() << "fail to create directory" << basePathFromPath(p_destFilePath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (p_isCut) {
|
if (p_isCut) {
|
||||||
QFile file(srcPath);
|
QFile file(srcPath);
|
||||||
if (!file.rename(destPath)) {
|
if (!file.rename(destPath)) {
|
||||||
@ -286,10 +302,10 @@ bool VUtils::copyFile(const QString &p_srcFilePath, const QString &p_destFilePat
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy @p_srcDirPath to be @p_destDirPath.
|
|
||||||
bool VUtils::copyDirectory(const QString &p_srcDirPath, const QString &p_destDirPath, bool p_isCut)
|
bool VUtils::copyDirectory(const QString &p_srcDirPath, const QString &p_destDirPath, bool p_isCut)
|
||||||
{
|
{
|
||||||
QString srcPath = QDir::cleanPath(p_srcDirPath);
|
QString srcPath = QDir::cleanPath(p_srcDirPath);
|
||||||
@ -298,17 +314,24 @@ bool VUtils::copyDirectory(const QString &p_srcDirPath, const QString &p_destDir
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a directory
|
if (QFileInfo::exists(destPath)) {
|
||||||
QDir parentDir(VUtils::basePathFromPath(p_destDirPath));
|
qWarning() << QString("target directory %1 already exists").arg(destPath);
|
||||||
QString dirName = VUtils::fileNameFromPath(p_destDirPath);
|
|
||||||
if (!parentDir.mkdir(dirName)) {
|
|
||||||
qWarning() << QString("fail to create target directory %1: already exists").arg(p_destDirPath);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle sub-dirs recursively and copy files.
|
// QDir.rename() could not move directory across drives.
|
||||||
QDir srcDir(p_srcDirPath);
|
|
||||||
QDir destDir(p_destDirPath);
|
// 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());
|
Q_ASSERT(srcDir.exists() && destDir.exists());
|
||||||
QFileInfoList nodes = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden
|
QFileInfoList nodes = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden
|
||||||
| QDir::NoSymLinks | QDir::NoDotAndDotDot);
|
| 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 (p_isCut) {
|
||||||
if (!srcDir.removeRecursively()) {
|
if (!destDir.rmdir(srcPath)) {
|
||||||
qWarning() << "fail to remove directory" << p_srcDirPath;
|
qWarning() << QString("fail to delete source directory %1 after cut").arg(srcPath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,19 +387,20 @@ QString VUtils::generateCopiedFileName(const QString &p_dirPath, const QString &
|
|||||||
suffix = p_fileName.right(p_fileName.size() - dotIdx);
|
suffix = p_fileName.right(p_fileName.size() - dotIdx);
|
||||||
base = p_fileName.left(dotIdx);
|
base = p_fileName.left(dotIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
QDir dir(p_dirPath);
|
QDir dir(p_dirPath);
|
||||||
QString name = p_fileName;
|
QString name = p_fileName;
|
||||||
QString filePath = dir.filePath(name);
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
while (QFile(filePath).exists()) {
|
while (dir.exists(name)) {
|
||||||
QString seq;
|
QString seq;
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
seq = QString::number(index);
|
seq = QString::number(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
name = QString("%1_copy%2%3").arg(base).arg(seq).arg(suffix);
|
name = QString("%1_copy%2%3").arg(base).arg(seq).arg(suffix);
|
||||||
filePath = dir.filePath(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -898,3 +922,16 @@ bool VUtils::fileExists(const QDir &p_dir, const QString &p_name, bool p_forceCa
|
|||||||
|
|
||||||
return false;
|
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 QRgb QRgbFromString(const QString &str);
|
||||||
static QString generateImageFileName(const QString &path, const QString &title,
|
static QString generateImageFileName(const QString &path, const QString &title,
|
||||||
const QString &format = "png");
|
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 generateCopiedFileName(const QString &p_dirPath, const QString &p_fileName);
|
||||||
|
|
||||||
static QString generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName);
|
static QString generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName);
|
||||||
static void processStyle(QString &style, const QVector<QPair<QString, QString> > &varMap);
|
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/.
|
// @p_path could be /home/tamlok/abc, /home/tamlok/abc/.
|
||||||
static bool makePath(const QString &p_path);
|
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);
|
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 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,
|
static int showMessage(QMessageBox::Icon p_icon, const QString &p_title, const QString &p_text,
|
||||||
const QString &p_infoText, QMessageBox::StandardButtons p_buttons,
|
const QString &p_infoText, QMessageBox::StandardButtons p_buttons,
|
||||||
QMessageBox::StandardButton p_defaultBtn, QWidget *p_parent,
|
QMessageBox::StandardButton p_defaultBtn, QWidget *p_parent,
|
||||||
@ -164,6 +183,9 @@ public:
|
|||||||
// @p_forceCaseInsensitive: if true, will check the name ignoring the case.
|
// @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);
|
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.
|
// Regular expression for image link.
|
||||||
// 
|
// 
|
||||||
// Captured texts (need to be trimmed):
|
// Captured texts (need to be trimmed):
|
||||||
|
@ -242,7 +242,7 @@ void VAttachmentList::addAttachments(const QStringList &p_files)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (addedFiles > 0) {
|
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)
|
||||||
.arg(addedFiles > 1 ? tr("files") : tr("file")));
|
.arg(addedFiles > 1 ? tr("files") : tr("file")));
|
||||||
}
|
}
|
||||||
@ -300,7 +300,7 @@ void VAttachmentList::handleItemActivated(QListWidgetItem *p_item)
|
|||||||
|
|
||||||
void VAttachmentList::deleteSelectedItems()
|
void VAttachmentList::deleteSelectedItems()
|
||||||
{
|
{
|
||||||
QVector<QString> names;
|
QVector<ConfirmItemInfo> items;
|
||||||
const QList<QListWidgetItem *> selectedItems = m_attachmentList->selectedItems();
|
const QList<QListWidgetItem *> selectedItems = m_attachmentList->selectedItems();
|
||||||
|
|
||||||
if (selectedItems.isEmpty()) {
|
if (selectedItems.isEmpty()) {
|
||||||
@ -308,24 +308,35 @@ void VAttachmentList::deleteSelectedItems()
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto const & item : selectedItems) {
|
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 "
|
QString text = tr("Are you sure to delete these attachments of note "
|
||||||
"<span style=\"%1\">%2</span>? "
|
"<span style=\"%1\">%2</span>?")
|
||||||
"You could find deleted files in the recycle "
|
|
||||||
"bin of this notebook.<br>"
|
|
||||||
"Click \"Cancel\" to leave them untouched.")
|
|
||||||
.arg(g_config->c_dataTextStyle).arg(m_file->getName());
|
.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"),
|
VConfirmDeletionDialog dialog(tr("Confirm Deleting Attachments"),
|
||||||
|
text,
|
||||||
info,
|
info,
|
||||||
names,
|
items,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
g_vnote->getMainWindow());
|
g_vnote->getMainWindow());
|
||||||
if (dialog.exec()) {
|
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)) {
|
if (!m_file->deleteAttachments(names)) {
|
||||||
VUtils::showMessage(QMessageBox::Warning,
|
VUtils::showMessage(QMessageBox::Warning,
|
||||||
@ -353,7 +364,10 @@ void VAttachmentList::sortItems()
|
|||||||
}
|
}
|
||||||
|
|
||||||
VSortDialog dialog(tr("Sort Attachments"),
|
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());
|
g_vnote->getMainWindow());
|
||||||
QTreeWidget *tree = dialog.getTreeWidget();
|
QTreeWidget *tree = dialog.getTreeWidget();
|
||||||
tree->clear();
|
tree->clear();
|
||||||
@ -372,16 +386,24 @@ void VAttachmentList::sortItems()
|
|||||||
dialog.treeUpdated();
|
dialog.treeUpdated();
|
||||||
|
|
||||||
if (dialog.exec()) {
|
if (dialog.exec()) {
|
||||||
int cnt = tree->topLevelItemCount();
|
QVector<QVariant> data = dialog.getSortedData();
|
||||||
Q_ASSERT(cnt == attas.size());
|
Q_ASSERT(data.size() == attas.size());
|
||||||
QVector<int> sortedIdx(cnt, -1);
|
QVector<int> sortedIdx(data.size(), -1);
|
||||||
for (int i = 0; i < cnt; ++i) {
|
for (int i = 0; i < data.size(); ++i) {
|
||||||
QTreeWidgetItem *item = tree->topLevelItem(i);
|
sortedIdx[i] = data[i].toInt();
|
||||||
Q_ASSERT(item);
|
|
||||||
sortedIdx[i] = item->data(0, Qt::UserRole).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;
|
// Orphan: external file;
|
||||||
enum class FileType { Note, Orphan };
|
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};
|
enum class OpenFileMode {Read = 0, Edit};
|
||||||
|
|
||||||
static const qreal c_webZoomFactorMax = 5;
|
static const qreal c_webZoomFactorMax = 5;
|
||||||
|
@ -470,24 +470,9 @@ bool VDirectory::removeFile(VNoteFile *p_file)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "note" << p_file->getName() << "removed from folder" << m_name;
|
|
||||||
|
|
||||||
return true;
|
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)
|
bool VDirectory::rename(const QString &p_name)
|
||||||
{
|
{
|
||||||
if (m_name == p_name) {
|
if (m_name == p_name) {
|
||||||
@ -519,126 +504,6 @@ bool VDirectory::rename(const QString &p_name)
|
|||||||
return true;
|
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.
|
// 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 *VDirectory::copyDirectory(VDirectory *p_destDir, const QString &p_destName,
|
||||||
VDirectory *p_srcDir, bool p_cut)
|
VDirectory *p_srcDir, bool p_cut)
|
||||||
@ -687,36 +552,6 @@ void VDirectory::setExpanded(bool p_expanded)
|
|||||||
m_expanded = 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)
|
VNoteFile *VDirectory::tryLoadFile(QStringList &p_filePath)
|
||||||
{
|
{
|
||||||
qDebug() << "directory" << m_name << "tryLoadFile()" << p_filePath.join("/");
|
qDebug() << "directory" << m_name << "tryLoadFile()" << p_filePath.join("/");
|
||||||
@ -755,3 +590,24 @@ VNoteFile *VDirectory::tryLoadFile(QStringList &p_filePath)
|
|||||||
|
|
||||||
return file;
|
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.
|
// Return the VNoteFile if succeed.
|
||||||
VNoteFile *addFile(const QString &p_name, int p_index);
|
VNoteFile *addFile(const QString &p_name, int p_index);
|
||||||
|
|
||||||
// Delete @p_file both from disk and config, as well as its local images.
|
// Add the file in the config and m_files. If @p_index is -1, add it at the end.
|
||||||
void deleteFile(VNoteFile *p_file);
|
bool addFile(VNoteFile *p_file, int p_index);
|
||||||
|
|
||||||
// Rename current directory to @p_name.
|
// Rename current directory to @p_name.
|
||||||
bool rename(const QString &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,
|
static VDirectory *copyDirectory(VDirectory *p_destDir, const QString &p_destName,
|
||||||
VDirectory *p_srcDir, bool p_cut);
|
VDirectory *p_srcDir, bool p_cut);
|
||||||
|
|
||||||
@ -83,10 +77,6 @@ public:
|
|||||||
bool isExpanded() const;
|
bool isExpanded() const;
|
||||||
void setExpanded(bool p_expanded);
|
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.
|
// Serialize current instance to json.
|
||||||
// Not including sections belonging to notebook.
|
// Not including sections belonging to notebook.
|
||||||
QJsonObject toConfigJson() const;
|
QJsonObject toConfigJson() const;
|
||||||
@ -108,6 +98,9 @@ public:
|
|||||||
|
|
||||||
QDateTime getCreatedTimeUtc() const;
|
QDateTime getCreatedTimeUtc() const;
|
||||||
|
|
||||||
|
// Reorder files in m_files by index.
|
||||||
|
bool sortFiles(const QVector<int> &p_sortedIdx);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Get the path of @p_dir recursively
|
// Get the path of @p_dir recursively
|
||||||
QString fetchPath(const VDirectory *p_dir) const;
|
QString fetchPath(const VDirectory *p_dir) const;
|
||||||
@ -121,9 +114,6 @@ private:
|
|||||||
// Should only be called with root directory.
|
// Should only be called with root directory.
|
||||||
void addNotebookConfig(QJsonObject &p_json) const;
|
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.
|
// Add the directory in the config and m_subDirs. If @p_index is -1, add it at the end.
|
||||||
// Return the VDirectory if succeed.
|
// Return the VDirectory if succeed.
|
||||||
VDirectory *addSubDirectory(const QString &p_name, int p_index);
|
VDirectory *addSubDirectory(const QString &p_name, int p_index);
|
||||||
|
@ -533,12 +533,12 @@ void VDirectoryTree::reloadFromDisk()
|
|||||||
curDir = getVDirectory(curItem);
|
curDir = getVDirectory(curItem);
|
||||||
info = tr("Are you sure to reload folder <span style=\"%1\">%2</span>?")
|
info = tr("Are you sure to reload folder <span style=\"%1\">%2</span>?")
|
||||||
.arg(g_config->c_dataTextStyle).arg(curDir->getName());
|
.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 {
|
} else {
|
||||||
// Reload notebook.
|
// Reload notebook.
|
||||||
info = tr("Are you sure to reload notebook <span style=\"%1\">%2</span>?")
|
info = tr("Are you sure to reload notebook <span style=\"%1\">%2</span>?")
|
||||||
.arg(g_config->c_dataTextStyle).arg(m_notebook->getName());
|
.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()) {
|
if (g_config->getConfirmReloadFolder()) {
|
||||||
|
@ -12,9 +12,13 @@
|
|||||||
#include "vconfigmanager.h"
|
#include "vconfigmanager.h"
|
||||||
#include "vmdedit.h"
|
#include "vmdedit.h"
|
||||||
#include "vmdtab.h"
|
#include "vmdtab.h"
|
||||||
|
#include "dialog/vconfirmdeletiondialog.h"
|
||||||
|
#include "dialog/vsortdialog.h"
|
||||||
|
#include "vmainwindow.h"
|
||||||
|
|
||||||
extern VConfigManager *g_config;
|
extern VConfigManager *g_config;
|
||||||
extern VNote *g_vnote;
|
extern VNote *g_vnote;
|
||||||
|
extern VMainWindow *g_mainWin;
|
||||||
|
|
||||||
const QString VFileList::c_infoShortcutSequence = "F2";
|
const QString VFileList::c_infoShortcutSequence = "F2";
|
||||||
const QString VFileList::c_copyShortcutSequence = "Ctrl+C";
|
const QString VFileList::c_copyShortcutSequence = "Ctrl+C";
|
||||||
@ -34,7 +38,6 @@ void VFileList::setupUI()
|
|||||||
fileList = new QListWidget(this);
|
fileList = new QListWidget(this);
|
||||||
fileList->setContextMenuPolicy(Qt::CustomContextMenu);
|
fileList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
fileList->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
fileList->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
fileList->setDragDropMode(QAbstractItemView::InternalMove);
|
|
||||||
fileList->setObjectName("FileList");
|
fileList->setObjectName("FileList");
|
||||||
|
|
||||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||||
@ -45,8 +48,6 @@ void VFileList::setupUI()
|
|||||||
this, &VFileList::contextMenuRequested);
|
this, &VFileList::contextMenuRequested);
|
||||||
connect(fileList, &QListWidget::itemClicked,
|
connect(fileList, &QListWidget::itemClicked,
|
||||||
this, &VFileList::handleItemClicked);
|
this, &VFileList::handleItemClicked);
|
||||||
connect(fileList->model(), &QAbstractItemModel::rowsMoved,
|
|
||||||
this, &VFileList::handleRowsMoved);
|
|
||||||
|
|
||||||
setLayout(mainLayout);
|
setLayout(mainLayout);
|
||||||
}
|
}
|
||||||
@ -78,7 +79,7 @@ void VFileList::initShortcuts()
|
|||||||
pasteShortcut->setContext(Qt::WidgetWithChildrenShortcut);
|
pasteShortcut->setContext(Qt::WidgetWithChildrenShortcut);
|
||||||
connect(pasteShortcut, &QShortcut::activated,
|
connect(pasteShortcut, &QShortcut::activated,
|
||||||
this, [this](){
|
this, [this](){
|
||||||
pasteFilesInCurDir();
|
pasteFilesFromClipboard();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +137,7 @@ void VFileList::initActions()
|
|||||||
tr("&Delete"), this);
|
tr("&Delete"), this);
|
||||||
deleteFileAct->setToolTip(tr("Delete selected note"));
|
deleteFileAct->setToolTip(tr("Delete selected note"));
|
||||||
connect(deleteFileAct, SIGNAL(triggered(bool)),
|
connect(deleteFileAct, SIGNAL(triggered(bool)),
|
||||||
this, SLOT(deleteFile()));
|
this, SLOT(deleteSelectedFiles()));
|
||||||
|
|
||||||
fileInfoAct = new QAction(QIcon(":/resources/icons/note_info.svg"),
|
fileInfoAct = new QAction(QIcon(":/resources/icons/note_info.svg"),
|
||||||
tr("&Info\t%1").arg(VUtils::getShortcutText(c_infoShortcutSequence)), this);
|
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);
|
tr("&Paste\t%1").arg(VUtils::getShortcutText(c_pasteShortcutSequence)), this);
|
||||||
pasteAct->setToolTip(tr("Paste notes in current folder"));
|
pasteAct->setToolTip(tr("Paste notes in current folder"));
|
||||||
connect(pasteAct, &QAction::triggered,
|
connect(pasteAct, &QAction::triggered,
|
||||||
this, &VFileList::pasteFilesInCurDir);
|
this, &VFileList::pasteFilesFromClipboard);
|
||||||
|
|
||||||
m_openLocationAct = new QAction(tr("&Open Note Location"), this);
|
m_openLocationAct = new QAction(tr("&Open Note Location"), this);
|
||||||
m_openLocationAct->setToolTip(tr("Open the folder containing this note in operating system"));
|
m_openLocationAct->setToolTip(tr("Open the folder containing this note in operating system"));
|
||||||
connect(m_openLocationAct, &QAction::triggered,
|
connect(m_openLocationAct, &QAction::triggered,
|
||||||
this, &VFileList::openFileLocation);
|
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)
|
void VFileList::setDirectory(VDirectory *p_directory)
|
||||||
@ -187,7 +195,6 @@ void VFileList::setDirectory(VDirectory *p_directory)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "filelist set folder" << m_directory->getName();
|
|
||||||
updateFileList();
|
updateFileList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,10 +222,11 @@ void VFileList::fileInfo()
|
|||||||
|
|
||||||
void VFileList::openFileLocation() const
|
void VFileList::openFileLocation() const
|
||||||
{
|
{
|
||||||
QListWidgetItem *curItem = fileList->currentItem();
|
QList<QListWidgetItem *> items = fileList->selectedItems();
|
||||||
V_ASSERT(curItem);
|
if (items.size() == 1) {
|
||||||
QUrl url = QUrl::fromLocalFile(getVFile(curItem)->fetchBasePath());
|
QUrl url = QUrl::fromLocalFile(getVFile(items[0])->fetchBasePath());
|
||||||
QDesktopServices::openUrl(url);
|
QDesktopServices::openUrl(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VFileList::fileInfo(VNoteFile *p_file)
|
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.
|
// Qt seems not to update the QListWidget correctly. Manually force it to repaint.
|
||||||
fileList->update();
|
fileList->update();
|
||||||
qDebug() << "VFileList adds" << file->getName();
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VFileList::removeFileListItem(QListWidgetItem *item)
|
void VFileList::removeFileListItem(VNoteFile *p_file)
|
||||||
{
|
{
|
||||||
fileList->setCurrentRow(-1);
|
if (!p_file) {
|
||||||
fileList->removeItemWidget(item);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QListWidgetItem *item = findItem(p_file);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int row = fileList->row(item);
|
||||||
|
Q_ASSERT(row >= 0);
|
||||||
|
|
||||||
|
fileList->takeItem(row);
|
||||||
delete item;
|
delete item;
|
||||||
|
|
||||||
// Qt seems not to update the QListWidget correctly. Manually force it to repaint.
|
// Qt seems not to update the QListWidget correctly. Manually force it to repaint.
|
||||||
fileList->update();
|
fileList->update();
|
||||||
}
|
}
|
||||||
@ -383,18 +402,21 @@ QVector<QListWidgetItem *> VFileList::updateFileListAdded()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
qDebug() << ret.size() << "items added";
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the file related to current item
|
void VFileList::deleteSelectedFiles()
|
||||||
void VFileList::deleteFile()
|
|
||||||
{
|
{
|
||||||
QList<QListWidgetItem *> items = fileList->selectedItems();
|
QList<QListWidgetItem *> items = fileList->selectedItems();
|
||||||
Q_ASSERT(!items.isEmpty());
|
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
|
// @p_file may or may not be listed in VFileList
|
||||||
@ -404,30 +426,83 @@ void VFileList::deleteFile(VNoteFile *p_file)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
VDirectory *dir = p_file->getDirectory();
|
QVector<VNoteFile *> files(1, p_file);
|
||||||
QString fileName = p_file->getName();
|
deleteFiles(files);
|
||||||
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);
|
|
||||||
|
|
||||||
// Remove the item before deleting it totally, or p_file will be invalid.
|
void VFileList::deleteFiles(const QVector<VNoteFile *> &p_files)
|
||||||
QListWidgetItem *item = findItem(p_file);
|
{
|
||||||
if (item) {
|
if (p_files.isEmpty()) {
|
||||||
removeFileListItem(item);
|
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);
|
menu.addAction(newFileAct);
|
||||||
|
|
||||||
|
if (fileList->count() > 1) {
|
||||||
|
menu.addAction(m_sortAct);
|
||||||
|
}
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
menu.addAction(deleteFileAct);
|
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
|
menu.addAction(deleteFileAct);
|
||||||
menu.addAction(copyAct);
|
menu.addAction(copyAct);
|
||||||
menu.addAction(cutAct);
|
menu.addAction(cutAct);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VUtils::opTypeInClipboard() == ClipboardOpType::CopyFile
|
if (pasteAvailable()) {
|
||||||
&& !m_copiedFiles.isEmpty()) {
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.addAction(pasteAct);
|
menu.addAction(pasteAct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,29 +596,59 @@ void VFileList::handleItemClicked(QListWidgetItem *currentItem)
|
|||||||
emit fileClicked(getVFile(currentItem), g_config->getNoteOpenMode());
|
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()) {
|
if (p_files.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) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
VNoteFile *destFile = m_directory->addFile(srcName, -1);
|
bool ret = true;
|
||||||
if (destFile) {
|
Q_ASSERT(m_directory && m_directory->isOpened());
|
||||||
return insertFileListItem(destFile, false);
|
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)
|
void VFileList::copySelectedFiles(bool p_isCut)
|
||||||
@ -548,19 +657,29 @@ void VFileList::copySelectedFiles(bool p_isCut)
|
|||||||
if (items.isEmpty()) {
|
if (items.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonArray files;
|
QJsonArray files;
|
||||||
m_copiedFiles.clear();
|
|
||||||
for (int i = 0; i < items.size(); ++i) {
|
for (int i = 0; i < items.size(); ++i) {
|
||||||
VNoteFile *file = getVFile(items[i]);
|
VNoteFile *file = getVFile(items[i]);
|
||||||
QJsonObject fileJson;
|
files.append(file->fetchPath());
|
||||||
fileJson["notebook"] = file->getNotebookName();
|
|
||||||
fileJson["path"] = file->fetchPath();
|
|
||||||
files.append(fileJson);
|
|
||||||
|
|
||||||
m_copiedFiles.append(file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
void VFileList::cutSelectedFiles()
|
||||||
@ -568,82 +687,123 @@ void VFileList::cutSelectedFiles()
|
|||||||
copySelectedFiles(true);
|
copySelectedFiles(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VFileList::copyFileInfoToClipboard(const QJsonArray &p_files, bool p_isCut)
|
void VFileList::pasteFilesFromClipboard()
|
||||||
{
|
{
|
||||||
QJsonObject clip;
|
if (!pasteAvailable()) {
|
||||||
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()) {
|
|
||||||
return;
|
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();
|
QClipboard *clipboard = QApplication::clipboard();
|
||||||
QString text = clipboard->text();
|
if (!p_destDir || p_files.isEmpty()) {
|
||||||
QJsonObject clip = QJsonDocument::fromJson(text.toLocal8Bit()).object();
|
clipboard->clear();
|
||||||
Q_ASSERT(!clip.isEmpty() && clip["operation"] == (int)ClipboardOpType::CopyFile);
|
return;
|
||||||
bool isCut = clip["is_cut"].toBool();
|
}
|
||||||
|
|
||||||
int nrPasted = 0;
|
int nrPasted = 0;
|
||||||
for (int i = 0; i < m_copiedFiles.size(); ++i) {
|
for (int i = 0; i < p_files.size(); ++i) {
|
||||||
QPointer<VNoteFile> srcFile = m_copiedFiles[i];
|
VNoteFile *file = g_vnote->getInternalFile(p_files[i]);
|
||||||
if (!srcFile) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString fileName = srcFile->getName();
|
QString fileName = file->getName();
|
||||||
VDirectory *srcDir = srcFile->getDirectory();
|
if (file->getDirectory() == p_destDir) {
|
||||||
if (srcDir == p_destDir && !isCut) {
|
if (p_isCut) {
|
||||||
// Copy and paste in the same directory.
|
qDebug() << "skip one note to cut and paste in the same folder" << fileName;
|
||||||
// Rename it to xx_copy.md
|
continue;
|
||||||
fileName = VUtils::generateCopiedFileName(srcDir->fetchPath(), fileName);
|
}
|
||||||
}
|
|
||||||
if (copyFile(p_destDir, fileName, srcFile, isCut)) {
|
// Copy and paste in the same folder.
|
||||||
nrPasted++;
|
// 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 {
|
} 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>.")
|
tr("Fail to copy note <span style=\"%1\">%2</span>.")
|
||||||
.arg(g_config->c_dataTextStyle).arg(srcFile->getName()),
|
.arg(g_config->c_dataTextStyle)
|
||||||
tr("Please check if there already exists a file with the same name in the target folder."),
|
.arg(p_files[i]),
|
||||||
QMessageBox::Ok, QMessageBox::Ok, this);
|
msg,
|
||||||
|
QMessageBox::Ok,
|
||||||
|
QMessageBox::Ok,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destFile) {
|
||||||
|
++nrPasted;
|
||||||
|
emit fileUpdated(destFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "pasted" << nrPasted << "files sucessfully";
|
qDebug() << "copy" << nrPasted << "files";
|
||||||
clipboard->clear();
|
if (nrPasted > 0) {
|
||||||
m_copiedFiles.clear();
|
g_mainWin->showStatusMessage(tr("%1 %2 pasted")
|
||||||
}
|
.arg(nrPasted)
|
||||||
|
.arg(nrPasted > 1 ? tr("notes") : tr("note")));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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();
|
updateFileList();
|
||||||
if (destFile) {
|
clipboard->clear();
|
||||||
emit fileUpdated(destFile);
|
getNewMagic();
|
||||||
}
|
|
||||||
return destFile != NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VFileList::keyPressEvent(QKeyEvent *event)
|
void VFileList::keyPressEvent(QKeyEvent *event)
|
||||||
@ -714,30 +874,6 @@ bool VFileList::locateFile(const VNoteFile *p_file)
|
|||||||
return false;
|
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)
|
void VFileList::registerNavigation(QChar p_majorKey)
|
||||||
{
|
{
|
||||||
m_majorKey = p_majorKey;
|
m_majorKey = p_majorKey;
|
||||||
@ -825,3 +961,105 @@ QList<QListWidgetItem *> VFileList::getVisibleItems() const
|
|||||||
return items;
|
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
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit VFileList(QWidget *parent = 0);
|
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);
|
inline void setEditArea(VEditArea *editArea);
|
||||||
|
|
||||||
|
// View and edit information of @p_file.
|
||||||
void fileInfo(VNoteFile *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);
|
void deleteFile(VNoteFile *p_file);
|
||||||
|
|
||||||
|
// Locate @p_file in the list widget.
|
||||||
bool locateFile(const VNoteFile *p_file);
|
bool locateFile(const VNoteFile *p_file);
|
||||||
|
|
||||||
inline const VDirectory *currentDirectory() const;
|
inline const VDirectory *currentDirectory() const;
|
||||||
|
|
||||||
// Implementations for VNavigationMode.
|
// Implementations for VNavigationMode.
|
||||||
@ -40,6 +52,13 @@ public:
|
|||||||
void hideNavigation() Q_DECL_OVERRIDE;
|
void hideNavigation() Q_DECL_OVERRIDE;
|
||||||
bool handleKeyNavigation(int p_key, bool &p_succeed) 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:
|
signals:
|
||||||
void fileClicked(VNoteFile *p_file, OpenFileMode mode = OpenFileMode::Read);
|
void fileClicked(VNoteFile *p_file, OpenFileMode mode = OpenFileMode::Read);
|
||||||
void fileCreated(VNoteFile *p_file, OpenFileMode mode = OpenFileMode::Read);
|
void fileCreated(VNoteFile *p_file, OpenFileMode mode = OpenFileMode::Read);
|
||||||
@ -48,18 +67,33 @@ signals:
|
|||||||
private slots:
|
private slots:
|
||||||
void contextMenuRequested(QPoint pos);
|
void contextMenuRequested(QPoint pos);
|
||||||
void handleItemClicked(QListWidgetItem *currentItem);
|
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:
|
// View and edit information of selected file.
|
||||||
void setDirectory(VDirectory *p_directory);
|
// Valid only when there is only one selected file.
|
||||||
void newFile();
|
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:
|
protected:
|
||||||
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
|
void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
|
||||||
@ -71,11 +105,16 @@ private:
|
|||||||
// Init shortcuts.
|
// Init shortcuts.
|
||||||
void initShortcuts();
|
void initShortcuts();
|
||||||
|
|
||||||
|
// Clear and re-fill the list widget according to m_directory.
|
||||||
void updateFileList();
|
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);
|
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.
|
// Init actions.
|
||||||
void initActions();
|
void initActions();
|
||||||
@ -83,25 +122,36 @@ private:
|
|||||||
// Return the corresponding QListWidgetItem of @p_file.
|
// Return the corresponding QListWidgetItem of @p_file.
|
||||||
QListWidgetItem *findItem(const VNoteFile *p_file);
|
QListWidgetItem *findItem(const VNoteFile *p_file);
|
||||||
|
|
||||||
void copyFileInfoToClipboard(const QJsonArray &p_files, bool p_isCut);
|
// Paste files given path by @p_files to destination directory @p_destDir.
|
||||||
void pasteFiles(VDirectory *p_destDir);
|
void pasteFiles(VDirectory *p_destDir,
|
||||||
bool copyFile(VDirectory *p_destDir, const QString &p_destName, VNoteFile *p_file, bool p_cut);
|
const QVector<QString> &p_files,
|
||||||
|
bool p_isCut);
|
||||||
|
|
||||||
// New items have been added to direcotry. Update file list accordingly.
|
// New items have been added to direcotry. Update file list accordingly.
|
||||||
QVector<QListWidgetItem *> updateFileListAdded();
|
QVector<QListWidgetItem *> updateFileListAdded();
|
||||||
|
|
||||||
inline QPointer<VNoteFile> getVFile(QListWidgetItem *p_item) const;
|
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;
|
QList<QListWidgetItem *> getVisibleItems() const;
|
||||||
|
|
||||||
// Fill the info of @p_item according to @p_file.
|
// Fill the info of @p_item according to @p_file.
|
||||||
void fillItem(QListWidgetItem *p_item, const VNoteFile *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;
|
VEditArea *editArea;
|
||||||
QListWidget *fileList;
|
QListWidget *fileList;
|
||||||
QPointer<VDirectory> m_directory;
|
QPointer<VDirectory> m_directory;
|
||||||
QVector<QPointer<VNoteFile> > m_copiedFiles;
|
|
||||||
|
// Magic number for clipboard operations.
|
||||||
|
int m_magicForClipboard;
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
QAction *m_openInReadAct;
|
QAction *m_openInReadAct;
|
||||||
@ -114,6 +164,7 @@ private:
|
|||||||
QAction *cutAct;
|
QAction *cutAct;
|
||||||
QAction *pasteAct;
|
QAction *pasteAct;
|
||||||
QAction *m_openLocationAct;
|
QAction *m_openLocationAct;
|
||||||
|
QAction *m_sortAct;
|
||||||
|
|
||||||
// Navigation Mode.
|
// Navigation Mode.
|
||||||
// Map second key to QListWidgetItem.
|
// Map second key to QListWidgetItem.
|
||||||
|
@ -1039,7 +1039,7 @@ void VMainWindow::importNoteFromFile()
|
|||||||
{
|
{
|
||||||
static QString lastPath = QDir::homePath();
|
static QString lastPath = QDir::homePath();
|
||||||
QStringList files = QFileDialog::getOpenFileNames(this,
|
QStringList files = QFileDialog::getOpenFileNames(this,
|
||||||
tr("Select Files (HTML or Markdown) To Create Notes"),
|
tr("Select Files To Create Notes"),
|
||||||
lastPath);
|
lastPath);
|
||||||
if (files.isEmpty()) {
|
if (files.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@ -1048,23 +1048,21 @@ void VMainWindow::importNoteFromFile()
|
|||||||
// Update lastPath
|
// Update lastPath
|
||||||
lastPath = QFileInfo(files[0]).path();
|
lastPath = QFileInfo(files[0]).path();
|
||||||
|
|
||||||
int failedFiles = 0;
|
QString msg;
|
||||||
for (int i = 0; i < files.size(); ++i) {
|
if (!fileList->importFiles(files, &msg)) {
|
||||||
bool ret = fileList->importFile(files[i]);
|
VUtils::showMessage(QMessageBox::Warning,
|
||||||
if (!ret) {
|
tr("Warning"),
|
||||||
++failedFiles;
|
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)
|
void VMainWindow::changeMarkdownConverter(QAction *action)
|
||||||
|
@ -286,21 +286,39 @@ void VMdEdit::clearUnusedImages()
|
|||||||
|
|
||||||
if (!unusedImages.isEmpty()) {
|
if (!unusedImages.isEmpty()) {
|
||||||
if (g_config->getConfirmImagesCleanUp()) {
|
if (g_config->getConfirmImagesCleanUp()) {
|
||||||
QString info = tr("Following images seems not to be used in this note anymore. "
|
QVector<ConfirmItemInfo> items;
|
||||||
"Please confirm the deletion of these images.<br>"
|
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.");
|
"Click \"Cancel\" to leave them untouched.");
|
||||||
|
|
||||||
VConfirmDeletionDialog dialog(tr("Confirm Cleaning Up Unused Images"),
|
VConfirmDeletionDialog dialog(tr("Confirm Cleaning Up Unused Images"),
|
||||||
|
text,
|
||||||
info,
|
info,
|
||||||
unusedImages,
|
items,
|
||||||
|
true,
|
||||||
true,
|
true,
|
||||||
g_config->getConfirmImagesCleanUp(),
|
|
||||||
true,
|
true,
|
||||||
this);
|
this);
|
||||||
|
|
||||||
|
unusedImages.clear();
|
||||||
if (dialog.exec()) {
|
if (dialog.exec()) {
|
||||||
unusedImages = dialog.getConfirmedFiles();
|
items = dialog.getConfirmedItems();
|
||||||
g_config->setConfirmImagesCleanUp(dialog.getAskAgainEnabled());
|
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)
|
void VNoteFile::setName(const QString &p_name)
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_name.isEmpty()
|
|
||||||
|| (m_docType == VUtils::docTypeFromName(p_name)));
|
|
||||||
|
|
||||||
m_name = p_name;
|
m_name = p_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +77,7 @@ bool VNoteFile::rename(const QString &p_name)
|
|||||||
m_name = p_name;
|
m_name = p_name;
|
||||||
|
|
||||||
// Update parent directory's config file.
|
// Update parent directory's config file.
|
||||||
if (!dir->writeToConfig()) {
|
if (!dir->updateFileConfig(this)) {
|
||||||
m_name = oldName;
|
m_name = oldName;
|
||||||
diskDir.rename(p_name, m_name);
|
diskDir.rename(p_name, m_name);
|
||||||
return false;
|
return false;
|
||||||
@ -175,32 +172,41 @@ QJsonObject VNoteFile::toConfigJson() const
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VNoteFile::deleteFile()
|
bool VNoteFile::deleteFile(QString *p_errMsg)
|
||||||
{
|
{
|
||||||
|
Q_ASSERT(!m_opened);
|
||||||
Q_ASSERT(parent());
|
Q_ASSERT(parent());
|
||||||
|
|
||||||
bool ret = false;
|
bool ret = true;
|
||||||
|
|
||||||
// Delete local images if it is Markdown.
|
// Delete local images if it is Markdown.
|
||||||
if (m_docType == DocType::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.
|
// Delete the file.
|
||||||
QString filePath = fetchPath();
|
QString filePath = fetchPath();
|
||||||
if (VUtils::deleteFile(getNotebook(), filePath, false)) {
|
if (VUtils::deleteFile(getNotebook(), filePath, false)) {
|
||||||
ret = true;
|
|
||||||
qDebug() << "deleted" << m_name << filePath;
|
qDebug() << "deleted" << m_name << filePath;
|
||||||
} else {
|
} else {
|
||||||
|
ret = false;
|
||||||
|
VUtils::addErrMsg(p_errMsg, tr("Fail to delete the note file."));
|
||||||
qWarning() << "fail to delete" << m_name << filePath;
|
qWarning() << "fail to delete" << m_name << filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VNoteFile::deleteInternalImages()
|
bool VNoteFile::deleteInternalImages()
|
||||||
{
|
{
|
||||||
Q_ASSERT(parent() && m_docType == DocType::Markdown);
|
Q_ASSERT(parent() && m_docType == DocType::Markdown);
|
||||||
|
|
||||||
@ -214,6 +220,8 @@ void VNoteFile::deleteInternalImages()
|
|||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "delete" << deleted << "images for" << m_name << fetchPath();
|
qDebug() << "delete" << deleted << "images for" << m_name << fetchPath();
|
||||||
|
|
||||||
|
return deleted == images.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VNoteFile::addAttachment(const QString &p_file)
|
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)) {
|
if (!getDirectory()->updateFileConfig(this)) {
|
||||||
|
ret = false;
|
||||||
qWarning() << "fail to update config of file" << m_name
|
qWarning() << "fail to update config of file" << m_name
|
||||||
<< "in directory" << fetchBasePath();
|
<< "in directory" << fetchBasePath();
|
||||||
ret = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@ -320,21 +338,25 @@ int VNoteFile::findAttachment(const QString &p_name, bool p_caseSensitive)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VNoteFile::sortAttachments(QVector<int> p_sortedIdx)
|
bool VNoteFile::sortAttachments(const QVector<int> &p_sortedIdx)
|
||||||
{
|
{
|
||||||
V_ASSERT(m_opened);
|
V_ASSERT(m_opened);
|
||||||
V_ASSERT(p_sortedIdx.size() == m_attachments.size());
|
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) {
|
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)) {
|
if (!getDirectory()->updateFileConfig(this)) {
|
||||||
qWarning() << "fail to reorder files in config" << p_sortedIdx;
|
qWarning() << "fail to reorder attachments in config" << p_sortedIdx;
|
||||||
m_attachments = oriFiles;
|
m_attachments = ori;
|
||||||
|
ret = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VNoteFile::renameAttachment(const QString &p_oldName, const QString &p_newName)
|
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;
|
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.
|
// Get the relative path related to the notebook.
|
||||||
QString fetchRelativePath() const;
|
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.
|
// Create a Json object from current instance.
|
||||||
QJsonObject toConfigJson() const;
|
QJsonObject toConfigJson() const;
|
||||||
|
|
||||||
// Delete this file in disk as well as all its images/attachments.
|
|
||||||
bool deleteFile();
|
|
||||||
|
|
||||||
const QString &getAttachmentFolder() const;
|
const QString &getAttachmentFolder() const;
|
||||||
|
|
||||||
|
void setAttachmentFolder(const QString &p_folder);
|
||||||
|
|
||||||
const QVector<VAttachment> &getAttachments() const;
|
const QVector<VAttachment> &getAttachments() const;
|
||||||
|
|
||||||
|
void setAttachments(const QVector<VAttachment> &p_attas);
|
||||||
|
|
||||||
// Add @p_file as an attachment to this note.
|
// Add @p_file as an attachment to this note.
|
||||||
bool addAttachment(const QString &p_file);
|
bool addAttachment(const QString &p_file);
|
||||||
|
|
||||||
@ -97,17 +92,43 @@ public:
|
|||||||
bool deleteAttachments(const QVector<QString> &p_names);
|
bool deleteAttachments(const QVector<QString> &p_names);
|
||||||
|
|
||||||
// Reorder attachments in m_attachments by index.
|
// 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.
|
// Return the index of @p_name in m_attachments.
|
||||||
// -1 if not found.
|
// -1 if not found.
|
||||||
int findAttachment(const QString &p_name, bool p_caseSensitive = true);
|
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);
|
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:
|
private:
|
||||||
// Delete internal images of this file.
|
// 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.
|
// Folder under the attachment folder of the notebook.
|
||||||
// Store all the attachments of current file.
|
// Store all the attachments of current file.
|
||||||
@ -122,9 +143,19 @@ inline const QString &VNoteFile::getAttachmentFolder() const
|
|||||||
return m_attachmentFolder;
|
return m_attachmentFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void VNoteFile::setAttachmentFolder(const QString &p_folder)
|
||||||
|
{
|
||||||
|
m_attachmentFolder = p_folder;
|
||||||
|
}
|
||||||
|
|
||||||
inline const QVector<VAttachment> &VNoteFile::getAttachments() const
|
inline const QVector<VAttachment> &VNoteFile::getAttachments() const
|
||||||
{
|
{
|
||||||
return m_attachments;
|
return m_attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void VNoteFile::setAttachments(const QVector<VAttachment> &p_attas)
|
||||||
|
{
|
||||||
|
m_attachments = p_attas;
|
||||||
|
}
|
||||||
|
|
||||||
#endif // VNOTEFILE_H
|
#endif // VNOTEFILE_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user