ViewArea: add Ctrl+G, H/J/K/L to navigate through ViewSplits

This commit is contained in:
Le Tan 2021-07-14 19:36:39 +08:00
parent c13d1803ce
commit 361bbc50b8
21 changed files with 219 additions and 14 deletions

View File

@ -5,7 +5,7 @@
A pleasant note-taking platform. A pleasant note-taking platform.
For more information, please visit [**VNote's Home Page**](https://vnotex.github.io/vnote). For more information, please visit [**VNote's Home Page**](https://vnotex.github.io/vnote) or [Home Page on Gitee](https://tamlok.gitee.io/vnote).
![VNote](pics/vnote.png) ![VNote](pics/vnote.png)

View File

@ -5,7 +5,7 @@
一个舒适的笔记平台! 一个舒适的笔记平台!
更多信息,请访问 [VNote 主页](https://vnotex.github.io/vnote) 。 更多信息,请访问 [VNote 主页](https://tamlok.gitee.io/vnote) 或者 [Gitee 托管主页](https://tamlok.gitee.io/vnote) 。
![VNote](pics/vnote.png) ![VNote](pics/vnote.png)

@ -1 +1 @@
Subproject commit 83f131edfa70ffce125f4f7ac4f9f75bf5f03078 Subproject commit 34ad7467eb42b5d1d228228d875a7675814f222b

View File

@ -53,6 +53,10 @@ namespace vnotex
ActivatePreviousTab, ActivatePreviousTab,
FocusContentArea, FocusContentArea,
OpenWithDefaultProgram, OpenWithDefaultProgram,
OneSplitLeft,
OneSplitDown,
OneSplitUp,
OneSplitRight,
MaxShortcut MaxShortcut
}; };
Q_ENUM(Shortcut) Q_ENUM(Shortcut)

View File

@ -117,5 +117,5 @@ QString MainConfig::getVersion(const QJsonObject &p_jobj)
void MainConfig::doVersionSpecificOverride() void MainConfig::doVersionSpecificOverride()
{ {
// In a new version, we may want to change one value by force. // In a new version, we may want to change one value by force.
m_coreConfig->m_shortcuts[CoreConfig::Shortcut::SearchDock].clear(); m_coreConfig->m_shortcuts[CoreConfig::Shortcut::LocationListDock] = "Ctrl+G, C";
} }

View File

@ -19,7 +19,7 @@
"OutlineDock" : "Ctrl+G, U", "OutlineDock" : "Ctrl+G, U",
"SearchDock" : "", "SearchDock" : "",
"SnippetDock" : "Ctrl+G, S", "SnippetDock" : "Ctrl+G, S",
"LocationListDock" : "Ctrl+G, L", "LocationListDock" : "Ctrl+G, C",
"Search" : "Ctrl+Alt+F", "Search" : "Ctrl+Alt+F",
"NavigationMode" : "Ctrl+G, W", "NavigationMode" : "Ctrl+G, W",
"LocateNode" : "Ctrl+G, D", "LocateNode" : "Ctrl+G, D",
@ -46,7 +46,11 @@
"ActivateNextTab" : "Ctrl+G, N", "ActivateNextTab" : "Ctrl+G, N",
"ActivatePreviousTab" : "Ctrl+G, P", "ActivatePreviousTab" : "Ctrl+G, P",
"FocusContentArea" : "Ctrl+G, Y", "FocusContentArea" : "Ctrl+G, Y",
"OpenWithDefaultProgram" : "F9" "OpenWithDefaultProgram" : "F9",
"OneSplitLeft" : "Ctrl+G, H",
"OneSplitDown" : "Ctrl+G, J",
"OneSplitUp" : "Ctrl+G, K",
"OneSplitRight" : "Ctrl+G, L"
}, },
"toolbar_icon_size" : 16, "toolbar_icon_size" : 16,
"note_management" : { "note_management" : {

View File

@ -1,5 +1,5 @@
<p> <p>
VNoteX is designed to be a pleasant note-taking platform, refactored from VNote, which is an open source note-taking application for Markdown since 2016. VNote shares most of the code base with VNoteX since version 3 and continue to be open source. VNoteX is designed to be a pleasant note-taking platform, refactored from VNote, which is an open source note-taking application for Markdown since 2016. VNote shares most of the code base with VNoteX since version 3 and continues to be open source.
<br/><br/> <br/><br/>
Source code of VNote could be found at <a href="https://github.com/vnotex/vnote">GitHub</a>. Source code of VNote could be found at <a href="https://github.com/vnotex/vnote">GitHub</a>.
<br/><br/> <br/><br/>

View File

@ -1,6 +1,7 @@
# Shortcuts # Shortcuts
1. All the keys without special notice are **case insensitive**; 1. All the keys without special notice are **case insensitive**;
2. On macOS, `Ctrl` corresponds to `Command` except in Vi mode. 2. On macOS, `Ctrl` corresponds to `Command` except in Vi mode;
3. For a complete shortcuts list, please view the `vnotex.json` configuration file.
## General ## General
- `Ctrl+G E` - `Ctrl+G E`

View File

@ -1,6 +1,7 @@
# 快捷键 # 快捷键
1. 以下按键除特别说明外,都不区分大小写; 1. 以下按键除特别说明外,都不区分大小写;
2. 在 macOS 下,`Ctrl`对应于`Command`,在 Vi 模式下除外。 2. 在 macOS 下,`Ctrl`对应于`Command`,在 Vi 模式下除外;
3. 可以通过查看配置文件 `vnotex.json` 来获取一个完整的快捷键列表。
## 通用 ## 通用
- `Ctrl+G E` - `Ctrl+G E`

View File

@ -1126,6 +1126,10 @@ vnotex--ViewSplit QTabBar::tab:selected {
background-color: @widgets#viewsplit#tabbar#tab#selected#bg; background-color: @widgets#viewsplit#tabbar#tab#selected#bg;
} }
vnotex--ViewSplit QTabBar[ViewSplitFlash="true"]::tab:selected {
background-color: @widgets#viewsplit#flash#bg;
}
vte--VTextEdit { vte--VTextEdit {
border: none; border: none;
} }

View File

@ -252,6 +252,9 @@
"bg" : "@base#content#bg" "bg" : "@base#content#bg"
} }
} }
},
"flash" : {
"bg" : "@base#master#alt"
} }
}, },
"qmainwindow" : { "qmainwindow" : {

View File

@ -123,3 +123,7 @@ vnotex--MainWindow QLabel#MainWindowTipsLabel {
font-size: 18pt; font-size: 18pt;
font-weight: bold; font-weight: bold;
} }
vnotex--ViewSplit QTabBar[ViewSplitFlash="true"]::tab:selected {
background-color: @widgets#viewsplit#flash#bg;
}

View File

@ -103,6 +103,9 @@
"active" : { "active" : {
"fg" : "@base#icon#fg" "fg" : "@base#icon#fg"
} }
},
"flash" : {
"bg" : "@base#lighter#fg"
} }
}, },
"qmainwindow" : { "qmainwindow" : {

View File

@ -1126,6 +1126,10 @@ vnotex--ViewSplit QTabBar::tab:selected {
background-color: @widgets#viewsplit#tabbar#tab#selected#bg; background-color: @widgets#viewsplit#tabbar#tab#selected#bg;
} }
vnotex--ViewSplit QTabBar[ViewSplitFlash="true"]::tab:selected {
background-color: @widgets#viewsplit#flash#bg;
}
vte--VTextEdit { vte--VTextEdit {
border: none; border: none;
} }

View File

@ -248,6 +248,9 @@
"bg" : "@base#content#bg" "bg" : "@base#content#bg"
} }
} }
},
"flash" : {
"bg" : "@base#master#alt"
} }
}, },
"qmainwindow" : { "qmainwindow" : {

View File

@ -581,6 +581,11 @@ void NotebookNodeExplorer::updateNode(Node *p_node)
item->setExpanded(expanded); item->setExpanded(expanded);
} else { } else {
if (m_notebook->isRecycleBinNode(p_node) && !m_recycleBinNodeVisible) {
// No need to update.
return;
}
saveNotebookTreeState(false); saveNotebookTreeState(false);
generateNodeTree(); generateNodeTree();

View File

@ -12,6 +12,8 @@ const char *PropertyDefs::c_dialogCentralWidget = "DialogCentralWidget";
const char *PropertyDefs::c_viewSplitCornerWidget = "ViewSplitCornerWidget"; const char *PropertyDefs::c_viewSplitCornerWidget = "ViewSplitCornerWidget";
const char *PropertyDefs::c_viewSplitFlash = "ViewSplitFlash";
const char *PropertyDefs::c_state = "State"; const char *PropertyDefs::c_state = "State";
const char *PropertyDefs::c_viewWindowToolBar = "ViewWindowToolBar"; const char *PropertyDefs::c_viewWindowToolBar = "ViewWindowToolBar";

View File

@ -19,6 +19,8 @@ namespace vnotex
static const char *c_viewSplitCornerWidget; static const char *c_viewSplitCornerWidget;
static const char *c_viewSplitFlash;
static const char *c_viewWindowToolBar; static const char *c_viewWindowToolBar;
static const char *c_consoleTextEdit; static const char *c_consoleTextEdit;

View File

@ -83,9 +83,9 @@ QToolBar *ToolBarHelper::setupFileToolBar(MainWindow *p_win, QToolBar *p_toolBar
emit VNoteX::getInst().importNotebookRequested(); emit VNoteX::getInst().importNotebookRequested();
}); });
// Import notebook of VNote 2.0. // Import notebook of VNote 2.
btnMenu->addAction(generateIcon("import_notebook_of_vnote2.svg"), btnMenu->addAction(generateIcon("import_notebook_of_vnote2.svg"),
MainWindow::tr("Import Legacy Notebook Of VNote 2.0"), MainWindow::tr("Import Legacy Notebook Of VNote 2"),
btnMenu, btnMenu,
[]() { []() {
emit VNoteX::getInst().importLegacyNotebookRequested(); emit VNoteX::getInst().importLegacyNotebookRequested();

View File

@ -12,13 +12,15 @@
#include <QApplication> #include <QApplication>
#include <QSet> #include <QSet>
#include <QHash> #include <QHash>
#include <QTabBar>
#include "viewwindow.h" #include "viewwindow.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "events.h" #include "propertydefs.h"
#include <utils/widgetutils.h> #include <utils/widgetutils.h>
#include <utils/docsutils.h> #include <utils/docsutils.h>
#include <utils/urldragdroputils.h> #include <utils/urldragdroputils.h>
#include <core/events.h>
#include <core/vnotex.h> #include <core/vnotex.h>
#include <core/configmgr.h> #include <core/configmgr.h>
#include <core/coreconfig.h> #include <core/coreconfig.h>
@ -314,7 +316,18 @@ void ViewArea::addFirstViewSplit()
void ViewArea::postFirstViewSplit() void ViewArea::postFirstViewSplit()
{ {
Q_ASSERT(!m_splits.isEmpty()); Q_ASSERT(!m_splits.isEmpty());
setCurrentViewSplit(m_splits.first(), false); auto currentSplit = m_splits.first();
// Check if any split has focus. If there is any, then set it as current split.
auto focusWidget = QApplication::focusWidget();
if (focusWidget) {
for (const auto &split : m_splits) {
if (split == focusWidget || split->isAncestorOf(focusWidget)) {
currentSplit = split;
break;
}
}
}
setCurrentViewSplit(currentSplit, false);
emit viewSplitsCountChanged(); emit viewSplitsCountChanged();
checkCurrentViewWindowChange(); checkCurrentViewWindowChange();
@ -372,7 +385,6 @@ void ViewArea::removeViewSplit(ViewSplit *p_split, bool p_removeWorkspace)
unwrapSplitter(splitter); unwrapSplitter(splitter);
} }
} else { } else {
Q_ASSERT(m_splits.isEmpty());
m_mainLayout->removeWidget(p_split); m_mainLayout->removeWidget(p_split);
if (!m_splits.isEmpty()) { if (!m_splits.isEmpty()) {
newCurrentSplit = m_splits.first(); newCurrentSplit = m_splits.first();
@ -427,6 +439,9 @@ void ViewArea::setCurrentViewSplit(ViewSplit *p_split, bool p_focus)
{ {
Q_ASSERT(!p_split || m_splits.contains(p_split)); Q_ASSERT(!p_split || m_splits.contains(p_split));
if (p_split == m_currentSplit) { if (p_split == m_currentSplit) {
if (p_split && p_focus) {
p_split->focus();
}
return; return;
} }
@ -829,6 +844,50 @@ void ViewArea::setupShortcuts()
}); });
} }
} }
// OneSplitLeft.
{
auto shortcut = WidgetUtils::createShortcut(coreConfig.getShortcut(CoreConfig::OneSplitLeft), this);
if (shortcut) {
connect(shortcut, &QShortcut::activated,
this, [this]() {
focusSplitByDirection(Direction::Left);
});
}
}
// OneSplitDown.
{
auto shortcut = WidgetUtils::createShortcut(coreConfig.getShortcut(CoreConfig::OneSplitDown), this);
if (shortcut) {
connect(shortcut, &QShortcut::activated,
this, [this]() {
focusSplitByDirection(Direction::Down);
});
}
}
// OneSplitUp.
{
auto shortcut = WidgetUtils::createShortcut(coreConfig.getShortcut(CoreConfig::OneSplitUp), this);
if (shortcut) {
connect(shortcut, &QShortcut::activated,
this, [this]() {
focusSplitByDirection(Direction::Up);
});
}
}
// OneSplitRight.
{
auto shortcut = WidgetUtils::createShortcut(coreConfig.getShortcut(CoreConfig::OneSplitRight), this);
if (shortcut) {
connect(shortcut, &QShortcut::activated,
this, [this]() {
focusSplitByDirection(Direction::Right);
});
}
}
} }
bool ViewArea::close(Node *p_node, bool p_force) bool ViewArea::close(Node *p_node, bool p_force)
@ -1232,3 +1291,95 @@ void ViewArea::openViewWindowFromSession(const ViewWindowSession &p_session)
emit VNoteX::getInst().openFileRequested(p_session.m_bufferPath, paras); emit VNoteX::getInst().openFileRequested(p_session.m_bufferPath, paras);
} }
void ViewArea::focusSplitByDirection(Direction p_direction)
{
if (!m_currentSplit) {
return;
}
QWidget *widget = m_currentSplit;
auto targetSplitType = SplitType::Vertical;
if (p_direction == Direction::Up || p_direction == Direction::Down) {
targetSplitType = SplitType::Horizontal;
}
int splitIdx = 0;
QSplitter *targetSplitter = nullptr;
while (true) {
auto splitter = tryGetParentSplitter(widget);
if (!splitter) {
return;
}
if (checkSplitType(splitter) == targetSplitType) {
targetSplitter = splitter;
splitIdx = splitter->indexOf(widget);
break;
} else {
widget = splitter;
}
}
Q_ASSERT(targetSplitter);
switch (p_direction) {
case Direction::Left:
--splitIdx;
break;
case Direction::Right:
++splitIdx;
break;
case Direction::Up:
--splitIdx;
break;
case Direction::Down:
++splitIdx;
break;
}
if (splitIdx < 0 || splitIdx >= targetSplitter->count()) {
return;
}
auto targetWidget = targetSplitter->widget(splitIdx);
// Find first split from targetWidget.
while (true) {
auto splitter = dynamic_cast<QSplitter *>(targetWidget);
if (splitter) {
if (splitter->count() == 0) {
// Should not be an empty splitter.
Q_ASSERT(false);
return;
}
targetWidget = splitter->widget(0);
} else {
auto viewSplit = dynamic_cast<ViewSplit *>(targetWidget);
Q_ASSERT(viewSplit);
setCurrentViewSplit(viewSplit, true);
flashViewSplit(viewSplit);
break;
}
}
}
ViewArea::SplitType ViewArea::checkSplitType(const QSplitter *p_splitter) const
{
return p_splitter->orientation() == Qt::Horizontal ? SplitType::Vertical : SplitType::Horizontal;
}
void ViewArea::flashViewSplit(ViewSplit *p_split)
{
auto tabBar = p_split->tabBar();
if (!tabBar) {
return;
}
// Directly set the property of ViewSplit won't work.
WidgetUtils::setPropertyDynamically(tabBar, PropertyDefs::c_viewSplitFlash, true);
QTimer::singleShot(1000, tabBar, [tabBar]() {
WidgetUtils::setPropertyDynamically(tabBar, PropertyDefs::c_viewSplitFlash, false);
});
}

View File

@ -144,6 +144,14 @@ namespace vnotex
Horizontal Horizontal
}; };
enum class Direction
{
Left,
Down,
Up,
Right
};
void setupUI(); void setupUI();
// Find given @p_buffer among all view splits. // Find given @p_buffer among all view splits.
@ -218,6 +226,12 @@ namespace vnotex
void openViewWindowFromSession(const ViewWindowSession &p_session); void openViewWindowFromSession(const ViewWindowSession &p_session);
void focusSplitByDirection(Direction p_direction);
SplitType checkSplitType(const QSplitter *p_splitter) const;
void flashViewSplit(ViewSplit *p_split);
QLayout *m_mainLayout = nullptr; QLayout *m_mainLayout = nullptr;
QWidget *m_sceneWidget = nullptr; QWidget *m_sceneWidget = nullptr;