diff --git a/src/core/buffer/filetypehelper.cpp b/src/core/buffer/filetypehelper.cpp index 405c281b..7be0e748 100644 --- a/src/core/buffer/filetypehelper.cpp +++ b/src/core/buffer/filetypehelper.cpp @@ -5,6 +5,8 @@ #include #include "buffer.h" +#include +#include using namespace vnotex; @@ -20,24 +22,38 @@ bool FileType::isMarkdown() const FileTypeHelper::FileTypeHelper() { - setupBuiltInTypes(); + reload(); +} - // TODO: read configuration file. +void FileTypeHelper::reload() +{ + setupBuiltInTypes(); setupSuffixTypeMap(); } void FileTypeHelper::setupBuiltInTypes() { + m_fileTypes.clear(); + + const auto &coreConfig = ConfigMgr::getInst().getCoreConfig(); + { FileType type; type.m_type = FileType::Markdown; - type.m_displayName = Buffer::tr("Markdown"); type.m_typeName = QStringLiteral("Markdown"); - type.m_suffixes << QStringLiteral("md") - << QStringLiteral("mkd") - << QStringLiteral("rmd") - << QStringLiteral("markdown"); + type.m_displayName = Buffer::tr("Markdown"); + + auto suffixes = coreConfig.findFileTypeSuffix(type.m_typeName); + if (suffixes && !suffixes->isEmpty()) { + type.m_suffixes = *suffixes; + } else { + type.m_suffixes << QStringLiteral("md") + << QStringLiteral("mkd") + << QStringLiteral("rmd") + << QStringLiteral("markdown"); + } + m_fileTypes.push_back(type); } @@ -46,7 +62,14 @@ void FileTypeHelper::setupBuiltInTypes() type.m_type = FileType::Text; type.m_typeName = QStringLiteral("Text"); type.m_displayName = Buffer::tr("Text"); - type.m_suffixes << QStringLiteral("txt") << QStringLiteral("text") << QStringLiteral("log"); + + auto suffixes = coreConfig.findFileTypeSuffix(type.m_typeName); + if (suffixes && !suffixes->isEmpty()) { + type.m_suffixes = *suffixes; + } else { + type.m_suffixes << QStringLiteral("txt") << QStringLiteral("text") << QStringLiteral("log"); + } + m_fileTypes.push_back(type); } @@ -88,10 +111,10 @@ const FileType &FileTypeHelper::getFileTypeBySuffix(const QString &p_suffix) con } } -#define ADD(x, y) m_suffixTypeMap.insert((x), (y)) - void FileTypeHelper::setupSuffixTypeMap() { + m_suffixTypeMap.clear(); + for (int i = 0; i < m_fileTypes.size(); ++i) { for (const auto &suffix : m_fileTypes[i].m_suffixes) { if (m_suffixTypeMap.contains(suffix)) { @@ -113,7 +136,7 @@ const FileType &FileTypeHelper::getFileType(int p_type) const return m_fileTypes[p_type]; } -const FileTypeHelper &FileTypeHelper::getInst() +FileTypeHelper &FileTypeHelper::getInst() { static FileTypeHelper helper; return helper; diff --git a/src/core/buffer/filetypehelper.h b/src/core/buffer/filetypehelper.h index 0a62342a..f45b0ab0 100644 --- a/src/core/buffer/filetypehelper.h +++ b/src/core/buffer/filetypehelper.h @@ -32,6 +32,7 @@ namespace vnotex bool isMarkdown() const; }; + // Only handle built-in editors. class FileTypeHelper { public: @@ -47,7 +48,9 @@ namespace vnotex bool checkFileType(const QString &p_filePath, int p_type) const; - static const FileTypeHelper &getInst(); + void reload(); + + static FileTypeHelper &getInst(); private: FileTypeHelper(); diff --git a/src/core/buffer/ibufferfactory.h b/src/core/buffer/ibufferfactory.h index 0b250ff9..b6cbdda7 100644 --- a/src/core/buffer/ibufferfactory.h +++ b/src/core/buffer/ibufferfactory.h @@ -18,6 +18,8 @@ namespace vnotex virtual Buffer *createBuffer(const BufferParameters &p_parameters, QObject *p_parent) = 0; + + virtual bool isBufferCreatedByFactory(const Buffer *p_buffer) const = 0; }; } // ns vnotex diff --git a/src/core/buffer/markdownbufferfactory.cpp b/src/core/buffer/markdownbufferfactory.cpp index 915f3cf8..1b084c29 100644 --- a/src/core/buffer/markdownbufferfactory.cpp +++ b/src/core/buffer/markdownbufferfactory.cpp @@ -9,3 +9,8 @@ Buffer *MarkdownBufferFactory::createBuffer(const BufferParameters &p_parameters { return new MarkdownBuffer(p_parameters, p_parent); } + +bool MarkdownBufferFactory::isBufferCreatedByFactory(const Buffer *p_buffer) const +{ + return dynamic_cast(p_buffer) != nullptr; +} diff --git a/src/core/buffer/markdownbufferfactory.h b/src/core/buffer/markdownbufferfactory.h index 5fbff6c3..316efa88 100644 --- a/src/core/buffer/markdownbufferfactory.h +++ b/src/core/buffer/markdownbufferfactory.h @@ -11,6 +11,8 @@ namespace vnotex public: Buffer *createBuffer(const BufferParameters &p_parameters, QObject *p_parent) Q_DECL_OVERRIDE; + + bool isBufferCreatedByFactory(const Buffer *p_buffer) const Q_DECL_OVERRIDE; }; } // vnotex diff --git a/src/core/buffer/textbufferfactory.cpp b/src/core/buffer/textbufferfactory.cpp index b288a139..cb5a1c23 100644 --- a/src/core/buffer/textbufferfactory.cpp +++ b/src/core/buffer/textbufferfactory.cpp @@ -9,3 +9,8 @@ Buffer *TextBufferFactory::createBuffer(const BufferParameters &p_parameters, { return new TextBuffer(p_parameters, p_parent); } + +bool TextBufferFactory::isBufferCreatedByFactory(const Buffer *p_buffer) const +{ + return dynamic_cast(p_buffer) != nullptr; +} diff --git a/src/core/buffer/textbufferfactory.h b/src/core/buffer/textbufferfactory.h index 7f03447d..5c53477f 100644 --- a/src/core/buffer/textbufferfactory.h +++ b/src/core/buffer/textbufferfactory.h @@ -11,6 +11,8 @@ namespace vnotex public: Buffer *createBuffer(const BufferParameters &p_parameters, QObject *p_parent) Q_DECL_OVERRIDE; + + bool isBufferCreatedByFactory(const Buffer *p_buffer) const Q_DECL_OVERRIDE; }; } diff --git a/src/core/buffermgr.cpp b/src/core/buffermgr.cpp index 59ae8749..0520f02f 100644 --- a/src/core/buffermgr.cpp +++ b/src/core/buffermgr.cpp @@ -11,14 +11,19 @@ #include #include #include +#include #include "notebookmgr.h" #include "vnotex.h" #include "externalfile.h" +#include "sessionconfig.h" +#include "configmgr.h" #include "fileopenparameters.h" using namespace vnotex; +QMap BufferMgr::s_suffixToFileType; + BufferMgr::BufferMgr(QObject *p_parent) : QObject(p_parent) { @@ -66,12 +71,27 @@ void BufferMgr::open(Node *p_node, const QSharedPointer &p_p return; } + const auto nodePath = p_node->fetchAbsolutePath(); + + auto fileType = p_paras->m_fileType; + if (fileType.isEmpty()) { + // Check if we need to open it with external program by default according to the suffix. + fileType = findFileTypeByFile(nodePath); + if (openWithExternalProgram(nodePath, fileType)) { + return; + } + } + auto buffer = findBuffer(p_node); - if (!buffer) { - auto nodePath = p_node->fetchAbsolutePath(); + if (!buffer || !isSameTypeBuffer(buffer, fileType)) { auto nodeFile = p_node->getContentFile(); Q_ASSERT(nodeFile); - auto fileType = nodeFile->getContentType().m_typeName; + if (fileType.isEmpty()) { + fileType = nodeFile->getContentType().m_typeName; + } else if (fileType != nodeFile->getContentType().m_typeName) { + nodeFile->setContentType(FileTypeHelper::getInst().getFileTypeByName(fileType).m_type); + } + auto factory = m_bufferServer->getItem(fileType); if (!factory) { // No factory to open this file type. @@ -96,6 +116,11 @@ void BufferMgr::open(const QString &p_filePath, const QSharedPointerm_fileType)) { + return; + } + QFileInfo finfo(p_filePath); if (!finfo.exists()) { auto msg = QString("Failed to open file that does not exist (%1)").arg(p_filePath); @@ -123,11 +148,25 @@ void BufferMgr::open(const QString &p_filePath, const QSharedPointerm_fileType; + if (fileType.isEmpty()) { + // Check if we need to open it with external program by default according to the suffix. + fileType = findFileTypeByFile(p_filePath); + if (openWithExternalProgram(p_filePath, fileType)) { + return; + } + } + auto buffer = findBuffer(p_filePath); - if (!buffer) { + if (!buffer || !isSameTypeBuffer(buffer, fileType)) { // Open it as external file. auto externalFile = QSharedPointer::create(p_filePath); - auto fileType = externalFile->getContentType().m_typeName; + if (fileType.isEmpty()) { + fileType = externalFile->getContentType().m_typeName; + } else if (fileType != externalFile->getContentType().m_typeName) { + externalFile->setContentType(FileTypeHelper::getInst().getFileTypeByName(fileType).m_type); + } + auto factory = m_bufferServer->getItem(fileType); if (!factory) { // No factory to open this file type. @@ -188,3 +227,64 @@ void BufferMgr::addBuffer(Buffer *p_buffer) p_buffer->deleteLater(); }); } + +bool BufferMgr::openWithExternalProgram(const QString &p_filePath, const QString &p_name) const +{ + if (p_name.isEmpty()) { + return false; + } + + if (auto pro = ConfigMgr::getInst().getSessionConfig().findExternalProgram(p_name)) { + const auto command = pro->fetchCommand(p_filePath); + if (!command.isEmpty()) { + ProcessUtils::startDetached(command); + } + return true; + } + + return false; +} + +bool BufferMgr::isSameTypeBuffer(const Buffer *p_buffer, const QString &p_typeName) const +{ + if (p_typeName.isEmpty()) { + return true; + } + + auto factory = m_bufferServer->getItem(p_typeName); + Q_ASSERT(factory); + if (factory) { + return factory->isBufferCreatedByFactory(p_buffer); + } + + return true; +} + +void BufferMgr::updateSuffixToFileType(const QVector &p_fileTypeSuffixes) +{ + s_suffixToFileType.clear(); + + for (const auto &fts : p_fileTypeSuffixes) { + for (const auto &suf : fts.m_suffixes) { + auto it = s_suffixToFileType.find(suf); + if (it != s_suffixToFileType.end()) { + qWarning() << "suffix conflicts for file types" << fts.m_name << it.value(); + it.value() = fts.m_name; + } else { + s_suffixToFileType.insert(suf, fts.m_name); + } + } + } +} + +QString BufferMgr::findFileTypeByFile(const QString &p_filePath) +{ + QFileInfo fi(p_filePath); + auto suffix = fi.suffix().toLower(); + auto it = s_suffixToFileType.find(suffix); + if (it != s_suffixToFileType.end()) { + return it.value(); + } else { + return QString(); + } +} diff --git a/src/core/buffermgr.h b/src/core/buffermgr.h index dd2fc312..b4eaf898 100644 --- a/src/core/buffermgr.h +++ b/src/core/buffermgr.h @@ -5,8 +5,10 @@ #include #include #include +#include #include "namebasedserver.h" +#include "coreconfig.h" namespace vnotex { @@ -30,6 +32,8 @@ namespace vnotex void open(const QString &p_filePath, const QSharedPointer &p_paras); + static void updateSuffixToFileType(const QVector &p_fileTypeSuffixes); + signals: void bufferRequested(Buffer *p_buffer, const QSharedPointer &p_paras); @@ -42,10 +46,19 @@ namespace vnotex void addBuffer(Buffer *p_buffer); + bool openWithExternalProgram(const QString &p_filePath, const QString &p_name) const; + + bool isSameTypeBuffer(const Buffer *p_buffer, const QString &p_typeName) const; + + static QString findFileTypeByFile(const QString &p_filePath); + QSharedPointer> m_bufferServer; // Managed by QObject. QVector m_buffers; + + // Mapping from suffix to file type or external program name. + static QMap s_suffixToFileType; }; } // ns vnotex diff --git a/src/core/coreconfig.cpp b/src/core/coreconfig.cpp index 801faf50..db8585c9 100644 --- a/src/core/coreconfig.cpp +++ b/src/core/coreconfig.cpp @@ -3,6 +3,8 @@ #include #include +#include + using namespace vnotex; #define READSTR(key) readString(appObj, userObj, (key)) @@ -10,6 +12,17 @@ using namespace vnotex; #define READBOOL(key) readBool(appObj, userObj, (key)) #define READSTRLIST(key) readStringList(appObj, userObj, (key)) +CoreConfig::FileTypeSuffix::FileTypeSuffix(const QString &p_name, const QStringList &p_suffixes) + : m_name(p_name), + m_suffixes(p_suffixes) +{ +} + +bool CoreConfig::FileTypeSuffix::operator==(const FileTypeSuffix &p_other) const +{ + return m_name == p_other.m_name && m_suffixes == p_other.m_suffixes; +} + QStringList CoreConfig::s_availableLocales; CoreConfig::CoreConfig(ConfigMgr *p_mgr, IConfig *p_topConfig) @@ -73,6 +86,8 @@ void CoreConfig::init(const QJsonObject &p_app, auto lineEnding = READSTR(QStringLiteral("line_ending")); m_lineEnding = stringToLineEndingPolicy(lineEnding); } + + loadFileTypeSuffixes(appObj, userObj); } QJsonObject CoreConfig::toJson() const @@ -89,6 +104,7 @@ QJsonObject CoreConfig::toJson() const obj[QStringLiteral("history_max_count")] = m_historyMaxCount; obj[QStringLiteral("per_notebook_history")] = m_perNotebookHistoryEnabled; obj[QStringLiteral("line_ending")] = lineEndingPolicyToString(m_lineEnding); + obj[QStringLiteral("file_type_suffixes")] = saveFileTypeSuffixes(); return obj; } @@ -241,3 +257,67 @@ void CoreConfig::setLineEndingPolicy(LineEndingPolicy p_ending) { updateConfig(m_lineEnding, p_ending, this); } + +void CoreConfig::loadFileTypeSuffixes(const QJsonObject &p_app, const QJsonObject &p_user) +{ + m_fileTypeSuffixes.clear(); + + QJsonArray arr; + if (p_user.contains(QStringLiteral("file_type_suffixes"))) { + arr = p_user[QStringLiteral("file_type_suffixes")].toArray(); + } else { + arr = p_app[QStringLiteral("file_type_suffixes")].toArray(); + } + + m_fileTypeSuffixes.reserve(arr.size()); + + for (int i = 0; i < arr.size(); ++i) { + const auto obj = arr[i].toObject(); + const auto name = obj[QStringLiteral("name")].toString(); + if (name.isEmpty()) { + continue; + } + const auto suffixes = readStringList(obj, QStringLiteral("suffixes")); + if (suffixes.isEmpty()) { + continue; + } + m_fileTypeSuffixes.push_back(FileTypeSuffix(name, Utils::toLower(suffixes))); + } +} + +QJsonArray CoreConfig::saveFileTypeSuffixes() const +{ + QJsonArray arr; + for (const auto &fts : m_fileTypeSuffixes) { + QJsonObject obj; + obj[QStringLiteral("name")] = fts.m_name; + writeStringList(obj, QStringLiteral("suffixes"), fts.m_suffixes); + arr.push_back(obj); + } + return arr; +} + +const QVector &CoreConfig::getFileTypeSuffixes() const +{ + return m_fileTypeSuffixes; +} + +void CoreConfig::setFileTypeSuffixes(const QVector &p_fileTypeSuffixes) +{ + updateConfig(m_fileTypeSuffixes, p_fileTypeSuffixes, this); +} + +const QStringList *CoreConfig::findFileTypeSuffix(const QString &p_name) const +{ + if (p_name.isEmpty()) { + return nullptr; + } + + for (const auto &fts : m_fileTypeSuffixes) { + if (fts.m_name == p_name) { + return &fts.m_suffixes; + } + } + + return nullptr; +} diff --git a/src/core/coreconfig.h b/src/core/coreconfig.h index c2bb9cb5..fb02854a 100644 --- a/src/core/coreconfig.h +++ b/src/core/coreconfig.h @@ -72,6 +72,19 @@ namespace vnotex }; Q_ENUM(Shortcut) + struct FileTypeSuffix + { + FileTypeSuffix() = default; + + FileTypeSuffix(const QString &p_name, const QStringList &p_suffixes); + + bool operator==(const FileTypeSuffix &p_other) const; + + QString m_name; + + QStringList m_suffixes; + }; + CoreConfig(ConfigMgr *p_mgr, IConfig *p_topConfig); void init(const QJsonObject &p_app, const QJsonObject &p_user) Q_DECL_OVERRIDE; @@ -115,6 +128,11 @@ namespace vnotex LineEndingPolicy getLineEndingPolicy() const; void setLineEndingPolicy(LineEndingPolicy p_ending); + const QVector &getFileTypeSuffixes() const; + void setFileTypeSuffixes(const QVector &p_fileTypeSuffixes); + + const QStringList *findFileTypeSuffix(const QString &p_name) const; + private: friend class MainConfig; @@ -124,6 +142,10 @@ namespace vnotex QJsonObject saveShortcuts() const; + void loadFileTypeSuffixes(const QJsonObject &p_app, const QJsonObject &p_user); + + QJsonArray saveFileTypeSuffixes() const; + // Theme name. QString m_theme; @@ -157,6 +179,8 @@ namespace vnotex LineEndingPolicy m_lineEnding = LineEndingPolicy::LF; + QVector m_fileTypeSuffixes; + static QStringList s_availableLocales; }; } // ns vnotex diff --git a/src/core/file.h b/src/core/file.h index 0cba68c7..f804e556 100644 --- a/src/core/file.h +++ b/src/core/file.h @@ -59,7 +59,6 @@ namespace vnotex const FileType &getContentType() const; - protected: void setContentType(int p_type); private: diff --git a/src/core/fileopenparameters.h b/src/core/fileopenparameters.h index 37b0b9c6..ad473aca 100644 --- a/src/core/fileopenparameters.h +++ b/src/core/fileopenparameters.h @@ -50,6 +50,9 @@ namespace vnotex // Whether should save this file into session. bool m_sessionEnabled = true; + // Whether specify the built-in file type to open as or the external program to open with. + QString m_fileType; + std::function m_hooks[Hook::MaxHook]; }; } diff --git a/src/core/sessionconfig.cpp b/src/core/sessionconfig.cpp index 05cb4d33..a984fce6 100644 --- a/src/core/sessionconfig.cpp +++ b/src/core/sessionconfig.cpp @@ -56,6 +56,13 @@ QJsonObject SessionConfig::ExternalProgram::toJson() const return jobj; } +QString SessionConfig::ExternalProgram::fetchCommand(const QString &p_file) const +{ + auto command(m_command); + command.replace(QStringLiteral("%1"), QString("\"%1\"").arg(p_file)); + return command; +} + SessionConfig::SessionConfig(ConfigMgr *p_mgr) : IConfig(p_mgr, nullptr) { @@ -438,6 +445,16 @@ const QVector &SessionConfig::getExternalProgram return m_externalPrograms; } +const SessionConfig::ExternalProgram *SessionConfig::findExternalProgram(const QString &p_name) const +{ + for (const auto &pro : m_externalPrograms) { + if (pro.m_name == p_name) { + return &pro; + } + } + return nullptr; +} + const QVector &SessionConfig::getHistory() const { return m_history; diff --git a/src/core/sessionconfig.h b/src/core/sessionconfig.h index 3e3bb04d..6e6f8a75 100644 --- a/src/core/sessionconfig.h +++ b/src/core/sessionconfig.h @@ -66,6 +66,8 @@ namespace vnotex QJsonObject toJson() const; + QString fetchCommand(const QString &p_file) const; + QString m_name; // %1: the file paths to open. @@ -134,6 +136,7 @@ namespace vnotex void removeQuickAccessFile(const QString &p_file); const QVector &getExternalPrograms() const; + const ExternalProgram *findExternalProgram(const QString &p_name) const; const QVector &getHistory() const; void addHistory(const HistoryItem &p_item); diff --git a/src/core/vnotex.cpp b/src/core/vnotex.cpp index 99b95fab..fec06acd 100644 --- a/src/core/vnotex.cpp +++ b/src/core/vnotex.cpp @@ -99,6 +99,8 @@ void VNoteX::initNotebookMgr() void VNoteX::initBufferMgr() { + BufferMgr::updateSuffixToFileType(ConfigMgr::getInst().getCoreConfig().getFileTypeSuffixes()); + Q_ASSERT(!m_bufferMgr); m_bufferMgr = new BufferMgr(this); m_bufferMgr->init(); diff --git a/src/data/core/vnotex.json b/src/data/core/vnotex.json index 24d87b1c..7c47211f 100644 --- a/src/data/core/vnotex.json +++ b/src/data/core/vnotex.json @@ -6,7 +6,7 @@ "version" : "3.12.0" }, "core" : { - "theme" : "moonlight", + "theme" : "pure", "locale" : "", "shortcuts" : { "FullScreen" : "F11", @@ -61,6 +61,25 @@ "MoveOneSplitRight" : "Ctrl+G, Shift+L", "OpenLastClosedFile" : "Ctrl+Shift+T" }, + "file_type_suffixes" : [ + { + "name" : "Markdown", + "suffixes" : [ + "md", + "mkd", + "rmd", + "markdown" + ] + }, + { + "name" : "Text", + "suffixes" : [ + "txt", + "text", + "log" + ] + } + ], "shortcut_leader_key" : "Ctrl+G", "toolbar_icon_size" : 18, "docks_tabbar_icon_size" : 24, diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 2ade0c72..e2c91553 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -204,3 +204,12 @@ QColor Utils::toColor(const QString &p_color) return QColor(p_color); } + +QStringList Utils::toLower(const QStringList &p_list) +{ + QStringList lowerList; + for (const auto &ele : p_list) { + lowerList << ele.toLower(); + } + return lowerList; +} diff --git a/src/utils/utils.h b/src/utils/utils.h index 8d847f76..1f6b72dd 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -63,6 +63,8 @@ namespace vnotex static QJsonValue parseAndReadJson(const QJsonObject &p_obj, const QString &p_exp); static QColor toColor(const QString &p_color); + + static QStringList toLower(const QStringList &p_list); }; } // ns vnotex diff --git a/src/utils/widgetutils.cpp b/src/utils/widgetutils.cpp index 9c97d661..db4db7d6 100644 --- a/src/utils/widgetutils.cpp +++ b/src/utils/widgetutils.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -460,3 +461,10 @@ bool WidgetUtils::distributeWidgetsOfSplitter(QSplitter *p_splitter) return false; } + +void WidgetUtils::clearLayout(QFormLayout *p_layout) +{ + for (int i = p_layout->rowCount() - 1; i >= 0; --i) { + p_layout->removeRow(i); + } +} diff --git a/src/utils/widgetutils.h b/src/utils/widgetutils.h index 53db0761..b6050fa9 100644 --- a/src/utils/widgetutils.h +++ b/src/utils/widgetutils.h @@ -22,6 +22,7 @@ class QLayout; class QPushButton; class QSplitter; class QScreen; +class QFormLayout; namespace vnotex { @@ -90,6 +91,8 @@ namespace vnotex static bool distributeWidgetsOfSplitter(QSplitter *p_splitter); + static void clearLayout(QFormLayout *p_layout); + private: static void resizeToHideScrollBar(QScrollArea *p_scroll, bool p_vertical, bool p_horizontal); }; diff --git a/src/widgets/dialogs/settings/fileassociationpage.cpp b/src/widgets/dialogs/settings/fileassociationpage.cpp new file mode 100644 index 00000000..8be1a7d1 --- /dev/null +++ b/src/widgets/dialogs/settings/fileassociationpage.cpp @@ -0,0 +1,132 @@ +#include "fileassociationpage.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace vnotex; + +const char *FileAssociationPage::c_nameProperty = "name"; + +const QChar FileAssociationPage::c_suffixSeparator = QLatin1Char(';'); + +FileAssociationPage::FileAssociationPage(QWidget *p_parent) + : SettingsPage(p_parent) +{ + setupUI(); +} + +void FileAssociationPage::setupUI() +{ + auto mainLayout = new QVBoxLayout(this); + + m_builtInFileTypesBox = new QGroupBox(tr("Built-In File Types"), this); + WidgetsFactory::createFormLayout(m_builtInFileTypesBox); + mainLayout->addWidget(m_builtInFileTypesBox); + + m_externalProgramsBox = new QGroupBox(tr("External Programs"), this); + WidgetsFactory::createFormLayout(m_externalProgramsBox); + mainLayout->addWidget(m_externalProgramsBox); +} + +void FileAssociationPage::loadInternal() +{ + loadBuiltInTypesGroup(m_builtInFileTypesBox); + + loadExternalProgramsGroup(m_externalProgramsBox); +} + +bool FileAssociationPage::saveInternal() +{ + auto &coreConfig = ConfigMgr::getInst().getCoreConfig(); + + QVector fileTypeSuffixes; + + auto lineEdits = m_builtInFileTypesBox->findChildren(QString()); + lineEdits << m_externalProgramsBox->findChildren(QString()); + fileTypeSuffixes.reserve(lineEdits.size()); + for (const auto lineEdit : lineEdits) { + auto name = lineEdit->property(c_nameProperty).toString(); + if (name.isEmpty()) { + continue; + } + auto suffixes = lineEdit->text().split(c_suffixSeparator, Qt::SkipEmptyParts); + if (suffixes.isEmpty()) { + continue; + } + fileTypeSuffixes.push_back(CoreConfig::FileTypeSuffix(name, Utils::toLower(suffixes))); + } + + coreConfig.setFileTypeSuffixes(fileTypeSuffixes); + + FileTypeHelper::getInst().reload(); + + BufferMgr::updateSuffixToFileType(coreConfig.getFileTypeSuffixes()); + + return true; +} + +QString FileAssociationPage::title() const +{ + return tr("File Associations"); +} + +void FileAssociationPage::loadBuiltInTypesGroup(QGroupBox *p_box) +{ + auto layout = static_cast(p_box->layout()); + WidgetUtils::clearLayout(layout); + + const auto &types = FileTypeHelper::getInst().getAllFileTypes(); + + for (const auto &ft : types) { + if (ft.m_type == FileType::Others) { + continue; + } + + auto lineEdit = WidgetsFactory::createLineEdit(p_box); + layout->addRow(ft.m_displayName, lineEdit); + connect(lineEdit, &QLineEdit::textChanged, + this, &FileAssociationPage::pageIsChanged); + + lineEdit->setPlaceholderText(tr("Suffixes separated by ;")); + lineEdit->setToolTip(tr("List of suffixes for this file type")); + lineEdit->setProperty(c_nameProperty, ft.m_typeName); + lineEdit->setText(ft.m_suffixes.join(c_suffixSeparator)); + } +} + +void FileAssociationPage::loadExternalProgramsGroup(QGroupBox *p_box) +{ + auto layout = static_cast(p_box->layout()); + WidgetUtils::clearLayout(layout); + + const auto &coreConfig = ConfigMgr::getInst().getCoreConfig(); + const auto &sessionConfig = ConfigMgr::getInst().getSessionConfig(); + for (const auto &pro : sessionConfig.getExternalPrograms()) { + auto lineEdit = WidgetsFactory::createLineEdit(p_box); + layout->addRow(pro.m_name, lineEdit); + connect(lineEdit, &QLineEdit::textChanged, + this, &FileAssociationPage::pageIsChanged); + + lineEdit->setPlaceholderText(tr("Suffixes separated by ;")); + lineEdit->setToolTip(tr("List of suffixes to open with external program")); + lineEdit->setProperty(c_nameProperty, pro.m_name); + + auto suffixes = coreConfig.findFileTypeSuffix(pro.m_name); + if (suffixes) { + lineEdit->setText(suffixes->join(c_suffixSeparator)); + } + } +} diff --git a/src/widgets/dialogs/settings/fileassociationpage.h b/src/widgets/dialogs/settings/fileassociationpage.h new file mode 100644 index 00000000..87062f85 --- /dev/null +++ b/src/widgets/dialogs/settings/fileassociationpage.h @@ -0,0 +1,40 @@ +#ifndef FILEASSOCIATIONPAGE_H +#define FILEASSOCIATIONPAGE_H + +#include "settingspage.h" + +class QGroupBox; + +namespace vnotex +{ + class FileAssociationPage : public SettingsPage + { + Q_OBJECT + public: + explicit FileAssociationPage(QWidget *p_parent = nullptr); + + QString title() const Q_DECL_OVERRIDE; + + protected: + void loadInternal() Q_DECL_OVERRIDE; + + bool saveInternal() Q_DECL_OVERRIDE; + + private: + void setupUI(); + + void loadBuiltInTypesGroup(QGroupBox *p_box); + + void loadExternalProgramsGroup(QGroupBox *p_box); + + QGroupBox *m_builtInFileTypesBox = nullptr; + + QGroupBox *m_externalProgramsBox = nullptr; + + static const char *c_nameProperty; + + static const QChar c_suffixSeparator; + }; +} + +#endif // FILEASSOCIATIONPAGE_H diff --git a/src/widgets/dialogs/settings/settingsdialog.cpp b/src/widgets/dialogs/settings/settingsdialog.cpp index 2d7cfeef..7d2c20be 100644 --- a/src/widgets/dialogs/settings/settingsdialog.cpp +++ b/src/widgets/dialogs/settings/settingsdialog.cpp @@ -27,6 +27,7 @@ #include "imagehostpage.h" #include "vipage.h" #include "notemanagementpage.h" +#include "fileassociationpage.h" using namespace vnotex; @@ -173,6 +174,12 @@ void SettingsDialog::setupPages() */ } + // File Association. + { + auto page = new FileAssociationPage(this); + addPage(page); + } + setChangesUnsaved(false); m_pageExplorer->setCurrentItem(m_pageExplorer->topLevelItem(0), 0, QItemSelectionModel::ClearAndSelect); m_pageExplorer->expandAll(); diff --git a/src/widgets/notebooknodeexplorer.cpp b/src/widgets/notebooknodeexplorer.cpp index 851735d5..1afee696 100644 --- a/src/widgets/notebooknodeexplorer.cpp +++ b/src/widgets/notebooknodeexplorer.cpp @@ -17,7 +17,6 @@ #include "mainwindow.h" #include #include -#include #include "treewidget.h" #include "listwidget.h" #include "dialogs/notepropertiesdialog.h" @@ -38,6 +37,7 @@ #include #include #include +#include using namespace vnotex; @@ -2106,15 +2106,32 @@ void NotebookNodeExplorer::addOpenWithMenu(QMenu *p_menu, bool p_master) { auto subMenu = p_menu->addMenu(tr("Open &With")); + const auto &types = FileTypeHelper::getInst().getAllFileTypes(); + + for (const auto &ft : types) { + if (ft.m_type == FileType::Others) { + continue; + } + + QAction *act = subMenu->addAction(ft.m_displayName); + connect(act, &QAction::triggered, + this, [this, act, p_master]() { + openSelectedNodesWithProgram(act->data().toString(), p_master); + }); + act->setData(ft.m_typeName); + } + + subMenu->addSeparator(); + { const auto &sessionConfig = ConfigMgr::getInst().getSessionConfig(); for (const auto &pro : sessionConfig.getExternalPrograms()) { QAction *act = subMenu->addAction(pro.m_name); connect(act, &QAction::triggered, this, [this, act, p_master]() { - openSelectedNodesWithCommand(act->data().toString(), p_master); + openSelectedNodesWithProgram(act->data().toString(), p_master); }); - act->setData(pro.m_command); + act->setData(pro.m_name); WidgetUtils::addActionShortcutText(act, pro.m_shortcut); } } @@ -2125,7 +2142,7 @@ void NotebookNodeExplorer::addOpenWithMenu(QMenu *p_menu, bool p_master) auto defaultAct = subMenu->addAction(tr("System Default Program")); connect(defaultAct, &QAction::triggered, this, [this, defaultAct, p_master]() { - openSelectedNodesWithCommand(QString(), p_master); + openSelectedNodesWithProgram(QString(), p_master); }); const auto &coreConfig = ConfigMgr::getInst().getCoreConfig(); WidgetUtils::addActionShortcutText(defaultAct, coreConfig.getShortcut(CoreConfig::OpenWithDefaultProgram)); @@ -2157,7 +2174,7 @@ void NotebookNodeExplorer::setupShortcuts() if (!isCombinedExploreMode()) { isMaster = m_masterExplorer->hasFocus(); } - openSelectedNodesWithCommand(QString(), isMaster); + openSelectedNodesWithProgram(QString(), isMaster); }); } } @@ -2165,21 +2182,21 @@ void NotebookNodeExplorer::setupShortcuts() const auto &sessionConfig = ConfigMgr::getInst().getSessionConfig(); for (const auto &pro : sessionConfig.getExternalPrograms()) { auto shortcut = WidgetUtils::createShortcut(pro.m_shortcut, this); - const auto &command = pro.m_command; + const auto &name = pro.m_name; if (shortcut) { connect(shortcut, &QShortcut::activated, - this, [this, command]() { + this, [this, name]() { bool isMaster = true; if (!isCombinedExploreMode()) { isMaster = m_masterExplorer->hasFocus(); } - openSelectedNodesWithCommand(command, isMaster); + openSelectedNodesWithProgram(name, isMaster); }); } } } -void NotebookNodeExplorer::openSelectedNodesWithCommand(const QString &p_command, bool p_master) +void NotebookNodeExplorer::openSelectedNodesWithProgram(const QString &p_name, bool p_master) { const bool closeBefore = ConfigMgr::getInst().getWidgetConfig().getNodeExplorerCloseBeforeOpenWithEnabled(); const auto files = getSelectedNodesPath(p_master); @@ -2196,12 +2213,12 @@ void NotebookNodeExplorer::openSelectedNodesWithCommand(const QString &p_command } } - if (p_command.isEmpty()) { + if (p_name.isEmpty()) { WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(file)); } else { - auto command = p_command; - command.replace(QStringLiteral("%1"), QString("\"%1\"").arg(file)); - ProcessUtils::startDetached(command); + auto paras = QSharedPointer::create(); + paras->m_fileType = p_name; + emit VNoteX::getInst().openFileRequested(file, paras); } } } diff --git a/src/widgets/notebooknodeexplorer.h b/src/widgets/notebooknodeexplorer.h index d051b536..6bb83a9f 100644 --- a/src/widgets/notebooknodeexplorer.h +++ b/src/widgets/notebooknodeexplorer.h @@ -297,7 +297,7 @@ namespace vnotex QStringList getSelectedNodesPath(bool p_master) const; - void openSelectedNodesWithCommand(const QString &p_command, bool p_master); + void openSelectedNodesWithProgram(const QString &p_name, bool p_master); bool belongsToMasterExplorer(const Node *p_node) const; diff --git a/src/widgets/webpage.cpp b/src/widgets/webpage.cpp index bfcc5237..a36d7d4b 100644 --- a/src/widgets/webpage.cpp +++ b/src/widgets/webpage.cpp @@ -9,7 +9,6 @@ using namespace vnotex; WebPage::WebPage(QWidget *p_parent) : QWebEnginePage(p_parent) { - } bool WebPage::acceptNavigationRequest(const QUrl &p_url, @@ -29,7 +28,7 @@ bool WebPage::acceptNavigationRequest(const QUrl &p_url, if (scheme == QStringLiteral("data")) { // Qt 5.12 and above will trigger this when calling QWebEngineView::setHtml(). return true; - } else if (scheme == QStringLiteral("chrome-devtools")) { + } else if (scheme == QStringLiteral("chrome-devtools") || scheme == QStringLiteral("devtools")) { return true; } diff --git a/src/widgets/widgets.pri b/src/widgets/widgets.pri index ece1136e..d7fada38 100644 --- a/src/widgets/widgets.pri +++ b/src/widgets/widgets.pri @@ -22,6 +22,7 @@ SOURCES += \ $$PWD/dialogs/selectionitemwidget.cpp \ $$PWD/dialogs/settings/appearancepage.cpp \ $$PWD/dialogs/settings/editorpage.cpp \ + $$PWD/dialogs/settings/fileassociationpage.cpp \ $$PWD/dialogs/settings/generalpage.cpp \ $$PWD/dialogs/settings/imagehostpage.cpp \ $$PWD/dialogs/settings/markdowneditorpage.cpp \ @@ -150,6 +151,7 @@ HEADERS += \ $$PWD/dialogs/selectionitemwidget.h \ $$PWD/dialogs/settings/appearancepage.h \ $$PWD/dialogs/settings/editorpage.h \ + $$PWD/dialogs/settings/fileassociationpage.h \ $$PWD/dialogs/settings/generalpage.h \ $$PWD/dialogs/settings/imagehostpage.h \ $$PWD/dialogs/settings/markdowneditorpage.h \