support file associations and open with built-in editors

This commit is contained in:
Le Tan 2022-01-30 19:36:20 +08:00
parent 737f9e51d8
commit b3a385693c
28 changed files with 556 additions and 35 deletions

View File

@ -5,6 +5,8 @@
#include <utils/fileutils.h>
#include "buffer.h"
#include <core/configmgr.h>
#include <core/coreconfig.h>
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;

View File

@ -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();

View File

@ -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

View File

@ -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<const MarkdownBuffer *>(p_buffer) != nullptr;
}

View File

@ -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

View File

@ -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<const TextBuffer *>(p_buffer) != nullptr;
}

View File

@ -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;
};
}

View File

@ -11,14 +11,19 @@
#include <buffer/nodebufferprovider.h>
#include <buffer/filebufferprovider.h>
#include <utils/widgetutils.h>
#include <utils/processutils.h>
#include "notebookmgr.h"
#include "vnotex.h"
#include "externalfile.h"
#include "sessionconfig.h"
#include "configmgr.h"
#include "fileopenparameters.h"
using namespace vnotex;
QMap<QString, QString> BufferMgr::s_suffixToFileType;
BufferMgr::BufferMgr(QObject *p_parent)
: QObject(p_parent)
{
@ -66,12 +71,27 @@ void BufferMgr::open(Node *p_node, const QSharedPointer<FileOpenParameters> &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 QSharedPointer<FileOpenPar
return;
}
// Check if it is requested to open with external program.
if (openWithExternalProgram(p_filePath, p_paras->m_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 QSharedPointer<FileOpenPar
return;
}
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(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<ExternalFile>::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<CoreConfig::FileTypeSuffix> &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();
}
}

View File

@ -5,8 +5,10 @@
#include <QScopedPointer>
#include <QSharedPointer>
#include <QVector>
#include <QMap>
#include "namebasedserver.h"
#include "coreconfig.h"
namespace vnotex
{
@ -30,6 +32,8 @@ namespace vnotex
void open(const QString &p_filePath, const QSharedPointer<FileOpenParameters> &p_paras);
static void updateSuffixToFileType(const QVector<CoreConfig::FileTypeSuffix> &p_fileTypeSuffixes);
signals:
void bufferRequested(Buffer *p_buffer, const QSharedPointer<FileOpenParameters> &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<NameBasedServer<IBufferFactory>> m_bufferServer;
// Managed by QObject.
QVector<Buffer *> m_buffers;
// Mapping from suffix to file type or external program name.
static QMap<QString, QString> s_suffixToFileType;
};
} // ns vnotex

View File

@ -3,6 +3,8 @@
#include <QMetaEnum>
#include <QLocale>
#include <utils/utils.h>
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::FileTypeSuffix> &CoreConfig::getFileTypeSuffixes() const
{
return m_fileTypeSuffixes;
}
void CoreConfig::setFileTypeSuffixes(const QVector<CoreConfig::FileTypeSuffix> &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;
}

View File

@ -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<FileTypeSuffix> &getFileTypeSuffixes() const;
void setFileTypeSuffixes(const QVector<FileTypeSuffix> &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<FileTypeSuffix> m_fileTypeSuffixes;
static QStringList s_availableLocales;
};
} // ns vnotex

View File

@ -59,7 +59,6 @@ namespace vnotex
const FileType &getContentType() const;
protected:
void setContentType(int p_type);
private:

View File

@ -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<void()> m_hooks[Hook::MaxHook];
};
}

View File

@ -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::ExternalProgram> &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<HistoryItem> &SessionConfig::getHistory() const
{
return m_history;

View File

@ -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<ExternalProgram> &getExternalPrograms() const;
const ExternalProgram *findExternalProgram(const QString &p_name) const;
const QVector<HistoryItem> &getHistory() const;
void addHistory(const HistoryItem &p_item);

View File

@ -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();

View File

@ -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,

View File

@ -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;
}

View File

@ -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

View File

@ -25,6 +25,7 @@
#include <QLayout>
#include <QPushButton>
#include <QSplitter>
#include <QFormLayout>
#include <core/global.h>
@ -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);
}
}

View File

@ -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);
};

View File

@ -0,0 +1,132 @@
#include "fileassociationpage.h"
#include <QFormLayout>
#include <QGroupBox>
#include <QVBoxLayout>
#include <QMap>
#include <widgets/widgetsfactory.h>
#include <widgets/lineedit.h>
#include <core/coreconfig.h>
#include <core/sessionconfig.h>
#include <core/configmgr.h>
#include <core/buffermgr.h>
#include <utils/widgetutils.h>
#include <utils/utils.h>
#include <core/vnotex.h>
#include <buffer/filetypehelper.h>
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<CoreConfig::FileTypeSuffix> fileTypeSuffixes;
auto lineEdits = m_builtInFileTypesBox->findChildren<QLineEdit *>(QString());
lineEdits << m_externalProgramsBox->findChildren<QLineEdit *>(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<QFormLayout *>(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<QFormLayout *>(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));
}
}
}

View File

@ -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

View File

@ -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();

View File

@ -17,7 +17,6 @@
#include "mainwindow.h"
#include <utils/iconutils.h>
#include <utils/docsutils.h>
#include <utils/processutils.h>
#include "treewidget.h"
#include "listwidget.h"
#include "dialogs/notepropertiesdialog.h"
@ -38,6 +37,7 @@
#include <core/coreconfig.h>
#include <core/sessionconfig.h>
#include <core/widgetconfig.h>
#include <buffer/filetypehelper.h>
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<FileOpenParameters>::create();
paras->m_fileType = p_name;
emit VNoteX::getInst().openFileRequested(file, paras);
}
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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 \