KeyboardLayout: support specifying keyboard layout mappings

Captain mode now supports different layout mappings.
This commit is contained in:
Le Tan 2018-09-29 18:37:26 +08:00
parent d014842bbf
commit 574aa4e70a
16 changed files with 1096 additions and 32 deletions

View File

@ -0,0 +1,497 @@
#include "vkeyboardlayoutmappingdialog.h"
#include <QtWidgets>
#include "vlineedit.h"
#include "utils/vkeyboardlayoutmanager.h"
#include "utils/vutils.h"
#include "utils/viconutils.h"
#include "vconfigmanager.h"
extern VConfigManager *g_config;
VKeyboardLayoutMappingDialog::VKeyboardLayoutMappingDialog(QWidget *p_parent)
: QDialog(p_parent),
m_mappingModified(false),
m_listenIndex(-1)
{
setupUI();
loadAvailableMappings();
}
void VKeyboardLayoutMappingDialog::setupUI()
{
QString info = tr("Manage keybaord layout mappings to used in shortcuts.");
info += "\n";
info += tr("Double click an item to set mapping key.");
QLabel *infoLabel = new QLabel(info, this);
// Selector.
m_selectorCombo = VUtils::getComboBox(this);
connect(m_selectorCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, [this](int p_idx) {
loadMappingInfo(m_selectorCombo->itemData(p_idx).toString());
});
// Add.
m_addBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/add.svg"), "", this);
m_addBtn->setToolTip(tr("New Mapping"));
m_addBtn->setProperty("FlatBtn", true);
connect(m_addBtn, &QPushButton::clicked,
this, &VKeyboardLayoutMappingDialog::newMapping);
// Delete.
m_deleteBtn = new QPushButton(VIconUtils::buttonDangerIcon(":/resources/icons/delete.svg"),
"",
this);
m_deleteBtn->setToolTip(tr("Delete Mapping"));
m_deleteBtn->setProperty("FlatBtn", true);
connect(m_deleteBtn, &QPushButton::clicked,
this, &VKeyboardLayoutMappingDialog::deleteCurrentMapping);
QHBoxLayout *selectLayout = new QHBoxLayout();
selectLayout->addWidget(new QLabel(tr("Keyboard layout mapping:"), this));
selectLayout->addWidget(m_selectorCombo);
selectLayout->addWidget(m_addBtn);
selectLayout->addWidget(m_deleteBtn);
selectLayout->addStretch();
// Name.
m_nameEdit = new VLineEdit(this);
connect(m_nameEdit, &QLineEdit::textEdited,
this, [this](const QString &p_text) {
Q_UNUSED(p_text);
setModified(true);
});
QHBoxLayout *editLayout = new QHBoxLayout();
editLayout->addWidget(new QLabel(tr("Name:"), this));
editLayout->addWidget(m_nameEdit);
editLayout->addStretch();
// Tree.
m_contentTree = new QTreeWidget(this);
m_contentTree->setProperty("ItemBorder", true);
m_contentTree->setRootIsDecorated(false);
m_contentTree->setColumnCount(2);
m_contentTree->setSelectionBehavior(QAbstractItemView::SelectRows);
QStringList headers;
headers << tr("Key") << tr("New Key");
m_contentTree->setHeaderLabels(headers);
m_contentTree->installEventFilter(this);
connect(m_contentTree, &QTreeWidget::itemDoubleClicked,
this, [this](QTreeWidgetItem *p_item, int p_column) {
Q_UNUSED(p_column);
int idx = m_contentTree->indexOfTopLevelItem(p_item);
if (m_listenIndex == -1) {
// Listen key for this item.
setListeningKey(idx);
} else if (idx == m_listenIndex) {
// Cancel listening key for this item.
cancelListeningKey();
} else {
// Recover previous item.
cancelListeningKey();
setListeningKey(idx);
}
});
connect(m_contentTree, &QTreeWidget::itemClicked,
this, [this](QTreeWidgetItem *p_item, int p_column) {
Q_UNUSED(p_column);
int idx = m_contentTree->indexOfTopLevelItem(p_item);
if (idx != m_listenIndex) {
cancelListeningKey();
}
});
QVBoxLayout *infoLayout = new QVBoxLayout();
infoLayout->addLayout(editLayout);
infoLayout->addWidget(m_contentTree);
QGroupBox *box = new QGroupBox(tr("Mapping Information"));
box->setLayout(infoLayout);
// Ok is the default button.
QDialogButtonBox *btnBox = new QDialogButtonBox(QDialogButtonBox::Ok
| QDialogButtonBox::Apply
| QDialogButtonBox::Cancel);
connect(btnBox, &QDialogButtonBox::accepted,
this, [this]() {
if (applyChanges()) {
QDialog::accept();
}
});
connect(btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
QPushButton *okBtn = btnBox->button(QDialogButtonBox::Ok);
okBtn->setProperty("SpecialBtn", true);
m_applyBtn = btnBox->button(QDialogButtonBox::Apply);
connect(m_applyBtn, &QPushButton::clicked,
this, &VKeyboardLayoutMappingDialog::applyChanges);
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addWidget(infoLabel);
mainLayout->addLayout(selectLayout);
mainLayout->addWidget(box);
mainLayout->addWidget(btnBox);
setLayout(mainLayout);
setWindowTitle(tr("Keyboard Layout Mappings"));
}
void VKeyboardLayoutMappingDialog::newMapping()
{
QString name = getNewMappingName();
if (!VKeyboardLayoutManager::addLayout(name)) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to add mapping <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle)
.arg(name),
tr("Please check the configuration file and try again."),
QMessageBox::Ok,
QMessageBox::Ok,
this);
return;
}
loadAvailableMappings();
setCurrentMapping(name);
}
QString VKeyboardLayoutMappingDialog::getNewMappingName() const
{
QString name;
QString baseName("layout_mapping");
int seq = 1;
do {
name = QString("%1_%2").arg(baseName).arg(QString::number(seq++), 3, '0');
} while (m_selectorCombo->findData(name) != -1);
return name;
}
void VKeyboardLayoutMappingDialog::deleteCurrentMapping()
{
QString mapping = currentMapping();
if (mapping.isEmpty()) {
return;
}
int ret = VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Are you sure to delete mapping <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle)
.arg(mapping),
"",
QMessageBox::Ok | QMessageBox::Cancel,
QMessageBox::Ok,
this);
if (ret != QMessageBox::Ok) {
return;
}
if (!VKeyboardLayoutManager::removeLayout(mapping)) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to delete mapping <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle)
.arg(mapping),
tr("Please check the configuration file and try again."),
QMessageBox::Ok,
QMessageBox::Ok,
this);
}
loadAvailableMappings();
}
void VKeyboardLayoutMappingDialog::loadAvailableMappings()
{
m_selectorCombo->setCurrentIndex(-1);
m_selectorCombo->clear();
QStringList layouts = VKeyboardLayoutManager::availableLayouts();
for (auto const & layout : layouts) {
m_selectorCombo->addItem(layout, layout);
}
if (m_selectorCombo->count() > 0) {
m_selectorCombo->setCurrentIndex(0);
}
}
static QList<int> keysNeededToMap()
{
QList<int> keys;
for (int i = Qt::Key_0; i <= Qt::Key_9; ++i) {
keys.append(i);
}
for (int i = Qt::Key_A; i <= Qt::Key_Z; ++i) {
keys.append(i);
}
QList<int> addi = g_config->getKeyboardLayoutMappingKeys();
for (auto tmp : addi) {
if (!keys.contains(tmp)) {
keys.append(tmp);
}
}
return keys;
}
static void recoverTreeItem(QTreeWidgetItem *p_item)
{
int key = p_item->data(0, Qt::UserRole).toInt();
QString text0 = QString("%1 (%2)").arg(VUtils::keyToChar(key, false))
.arg(key);
p_item->setText(0, text0);
int newKey = p_item->data(1, Qt::UserRole).toInt();
QString text1;
if (newKey > 0) {
text1 = QString("%1 (%2)").arg(VUtils::keyToChar(newKey, false))
.arg(newKey);
}
p_item->setText(1, text1);
}
// @p_newKey, 0 if there is no mapping.
static void fillTreeItem(QTreeWidgetItem *p_item, int p_key, int p_newKey)
{
p_item->setData(0, Qt::UserRole, p_key);
p_item->setData(1, Qt::UserRole, p_newKey);
recoverTreeItem(p_item);
}
static void setTreeItemMapping(QTreeWidgetItem *p_item, int p_newKey)
{
p_item->setData(1, Qt::UserRole, p_newKey);
}
static void fillMappingTree(QTreeWidget *p_tree, const QHash<int, int> &p_mappings)
{
QList<int> keys = keysNeededToMap();
for (auto key : keys) {
int val = 0;
auto it = p_mappings.find(key);
if (it != p_mappings.end()) {
val = it.value();
}
QTreeWidgetItem *item = new QTreeWidgetItem(p_tree);
fillTreeItem(item, key, val);
}
}
static QHash<int, int> retrieveMappingFromTree(QTreeWidget *p_tree)
{
QHash<int, int> mappings;
int cnt = p_tree->topLevelItemCount();
for (int i = 0; i < cnt; ++i) {
QTreeWidgetItem *item = p_tree->topLevelItem(i);
int key = item->data(0, Qt::UserRole).toInt();
int newKey = item->data(1, Qt::UserRole).toInt();
if (newKey > 0) {
mappings.insert(key, newKey);
}
}
return mappings;
}
void VKeyboardLayoutMappingDialog::loadMappingInfo(const QString &p_layout)
{
setModified(false);
if (p_layout.isEmpty()) {
m_nameEdit->clear();
m_contentTree->clear();
m_nameEdit->setEnabled(false);
m_contentTree->setEnabled(false);
return;
}
m_nameEdit->setText(p_layout);
m_nameEdit->setEnabled(true);
m_contentTree->clear();
if (!p_layout.isEmpty()) {
auto mappings = VKeyboardLayoutManager::readLayoutMapping(p_layout);
fillMappingTree(m_contentTree, mappings);
}
m_contentTree->setEnabled(true);
}
void VKeyboardLayoutMappingDialog::updateButtons()
{
QString mapping = currentMapping();
m_deleteBtn->setEnabled(!mapping.isEmpty());
m_applyBtn->setEnabled(m_mappingModified);
}
QString VKeyboardLayoutMappingDialog::currentMapping() const
{
return m_selectorCombo->currentData().toString();
}
void VKeyboardLayoutMappingDialog::setCurrentMapping(const QString &p_layout)
{
return m_selectorCombo->setCurrentIndex(m_selectorCombo->findData(p_layout));
}
bool VKeyboardLayoutMappingDialog::applyChanges()
{
if (!m_mappingModified) {
return true;
}
QString mapping = currentMapping();
if (mapping.isEmpty()) {
setModified(false);
return true;
}
// Check the name.
QString newName = m_nameEdit->text();
if (newName.isEmpty() || newName.toLower() == "global") {
// Set back the original name.
m_nameEdit->setText(mapping);
m_nameEdit->selectAll();
m_nameEdit->setFocus();
return false;
} else if (newName != mapping) {
// Rename the mapping.
if (!VKeyboardLayoutManager::renameLayout(mapping, newName)) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to rename mapping <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle)
.arg(mapping),
tr("Please check the configuration file and try again."),
QMessageBox::Ok,
QMessageBox::Ok,
this);
m_nameEdit->setText(mapping);
m_nameEdit->selectAll();
m_nameEdit->setFocus();
return false;
}
// Update the combobox.
int idx = m_selectorCombo->currentIndex();
m_selectorCombo->setItemText(idx, newName);
m_selectorCombo->setItemData(idx, newName);
mapping = newName;
}
// Check the mappings.
QHash<int, int> mappings = retrieveMappingFromTree(m_contentTree);
if (!VKeyboardLayoutManager::updateLayout(mapping, mappings)) {
VUtils::showMessage(QMessageBox::Warning,
tr("Warning"),
tr("Fail to update mapping <span style=\"%1\">%2</span>.")
.arg(g_config->c_dataTextStyle)
.arg(mapping),
tr("Please check the configuration file and try again."),
QMessageBox::Ok,
QMessageBox::Ok,
this);
return false;
}
setModified(false);
return true;
}
bool VKeyboardLayoutMappingDialog::eventFilter(QObject *p_obj, QEvent *p_event)
{
if (p_obj == m_contentTree) {
switch (p_event->type()) {
case QEvent::FocusOut:
cancelListeningKey();
break;
case QEvent::KeyPress:
if (listenKey(static_cast<QKeyEvent *>(p_event))) {
return true;
}
break;
default:
break;
}
}
return QDialog::eventFilter(p_obj, p_event);
}
bool VKeyboardLayoutMappingDialog::listenKey(QKeyEvent *p_event)
{
if (m_listenIndex == -1) {
return false;
}
int key = p_event->key();
if (VUtils::isMetaKey(key)) {
return false;
}
if (key == Qt::Key_Escape) {
cancelListeningKey();
return true;
}
// Set the mapping.
QTreeWidgetItem *item = m_contentTree->topLevelItem(m_listenIndex);
setTreeItemMapping(item, key);
setModified(true);
// Try next item automatically.
int nextIdx = m_listenIndex + 1;
cancelListeningKey();
if (nextIdx < m_contentTree->topLevelItemCount()) {
QTreeWidgetItem *item = m_contentTree->topLevelItem(nextIdx);
m_contentTree->clearSelection();
m_contentTree->setCurrentItem(item);
setListeningKey(nextIdx);
}
return true;
}
void VKeyboardLayoutMappingDialog::cancelListeningKey()
{
if (m_listenIndex > -1) {
// Recover that item.
recoverTreeItem(m_contentTree->topLevelItem(m_listenIndex));
m_listenIndex = -1;
}
}
void VKeyboardLayoutMappingDialog::setListeningKey(int p_idx)
{
Q_ASSERT(m_listenIndex == -1 && p_idx > -1);
m_listenIndex = p_idx;
QTreeWidgetItem *item = m_contentTree->topLevelItem(m_listenIndex);
item->setText(1, tr("Press key to set mapping"));
}

View File

@ -0,0 +1,73 @@
#ifndef VKEYBOARDLAYOUTMAPPINGDIALOG_H
#define VKEYBOARDLAYOUTMAPPINGDIALOG_H
#include <QDialog>
class QDialogButtonBox;
class QString;
class QTreeWidget;
class VLineEdit;
class QPushButton;
class QComboBox;
class VKeyboardLayoutMappingDialog : public QDialog
{
Q_OBJECT
public:
explicit VKeyboardLayoutMappingDialog(QWidget *p_parent = nullptr);
protected:
bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE;
private slots:
void newMapping();
void deleteCurrentMapping();
// Return true if changes are saved.
bool applyChanges();
private:
void setupUI();
void loadAvailableMappings();
void loadMappingInfo(const QString &p_layout);
void updateButtons();
QString currentMapping() const;
void setCurrentMapping(const QString &p_layout);
QString getNewMappingName() const;
bool listenKey(QKeyEvent *p_event);
void cancelListeningKey();
void setListeningKey(int p_idx);
void setModified(bool p_modified);
QComboBox *m_selectorCombo;
QPushButton *m_addBtn;
QPushButton *m_deleteBtn;
VLineEdit *m_nameEdit;
QTreeWidget *m_contentTree;
QPushButton *m_applyBtn;
bool m_mappingModified;
// Index of the item in the tree which is listening key.
// -1 for not listening.
int m_listenIndex;
};
inline void VKeyboardLayoutMappingDialog::setModified(bool p_modified)
{
m_mappingModified = p_modified;
updateButtons();
}
#endif // VKEYBOARDLAYOUTMAPPINGDIALOG_H

View File

@ -9,6 +9,8 @@
#include "vlineedit.h"
#include "vplantumlhelper.h"
#include "vgraphvizhelper.h"
#include "utils/vkeyboardlayoutmanager.h"
#include "dialog/vkeyboardlayoutmappingdialog.h"
extern VConfigManager *g_config;
@ -324,11 +326,29 @@ VGeneralTab::VGeneralTab(QWidget *p_parent)
qaLayout->addWidget(m_quickAccessEdit);
qaLayout->addWidget(browseBtn);
// Keyboard layout mappings.
m_keyboardLayoutCombo = VUtils::getComboBox(this);
m_keyboardLayoutCombo->setToolTip(tr("Choose the keyboard layout mapping to use in shortcuts"));
QPushButton *editLayoutBtn = new QPushButton(tr("Edit"), this);
connect(editLayoutBtn, &QPushButton::clicked,
this, [this]() {
VKeyboardLayoutMappingDialog dialog(this);
dialog.exec();
loadKeyboardLayoutMapping();
});
QHBoxLayout *klLayout = new QHBoxLayout();
klLayout->addWidget(m_keyboardLayoutCombo);
klLayout->addWidget(editLayoutBtn);
klLayout->addStretch();
QFormLayout *optionLayout = new QFormLayout();
optionLayout->addRow(tr("Language:"), m_langCombo);
optionLayout->addRow(m_systemTray);
optionLayout->addRow(tr("Startup pages:"), startupLayout);
optionLayout->addRow(tr("Quick access:"), qaLayout);
optionLayout->addRow(tr("Keyboard layout mapping:"), klLayout);
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addLayout(optionLayout);
@ -409,6 +429,10 @@ bool VGeneralTab::loadConfiguration()
return false;
}
if (!loadKeyboardLayoutMapping()) {
return false;
}
return true;
}
@ -430,6 +454,10 @@ bool VGeneralTab::saveConfiguration()
return false;
}
if (!saveKeyboardLayoutMapping()) {
return false;
}
return true;
}
@ -528,6 +556,38 @@ bool VGeneralTab::saveQuickAccess()
return true;
}
bool VGeneralTab::loadKeyboardLayoutMapping()
{
m_keyboardLayoutCombo->clear();
m_keyboardLayoutCombo->addItem(tr("None"), "");
QStringList layouts = VKeyboardLayoutManager::availableLayouts();
for (auto const & layout : layouts) {
m_keyboardLayoutCombo->addItem(layout, layout);
}
int idx = 0;
const auto &cur = VKeyboardLayoutManager::currentLayout();
if (!cur.m_name.isEmpty()) {
idx = m_keyboardLayoutCombo->findData(cur.m_name);
if (idx == -1) {
idx = 0;
VKeyboardLayoutManager::setCurrentLayout("");
}
}
m_keyboardLayoutCombo->setCurrentIndex(idx);
return true;
}
bool VGeneralTab::saveKeyboardLayoutMapping()
{
g_config->setKeyboardLayout(m_keyboardLayoutCombo->currentData().toString());
VKeyboardLayoutManager::update();
return true;
}
VLookTab::VLookTab(QWidget *p_parent)
: QWidget(p_parent)
{

View File

@ -40,6 +40,9 @@ private:
bool loadQuickAccess();
bool saveQuickAccess();
bool loadKeyboardLayoutMapping();
bool saveKeyboardLayoutMapping();
// Language
QComboBox *m_langCombo;
@ -58,6 +61,9 @@ private:
// Quick access note path.
VLineEdit *m_quickAccessEdit;
// Keyboard layout mappings.
QComboBox *m_keyboardLayoutCombo;
static const QVector<QString> c_availableLangs;
};

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<polygon style="fill:#000000" points="448,224 288,224 288,64 224,64 224,224 64,224 64,288 224,288 224,448 288,448 288,288 448,288 "/>
</svg>

After

Width:  |  Height:  |  Size: 618 B

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#000000" d="M341,128V99c0-19.1-14.5-35-34.5-35H205.4C185.5,64,171,79.9,171,99v29H80v32h9.2c0,0,5.4,0.6,8.2,3.4c2.8,2.8,3.9,9,3.9,9
l19,241.7c1.5,29.4,1.5,33.9,36,33.9h199.4c34.5,0,34.5-4.4,36-33.8l19-241.6c0,0,1.1-6.3,3.9-9.1c2.8-2.8,8.2-3.4,8.2-3.4h9.2v-32
h-91V128z M192,99c0-9.6,7.8-15,17.7-15h91.7c9.9,0,18.6,5.5,18.6,15v29H192V99z M183.5,384l-10.3-192h20.3L204,384H183.5z
M267.1,384h-22V192h22V384z M328.7,384h-20.4l10.5-192h20.3L328.7,384z"/>
</svg>

After

Width:  |  Height:  |  Size: 955 B

View File

@ -256,10 +256,6 @@ max_num_of_tag_labels=3
; 2 - web to editor
smart_live_preview=3
; Support multiple keyboard layout
; Not valid on macOS
multiple_keyboard_layout=true
; Whether insert new note in front
insert_new_note_in_front=false
@ -269,6 +265,13 @@ highlight_matches_in_page=true
; Incremental search in page
find_incremental_search=true
; Additional Qt::Key_XXX which will be mapped in different layouts
; List of integer values.
keyboard_layout_mapping_keys=
; Chosen keyboard layout mapping from keyboard_layouts.ini
keyboard_layout=
[editor]
; Auto indent as previous line
auto_indent=true

View File

@ -146,7 +146,9 @@ SOURCES += main.cpp\
pegmarkdownhighlighter.cpp \
pegparser.cpp \
peghighlighterresult.cpp \
vtexteditcompleter.cpp
vtexteditcompleter.cpp \
utils/vkeyboardlayoutmanager.cpp \
dialog/vkeyboardlayoutmappingdialog.cpp
HEADERS += vmainwindow.h \
vdirectorytree.h \
@ -285,7 +287,9 @@ HEADERS += vmainwindow.h \
pegparser.h \
peghighlighterresult.h \
vtexteditcompleter.h \
vtextdocumentlayoutdata.h
vtextdocumentlayoutdata.h \
utils/vkeyboardlayoutmanager.h \
dialog/vkeyboardlayoutmappingdialog.h
RESOURCES += \
vnote.qrc \

View File

@ -0,0 +1,298 @@
#include "vkeyboardlayoutmanager.h"
#include <QSharedPointer>
#include <QSettings>
#include <QFileInfo>
#include <QDebug>
#include "vconfigmanager.h"
extern VConfigManager *g_config;
VKeyboardLayoutManager *VKeyboardLayoutManager::s_inst = NULL;
VKeyboardLayoutManager *VKeyboardLayoutManager::inst()
{
if (!s_inst) {
s_inst = new VKeyboardLayoutManager();
s_inst->update(g_config);
}
return s_inst;
}
static QSharedPointer<QSettings> layoutSettings(const VConfigManager *p_config,
bool p_create = false)
{
QSharedPointer<QSettings> settings;
QString file = p_config->getKeyboardLayoutConfigFilePath();
if (file.isEmpty()) {
return settings;
}
if (!QFileInfo::exists(file) && !p_create) {
return settings;
}
settings.reset(new QSettings(file, QSettings::IniFormat));
return settings;
}
static void clearLayoutMapping(const QSharedPointer<QSettings> &p_settings,
const QString &p_name)
{
p_settings->beginGroup(p_name);
p_settings->remove("");
p_settings->endGroup();
}
static QHash<int, int> readLayoutMappingInternal(const QSharedPointer<QSettings> &p_settings,
const QString &p_name)
{
QHash<int, int> mappings;
p_settings->beginGroup(p_name);
QStringList keys = p_settings->childKeys();
for (auto const & key : keys) {
if (key.isEmpty()) {
continue;
}
bool ok;
int keyNum = key.toInt(&ok);
if (!ok) {
qWarning() << "readLayoutMappingInternal() skip bad key" << key << "in layout" << p_name;
continue;
}
int valNum = p_settings->value(key).toInt();
mappings.insert(keyNum, valNum);
}
p_settings->endGroup();
return mappings;
}
static bool writeLayoutMapping(const QSharedPointer<QSettings> &p_settings,
const QString &p_name,
const QHash<int, int> &p_mappings)
{
clearLayoutMapping(p_settings, p_name);
p_settings->beginGroup(p_name);
for (auto it = p_mappings.begin(); it != p_mappings.end(); ++it) {
p_settings->setValue(QString::number(it.key()), it.value());
}
p_settings->endGroup();
return true;
}
void VKeyboardLayoutManager::update(VConfigManager *p_config)
{
m_layout.clear();
m_layout.m_name = p_config->getKeyboardLayout();
if (m_layout.m_name.isEmpty()) {
// No mapping.
return;
}
qDebug() << "using keyboard layout mapping" << m_layout.m_name;
auto settings = layoutSettings(p_config);
if (settings.isNull()) {
return;
}
m_layout.setMapping(readLayoutMappingInternal(settings, m_layout.m_name));
}
void VKeyboardLayoutManager::update()
{
inst()->update(g_config);
}
const VKeyboardLayoutManager::Layout &VKeyboardLayoutManager::currentLayout()
{
return inst()->m_layout;
}
static QStringList readAvailableLayoutMappings(const QSharedPointer<QSettings> &p_settings)
{
QString fullKey("global/layout_mappings");
return p_settings->value(fullKey).toStringList();
}
static void writeAvailableLayoutMappings(const QSharedPointer<QSettings> &p_settings,
const QStringList &p_layouts)
{
QString fullKey("global/layout_mappings");
return p_settings->setValue(fullKey, p_layouts);
}
QStringList VKeyboardLayoutManager::availableLayouts()
{
QStringList layouts;
auto settings = layoutSettings(g_config);
if (settings.isNull()) {
return layouts;
}
layouts = readAvailableLayoutMappings(settings);
return layouts;
}
void VKeyboardLayoutManager::setCurrentLayout(const QString &p_name)
{
auto mgr = inst();
if (mgr->m_layout.m_name == p_name) {
return;
}
g_config->setKeyboardLayout(p_name);
mgr->update(g_config);
}
static bool isValidLayoutName(const QString &p_name)
{
return !p_name.isEmpty() && p_name.toLower() != "global";
}
bool VKeyboardLayoutManager::addLayout(const QString &p_name)
{
Q_ASSERT(isValidLayoutName(p_name));
auto settings = layoutSettings(g_config, true);
if (settings.isNull()) {
qWarning() << "fail to open keyboard layout QSettings";
return false;
}
QStringList layouts = readAvailableLayoutMappings(settings);
if (layouts.contains(p_name)) {
qWarning() << "Keyboard layout mapping" << p_name << "already exists";
return false;
}
layouts.append(p_name);
writeAvailableLayoutMappings(settings, layouts);
clearLayoutMapping(settings, p_name);
return true;
}
bool VKeyboardLayoutManager::removeLayout(const QString &p_name)
{
Q_ASSERT(isValidLayoutName(p_name));
auto settings = layoutSettings(g_config, true);
if (settings.isNull()) {
qWarning() << "fail to open keyboard layout QSettings";
return false;
}
QStringList layouts = readAvailableLayoutMappings(settings);
int idx = layouts.indexOf(p_name);
if (idx == -1) {
return true;
}
layouts.removeAt(idx);
writeAvailableLayoutMappings(settings, layouts);
clearLayoutMapping(settings, p_name);
return true;
}
bool VKeyboardLayoutManager::renameLayout(const QString &p_name, const QString &p_newName)
{
Q_ASSERT(isValidLayoutName(p_name));
Q_ASSERT(isValidLayoutName(p_newName));
auto settings = layoutSettings(g_config, true);
if (settings.isNull()) {
qWarning() << "fail to open keyboard layout QSettings";
return false;
}
QStringList layouts = readAvailableLayoutMappings(settings);
int idx = layouts.indexOf(p_name);
if (idx == -1) {
qWarning() << "fail to find keyboard layout mapping" << p_name << "to rename";
return false;
}
if (layouts.indexOf(p_newName) != -1) {
qWarning() << "keyboard layout mapping" << p_newName << "already exists";
return false;
}
auto content = readLayoutMappingInternal(settings, p_name);
// Copy the group.
if (!writeLayoutMapping(settings, p_newName, content)) {
qWarning() << "fail to write new layout mapping" << p_newName;
return false;
}
clearLayoutMapping(settings, p_name);
layouts.replace(idx, p_newName);
writeAvailableLayoutMappings(settings, layouts);
// Check current layout.
if (g_config->getKeyboardLayout() == p_name) {
Q_ASSERT(inst()->m_layout.m_name == p_name);
g_config->setKeyboardLayout(p_newName);
inst()->m_layout.m_name = p_newName;
}
return true;
}
QHash<int, int> VKeyboardLayoutManager::readLayoutMapping(const QString &p_name)
{
QHash<int, int> mappings;
if (p_name.isEmpty()) {
return mappings;
}
auto settings = layoutSettings(g_config);
if (settings.isNull()) {
return mappings;
}
return readLayoutMappingInternal(settings, p_name);
}
bool VKeyboardLayoutManager::updateLayout(const QString &p_name,
const QHash<int, int> &p_mapping)
{
Q_ASSERT(isValidLayoutName(p_name));
auto settings = layoutSettings(g_config, true);
if (settings.isNull()) {
qWarning() << "fail to open keyboard layout QSettings";
return false;
}
QStringList layouts = readAvailableLayoutMappings(settings);
int idx = layouts.indexOf(p_name);
if (idx == -1) {
qWarning() << "fail to find keyboard layout mapping" << p_name << "to update";
return false;
}
if (!writeLayoutMapping(settings, p_name, p_mapping)) {
qWarning() << "fail to write layout mapping" << p_name;
return false;
}
// Check current layout.
if (inst()->m_layout.m_name == p_name) {
inst()->m_layout.setMapping(p_mapping);
}
return true;
}

View File

@ -0,0 +1,79 @@
#ifndef VKEYBOARDLAYOUTMANAGER_H
#define VKEYBOARDLAYOUTMANAGER_H
#include <QString>
#include <QStringList>
#include <QHash>
class VConfigManager;
class VKeyboardLayoutManager
{
public:
struct Layout
{
void clear()
{
m_name.clear();
m_mapping.clear();
}
void setMapping(const QHash<int, int> &p_mapping)
{
m_mapping.clear();
for (auto it = p_mapping.begin(); it != p_mapping.end(); ++it) {
m_mapping.insert(it.value(), it.key());
}
}
QString m_name;
// Reversed mapping.
QHash<int, int> m_mapping;
};
static void update();
static const VKeyboardLayoutManager::Layout &currentLayout();
static void setCurrentLayout(const QString &p_name);
static QStringList availableLayouts();
static bool addLayout(const QString &p_name);
static bool removeLayout(const QString &p_name);
static bool renameLayout(const QString &p_name, const QString &p_newName);
static bool updateLayout(const QString &p_name, const QHash<int, int> &p_mapping);
static QHash<int, int> readLayoutMapping(const QString &p_name);
static int mapKey(int p_key);
private:
VKeyboardLayoutManager() {}
static VKeyboardLayoutManager *inst();
void update(VConfigManager *p_config);
Layout m_layout;
static VKeyboardLayoutManager *s_inst;
};
inline int VKeyboardLayoutManager::mapKey(int p_key)
{
const Layout &layout = inst()->m_layout;
if (!layout.m_name.isEmpty()) {
auto it = layout.m_mapping.find(p_key);
if (it != layout.m_mapping.end()) {
return it.value();
}
}
return p_key;
}
#endif // VKEYBOARDLAYOUTMANAGER_H

View File

@ -1376,6 +1376,10 @@ bool VUtils::isMetaKey(int p_key)
return p_key == Qt::Key_Control
|| p_key == Qt::Key_Shift
|| p_key == Qt::Key_Meta
#if defined(Q_OS_LINUX)
// For mapping Caps as Ctrl in KDE.
|| p_key == Qt::Key_CapsLock
#endif
|| p_key == Qt::Key_Alt;
}

View File

@ -571,11 +571,7 @@ bool VVim::handleKeyPressEvent(int key, int modifiers, int *p_autoIndentPos)
if (m_registerPending) {
// Ctrl and Shift may be sent out first.
if (key == Qt::Key_Control
|| key == Qt::Key_Shift
|| key == Qt::Key_Meta
// For mapping Caps as Ctrl in KDE.
|| key == Qt::Key_CapsLock) {
if (VUtils::isMetaKey(key)) {
goto accept;
}

View File

@ -8,6 +8,7 @@
#include "vfilelist.h"
#include "vnavigationmode.h"
#include "vconfigmanager.h"
#include "utils/vkeyboardlayoutmanager.h"
extern VConfigManager *g_config;
@ -95,10 +96,7 @@ void VCaptain::keyPressEvent(QKeyEvent *p_event)
return;
}
if (g_config->getMultipleKeyboardLayout()) {
// Use virtual key here for different layout.
key = p_event->nativeVirtualKey();
}
key = VKeyboardLayoutManager::mapKey(key);
if (handleKeyPress(key, modifiers)) {
p_event->accept();

View File

@ -29,6 +29,8 @@ const QString VConfigManager::c_sessionConfigFile = QString("session.ini");
const QString VConfigManager::c_snippetConfigFile = QString("snippet.json");
const QString VConfigManager::c_keyboardLayoutConfigFile = QString("keyboard_layouts.ini");
const QString VConfigManager::c_styleConfigFolder = QString("styles");
const QString VConfigManager::c_themeConfigFolder = QString("themes");
@ -314,13 +316,6 @@ void VConfigManager::initialize()
m_smartLivePreview = getConfigFromSettings("global",
"smart_live_preview").toInt();
#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
m_multipleKeyboardLayout = false;
#else
m_multipleKeyboardLayout = getConfigFromSettings("global",
"multiple_keyboard_layout").toBool();
#endif
m_insertNewNoteInFront = getConfigFromSettings("global",
"insert_new_note_in_front").toBool();
@ -862,6 +857,11 @@ const QString &VConfigManager::getSnippetConfigFilePath() const
return path;
}
const QString VConfigManager::getKeyboardLayoutConfigFilePath() const
{
return QDir(getConfigFolder()).filePath(c_keyboardLayoutConfigFile);
}
QString VConfigManager::getThemeFile() const
{
auto it = m_themes.find(m_theme);

View File

@ -449,6 +449,8 @@ public:
const QString &getSnippetConfigFilePath() const;
const QString getKeyboardLayoutConfigFilePath() const;
// Read all available templates files in c_templateConfigFolder.
QVector<QString> getNoteTemplates(DocType p_type = DocType::Unknown) const;
@ -565,11 +567,14 @@ public:
int getSmartLivePreview() const;
void setSmartLivePreview(int p_preview);
bool getMultipleKeyboardLayout() const;
bool getInsertNewNoteInFront() const;
void setInsertNewNoteInFront(bool p_enabled);
QString getKeyboardLayout() const;
void setKeyboardLayout(const QString &p_name);
QList<int> getKeyboardLayoutMappingKeys() const;
private:
// Look up a config from user and default settings.
QVariant getConfigFromSettings(const QString &section, const QString &key) const;
@ -1019,9 +1024,6 @@ private:
// Smart live preview.
int m_smartLivePreview;
// Support multiple keyboard layout.
bool m_multipleKeyboardLayout;
// Whether insert new note in front.
bool m_insertNewNoteInFront;
@ -1040,6 +1042,9 @@ private:
// The name of the config file for snippets folder.
static const QString c_snippetConfigFile;
// The name of the config file for keyboard layouts.
static const QString c_keyboardLayoutConfigFile;
// QSettings for the user configuration
QSettings *userSettings;
@ -2613,11 +2618,6 @@ inline void VConfigManager::setSmartLivePreview(int p_preview)
setConfigToSettings("global", "smart_live_preview", m_smartLivePreview);
}
inline bool VConfigManager::getMultipleKeyboardLayout() const
{
return m_multipleKeyboardLayout;
}
inline bool VConfigManager::getInsertNewNoteInFront() const
{
return m_insertNewNoteInFront;
@ -2657,4 +2657,31 @@ inline void VConfigManager::setHighlightMatchesInPage(bool p_enabled)
m_highlightMatchesInPage = p_enabled;
setConfigToSettings("global", "highlight_matches_in_page", m_highlightMatchesInPage);
}
inline QString VConfigManager::getKeyboardLayout() const
{
return getConfigFromSettings("global", "keyboard_layout").toString();
}
inline void VConfigManager::setKeyboardLayout(const QString &p_name)
{
setConfigToSettings("global", "keyboard_layout", p_name);
}
inline QList<int> VConfigManager::getKeyboardLayoutMappingKeys() const
{
QStringList keyStrs = getConfigFromSettings("global",
"keyboard_layout_mapping_keys").toStringList();
QList<int> keys;
for (auto & str : keyStrs) {
bool ok;
int tmp = str.toInt(&ok);
if (ok) {
keys.append(tmp);
}
}
return keys;
}
#endif // VCONFIGMANAGER_H

View File

@ -274,5 +274,7 @@
<file>resources/export/export_template.html</file>
<file>resources/export/outline.css</file>
<file>resources/export/outline.js</file>
<file>resources/icons/add.svg</file>
<file>resources/icons/delete.svg</file>
</qresource>
</RCC>