From 9bf3f9394fbffaab8fb2688506b309c1cbb0afa0 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Wed, 24 May 2017 19:54:58 +0800 Subject: [PATCH] support custom image folder for both global scope and notebook scope --- src/dialog/vnewnotebookdialog.cpp | 71 ++++++++++++++++++---- src/dialog/vnewnotebookdialog.h | 6 +- src/dialog/vnotebookinfodialog.cpp | 84 +++++++++++++++++-------- src/dialog/vnotebookinfodialog.h | 29 +++++---- src/dialog/vsettingsdialog.cpp | 98 +++++++++++++++++++++++++++++- src/dialog/vsettingsdialog.h | 21 +++++++ src/resources/vnote.ini | 3 + src/utils/vutils.cpp | 2 + src/utils/vutils.h | 4 ++ src/vconfigmanager.cpp | 37 ++++++++--- src/vconfigmanager.h | 55 +++++++++++++++-- src/vconstants.h | 9 +++ src/vdirectory.cpp | 37 ++++++++--- src/vdirectory.h | 10 ++- src/vdirectorytree.cpp | 1 - src/vmainwindow.cpp | 3 +- src/vnotebook.cpp | 80 +++++++++++++++++++++--- src/vnotebook.h | 33 ++++++++-- src/vnotebookselector.cpp | 68 +++++++++++++-------- src/vnotebookselector.h | 8 ++- 20 files changed, 538 insertions(+), 121 deletions(-) diff --git a/src/dialog/vnewnotebookdialog.cpp b/src/dialog/vnewnotebookdialog.cpp index fb0f0154..1ba36ef9 100644 --- a/src/dialog/vnewnotebookdialog.cpp +++ b/src/dialog/vnewnotebookdialog.cpp @@ -2,6 +2,7 @@ #include #include "vnewnotebookdialog.h" #include "vconfigmanager.h" +#include "utils/vutils.h" extern VConfigManager vconfig; @@ -18,6 +19,7 @@ VNewNotebookDialog::VNewNotebookDialog(const QString &title, const QString &info connect(browseBtn, &QPushButton::clicked, this, &VNewNotebookDialog::handleBrowseBtnClicked); enableOkButton(); + checkRootFolder(pathEdit->text()); } void VNewNotebookDialog::setupUI() @@ -26,6 +28,7 @@ void VNewNotebookDialog::setupUI() infoLabel = new QLabel(info); infoLabel->setWordWrap(true); } + nameLabel = new QLabel(tr("Notebook &name:")); nameEdit = new QLineEdit(defaultName); nameLabel->setBuddy(nameEdit); @@ -35,9 +38,22 @@ void VNewNotebookDialog::setupUI() pathLabel->setBuddy(pathEdit); browseBtn = new QPushButton(tr("&Browse")); - importCheck = new QCheckBox(tr("Try to import existing notebook"), this); - importCheck->setChecked(true); - importCheck->setToolTip(tr("When checked, VNote won't create a new config file if there already exists one")); + importCheck = new QCheckBox(tr("Import existing notebook")); + importCheck->setToolTip(tr("When checked, VNote will read the configuration file to import an existing notebook")); + connect(importCheck, &QCheckBox::stateChanged, + this, &VNewNotebookDialog::importCheckChanged); + + QLabel *imageFolderLabel = new QLabel(tr("&Image folder:")); + m_imageFolderEdit = new QLineEdit(); + m_imageFolderEdit->setPlaceholderText(tr("Use global configuration (%1)") + .arg(vconfig.getImageFolder())); + imageFolderLabel->setBuddy(m_imageFolderEdit); + QString imageFolderTip = tr("Set the name of the folder for all the notes of this notebook to store images " + "(empty to use global configuration)"); + m_imageFolderEdit->setToolTip(imageFolderTip); + imageFolderLabel->setToolTip(imageFolderTip); + QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit); + m_imageFolderEdit->setValidator(validator); QGridLayout *topLayout = new QGridLayout(); topLayout->addWidget(nameLabel, 0, 0); @@ -46,28 +62,33 @@ void VNewNotebookDialog::setupUI() topLayout->addWidget(pathEdit, 1, 1); topLayout->addWidget(browseBtn, 1, 2); topLayout->addWidget(importCheck, 2, 1); + topLayout->addWidget(imageFolderLabel, 3, 0); + topLayout->addWidget(m_imageFolderEdit, 3, 1); // Ok is the default button. m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok); + pathEdit->setMinimumWidth(okBtn->sizeHint().width() * 3); + // Warning label. m_warnLabel = new QLabel(tr("WARNING: The folder you choose is NOT empty! " "It is highly recommended to use an EMPTY and EXCLUSIVE folder for a notebook. " "Ignore this warning if you do want to import an existing VNote notebook folder.") - .arg(vconfig.c_warningTextStyle), - this); + .arg(vconfig.c_warningTextStyle)); m_warnLabel->setWordWrap(true); m_warnLabel->hide(); - QVBoxLayout *mainLayout = new QVBoxLayout(); + QVBoxLayout *mainLayout = new QVBoxLayout(this); if (infoLabel) { mainLayout->addWidget(infoLabel); } mainLayout->addLayout(topLayout); mainLayout->addWidget(m_warnLabel); mainLayout->addWidget(m_btnBox); + // Will set the parent of above widgets properly. setLayout(mainLayout); mainLayout->setSizeConstraint(QLayout::SetFixedSize); setWindowTitle(title); @@ -91,11 +112,19 @@ QString VNewNotebookDialog::getPathInput() const return pathEdit->text(); } +QString VNewNotebookDialog::getImageFolder() const +{ + return m_imageFolderEdit->text(); +} + void VNewNotebookDialog::handleBrowseBtnClicked() { QString dirPath = QFileDialog::getExistingDirectory(this, tr("Select Root Folder Of The Notebook"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); - pathEdit->setText(dirPath); + + if (!dirPath.isEmpty()) { + pathEdit->setText(dirPath); + } } bool VNewNotebookDialog::getImportCheck() const @@ -111,16 +140,36 @@ void VNewNotebookDialog::showEvent(QShowEvent *event) void VNewNotebookDialog::handlePathChanged(const QString &p_text) { - if (!p_text.isEmpty()) { - QDir dir(p_text); + enableOkButton(); + checkRootFolder(p_text); +} + +void VNewNotebookDialog::importCheckChanged(int p_state) +{ + // If import existing notebook, disable setting new configs. + bool checked = p_state == Qt::Checked; + + m_imageFolderEdit->setEnabled(!checked); +} + +void VNewNotebookDialog::checkRootFolder(const QString &p_path) +{ + bool existConfig = false; + + if (!p_path.isEmpty()) { + QDir dir(p_path); QStringList files = dir.entryList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::Hidden); - if (dir.exists() && !files.isEmpty()) { + if (!files.isEmpty()) { m_warnLabel->show(); } else { m_warnLabel->hide(); } + + existConfig = VConfigManager::directoryConfigExist(p_path); } else { m_warnLabel->hide(); } - enableOkButton(); + + importCheck->setChecked(existConfig); + importCheck->setEnabled(existConfig); } diff --git a/src/dialog/vnewnotebookdialog.h b/src/dialog/vnewnotebookdialog.h index 06118cd9..c15b3e8f 100644 --- a/src/dialog/vnewnotebookdialog.h +++ b/src/dialog/vnewnotebookdialog.h @@ -19,17 +19,20 @@ public: QString getNameInput() const; QString getPathInput() const; bool getImportCheck() const; + QString getImageFolder() const; private slots: void enableOkButton(); void handleBrowseBtnClicked(); void handlePathChanged(const QString &p_text); + void importCheckChanged(int p_state); protected: void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; private: void setupUI(); + void checkRootFolder(const QString &p_path); QLabel *infoLabel; QLabel *nameLabel; @@ -37,8 +40,9 @@ private: QLineEdit *pathEdit; QCheckBox *importCheck; QPushButton *browseBtn; - QDialogButtonBox *m_btnBox; QLabel *m_warnLabel; + QLineEdit *m_imageFolderEdit; + QDialogButtonBox *m_btnBox; QString title; QString info; diff --git a/src/dialog/vnotebookinfodialog.cpp b/src/dialog/vnotebookinfodialog.cpp index d34149b6..67b96325 100644 --- a/src/dialog/vnotebookinfodialog.cpp +++ b/src/dialog/vnotebookinfodialog.cpp @@ -1,37 +1,55 @@ #include #include "vnotebookinfodialog.h" +#include "vnotebook.h" +#include "utils/vutils.h" +#include "vconfigmanager.h" -VNotebookInfoDialog::VNotebookInfoDialog(const QString &title, const QString &info, - const QString &defaultName, const QString &defaultPath, - QWidget *parent) - : QDialog(parent), infoLabel(NULL), title(title), info(info), defaultName(defaultName), - defaultPath(defaultPath) +extern VConfigManager vconfig; + +VNotebookInfoDialog::VNotebookInfoDialog(const QString &p_title, const QString &p_info, + const VNotebook *p_notebook, QWidget *p_parent) + : QDialog(p_parent), m_notebook(p_notebook), m_infoLabel(NULL) { - setupUI(); + setupUI(p_title, p_info); - connect(nameEdit, &QLineEdit::textChanged, this, &VNotebookInfoDialog::enableOkButton); + connect(m_nameEdit, &QLineEdit::textChanged, + this, &VNotebookInfoDialog::enableOkButton); enableOkButton(); } -void VNotebookInfoDialog::setupUI() +void VNotebookInfoDialog::setupUI(const QString &p_title, const QString &p_info) { - if (!info.isEmpty()) { - infoLabel = new QLabel(info); + if (!p_info.isEmpty()) { + m_infoLabel = new QLabel(p_info); } - nameLabel = new QLabel(tr("Notebook &name:")); - nameEdit = new QLineEdit(defaultName); - nameEdit->selectAll(); - nameLabel->setBuddy(nameEdit); - QLabel *pathLabel = new QLabel(tr("Notebook &path:")); - pathEdit = new QLineEdit(defaultPath); - pathLabel->setBuddy(pathEdit); - pathEdit->setReadOnly(true); + QLabel *nameLabel = new QLabel(tr("Notebook &name:")); + m_nameEdit = new QLineEdit(m_notebook->getName()); + m_nameEdit->selectAll(); + nameLabel->setBuddy(m_nameEdit); + + QLabel *pathLabel = new QLabel(tr("Notebook &root folder:")); + m_pathEdit = new QLineEdit(m_notebook->getPath()); + pathLabel->setBuddy(m_pathEdit); + m_pathEdit->setReadOnly(true); + + QLabel *imageFolderLabel = new QLabel(tr("&Image folder:")); + m_imageFolderEdit = new QLineEdit(m_notebook->getImageFolderConfig()); + m_imageFolderEdit->setPlaceholderText(tr("Use global configuration (%1)") + .arg(vconfig.getImageFolder())); + imageFolderLabel->setBuddy(m_imageFolderEdit); + QString imageFolderTip = tr("Set the name of the folder for all the notes of this notebook to store images " + "(empty to use global configuration)"); + m_imageFolderEdit->setToolTip(imageFolderTip); + imageFolderLabel->setToolTip(imageFolderTip); + QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), m_imageFolderEdit); + m_imageFolderEdit->setValidator(validator); QFormLayout *topLayout = new QFormLayout(); - topLayout->addRow(nameLabel, nameEdit); - topLayout->addRow(pathLabel, pathEdit); + topLayout->addRow(nameLabel, m_nameEdit); + topLayout->addRow(pathLabel, m_pathEdit); + topLayout->addRow(imageFolderLabel, m_imageFolderEdit); // Ok is the default button. m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); @@ -39,26 +57,38 @@ void VNotebookInfoDialog::setupUI() connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject); QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok); - pathEdit->setMinimumWidth(okBtn->sizeHint().width() * 3); + m_pathEdit->setMinimumWidth(okBtn->sizeHint().width() * 3); QVBoxLayout *mainLayout = new QVBoxLayout(); - if (infoLabel) { - mainLayout->addWidget(infoLabel); + if (m_infoLabel) { + mainLayout->addWidget(m_infoLabel); } mainLayout->addLayout(topLayout); mainLayout->addWidget(m_btnBox); setLayout(mainLayout); mainLayout->setSizeConstraint(QLayout::SetFixedSize); - setWindowTitle(title); + setWindowTitle(p_title); } void VNotebookInfoDialog::enableOkButton() { QPushButton *okBtn = m_btnBox->button(QDialogButtonBox::Ok); - okBtn->setEnabled(!nameEdit->text().isEmpty()); + okBtn->setEnabled(!m_nameEdit->text().isEmpty()); } -QString VNotebookInfoDialog::getNameInput() const +QString VNotebookInfoDialog::getName() const { - return nameEdit->text(); + return m_nameEdit->text(); } + +QString VNotebookInfoDialog::getImageFolder() const +{ + return m_imageFolderEdit->text(); +} + +void VNotebookInfoDialog::showEvent(QShowEvent *p_event) +{ + m_nameEdit->setFocus(); + QDialog::showEvent(p_event); +} + diff --git a/src/dialog/vnotebookinfodialog.h b/src/dialog/vnotebookinfodialog.h index d882a058..333ea4ff 100644 --- a/src/dialog/vnotebookinfodialog.h +++ b/src/dialog/vnotebookinfodialog.h @@ -7,31 +7,34 @@ class QLabel; class QLineEdit; class QDialogButtonBox; class QString; +class VNotebook; class VNotebookInfoDialog : public QDialog { Q_OBJECT public: - VNotebookInfoDialog(const QString &title, const QString &info, const QString &defaultName, - const QString &defaultPath, QWidget *parent = 0); - QString getNameInput() const; + VNotebookInfoDialog(const QString &p_title, const QString &p_info, + const VNotebook *p_notebook, QWidget *p_parent = 0); + + QString getName() const; + QString getImageFolder() const; private slots: void enableOkButton(); +protected: + void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE; + private: - void setupUI(); + void setupUI(const QString &p_title, const QString &p_info); - QLabel *infoLabel; - QLabel *nameLabel; - QLineEdit *nameEdit; - QLineEdit *pathEdit; + const VNotebook *m_notebook; + + QLabel *m_infoLabel; + QLineEdit *m_nameEdit; + QLineEdit *m_pathEdit; + QLineEdit *m_imageFolderEdit; QDialogButtonBox *m_btnBox; - - QString title; - QString info; - QString defaultName; - QString defaultPath; }; #endif // VNOTEBOOKINFODIALOG_H diff --git a/src/dialog/vsettingsdialog.cpp b/src/dialog/vsettingsdialog.cpp index d641757f..42e03030 100644 --- a/src/dialog/vsettingsdialog.cpp +++ b/src/dialog/vsettingsdialog.cpp @@ -1,5 +1,6 @@ #include "vsettingsdialog.h" #include +#include #include "vconfigmanager.h" #include "utils/vutils.h" #include "vconstants.h" @@ -12,6 +13,7 @@ VSettingsDialog::VSettingsDialog(QWidget *p_parent) m_tabs = new QTabWidget; m_tabs->addTab(new VGeneralTab(), tr("General")); m_tabs->addTab(new VReadEditTab(), tr("Read/Edit")); + m_tabs->addTab(new VNoteManagementTab(), tr("Note Management")); m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(m_btnBox, &QDialogButtonBox::accepted, this, &VSettingsDialog::saveConfiguration); @@ -47,6 +49,15 @@ void VSettingsDialog::loadConfiguration() } } + // Note Management Tab. + { + VNoteManagementTab *noteManagementTab = dynamic_cast(m_tabs->widget(2)); + Q_ASSERT(noteManagementTab); + if (!noteManagementTab->loadConfiguration()) { + goto err; + } + } + return; err: VUtils::showMessage(QMessageBox::Warning, tr("Warning"), @@ -75,6 +86,15 @@ void VSettingsDialog::saveConfiguration() } } + // Note Management Tab. + { + VNoteManagementTab *noteManagementTab = dynamic_cast(m_tabs->widget(2)); + Q_ASSERT(noteManagementTab); + if (!noteManagementTab->saveConfiguration()) { + goto err; + } + } + accept(); return; err: @@ -231,9 +251,81 @@ bool VReadEditTab::saveWebZoomFactor() void VReadEditTab::customWebZoomChanged(int p_state) { - if (p_state == Qt::Unchecked) { - m_webZoomFactorSpin->setEnabled(false); + m_webZoomFactorSpin->setEnabled(p_state == Qt::Checked); +} + +VNoteManagementTab::VNoteManagementTab(QWidget *p_parent) + : QWidget(p_parent) +{ + // Image folder. + m_customImageFolder = new QCheckBox(tr("Custom image folder"), this); + m_customImageFolder->setToolTip(tr("Set the global name of the image folder to store images " + "of notes (restart VNote to make it work)")); + connect(m_customImageFolder, &QCheckBox::stateChanged, + this, &VNoteManagementTab::customImageFolderChanged); + + m_imageFolderEdit = new QLineEdit(this); + m_imageFolderEdit->setPlaceholderText(tr("Name of the image folder")); + QValidator *validator = new QRegExpValidator(QRegExp(VUtils::c_fileNameRegExp), this); + m_imageFolderEdit->setValidator(validator); + + QHBoxLayout *imageFolderLayout = new QHBoxLayout(); + imageFolderLayout->addWidget(m_customImageFolder); + imageFolderLayout->addWidget(m_imageFolderEdit); + + QFormLayout *mainLayout = new QFormLayout(); + mainLayout->addRow(imageFolderLayout); + + setLayout(mainLayout); +} + +bool VNoteManagementTab::loadConfiguration() +{ + if (!loadImageFolder()) { + return false; + } + + return true; +} + +bool VNoteManagementTab::saveConfiguration() +{ + if (!saveImageFolder()) { + return false; + } + + return true; +} + +bool VNoteManagementTab::loadImageFolder() +{ + bool isCustom = vconfig.isCustomImageFolder(); + + m_customImageFolder->setChecked(isCustom); + m_imageFolderEdit->setText(vconfig.getImageFolder()); + m_imageFolderEdit->setEnabled(isCustom); + + return true; +} + +bool VNoteManagementTab::saveImageFolder() +{ + if (m_customImageFolder->isChecked()) { + vconfig.setImageFolder(m_imageFolderEdit->text()); } else { - m_webZoomFactorSpin->setEnabled(true); + vconfig.setImageFolder(""); + } + + return true; +} + +void VNoteManagementTab::customImageFolderChanged(int p_state) +{ + if (p_state == Qt::Checked) { + m_imageFolderEdit->setEnabled(true); + m_imageFolderEdit->selectAll(); + m_imageFolderEdit->setFocus(); + } else { + m_imageFolderEdit->setEnabled(false); } } diff --git a/src/dialog/vsettingsdialog.h b/src/dialog/vsettingsdialog.h index 3a11a1f2..db2d0fa1 100644 --- a/src/dialog/vsettingsdialog.h +++ b/src/dialog/vsettingsdialog.h @@ -11,6 +11,7 @@ class QComboBox; class QGroupBox; class QDoubleSpinBox; class QCheckBox; +class QLineEdit; class VGeneralTab : public QWidget { @@ -53,6 +54,26 @@ private: bool saveWebZoomFactor(); }; +class VNoteManagementTab : public QWidget +{ + Q_OBJECT +public: + explicit VNoteManagementTab(QWidget *p_parent = 0); + bool loadConfiguration(); + bool saveConfiguration(); + + // Image folder. + QCheckBox *m_customImageFolder; + QLineEdit *m_imageFolderEdit; + +private slots: + void customImageFolderChanged(int p_state); + +private: + bool loadImageFolder(); + bool saveImageFolder(); +}; + class VSettingsDialog : public QDialog { Q_OBJECT diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini index 0468ab40..9b4c7591 100644 --- a/src/resources/vnote.ini +++ b/src/resources/vnote.ini @@ -39,6 +39,9 @@ enable_image_constraint=true ; Center image and add the alt text as caption enable_image_caption=false +; Image folder name for the notes +image_folder=_v_images + [session] tools_dock_checked=true diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index 64a5475c..b19a0fc6 100644 --- a/src/utils/vutils.cpp +++ b/src/utils/vutils.cpp @@ -27,6 +27,8 @@ const QVector> VUtils::c_availableLanguages = {QPair\\|]*"); + VUtils::VUtils() { } diff --git a/src/utils/vutils.h b/src/utils/vutils.h index feafe45f..fb36a5e8 100644 --- a/src/utils/vutils.h +++ b/src/utils/vutils.h @@ -103,6 +103,10 @@ public: // 4. Unused; static const QString c_imageLinkRegExp; + // Regular expression for file/directory name. + // Forbidden char: \/:*?"<>| + static const QString c_fileNameRegExp; + private: // static const QVector> c_availableLanguages; diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp index e85639ab..5dbcbbfa 100644 --- a/src/vconfigmanager.cpp +++ b/src/vconfigmanager.cpp @@ -140,6 +140,9 @@ void VConfigManager::initialize() m_enableImageCaption = getConfigFromSettings("global", "enable_image_caption").toBool(); + + m_imageFolder = getConfigFromSettings("global", + "image_folder").toString(); } void VConfigManager::readPredefinedColorsFromSettings() @@ -167,6 +170,7 @@ void VConfigManager::readNotebookFromSettings(QVector &p_notebooks, QString name = userSettings->value("name").toString(); QString path = userSettings->value("path").toString(); VNotebook *notebook = new VNotebook(name, path, parent); + notebook->readConfig(); p_notebooks.append(notebook); } userSettings->endArray(); @@ -193,7 +197,7 @@ void VConfigManager::writeNotebookToSettings(const QVector &p_noteb << "notebook items in [notebooks] section"; } -QVariant VConfigManager::getConfigFromSettings(const QString §ion, const QString &key) +QVariant VConfigManager::getConfigFromSettings(const QString §ion, const QString &key) const { QString fullKey = section + "/" + key; // First, look up the user-scoped config file @@ -204,9 +208,7 @@ QVariant VConfigManager::getConfigFromSettings(const QString §ion, const QSt } // Second, look up the default config file - value = defaultSettings->value(fullKey); - qDebug() << "default config:" << fullKey << value.toString(); - return value; + return getDefaultConfig(section, key); } void VConfigManager::setConfigToSettings(const QString §ion, const QString &key, const QVariant &value) @@ -217,6 +219,24 @@ void VConfigManager::setConfigToSettings(const QString §ion, const QString & qDebug() << "set user config:" << fullKey << value.toString(); } +QVariant VConfigManager::getDefaultConfig(const QString &p_section, const QString &p_key) const +{ + QString fullKey = p_section + "/" + p_key; + + QVariant value = defaultSettings->value(fullKey); + qDebug() << "default config:" << fullKey << value.toString(); + + return value; +} + +QVariant VConfigManager::resetDefaultConfig(const QString &p_section, const QString &p_key) +{ + QVariant defaultValue = getDefaultConfig(p_section, p_key); + setConfigToSettings(p_section, p_key, defaultValue); + + return defaultValue; +} + QString VConfigManager::fetchDirConfigFilePath(const QString &p_path) { QDir dir(p_path); @@ -230,8 +250,9 @@ QString VConfigManager::fetchDirConfigFilePath(const QString &p_path) qDebug() << "rename old directory config file:" << fileName; } - qDebug() << "use directory config file:" << fileName; - return dir.filePath(fileName); + QString filePath = QDir::cleanPath(dir.filePath(fileName)); + qDebug() << "use directory config file:" << filePath; + return filePath; } QJsonObject VConfigManager::readDirectoryConfig(const QString &path) @@ -251,9 +272,7 @@ QJsonObject VConfigManager::readDirectoryConfig(const QString &path) bool VConfigManager::directoryConfigExist(const QString &path) { - QString configFile = fetchDirConfigFilePath(path); - QFile config(configFile); - return config.exists(); + return QFileInfo::exists(fetchDirConfigFilePath(path)); } bool VConfigManager::writeDirectoryConfig(const QString &path, const QJsonObject &configJson) diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index f2e7c727..2890a3c4 100644 --- a/src/vconfigmanager.h +++ b/src/vconfigmanager.h @@ -23,7 +23,7 @@ enum MarkdownConverterType struct VColor { QString name; - QString rgb; // 'FFFFFF', ithout '#' + QString rgb; // 'FFFFFF', without '#' }; class VConfigManager @@ -33,9 +33,10 @@ public: ~VConfigManager(); void initialize(); - // Static helper functions - // Read config from the directory config json file into a QJsonObject + // Read config from the directory config json file into a QJsonObject. + // @path is the directory containing the config json file. static QJsonObject readDirectoryConfig(const QString &path); + static bool writeDirectoryConfig(const QString &path, const QJsonObject &configJson); static bool directoryConfigExist(const QString &path); static bool deleteDirectoryConfig(const QString &path); @@ -172,6 +173,13 @@ public: inline bool getEnableImageCaption() const; inline void setEnableImageCaption(bool p_enabled); + inline const QString &getImageFolder() const; + + // Empty string to reset the default folder. + inline void setImageFolder(const QString &p_folder); + + inline bool isCustomImageFolder() const; + // Get the folder the ini file exists. QString getConfigFolder() const; @@ -185,14 +193,23 @@ public: QVector getEditorStyles() const; private: - QVariant getConfigFromSettings(const QString §ion, const QString &key); + QVariant getConfigFromSettings(const QString §ion, const QString &key) const; void setConfigToSettings(const QString §ion, const QString &key, const QVariant &value); + + // Get default config from vnote.ini. + QVariant getDefaultConfig(const QString &p_section, const QString &p_key) const; + + // Reset user config to default config and return the default config value. + QVariant resetDefaultConfig(const QString &p_section, const QString &p_key); + void readNotebookFromSettings(QVector &p_notebooks, QObject *parent); void writeNotebookToSettings(const QVector &p_notebooks); void readPredefinedColorsFromSettings(); + // 1. Update styles common in HTML and Markdown; // 2. Update styles for Markdown. void updateEditStyle(); + void updateMarkdownEditStyle(); // Migrate ini file from tamlok/vnote.ini to vnote/vnote.ini. @@ -291,6 +308,10 @@ private: // Center image and add the alt text as caption. bool m_enableImageCaption; + // Global default folder name to store images of all the notes. + // Each notebook can specify its custom folder. + QString m_imageFolder; + // The name of the config file in each directory, obsolete. // Use c_dirConfigFile instead. static const QString c_obsoleteDirConfigFile; @@ -779,4 +800,30 @@ inline void VConfigManager::setEnableImageCaption(bool p_enabled) m_enableImageCaption); } +inline const QString &VConfigManager::getImageFolder() const +{ + return m_imageFolder; +} + +inline void VConfigManager::setImageFolder(const QString &p_folder) +{ + if (p_folder.isEmpty()) { + // Reset the default folder. + m_imageFolder = resetDefaultConfig("global", "image_folder").toString(); + return; + } + + if (m_imageFolder == p_folder) { + return; + } + + m_imageFolder = p_folder; + setConfigToSettings("global", "image_folder", m_imageFolder); +} + +inline bool VConfigManager::isCustomImageFolder() const +{ + return m_imageFolder != getDefaultConfig("global", "image_folder").toString(); +} + #endif // VCONFIGMANAGER_H diff --git a/src/vconstants.h b/src/vconstants.h index 046afec6..cb33ab91 100644 --- a/src/vconstants.h +++ b/src/vconstants.h @@ -18,4 +18,13 @@ static const int c_tabSequenceBase = 1; // HTML and JS. static const QString c_htmlJSHolder = "JS_PLACE_HOLDER"; static const QString c_htmlExtraHolder = ""; + +// Directory Config file items. +namespace DirConfig +{ + static const QString c_version = "version"; + static const QString c_subDirectories = "sub_directories"; + static const QString c_files = "files"; + static const QString c_imageFolder = "image_folder"; +} #endif diff --git a/src/vdirectory.cpp b/src/vdirectory.cpp index 0d9a06b4..63ff5915 100644 --- a/src/vdirectory.cpp +++ b/src/vdirectory.cpp @@ -105,15 +105,36 @@ QString VDirectory::retriveRelativePath(const VDirectory *p_dir) const } } -QJsonObject VDirectory::createDirectoryJson() +QJsonObject VDirectory::toConfigJson() const { QJsonObject dirJson; - dirJson["version"] = "1"; - dirJson["sub_directories"] = QJsonArray(); - dirJson["files"] = QJsonArray(); + dirJson[DirConfig::c_version] = "1"; + + QJsonArray subDirs; + for (int i = 0; i < m_subDirs.size(); ++i) { + subDirs.append(m_subDirs[i]->getName()); + } + dirJson[DirConfig::c_subDirectories] = subDirs; + + QJsonArray files; + for (int i = 0; i < m_files.size(); ++i) { + files.append(m_files[i]->getName()); + } + dirJson[DirConfig::c_files] = files; + return dirJson; } +bool VDirectory::readConfig() +{ + return true; +} + +bool VDirectory::writeToConfig() const +{ + return VConfigManager::writeDirectoryConfig(retrivePath(), toConfigJson()); +} + VDirectory *VDirectory::createSubDirectory(const QString &p_name) { Q_ASSERT(!p_name.isEmpty()); @@ -128,19 +149,21 @@ VDirectory *VDirectory::createSubDirectory(const QString &p_name) return NULL; } - QJsonObject subJson = createDirectoryJson(); - if (!VConfigManager::writeDirectoryConfig(QDir::cleanPath(QDir(path).filePath(p_name)), subJson)) { + VDirectory *ret = new VDirectory(m_notebook, p_name, this); + if (!VConfigManager::writeDirectoryConfig(QDir::cleanPath(QDir(path).filePath(p_name)), + ret->toConfigJson())) { dir.rmdir(p_name); + delete ret; return NULL; } if (!createSubDirectoryInConfig(p_name)) { VConfigManager::deleteDirectoryConfig(QDir(path).filePath(p_name)); dir.rmdir(p_name); + delete ret; return NULL; } - VDirectory *ret = new VDirectory(m_notebook, p_name, this); m_subDirs.append(ret); return ret; } diff --git a/src/vdirectory.h b/src/vdirectory.h index cc102960..89908e3b 100644 --- a/src/vdirectory.h +++ b/src/vdirectory.h @@ -65,7 +65,15 @@ public: void reorderFiles(int p_first, int p_last, int p_destStart); bool reorderFilesInConfig(int p_first, int p_last, int p_destStart); - static QJsonObject createDirectoryJson(); + // Serialize current instance to json. + QJsonObject toConfigJson() const; + + // Read configurations (excluding "sub_directories" and "files" section) + // from config file. + bool readConfig(); + + // Write current instance to config file. + bool writeToConfig() const; private: // Get the path of @p_dir recursively diff --git a/src/vdirectorytree.cpp b/src/vdirectorytree.cpp index 782ce222..765c14f0 100644 --- a/src/vdirectorytree.cpp +++ b/src/vdirectorytree.cpp @@ -142,7 +142,6 @@ void VDirectoryTree::updateDirectoryTree() bool VDirectoryTree::restoreCurrentItem() { - qDebug() << m_notebook << m_notebookCurrentDirMap; auto it = m_notebookCurrentDirMap.find(m_notebook); if (it != m_notebookCurrentDirMap.end()) { bool rootDirectory; diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 65599071..dd809aef 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -455,8 +455,7 @@ void VMainWindow::initFileMenu() fileMenu->addSeparator(); // Export as PDF. - m_exportAsPDFAct = new QAction(QIcon(":/resources/icons/export_pdf.svg"), - tr("Export As &PDF"), this); + m_exportAsPDFAct = new QAction(tr("Export As &PDF"), this); m_exportAsPDFAct->setToolTip(tr("Export current note as PDF file")); connect(m_exportAsPDFAct, &QAction::triggered, this, &VMainWindow::exportAsPDF); diff --git a/src/vnotebook.cpp b/src/vnotebook.cpp index e16e7705..7c324f0c 100644 --- a/src/vnotebook.cpp +++ b/src/vnotebook.cpp @@ -6,10 +6,10 @@ #include "vconfigmanager.h" #include "vfile.h" -const QString VNotebook::c_defaultImageFolder = "_v_images"; +extern VConfigManager vconfig; VNotebook::VNotebook(const QString &name, const QString &path, QObject *parent) - : QObject(parent), m_name(name), m_imageFolder(c_defaultImageFolder) + : QObject(parent), m_name(name) { m_path = QDir::cleanPath(path); m_rootDir = new VDirectory(this, VUtils::directoryNameFromPath(path)); @@ -20,6 +20,54 @@ VNotebook::~VNotebook() delete m_rootDir; } +bool VNotebook::readConfig() +{ + QJsonObject configJson = VConfigManager::readDirectoryConfig(m_path); + if (configJson.isEmpty()) { + qWarning() << "fail to read notebook configuration" << m_path; + return false; + } + + // [image_folder] section. + auto it = configJson.find(DirConfig::c_imageFolder); + if (it != configJson.end()) { + m_imageFolder = it.value().toString(); + } + + return true; +} + +QJsonObject VNotebook::toConfigJson() const +{ + QJsonObject json = m_rootDir->toConfigJson(); + + // [image_folder] section. + json[DirConfig::c_imageFolder] = m_imageFolder; + + return json; +} + +bool VNotebook::writeToConfig() const +{ + return VConfigManager::writeDirectoryConfig(m_path, toConfigJson()); +} + +bool VNotebook::writeConfig() const +{ + QJsonObject json = toConfigJson(); + + QJsonObject configJson = VConfigManager::readDirectoryConfig(m_path); + if (configJson.isEmpty()) { + qWarning() << "fail to read notebook configuration" << m_path; + return false; + } + + json[DirConfig::c_subDirectories] = configJson[DirConfig::c_subDirectories]; + json[DirConfig::c_files] = configJson[DirConfig::c_files]; + + return VConfigManager::writeDirectoryConfig(m_path, json); +} + QString VNotebook::getName() const { return m_name; @@ -41,25 +89,24 @@ bool VNotebook::open() } VNotebook *VNotebook::createNotebook(const QString &p_name, const QString &p_path, - bool p_import, QObject *p_parent) + bool p_import, const QString &p_imageFolder, + QObject *p_parent) { VNotebook *nb = new VNotebook(p_name, p_path, p_parent); - if (!nb) { - return nb; - } + nb->setImageFolder(p_imageFolder); // Check if there alread exists a config file. if (p_import && VConfigManager::directoryConfigExist(p_path)) { qDebug() << "import existing notebook"; + nb->readConfig(); return nb; } - // Create directory config in @p_path - QJsonObject configJson = VDirectory::createDirectoryJson(); - if (!VConfigManager::writeDirectoryConfig(p_path, configJson)) { + if (!nb->writeToConfig()) { delete nb; return NULL; } + return nb; } @@ -112,6 +159,7 @@ void VNotebook::rename(const QString &p_name) if (p_name == m_name || p_name.isEmpty()) { return; } + m_name = p_name; } @@ -121,6 +169,20 @@ bool VNotebook::containsFile(const VFile *p_file) const } const QString &VNotebook::getImageFolder() const +{ + if (m_imageFolder.isEmpty()) { + return vconfig.getImageFolder(); + } else { + return m_imageFolder; + } +} + +void VNotebook::setImageFolder(const QString &p_imageFolder) +{ + m_imageFolder = p_imageFolder; +} + +const QString &VNotebook::getImageFolderConfig() const { return m_imageFolder; } diff --git a/src/vnotebook.h b/src/vnotebook.h index 0115d03e..5849b46a 100644 --- a/src/vnotebook.h +++ b/src/vnotebook.h @@ -16,9 +16,11 @@ public: // Open the root directory to load contents bool open(); + // Close all the directory and files of this notebook. // Please make sure all files belonging to this notebook have been closed in the tab. void close(); + bool containsFile(const VFile *p_file) const; QString getName() const; @@ -26,26 +28,47 @@ public: inline VDirectory *getRootDir(); void rename(const QString &p_name); - static VNotebook *createNotebook(const QString &p_name, const QString &p_path, bool p_import, + static VNotebook *createNotebook(const QString &p_name, const QString &p_path, + bool p_import, const QString &p_imageFolder, QObject *p_parent = 0); + static bool deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles); + // Get the image folder for this notebook to use (not exactly the same as + // m_imageFolder if it is empty). const QString &getImageFolder() const; + // Return m_imageFolder. + const QString &getImageFolderConfig() const; + + void setImageFolder(const QString &p_imageFolder); + + // Read configurations (excluding "sub_directories" and "files" section) + // from root directory config file. + bool readConfig(); + + // Write configurations (excluding "sub_directories" and "files" section) + // to root directory config file. + bool writeConfig() const; + signals: void contentChanged(); private: + // Serialize current instance to json. + QJsonObject toConfigJson() const; + + // Write current instance to config file. + bool writeToConfig() const; + QString m_name; QString m_path; // Folder name to store images. - // VNote will store images in this folder within the same directory of the note. + // If not empty, VNote will store images in this folder within the same directory of the note. + // Otherwise, VNote will use the global configured folder. QString m_imageFolder; - // Default folder name to store images of all the notes within this notebook. - static const QString c_defaultImageFolder; - // Parent is NULL for root directory VDirectory *m_rootDir; }; diff --git a/src/vnotebookselector.cpp b/src/vnotebookselector.cpp index 7db6658c..0a90bbe6 100644 --- a/src/vnotebookselector.cpp +++ b/src/vnotebookselector.cpp @@ -163,7 +163,7 @@ void VNotebookSelector::update() bool VNotebookSelector::newNotebook() { QString info(tr("Please type the name of the notebook and " - "choose an existing directory as Root Folder of the notebook.")); + "choose an existing folder as Root Folder of the notebook.")); info += "\n"; info += tr("The root folder should be used EXCLUSIVELY by VNote and " "it is recommended to be EMPTY."); @@ -171,23 +171,25 @@ bool VNotebookSelector::newNotebook() QString defaultName("new_notebook"); QString defaultPath; + VNewNotebookDialog dialog(tr("Add Notebook"), info, defaultName, + defaultPath, this); do { - VNewNotebookDialog dialog(tr("Add Notebook"), info, defaultName, - defaultPath, this); if (dialog.exec() == QDialog::Accepted) { QString name = dialog.getNameInput(); QString path = dialog.getPathInput(); if (findNotebook(name)) { - info = tr("Name already exists. Please choose another name."); - defaultName = name; - defaultPath = path; + VUtils::showMessage(QMessageBox::Warning, tr("Warning"), + tr("Name already exists. Please choose another name."), "", + QMessageBox::Ok, QMessageBox::Ok, this); continue; } - createNotebook(name, path, dialog.getImportCheck()); + + createNotebook(name, path, dialog.getImportCheck(), dialog.getImageFolder()); return true; } break; } while (true); + return false; } @@ -201,9 +203,13 @@ VNotebook *VNotebookSelector::findNotebook(const QString &p_name) return NULL; } -void VNotebookSelector::createNotebook(const QString &p_name, const QString &p_path, bool p_import) +void VNotebookSelector::createNotebook(const QString &p_name, + const QString &p_path, + bool p_import, + const QString &p_imageFolder) { - VNotebook *nb = VNotebook::createNotebook(p_name, p_path, p_import, m_vnote); + VNotebook *nb = VNotebook::createNotebook(p_name, p_path, p_import, + p_imageFolder, m_vnote); m_notebooks.append(nb); vconfig.setNotebooks(m_notebooks); @@ -251,7 +257,7 @@ void VNotebookSelector::deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles int cho = VUtils::showMessage(QMessageBox::Information, tr("Delete Notebook Folder From Disk"), tr("Fail to delete the root folder of notebook " "%2 from disk. You may open " - "the directory and check it manually.") + "the folder and check it manually.") .arg(vconfig.c_dataTextStyle).arg(name), "", QMessageBox::Open | QMessageBox::Ok, QMessageBox::Ok, this); @@ -285,27 +291,37 @@ void VNotebookSelector::editNotebookInfo() int index = indexOfListItem(item); VNotebook *notebook = getNotebookFromComboIndex(index); - QString info; QString curName = notebook->getName(); - QString defaultPath = notebook->getPath(); - QString defaultName(curName); + + VNotebookInfoDialog dialog(tr("Notebook Information"), "", notebook, this); do { - VNotebookInfoDialog dialog(tr("Notebook Information"), info, defaultName, - defaultPath, this); if (dialog.exec() == QDialog::Accepted) { - QString name = dialog.getNameInput(); - if (name == curName) { - return; + bool updated = false; + QString name = dialog.getName(); + if (name != curName) { + if (findNotebook(name)) { + VUtils::showMessage(QMessageBox::Warning, tr("Warning"), + tr("Name already exists. Please choose another name."), "", + QMessageBox::Ok, QMessageBox::Ok, this); + continue; + } + + updated = true; + notebook->rename(name); + updateComboBoxItem(index, name); + vconfig.setNotebooks(m_notebooks); } - if (findNotebook(name)) { - info = "Name already exists. Please choose another name."; - defaultName = name; - continue; + + QString imageFolder = dialog.getImageFolder(); + if (imageFolder != notebook->getImageFolderConfig()) { + updated = true; + notebook->setImageFolder(imageFolder); + notebook->writeConfig(); + } + + if (updated) { + emit notebookUpdated(notebook); } - notebook->rename(name); - updateComboBoxItem(index, name); - vconfig.setNotebooks(m_notebooks); - emit notebookUpdated(notebook); } break; } while (true); diff --git a/src/vnotebookselector.h b/src/vnotebookselector.h index 3128e890..d0c8dd3c 100644 --- a/src/vnotebookselector.h +++ b/src/vnotebookselector.h @@ -55,8 +55,12 @@ private: VNotebook *findNotebook(const QString &p_name); // Return the index of @p_notebook in m_noteboks. int indexOfNotebook(const VNotebook *p_notebook); - // if @p_import is true, we will use the existing config file. - void createNotebook(const QString &p_name, const QString &p_path, bool p_import); + + // If @p_import is true, we will use the existing config file. + // If @p_imageFolder is empty, we will use the global one. + void createNotebook(const QString &p_name, const QString &p_path, + bool p_import, const QString &p_imageFolder); + void deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles); void addNotebookItem(const QString &p_name); // @p_index is the index of m_notebooks, NOT of QComboBox.