diff --git a/src/resources/docs/shortcuts_en.md b/src/resources/docs/shortcuts_en.md
index cdb6e1f5..883d7c3f 100644
--- a/src/resources/docs/shortcuts_en.md
+++ b/src/resources/docs/shortcuts_en.md
@@ -72,6 +72,36 @@ Expand the selection to the beginning or end of current line.
- `Ctrl+Shift+Home`, `Ctrl+Shift+End`
Expand the selection to the beginning or end of current note.
+## Custom Shortcuts
+VNote supports customing some standard shortcuts, though it is not recommended. VNote stores shortcuts' configuration information in the `[shortcuts]` section of user configuration file `vnote.ini`.
+
+For example, the default configruation may look like this:
+
+```ini
+[shortcuts]
+1\operation=NewNote
+1\keysequence=Ctrl+N
+2\operation=SaveNote
+2\keysequence=Ctrl+S
+3\operation=SaveAndRead
+3\keysequence=Ctrl+T
+4\operation=EditNote
+4\keysequence=Ctrl+W
+5\operation=CloseNote
+5\keysequence=
+6\operation=Find
+6\keysequence=Ctrl+F
+7\operation=FindNext
+7\keysequence=F3
+8\operation=FindPrevious
+8\keysequence=Shift+F3
+size=8
+```
+
+`size=8` tells VNote that there are 8 shotcuts defined here, with each beginning with the number sequence. You could change the `keysequence` value to change the default key sequence of a specified operation. Leave the `keysequence` empty (`keysequence=`) to disable shortcut for that operation.
+
+Pay attention that `Ctrl+E` is reserved for *Captain Mode* and `Ctrl+Q` is reserved for quitting VNote.
+
# Captain Mode
To efficiently utilize the shortcuts, VNote supports the **Captain Mode**.
@@ -117,7 +147,7 @@ Move current tab one split window right.
Display shortcuts documentation.
## Navigation Mode
-Within the Captain MOde, `W` will turn VNote into **Navigation Mode**. In this mode, VNote will display at most two characters on some major widgets, and then pressing corresponding characters will jump to that widget.
+Within the Captain Mode, `W` will turn VNote into **Navigation Mode**. In this mode, VNote will display at most two characters on some major widgets, and then pressing corresponding characters will jump to that widget.
# Vim Mode
VNote supports a simple but useful Vim mode, including **Normal**, **Insert**, **Visual**, and **VisualLine** modes.
diff --git a/src/resources/docs/shortcuts_zh.md b/src/resources/docs/shortcuts_zh.md
index e1a4f0c5..47bd8a8e 100644
--- a/src/resources/docs/shortcuts_zh.md
+++ b/src/resources/docs/shortcuts_zh.md
@@ -72,6 +72,37 @@
- `Ctrl+Shift+Home`, `Ctrl+Shift+End`
扩展选定到笔记开始或结尾处。
+## 自定义快捷键
+VNote支持自定义部分标准快捷键(但并不建议这么做)。VNote将快捷键信息保存在用户配置文件`vnote.ini`中的`[shortcuts]`小节。
+
+例如,默认的配置可能是这样子的:
+
+
+```ini
+[shortcuts]
+1\operation=NewNote
+1\keysequence=Ctrl+N
+2\operation=SaveNote
+2\keysequence=Ctrl+S
+3\operation=SaveAndRead
+3\keysequence=Ctrl+T
+4\operation=EditNote
+4\keysequence=Ctrl+W
+5\operation=CloseNote
+5\keysequence=
+6\operation=Find
+6\keysequence=Ctrl+F
+7\operation=FindNext
+7\keysequence=F3
+8\operation=FindPrevious
+8\keysequence=Shift+F3
+size=8
+```
+
+`size=8` 告诉VNote这里定义了8组快捷键,每组快捷键都以一个数字序号开始。通过改变每组快捷键中`keysequence`的值来改变某个操作的默认快捷键。将`keysequence`设置为空(`keysequence=`)则会禁用该操作的任何快捷键。
+
+注意,`Ctrl+E`保留作为*舰长模式*的前导键,`Ctrl+Q`保留为退出VNote。
+
# 舰长模式
为了更有效地利用快捷键,VNote支持 **舰长模式**。
diff --git a/src/resources/icons/close_note_tb.svg b/src/resources/icons/close_note_tb.svg
new file mode 100644
index 00000000..2e111a9e
--- /dev/null
+++ b/src/resources/icons/close_note_tb.svg
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/src/resources/styles/default.mdhl b/src/resources/styles/default.mdhl
index 6af90e9b..0668915d 100644
--- a/src/resources/styles/default.mdhl
+++ b/src/resources/styles/default.mdhl
@@ -13,7 +13,7 @@ editor
# Do not use "" to quote the name
font-family: Hiragino Sans GB, 冬青黑体, Microsoft YaHei, 微软雅黑, Microsoft YaHei UI, WenQuanYi Micro Hei, 文泉驿雅黑, Dengxian, 等线体, STXihei, 华文细黑, Liberation Sans, Droid Sans, NSimSun, 新宋体, SimSun, 宋体, Helvetica, sans-serif, Tahoma, Arial, Verdana, Geneva, Georgia, Times New Roman
# [VNote] Style for trailing space
-trailing-space: ffebee
+trailing-space: a8a8a8
font-size: 12
line-number-background: bdbdbd
line-number-foreground: 424242
diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini
index 8cb2df1c..556e1f4c 100644
--- a/src/resources/vnote.ini
+++ b/src/resources/vnote.ini
@@ -70,3 +70,27 @@ tools_dock_checked=true
4\name=LightGrey
4\rgb=D3D3D3
size=4
+
+[shortcuts]
+; Define shortcuts here, with each item in the form "operation->keysequence".
+; Leave keysequence empty to disable the shortcut of an operation.
+; Custom shortcuts may conflict with some key bindings in edit mode or Vim mode.
+; Ctrl+E is reserved for Captain Mode.
+; Ctrl+Q is reserved for quitting VNote.
+1\operation=NewNote
+1\keysequence=Ctrl+N
+2\operation=SaveNote
+2\keysequence=Ctrl+S
+3\operation=SaveAndRead
+3\keysequence=Ctrl+T
+4\operation=EditNote
+4\keysequence=Ctrl+W
+5\operation=CloseNote
+5\keysequence=
+6\operation=Find
+6\keysequence=Ctrl+F
+7\operation=FindNext
+7\keysequence=F3
+8\operation=FindPrevious
+8\keysequence=Shift+F3
+size=8
diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp
index aa02e694..9ca31811 100644
--- a/src/vconfigmanager.cpp
+++ b/src/vconfigmanager.cpp
@@ -153,6 +153,8 @@ void VConfigManager::initialize()
m_editorLineNumber = getConfigFromSettings("global",
"editor_line_number").toInt();
+
+ readShortcutsFromSettings();
}
void VConfigManager::readPredefinedColorsFromSettings()
@@ -338,7 +340,7 @@ void VConfigManager::updateMarkdownEditStyle()
static const QString defaultVimInsertBg = "#CDC0B0";
static const QString defaultVimVisualBg = "#90CAF9";
static const QString defaultVimReplaceBg = "#F8BBD0";
- static const QString defaultTrailingSpaceBackground = "#FFEBEE";
+ static const QString defaultTrailingSpaceBackground = "#A8A8A8";
static const QString defaultLineNumberBg = "#BDBDBD";
static const QString defaultLineNumberFg = "#424242";
@@ -483,6 +485,13 @@ QString VConfigManager::getConfigFolder() const
return VUtils::basePathFromPath(iniPath);
}
+QString VConfigManager::getConfigFilePath() const
+{
+ V_ASSERT(userSettings);
+
+ return userSettings->fileName();
+}
+
QString VConfigManager::getStyleConfigFolder() const
{
return getConfigFolder() + QDir::separator() + c_styleConfigFolder;
@@ -690,3 +699,85 @@ QString VConfigManager::getVnoteNotebookFolderPath()
{
return QDir::home().filePath(c_vnoteNotebookFolderName);
}
+
+bool VConfigManager::isValidKeySequence(const QString &p_seq)
+{
+ QString lower = p_seq.toLower();
+ return lower != "ctrl+q" && lower != "ctrl+e";
+}
+
+void VConfigManager::readShortcutsFromSettings()
+{
+ m_shortcuts.clear();
+ int size = defaultSettings->beginReadArray("shortcuts");
+ for (int i = 0; i < size; ++i) {
+ defaultSettings->setArrayIndex(i);
+ QString op = defaultSettings->value("operation").toString();
+ QString seq = defaultSettings->value("keysequence").toString().trimmed();
+
+ if (isValidKeySequence(seq)) {
+ qDebug() << "read shortcut config" << op << seq;
+ m_shortcuts[op] = seq;
+ }
+ }
+
+ defaultSettings->endArray();
+
+ // Whether we need to update user settings.
+ bool needUpdate = false;
+ size = userSettings->beginReadArray("shortcuts");
+ QSet matched;
+ matched.reserve(m_shortcuts.size());
+ for (int i = 0; i < size; ++i) {
+ userSettings->setArrayIndex(i);
+ QString op = userSettings->value("operation").toString();
+ QString seq = userSettings->value("keysequence").toString().trimmed();
+
+ if (isValidKeySequence(seq)) {
+ qDebug() << "read user shortcut config" << op << seq;
+ auto it = m_shortcuts.find(op);
+ if (it == m_shortcuts.end()) {
+ // Could not find this in default settings.
+ needUpdate = true;
+ } else {
+ matched.insert(op);
+ *it = seq;
+ }
+ }
+ }
+
+ userSettings->endArray();
+
+ if (needUpdate || matched.size() < m_shortcuts.size()) {
+ // Write the combined config to user settings.
+ writeShortcutsToSettings();
+ }
+}
+
+void VConfigManager::writeShortcutsToSettings()
+{
+ // Clear it first
+ userSettings->beginGroup("shortcuts");
+ userSettings->remove("");
+ userSettings->endGroup();
+
+ userSettings->beginWriteArray("shortcuts");
+ int idx = 0;
+ for (auto it = m_shortcuts.begin(); it != m_shortcuts.end(); ++it, ++idx) {
+ userSettings->setArrayIndex(idx);
+ userSettings->setValue("operation", it.key());
+ userSettings->setValue("keysequence", it.value());
+ }
+
+ userSettings->endArray();
+}
+
+QString VConfigManager::getShortcutKeySequence(const QString &p_operation) const
+{
+ auto it = m_shortcuts.find(p_operation);
+ if (it == m_shortcuts.end()) {
+ return QString();
+ }
+
+ return *it;
+}
diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h
index 048e77fa..4bc3c1e8 100644
--- a/src/vconfigmanager.h
+++ b/src/vconfigmanager.h
@@ -205,9 +205,16 @@ public:
const QString &getEditorLineNumberBg() const;
const QString &getEditorLineNumberFg() const;
+ // Return the configured key sequence of @p_operation.
+ // Return empty if there is no corresponding config.
+ QString getShortcutKeySequence(const QString &p_operation) const;
+
// Get the folder the ini file exists.
QString getConfigFolder() const;
+ // Get the ini config file path.
+ QString getConfigFilePath() const;
+
// Get the folder c_styleConfigFolder in the config folder.
QString getStyleConfigFolder() const;
@@ -248,6 +255,20 @@ private:
// the new one; if not, use the c_dirConfigFile.
static QString fetchDirConfigFilePath(const QString &p_path);
+ // Read the [shortcuts] section in settings to init m_shortcuts.
+ // Will remove invalid config items.
+ // First read the config in default settings;
+ // Second read the config in user settings and overwrite the default ones;
+ // If there is any config in deafult settings that is absent in user settings,
+ // write the combined configs to user settings.
+ void readShortcutsFromSettings();
+
+ // Write m_shortcuts to the [shortcuts] section in the user settings.
+ void writeShortcutsToSettings();
+
+ // Whether @p_seq is a valid key sequence for shortcuts.
+ bool isValidKeySequence(const QString &p_seq);
+
// Default font and palette.
QFont m_defaultEditFont;
QPalette m_defaultEditPalette;
@@ -379,6 +400,10 @@ private:
// The foreground color of the line number area.
QString m_editorLineNumberFg;
+ // Shortcuts config.
+ // Operation -> KeySequence.
+ QHash m_shortcuts;
+
// The name of the config file in each directory, obsolete.
// Use c_dirConfigFile instead.
static const QString c_obsoleteDirConfigFile;
diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp
index c76b8f63..077c0390 100644
--- a/src/vmainwindow.cpp
+++ b/src/vmainwindow.cpp
@@ -301,7 +301,9 @@ void VMainWindow::initFileToolBar()
newNoteAct = new QAction(QIcon(":/resources/icons/create_note_tb.svg"),
tr("New &Note"), this);
newNoteAct->setStatusTip(tr("Create a note in current folder"));
- newNoteAct->setShortcut(QKeySequence::New);
+ QString keySeq = vconfig.getShortcutKeySequence("NewNote");
+ qDebug() << "set NewNote shortcut to" << keySeq;
+ newNoteAct->setShortcut(QKeySequence(keySeq));
connect(newNoteAct, &QAction::triggered,
fileList, &VFileList::newFile);
@@ -317,10 +319,25 @@ void VMainWindow::initFileToolBar()
connect(deleteNoteAct, &QAction::triggered,
this, &VMainWindow::deleteCurNote);
+ m_closeNoteAct = new QAction(QIcon(":/resources/icons/close_note_tb.svg"),
+ tr("&Close Note"), this);
+ m_closeNoteAct->setStatusTip(tr("Close current note"));
+ keySeq = vconfig.getShortcutKeySequence("CloseNote");
+ qDebug() << "set CloseNote shortcut to" << keySeq;
+ m_closeNoteAct->setShortcut(QKeySequence(keySeq));
+ connect(m_closeNoteAct, &QAction::triggered,
+ this, [this](){
+ if (m_curFile) {
+ editArea->closeFile(m_curFile, false);
+ }
+ });
+
editNoteAct = new QAction(QIcon(":/resources/icons/edit_note.svg"),
tr("&Edit"), this);
editNoteAct->setStatusTip(tr("Edit current note"));
- editNoteAct->setShortcut(QKeySequence("Ctrl+W"));
+ keySeq = vconfig.getShortcutKeySequence("EditNote");
+ qDebug() << "set EditNote shortcut to" << keySeq;
+ editNoteAct->setShortcut(QKeySequence(keySeq));
connect(editNoteAct, &QAction::triggered,
editArea, &VEditArea::editFile);
@@ -339,21 +356,26 @@ void VMainWindow::initFileToolBar()
tr("Save Changes And Read (Ctrl+T)"), this);
saveExitAct->setStatusTip(tr("Save changes and exit edit mode"));
saveExitAct->setMenu(exitEditMenu);
- saveExitAct->setShortcut(QKeySequence("Ctrl+T"));
+ keySeq = vconfig.getShortcutKeySequence("SaveAndRead");
+ qDebug() << "set SaveAndRead shortcut to" << keySeq;
+ saveExitAct->setShortcut(QKeySequence(keySeq));
connect(saveExitAct, &QAction::triggered,
editArea, &VEditArea::saveAndReadFile);
saveNoteAct = new QAction(QIcon(":/resources/icons/save_note.svg"),
tr("Save"), this);
saveNoteAct->setStatusTip(tr("Save changes to current note"));
- saveNoteAct->setShortcut(QKeySequence::Save);
+ keySeq = vconfig.getShortcutKeySequence("SaveNote");
+ qDebug() << "set SaveNote shortcut to" << keySeq;
+ saveNoteAct->setShortcut(QKeySequence(keySeq));
connect(saveNoteAct, &QAction::triggered,
editArea, &VEditArea::saveFile);
newRootDirAct->setEnabled(false);
newNoteAct->setEnabled(false);
- noteInfoAct->setEnabled(false);
- deleteNoteAct->setEnabled(false);
+ noteInfoAct->setVisible(false);
+ deleteNoteAct->setVisible(false);
+ m_closeNoteAct->setVisible(false);
editNoteAct->setVisible(false);
saveExitAct->setVisible(false);
discardExitAct->setVisible(false);
@@ -361,9 +383,10 @@ void VMainWindow::initFileToolBar()
fileToolBar->addAction(newRootDirAct);
fileToolBar->addAction(newNoteAct);
+ fileToolBar->addSeparator();
fileToolBar->addAction(noteInfoAct);
fileToolBar->addAction(deleteNoteAct);
- fileToolBar->addSeparator();
+ fileToolBar->addAction(m_closeNoteAct);
fileToolBar->addAction(editNoteAct);
fileToolBar->addAction(saveExitAct);
fileToolBar->addAction(saveNoteAct);
@@ -634,6 +657,29 @@ void VMainWindow::initFileMenu()
fileMenu->addAction(settingsAct);
+ QAction *customShortcutAct = new QAction(tr("Custom Shortcuts"), this);
+ customShortcutAct->setToolTip(tr("Custom some standard shortcuts"));
+ connect(customShortcutAct, &QAction::triggered,
+ this, [this](){
+ int ret = VUtils::showMessage(QMessageBox::Information,
+ tr("Custom Shortcuts"),
+ tr("VNote supports customing some standard shorcuts by "
+ "editing user's configuration file (vnote.ini). Please "
+ "reference the shortcuts help documentation for more "
+ "information."),
+ tr("Click \"OK\" to custom shortcuts."),
+ QMessageBox::Ok | QMessageBox::Cancel,
+ QMessageBox::Ok,
+ this);
+
+ if (ret == QMessageBox::Ok) {
+ QUrl url = QUrl::fromLocalFile(vconfig.getConfigFilePath());
+ QDesktopServices::openUrl(url);
+ }
+ });
+
+ fileMenu->addAction(customShortcutAct);
+
fileMenu->addSeparator();
// Exit.
@@ -663,19 +709,25 @@ void VMainWindow::initEditMenu()
m_findReplaceAct = newAction(QIcon(":/resources/icons/find_replace.svg"),
tr("Find/Replace"), this);
m_findReplaceAct->setToolTip(tr("Open Find/Replace dialog to search in current note"));
- m_findReplaceAct->setShortcut(QKeySequence::Find);
+ QString keySeq = vconfig.getShortcutKeySequence("Find");
+ qDebug() << "set Find shortcut to" << keySeq;
+ m_findReplaceAct->setShortcut(QKeySequence(keySeq));
connect(m_findReplaceAct, &QAction::triggered,
this, &VMainWindow::openFindDialog);
m_findNextAct = new QAction(tr("Find Next"), this);
m_findNextAct->setToolTip(tr("Find next occurence"));
- m_findNextAct->setShortcut(QKeySequence::FindNext);
+ keySeq = vconfig.getShortcutKeySequence("FindNext");
+ qDebug() << "set FindNext shortcut to" << keySeq;
+ m_findNextAct->setShortcut(QKeySequence(keySeq));
connect(m_findNextAct, SIGNAL(triggered(bool)),
m_findReplaceDialog, SLOT(findNext()));
m_findPreviousAct = new QAction(tr("Find Previous"), this);
m_findPreviousAct->setToolTip(tr("Find previous occurence"));
- m_findPreviousAct->setShortcut(QKeySequence::FindPrevious);
+ keySeq = vconfig.getShortcutKeySequence("FindPrevious");
+ qDebug() << "set FindPrevious shortcut to" << keySeq;
+ m_findPreviousAct->setShortcut(QKeySequence(keySeq));
connect(m_findPreviousAct, SIGNAL(triggered(bool)),
m_findReplaceDialog, SLOT(findPrevious()));
@@ -1279,8 +1331,9 @@ void VMainWindow::updateActionStateFromTabStatusChange(const VFile *p_file,
discardExitAct->setVisible(p_file && p_editMode);
saveExitAct->setVisible(p_file && p_editMode);
saveNoteAct->setVisible(p_file && p_editMode);
- deleteNoteAct->setEnabled(p_file && p_file->isModifiable());
- noteInfoAct->setEnabled(p_file && p_file->getType() == FileType::Normal);
+ deleteNoteAct->setVisible(p_file && p_file->isModifiable());
+ noteInfoAct->setVisible(p_file && p_file->getType() == FileType::Normal);
+ m_closeNoteAct->setVisible(p_file);
m_insertImageAct->setEnabled(p_file && p_editMode);
diff --git a/src/vmainwindow.h b/src/vmainwindow.h
index 9bd534dc..f3624dd2 100644
--- a/src/vmainwindow.h
+++ b/src/vmainwindow.h
@@ -178,6 +178,7 @@ private:
QAction *newNoteAct;
QAction *noteInfoAct;
QAction *deleteNoteAct;
+ QAction *m_closeNoteAct;
QAction *editNoteAct;
QAction *saveNoteAct;
QAction *saveExitAct;
diff --git a/src/vnote.qrc b/src/vnote.qrc
index f6f58715..c0075ff6 100644
--- a/src/vnote.qrc
+++ b/src/vnote.qrc
@@ -115,5 +115,6 @@
resources/icons/italic.svg
resources/icons/strikethrough.svg
resources/icons/inline_code.svg
+ resources/icons/close_note_tb.svg