From dc0c0ca8499cff0d8d189d6a5580e9242c60bd41 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Fri, 3 Sep 2021 20:37:26 +0800 Subject: [PATCH] Notebook: support scan and import external files --- src/core/notebook/notebook.cpp | 5 ++ src/core/notebook/notebook.h | 2 + .../notebookconfigmgr/inotebookconfigmgr.h | 2 + .../notebookconfigmgr/vxnotebookconfigmgr.cpp | 56 +++++++++++++++++++ .../notebookconfigmgr/vxnotebookconfigmgr.h | 4 ++ src/data/core/core.qrc | 1 + src/data/core/icons/scan_import.svg | 1 + src/utils/fileutils.cpp | 10 ++++ src/utils/fileutils.h | 2 + src/widgets/dialogs/imageinsertdialog.cpp | 34 +++++++++-- src/widgets/dialogs/imageinsertdialog.h | 4 ++ src/widgets/notebookexplorer.cpp | 32 +++++++++++ 12 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 src/data/core/icons/scan_import.svg diff --git a/src/core/notebook/notebook.cpp b/src/core/notebook/notebook.cpp index b1f67283..18da9087 100644 --- a/src/core/notebook/notebook.cpp +++ b/src/core/notebook/notebook.cpp @@ -378,3 +378,8 @@ QList> Notebook::collectFiles() return files; } + +QStringList Notebook::scanAndImportExternalFiles() +{ + return m_configMgr->scanAndImportExternalFiles(getRootNode().data()); +} diff --git a/src/core/notebook/notebook.h b/src/core/notebook/notebook.h index b4d84ab0..f02ac65b 100644 --- a/src/core/notebook/notebook.h +++ b/src/core/notebook/notebook.h @@ -144,6 +144,8 @@ namespace vnotex // Get content files recursively. QList> collectFiles(); + QStringList scanAndImportExternalFiles(); + static const QString c_defaultAttachmentFolder; static const QString c_defaultImageFolder; diff --git a/src/core/notebookconfigmgr/inotebookconfigmgr.h b/src/core/notebookconfigmgr/inotebookconfigmgr.h index c8a1f882..b6177372 100644 --- a/src/core/notebookconfigmgr/inotebookconfigmgr.h +++ b/src/core/notebookconfigmgr/inotebookconfigmgr.h @@ -80,6 +80,8 @@ namespace vnotex virtual bool checkNodeExists(Node *p_node) = 0; + virtual QStringList scanAndImportExternalFiles(Node *p_node) = 0; + protected: // Version of the config processing code. virtual QString getCodeVersion() const; diff --git a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp index a0a269aa..0d15c15d 100644 --- a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp +++ b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp @@ -996,3 +996,59 @@ bool VXNotebookConfigMgr::checkNodeExists(Node *p_node) p_node->setExists(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; +} diff --git a/src/core/notebookconfigmgr/vxnotebookconfigmgr.h b/src/core/notebookconfigmgr/vxnotebookconfigmgr.h index 64e61c5f..19928431 100644 --- a/src/core/notebookconfigmgr/vxnotebookconfigmgr.h +++ b/src/core/notebookconfigmgr/vxnotebookconfigmgr.h @@ -74,6 +74,8 @@ namespace vnotex bool checkNodeExists(Node *p_node) Q_DECL_OVERRIDE; + QStringList scanAndImportExternalFiles(Node *p_node) Q_DECL_OVERRIDE; + private: // Config of a file child. struct NodeFileConfig @@ -195,6 +197,8 @@ namespace vnotex bool isExcludedFromExternalNode(const QString &p_name) const; + static bool isLikelyImageFolder(const QString &p_dirPath); + Info m_info; static bool s_initialized; diff --git a/src/data/core/core.qrc b/src/data/core/core.qrc index 49002d88..c669818e 100644 --- a/src/data/core/core.qrc +++ b/src/data/core/core.qrc @@ -37,6 +37,7 @@ icons/up_parent_node.svg icons/properties.svg icons/recycle_bin.svg + icons/scan_import.svg icons/search_location_list.svg icons/save_editor.svg icons/buffer.svg diff --git a/src/data/core/icons/scan_import.svg b/src/data/core/icons/scan_import.svg new file mode 100644 index 00000000..7d38bbc1 --- /dev/null +++ b/src/data/core/icons/scan_import.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/utils/fileutils.cpp b/src/utils/fileutils.cpp index 6a3eda5d..d64ac892 100644 --- a/src/utils/fileutils.cpp +++ b/src/utils/fileutils.cpp @@ -261,6 +261,16 @@ bool FileUtils::isText(const QString &p_filePath) 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 img(p_filePath); diff --git a/src/utils/fileutils.h b/src/utils/fileutils.h index 01657bc2..edf8ae2b 100644 --- a/src/utils/fileutils.h +++ b/src/utils/fileutils.h @@ -56,6 +56,8 @@ namespace vnotex static bool isText(const QString &p_filePath); + static bool isImage(const QString &p_filePath); + static QImage imageFromFile(const QString &p_filePath); static QPixmap pixmapFromFile(const QString &p_filePath); diff --git a/src/widgets/dialogs/imageinsertdialog.cpp b/src/widgets/dialogs/imageinsertdialog.cpp index a03ef4c7..5cfe43de 100644 --- a/src/widgets/dialogs/imageinsertdialog.cpp +++ b/src/widgets/dialogs/imageinsertdialog.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,10 @@ using namespace vnotex; int ImageInsertDialog::s_lastScaleSliderValue = 10; +int ImageInsertDialog::s_lastScaleWidth = -1; + +bool ImageInsertDialog::s_fixedScaleWidth = false; + ImageInsertDialog::ImageInsertDialog(const QString &p_title, const QString &p_imageTitle, 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()); m_imageLabel->resize(p_val, height); + + s_lastScaleWidth = p_val; }); // 0.1 to 2.0 -> 1 to 20. m_scaleSlider = new QSlider(mainWidget); m_scaleSlider->setOrientation(Qt::Horizontal); m_scaleSlider->setMinimum(1); - m_scaleSlider->setMaximum(20); + m_scaleSlider->setMaximum(50); m_scaleSlider->setValue(s_lastScaleSliderValue); m_scaleSlider->setSingleStep(1); 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_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. m_imageLabel = new QLabel(mainWidget); m_imageLabel->setScaledContents(true); @@ -132,7 +149,7 @@ void ImageInsertDialog::setupUI(const QString &p_title, m_previewArea->setBackgroundRole(QPalette::Dark); m_previewArea->setWidget(m_imageLabel); m_previewArea->setMinimumSize(256, 256); - gridLayout->addWidget(m_previewArea, 4, 0, 1, 5); + gridLayout->addWidget(m_previewArea, 5, 0, 1, 5); setImageControlsVisible(false); @@ -271,11 +288,16 @@ void ImageInsertDialog::setImage(const QImage &p_image) m_widthSpin->setMaximum(m_image.width() * 5); - // Set the scaling widgets. - if (m_scaleSlider->value() == s_lastScaleSliderValue) { - handleScaleSliderValueChanged(s_lastScaleSliderValue); + if (s_fixedScaleWidth) { + m_widthSpin->setValue(s_lastScaleWidth); } 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); diff --git a/src/widgets/dialogs/imageinsertdialog.h b/src/widgets/dialogs/imageinsertdialog.h index 7fc57ae5..08fe4e92 100644 --- a/src/widgets/dialogs/imageinsertdialog.h +++ b/src/widgets/dialogs/imageinsertdialog.h @@ -112,6 +112,10 @@ namespace vnotex QSharedPointer m_tempFile; static int s_lastScaleSliderValue; + + static int s_lastScaleWidth; + + static bool s_fixedScaleWidth; }; } diff --git a/src/widgets/notebookexplorer.cpp b/src/widgets/notebookexplorer.cpp index c32fd1cc..0920e3cf 100644 --- a/src/widgets/notebookexplorer.cpp +++ b/src/widgets/notebookexplorer.cpp @@ -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"), tr("&Manage Notebooks"), titleBar,