Notebook: support scan and import external files

This commit is contained in:
Le Tan 2021-09-03 20:37:26 +08:00
parent 8973f40020
commit dc0c0ca849
12 changed files with 147 additions and 6 deletions

View File

@ -378,3 +378,8 @@ QList<QSharedPointer<File>> Notebook::collectFiles()
return files; return files;
} }
QStringList Notebook::scanAndImportExternalFiles()
{
return m_configMgr->scanAndImportExternalFiles(getRootNode().data());
}

View File

@ -144,6 +144,8 @@ namespace vnotex
// Get content files recursively. // Get content files recursively.
QList<QSharedPointer<File>> collectFiles(); QList<QSharedPointer<File>> collectFiles();
QStringList scanAndImportExternalFiles();
static const QString c_defaultAttachmentFolder; static const QString c_defaultAttachmentFolder;
static const QString c_defaultImageFolder; static const QString c_defaultImageFolder;

View File

@ -80,6 +80,8 @@ namespace vnotex
virtual bool checkNodeExists(Node *p_node) = 0; virtual bool checkNodeExists(Node *p_node) = 0;
virtual QStringList scanAndImportExternalFiles(Node *p_node) = 0;
protected: protected:
// Version of the config processing code. // Version of the config processing code.
virtual QString getCodeVersion() const; virtual QString getCodeVersion() const;

View File

@ -996,3 +996,59 @@ bool VXNotebookConfigMgr::checkNodeExists(Node *p_node)
p_node->setExists(exists); p_node->setExists(exists);
return exists; return exists;
} }
QStringList VXNotebookConfigMgr::scanAndImportExternalFiles(Node *p_node)
{
QStringList files;
if (!p_node->isContainer() || p_node->getUse() == Node::Use::RecycleBin) {
return files;
}
// External nodes.
auto dir = p_node->toDir();
auto externalNodes = fetchExternalChildren(p_node);
for (const auto &node : externalNodes) {
Node::Flags flags = Node::Flag::Content;
if (node->isFolder()) {
if (isLikelyImageFolder(dir.filePath(node->getName()))) {
qWarning() << "skip importing folder containing only images" << node->getName();
continue;
}
flags = Node::Flag::Container;
}
addAsNode(p_node, flags, node->getName(), NodeParameters());
files << dir.filePath(node->getName());
}
// Children folders (including newly-added external nodes).
for (const auto &child : p_node->getChildrenRef()) {
if (child->isContainer()) {
files << scanAndImportExternalFiles(child.data());
}
}
return files;
}
bool VXNotebookConfigMgr::isLikelyImageFolder(const QString &p_dirPath)
{
QDir dir(p_dirPath);
const auto folders = dir.entryList(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
if (!folders.isEmpty()) {
return false;
}
const auto files = dir.entryList(QDir::Files);
if (files.isEmpty()) {
return false;
}
for (const auto &file : files) {
if (!FileUtils::isImage(dir.filePath(file))) {
return false;
}
}
return true;
}

View File

@ -74,6 +74,8 @@ namespace vnotex
bool checkNodeExists(Node *p_node) Q_DECL_OVERRIDE; bool checkNodeExists(Node *p_node) Q_DECL_OVERRIDE;
QStringList scanAndImportExternalFiles(Node *p_node) Q_DECL_OVERRIDE;
private: private:
// Config of a file child. // Config of a file child.
struct NodeFileConfig struct NodeFileConfig
@ -195,6 +197,8 @@ namespace vnotex
bool isExcludedFromExternalNode(const QString &p_name) const; bool isExcludedFromExternalNode(const QString &p_name) const;
static bool isLikelyImageFolder(const QString &p_dirPath);
Info m_info; Info m_info;
static bool s_initialized; static bool s_initialized;

View File

@ -37,6 +37,7 @@
<file>icons/up_parent_node.svg</file> <file>icons/up_parent_node.svg</file>
<file>icons/properties.svg</file> <file>icons/properties.svg</file>
<file>icons/recycle_bin.svg</file> <file>icons/recycle_bin.svg</file>
<file>icons/scan_import.svg</file>
<file>icons/search_location_list.svg</file> <file>icons/search_location_list.svg</file>
<file>icons/save_editor.svg</file> <file>icons/save_editor.svg</file>
<file>icons/buffer.svg</file> <file>icons/buffer.svg</file>

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1630643879525" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4410" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M810.666667 512V384h-170.666667a85.333333 85.333333 0 0 1-85.333333-85.333333V128H213.333333v384h597.333334zM128 512V128a85.333333 85.333333 0 0 1 85.333333-85.333333h444.330667L896 281.002667V512h85.333333v85.333333H42.666667v-85.333333h85.333333z m682.666667 170.666667h85.333333v213.333333a85.333333 85.333333 0 0 1-85.333333 85.333333H213.333333a85.333333 85.333333 0 0 1-85.333333-85.333333v-213.333333h85.333333v213.333333h597.333334v-213.333333zM640 145.664V298.666667h153.002667L640 145.664z" p-id="4411" fill="#000000"></path></svg>

After

Width:  |  Height:  |  Size: 918 B

View File

@ -261,6 +261,16 @@ bool FileUtils::isText(const QString &p_filePath)
return mimeType.inherits(QStringLiteral("text/plain")); return mimeType.inherits(QStringLiteral("text/plain"));
} }
bool FileUtils::isImage(const QString &p_filePath)
{
QMimeDatabase mimeDatabase;
auto mimeType = mimeDatabase.mimeTypeForFile(p_filePath);
if (mimeType.name().startsWith(QStringLiteral("image/"))) {
return true;
}
return false;
}
QImage FileUtils::imageFromFile(const QString &p_filePath) QImage FileUtils::imageFromFile(const QString &p_filePath)
{ {
QImage img(p_filePath); QImage img(p_filePath);

View File

@ -56,6 +56,8 @@ namespace vnotex
static bool isText(const QString &p_filePath); static bool isText(const QString &p_filePath);
static bool isImage(const QString &p_filePath);
static QImage imageFromFile(const QString &p_filePath); static QImage imageFromFile(const QString &p_filePath);
static QPixmap pixmapFromFile(const QString &p_filePath); static QPixmap pixmapFromFile(const QString &p_filePath);

View File

@ -15,6 +15,7 @@
#include <QFileDialog> #include <QFileDialog>
#include <QTimer> #include <QTimer>
#include <QTemporaryFile> #include <QTemporaryFile>
#include <QCheckBox>
#include <vtextedit/markdownutils.h> #include <vtextedit/markdownutils.h>
#include <vtextedit/networkutils.h> #include <vtextedit/networkutils.h>
@ -28,6 +29,10 @@ using namespace vnotex;
int ImageInsertDialog::s_lastScaleSliderValue = 10; int ImageInsertDialog::s_lastScaleSliderValue = 10;
int ImageInsertDialog::s_lastScaleWidth = -1;
bool ImageInsertDialog::s_fixedScaleWidth = false;
ImageInsertDialog::ImageInsertDialog(const QString &p_title, ImageInsertDialog::ImageInsertDialog(const QString &p_title,
const QString &p_imageTitle, const QString &p_imageTitle,
const QString &p_imageAlt, const QString &p_imageAlt,
@ -108,12 +113,14 @@ void ImageInsertDialog::setupUI(const QString &p_title,
int height = m_image.height() * (1.0 * p_val / m_image.width()); int height = m_image.height() * (1.0 * p_val / m_image.width());
m_imageLabel->resize(p_val, height); m_imageLabel->resize(p_val, height);
s_lastScaleWidth = p_val;
}); });
// 0.1 to 2.0 -> 1 to 20. // 0.1 to 2.0 -> 1 to 20.
m_scaleSlider = new QSlider(mainWidget); m_scaleSlider = new QSlider(mainWidget);
m_scaleSlider->setOrientation(Qt::Horizontal); m_scaleSlider->setOrientation(Qt::Horizontal);
m_scaleSlider->setMinimum(1); m_scaleSlider->setMinimum(1);
m_scaleSlider->setMaximum(20); m_scaleSlider->setMaximum(50);
m_scaleSlider->setValue(s_lastScaleSliderValue); m_scaleSlider->setValue(s_lastScaleSliderValue);
m_scaleSlider->setSingleStep(1); m_scaleSlider->setSingleStep(1);
m_scaleSlider->setPageStep(5); m_scaleSlider->setPageStep(5);
@ -125,6 +132,16 @@ void ImageInsertDialog::setupUI(const QString &p_title,
gridLayout->addWidget(m_scaleSlider, 3, 2, 1, 2); gridLayout->addWidget(m_scaleSlider, 3, 2, 1, 2);
gridLayout->addWidget(m_sliderLabel, 3, 4, 1, 1); gridLayout->addWidget(m_sliderLabel, 3, 4, 1, 1);
{
auto fixedWidthCheckBox = WidgetsFactory::createCheckBox(tr("Fixed scaling width"), mainWidget);
fixedWidthCheckBox->setChecked(s_fixedScaleWidth);
connect(fixedWidthCheckBox, &QCheckBox::stateChanged,
this, [this](int p_state) {
s_fixedScaleWidth = p_state == Qt::Checked;
});
gridLayout->addWidget(fixedWidthCheckBox, 4, 1, 1, 1);
}
// Preview area. // Preview area.
m_imageLabel = new QLabel(mainWidget); m_imageLabel = new QLabel(mainWidget);
m_imageLabel->setScaledContents(true); m_imageLabel->setScaledContents(true);
@ -132,7 +149,7 @@ void ImageInsertDialog::setupUI(const QString &p_title,
m_previewArea->setBackgroundRole(QPalette::Dark); m_previewArea->setBackgroundRole(QPalette::Dark);
m_previewArea->setWidget(m_imageLabel); m_previewArea->setWidget(m_imageLabel);
m_previewArea->setMinimumSize(256, 256); m_previewArea->setMinimumSize(256, 256);
gridLayout->addWidget(m_previewArea, 4, 0, 1, 5); gridLayout->addWidget(m_previewArea, 5, 0, 1, 5);
setImageControlsVisible(false); setImageControlsVisible(false);
@ -271,11 +288,16 @@ void ImageInsertDialog::setImage(const QImage &p_image)
m_widthSpin->setMaximum(m_image.width() * 5); m_widthSpin->setMaximum(m_image.width() * 5);
// Set the scaling widgets. if (s_fixedScaleWidth) {
if (m_scaleSlider->value() == s_lastScaleSliderValue) { m_widthSpin->setValue(s_lastScaleWidth);
handleScaleSliderValueChanged(s_lastScaleSliderValue);
} else { } else {
m_scaleSlider->setValue(s_lastScaleSliderValue); // Set the scaling widgets.
if (m_scaleSlider->value() == s_lastScaleSliderValue) {
// Trigger it manually.
handleScaleSliderValueChanged(s_lastScaleSliderValue);
} else {
m_scaleSlider->setValue(s_lastScaleSliderValue);
}
} }
setImageControlsVisible(true); setImageControlsVisible(true);

View File

@ -112,6 +112,10 @@ namespace vnotex
QSharedPointer<QTemporaryFile> m_tempFile; QSharedPointer<QTemporaryFile> m_tempFile;
static int s_lastScaleSliderValue; static int s_lastScaleSliderValue;
static int s_lastScaleWidth;
static bool s_fixedScaleWidth;
}; };
} }

View File

@ -132,6 +132,38 @@ TitleBar *NotebookExplorer::setupTitleBar(QWidget *p_parent)
}); });
} }
{
auto btn = titleBar->addActionButton(QStringLiteral("scan_import.svg"), tr("Scan and Import"));
connect(btn, &QToolButton::clicked,
this, [this]() {
if (!m_currentNotebook) {
MessageBoxHelper::notify(MessageBoxHelper::Warning,
tr("Please select one notebook first."),
VNoteX::getInst().getMainWindow());
return;
}
int ret = MessageBoxHelper::questionOkCancel(MessageBoxHelper::Warning,
tr("Scan the whole notebook (%1) and import external files automatically.").arg(m_currentNotebook->getName()),
tr("This operation helps importing external files that are added outside VNote. "
"It may import unexpected files."),
tr("It is recommended to always manage files within VNote."),
VNoteX::getInst().getMainWindow());
if (ret != QMessageBox::Ok) {
return;
}
auto importedFiles = m_currentNotebook->scanAndImportExternalFiles();
MessageBoxHelper::notify(MessageBoxHelper::Information,
tr("Imported %n file(s).", "", importedFiles.size()),
QString(),
importedFiles.join('\n'),
VNoteX::getInst().getMainWindow());
if (!importedFiles.isEmpty()) {
m_nodeExplorer->reload();
}
});
}
titleBar->addMenuAction(QStringLiteral("manage_notebooks.svg"), titleBar->addMenuAction(QStringLiteral("manage_notebooks.svg"),
tr("&Manage Notebooks"), tr("&Manage Notebooks"),
titleBar, titleBar,