Răsfoiți Sursa

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

Le Tan 4 ani în urmă
părinte
comite
361bbc50b8

+ 1 - 1
README.md

@@ -5,7 +5,7 @@
 
 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)
 

+ 1 - 1
README_zh_CN.md

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

+ 1 - 1
libs/vtextedit

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

+ 4 - 0
src/core/coreconfig.h

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

+ 1 - 1
src/core/mainconfig.cpp

@@ -117,5 +117,5 @@ QString MainConfig::getVersion(const QJsonObject &p_jobj)
 void MainConfig::doVersionSpecificOverride()
 {
     // 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";
 }

+ 6 - 2
src/data/core/vnotex.json

@@ -19,7 +19,7 @@
             "OutlineDock" : "Ctrl+G, U",
             "SearchDock" : "",
             "SnippetDock" : "Ctrl+G, S",
-            "LocationListDock" : "Ctrl+G, L",
+            "LocationListDock" : "Ctrl+G, C",
             "Search" : "Ctrl+Alt+F",
             "NavigationMode" : "Ctrl+G, W",
             "LocateNode" : "Ctrl+G, D",
@@ -46,7 +46,11 @@
             "ActivateNextTab" : "Ctrl+G, N",
             "ActivatePreviousTab" : "Ctrl+G, P",
             "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,
         "note_management" : {

+ 1 - 1
src/data/extra/docs/en/about_vnotex.txt

@@ -1,5 +1,5 @@
 <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/>
 Source code of VNote could be found at <a href="https://github.com/vnotex/vnote">GitHub</a>.
 <br/><br/>

+ 2 - 1
src/data/extra/docs/en/shortcuts.md

@@ -1,6 +1,7 @@
 # Shortcuts
 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
 - `Ctrl+G E`  

+ 2 - 1
src/data/extra/docs/zh_CN/shortcuts.md

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

+ 4 - 0
src/data/extra/themes/moonlight/interface.qss

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

+ 3 - 0
src/data/extra/themes/moonlight/palette.json

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

+ 4 - 0
src/data/extra/themes/native/interface.qss

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

+ 3 - 0
src/data/extra/themes/native/palette.json

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

+ 4 - 0
src/data/extra/themes/pure/interface.qss

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

+ 3 - 0
src/data/extra/themes/pure/palette.json

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

+ 5 - 0
src/widgets/notebooknodeexplorer.cpp

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

+ 2 - 0
src/widgets/propertydefs.cpp

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

+ 2 - 0
src/widgets/propertydefs.h

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

+ 2 - 2
src/widgets/toolbarhelper.cpp

@@ -83,9 +83,9 @@ QToolBar *ToolBarHelper::setupFileToolBar(MainWindow *p_win, QToolBar *p_toolBar
                                emit VNoteX::getInst().importNotebookRequested();
                            });
 
-        // Import notebook of VNote 2.0.
+        // Import notebook of VNote 2.
         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,
                            []() {
                                emit VNoteX::getInst().importLegacyNotebookRequested();

+ 154 - 3
src/widgets/viewarea.cpp

@@ -12,13 +12,15 @@
 #include <QApplication>
 #include <QSet>
 #include <QHash>
+#include <QTabBar>
 
 #include "viewwindow.h"
 #include "mainwindow.h"
-#include "events.h"
+#include "propertydefs.h"
 #include <utils/widgetutils.h>
 #include <utils/docsutils.h>
 #include <utils/urldragdroputils.h>
+#include <core/events.h>
 #include <core/vnotex.h>
 #include <core/configmgr.h>
 #include <core/coreconfig.h>
@@ -314,7 +316,18 @@ void ViewArea::addFirstViewSplit()
 void ViewArea::postFirstViewSplit()
 {
     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();
     checkCurrentViewWindowChange();
@@ -372,7 +385,6 @@ void ViewArea::removeViewSplit(ViewSplit *p_split, bool p_removeWorkspace)
             unwrapSplitter(splitter);
         }
     } else {
-        Q_ASSERT(m_splits.isEmpty());
         m_mainLayout->removeWidget(p_split);
         if (!m_splits.isEmpty()) {
             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));
     if (p_split == m_currentSplit) {
+        if (p_split && p_focus) {
+            p_split->focus();
+        }
         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)
@@ -1232,3 +1291,95 @@ void ViewArea::openViewWindowFromSession(const ViewWindowSession &p_session)
 
     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);
+            });
+}

+ 14 - 0
src/widgets/viewarea.h

@@ -144,6 +144,14 @@ namespace vnotex
             Horizontal
         };
 
+        enum class Direction
+        {
+            Left,
+            Down,
+            Up,
+            Right
+        };
+
         void setupUI();
 
         // Find given @p_buffer among all view splits.
@@ -218,6 +226,12 @@ namespace vnotex
 
         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;
 
         QWidget *m_sceneWidget = nullptr;