瀏覽代碼

register VEditArea and VOutline for Navigation Mode

1. Register VEditArea and VOutline for Navigation mode;
2. Support Ctrl+J and Ctrl+K navigation in VOutline;
Le Tan 8 年之前
父節點
當前提交
ab824946e8
共有 11 個文件被更改,包括 354 次插入45 次删除
  1. 1 0
      src/vcaptain.cpp
  2. 14 5
      src/vdirectorytree.cpp
  3. 75 1
      src/veditarea.cpp
  4. 13 1
      src/veditarea.h
  5. 13 5
      src/vfilelist.cpp
  6. 2 0
      src/vmainwindow.cpp
  7. 53 20
      src/vnote.cpp
  8. 2 0
      src/vnote.h
  9. 14 10
      src/vnotebookselector.cpp
  10. 147 2
      src/voutline.cpp
  11. 20 1
      src/voutline.h

+ 1 - 0
src/vcaptain.cpp

@@ -281,6 +281,7 @@ bool VCaptain::handleKeyPress(int p_key, Qt::KeyboardModifiers p_modifiers)
     case Qt::Key_W:
         // Enter navigation mode.
         triggerNavigationMode();
+        m_ignoreFocusChange = false;
         return ret;
 
     case Qt::Key_X:

+ 14 - 5
src/vdirectorytree.cpp

@@ -11,7 +11,8 @@
 extern VNote *g_vnote;
 
 VDirectoryTree::VDirectoryTree(VNote *vnote, QWidget *parent)
-    : QTreeWidget(parent), vnote(vnote), m_editArea(NULL)
+    : QTreeWidget(parent), VNavigationMode(),
+      vnote(vnote), m_editArea(NULL)
 {
     setColumnCount(1);
     setHeaderHidden(true);
@@ -720,6 +721,10 @@ void VDirectoryTree::showNavigation()
     }
     m_naviLabels.clear();
 
+    if (!isVisible()) {
+        return;
+    }
+
     // Generate labels for visible items.
     auto items = getVisibleItems();
     for (int i = 0; i < 26 && i < items.size(); ++i) {
@@ -752,17 +757,21 @@ bool VDirectoryTree::handleKeyNavigation(int p_key, bool &p_succeed)
     QChar keyChar = VUtils::keyToChar(p_key);
     if (secondKey && !keyChar.isNull()) {
         secondKey = false;
+        p_succeed = true;
+        ret = true;
         auto it = m_keyMap.find(keyChar);
         if (it != m_keyMap.end()) {
             setCurrentItem(it.value());
             setFocus();
-            p_succeed = true;
-            ret = true;
         }
     } else if (keyChar == m_majorKey) {
         // Major key pressed.
-        // Need second key.
-        secondKey = true;
+        // Need second key if m_keyMap is not empty.
+        if (m_keyMap.isEmpty()) {
+            p_succeed = true;
+        } else {
+            secondKey = true;
+        }
         ret = true;
     }
     return ret;

+ 75 - 1
src/veditarea.cpp

@@ -6,11 +6,14 @@
 #include "vconfigmanager.h"
 #include "vfile.h"
 #include "dialog/vfindreplacedialog.h"
+#include "utils/vutils.h"
 
 extern VConfigManager vconfig;
+extern VNote *g_vnote;
 
 VEditArea::VEditArea(VNote *vnote, QWidget *parent)
-    : QWidget(parent), vnote(vnote), curWindowIndex(-1)
+    : QWidget(parent), VNavigationMode(),
+      vnote(vnote), curWindowIndex(-1)
 {
     setupUI();
 
@@ -570,3 +573,74 @@ VEditWindow *VEditArea::getCurrentWindow() const
     }
     return getWindow(curWindowIndex);
 }
+
+void VEditArea::registerNavigation(QChar p_majorKey)
+{
+    m_majorKey = p_majorKey;
+    V_ASSERT(m_keyMap.empty());
+    V_ASSERT(m_naviLabels.empty());
+}
+
+void VEditArea::showNavigation()
+{
+    // Clean up.
+    m_keyMap.clear();
+    for (auto label : m_naviLabels) {
+        delete label;
+    }
+    m_naviLabels.clear();
+
+    if (!isVisible()) {
+        return;
+    }
+
+    // Generate labels for VEditWindow.
+    for (int i = 0; i < 26 && i < splitter->count(); ++i) {
+        QChar key('a' + i);
+        m_keyMap[key] = getWindow(i);
+
+        QString str = QString(m_majorKey) + key;
+        QLabel *label = new QLabel(str, this);
+        label->setStyleSheet(g_vnote->getNavigationLabelStyle(str));
+        label->move(getWindow(i)->geometry().topLeft());
+        label->show();
+        m_naviLabels.append(label);
+    }
+}
+
+void VEditArea::hideNavigation()
+{
+    m_keyMap.clear();
+    for (auto label : m_naviLabels) {
+        delete label;
+    }
+    m_naviLabels.clear();
+}
+
+bool VEditArea::handleKeyNavigation(int p_key, bool &p_succeed)
+{
+    static bool secondKey = false;
+    bool ret = false;
+    p_succeed = false;
+    QChar keyChar = VUtils::keyToChar(p_key);
+    if (secondKey && !keyChar.isNull()) {
+        secondKey = false;
+        p_succeed = true;
+        ret = true;
+        auto it = m_keyMap.find(keyChar);
+        if (it != m_keyMap.end()) {
+            setCurrentWindow(splitter->indexOf(it.value()), true);
+        }
+    } else if (keyChar == m_majorKey) {
+        // Major key pressed.
+        // Need second key if m_keyMap is not empty.
+        if (m_keyMap.isEmpty()) {
+            p_succeed = true;
+        } else {
+            secondKey = true;
+        }
+        ret = true;
+    }
+    return ret;
+}
+

+ 13 - 1
src/veditarea.h

@@ -13,13 +13,14 @@
 #include "vnotebook.h"
 #include "veditwindow.h"
 #include "vtoc.h"
+#include "vnavigationmode.h"
 
 class VNote;
 class VFile;
 class VDirectory;
 class VFindReplaceDialog;
 
-class VEditArea : public QWidget
+class VEditArea : public QWidget, public VNavigationMode
 {
     Q_OBJECT
 public:
@@ -50,6 +51,12 @@ public:
     void moveCurrentTabOneSplit(bool p_right);
     VEditWindow *getCurrentWindow() const;
 
+    // Implementations for VNavigationMode.
+    void registerNavigation(QChar p_majorKey);
+    void showNavigation();
+    void hideNavigation();
+    bool handleKeyNavigation(int p_key, bool &p_succeed);
+
 signals:
     void curTabStatusChanged(const VFile *p_file, const VEditTab *p_editTab, bool p_editMode);
     void outlineChanged(const VToc &toc);
@@ -101,6 +108,11 @@ private:
     // Splitter holding multiple split windows
     QSplitter *splitter;
     VFindReplaceDialog *m_findReplace;
+
+    // Navigation Mode.
+    // Map second key to VEditWindow.
+    QMap<QChar, VEditWindow *> m_keyMap;
+    QVector<QLabel *> m_naviLabels;
 };
 
 inline VEditWindow* VEditArea::getWindow(int windowIndex) const

+ 13 - 5
src/vfilelist.cpp

@@ -12,7 +12,7 @@
 extern VNote *g_vnote;
 
 VFileList::VFileList(QWidget *parent)
-    : QWidget(parent)
+    : QWidget(parent), VNavigationMode()
 {
     setupUI();
     initActions();
@@ -565,6 +565,10 @@ void VFileList::showNavigation()
     }
     m_naviLabels.clear();
 
+    if (!isVisible()) {
+        return;
+    }
+
     // Generate labels for visible items.
     auto items = getVisibleItems();
     for (int i = 0; i < 26 && i < items.size(); ++i) {
@@ -597,17 +601,21 @@ bool VFileList::handleKeyNavigation(int p_key, bool &p_succeed)
     QChar keyChar = VUtils::keyToChar(p_key);
     if (secondKey && !keyChar.isNull()) {
         secondKey = false;
+        p_succeed = true;
+        ret = true;
         auto it = m_keyMap.find(keyChar);
         if (it != m_keyMap.end()) {
             fileList->setCurrentItem(it.value());
             fileList->setFocus();
-            p_succeed = true;
-            ret = true;
         }
     } else if (keyChar == m_majorKey) {
         // Major key pressed.
-        // Need second key.
-        secondKey = true;
+        // Need second key if m_keyMap is not empty.
+        if (m_keyMap.isEmpty()) {
+            p_succeed = true;
+        } else {
+            secondKey = true;
+        }
         ret = true;
     }
     return ret;

+ 2 - 0
src/vmainwindow.cpp

@@ -53,6 +53,8 @@ void VMainWindow::initCaptain()
     m_captain->registerNavigationTarget(notebookSelector);
     m_captain->registerNavigationTarget(directoryTree);
     m_captain->registerNavigationTarget(fileList);
+    m_captain->registerNavigationTarget(editArea);
+    m_captain->registerNavigationTarget(outline);
 }
 
 void VMainWindow::setupUI()

+ 53 - 20
src/vnote.cpp

@@ -5,6 +5,8 @@
 #include <QDir>
 #include <QFont>
 #include <QFontMetrics>
+#include <QStringList>
+#include <QFontDatabase>
 #include "vnote.h"
 #include "utils/vutils.h"
 #include "vconfigmanager.h"
@@ -176,25 +178,56 @@ QVector<VNotebook *> &VNote::getNotebooks()
 
 QString VNote::getNavigationLabelStyle(const QString &p_str) const
 {
+    static int lastLen = -1;
+    static int pxWidth = 24;
     int fontPt = 15;
-    QString fontFamily("Monospace");
-    QFont font(fontFamily, fontPt);
-    font.setBold(true);
-    QFontMetrics fm(font);
-    int pxWidth = fm.width(p_str);
-
-    QString stylesheet = QString("background-color: %1;"
-                                 "color: %2;"
-                                 "font-size: %3pt;"
-                                 "font: bold;"
-                                 "font-family: %4;"
-                                 "border-radius: 3px;"
-                                 "min-width: %5px;"
-                                 "max-width: %5px;")
-                                 .arg(getColorFromPalette("logo-base"))
-                                 .arg(getColorFromPalette("logo-max"))
-                                 .arg(fontPt)
-                                 .arg(fontFamily)
-                                 .arg(pxWidth);
-    return stylesheet;
+    QString fontFamily = getMonospacedFont();
+
+    if (p_str.size() != lastLen) {
+        QFont font(fontFamily, fontPt);
+        font.setBold(true);
+        QFontMetrics fm(font);
+        pxWidth = fm.width(p_str);
+        lastLen = p_str.size();
+    }
+
+    return QString("background-color: %1;"
+                   "color: %2;"
+                   "font-size: %3pt;"
+                   "font: bold;"
+                   "font-family: %4;"
+                   "border-radius: 3px;"
+                   "min-width: %5px;"
+                   "max-width: %5px;")
+                   .arg(getColorFromPalette("logo-base"))
+                   .arg(getColorFromPalette("logo-max"))
+                   .arg(fontPt)
+                   .arg(fontFamily)
+                   .arg(pxWidth);
+}
+
+const QString &VNote::getMonospacedFont() const
+{
+    static QString font;
+    if (font.isNull()) {
+        QStringList candidates;
+        candidates << "Consolas" << "Monaco" << "Andale Mono" << "Monospace" << "Courier New";
+        QStringList availFamilies = QFontDatabase().families();
+
+        for (int i = 0; i < candidates.size(); ++i) {
+            QString family = candidates[i].trimmed().toLower();
+            for (int j = 0; j < availFamilies.size(); ++j) {
+                QString availFamily = availFamilies[j];
+                availFamily.remove(QRegExp("\\[.*\\]"));
+                if (family == availFamily.trimmed().toLower()) {
+                    font = availFamily;
+                    return font;
+                }
+            }
+        }
+
+        // Fallback to current font.
+        font = QFont().family();
+    }
+    return font;
 }

+ 2 - 0
src/vnote.h

@@ -60,6 +60,8 @@ public slots:
     void updateTemplate();
 
 private:
+    const QString &getMonospacedFont() const;
+
     // Maintain all the notebooks. Other holder should use QPointer.
     QVector<VNotebook *> m_notebooks;
     QVector<QPair<QString, QString> > m_palette;

+ 14 - 10
src/vnotebookselector.cpp

@@ -399,37 +399,41 @@ void VNotebookSelector::resizeListWidgetToContent()
 void VNotebookSelector::registerNavigation(QChar p_majorKey)
 {
     Q_ASSERT(!m_naviLabel);
-    qDebug() << "VNotebookSelector register for navigation key" << p_majorKey;
     m_majorKey = p_majorKey;
-
-    m_naviLabel = new QLabel(m_majorKey, this);
-    m_naviLabel->setStyleSheet(g_vnote->getNavigationLabelStyle(m_majorKey));
-    m_naviLabel->hide();
 }
 
 void VNotebookSelector::showNavigation()
 {
-    qDebug() << "VNotebookSelector show navigation";
+    if (!isVisible()) {
+        return;
+    }
+
+    V_ASSERT(!m_naviLabel);
+    m_naviLabel = new QLabel(m_majorKey, this);
+    m_naviLabel->setStyleSheet(g_vnote->getNavigationLabelStyle(m_majorKey));
     m_naviLabel->show();
 }
 
 void VNotebookSelector::hideNavigation()
 {
-    qDebug() << "VNotebookSelector hide navigation";
-    m_naviLabel->hide();
+    if (m_naviLabel) {
+        delete m_naviLabel;
+        m_naviLabel = NULL;
+    }
 }
 
 bool VNotebookSelector::handleKeyNavigation(int p_key, bool &p_succeed)
 {
-    qDebug() << "VNotebookSelector handle key navigation" << p_key;
     bool ret = false;
     p_succeed = false;
     QChar keyChar = VUtils::keyToChar(p_key);
     if (keyChar == m_majorKey) {
         // Hit.
         p_succeed = true;
-        showPopup();
         ret = true;
+        if (m_naviLabel) {
+            showPopup();
+        }
     }
     return ret;
 }

+ 147 - 2
src/voutline.cpp

@@ -3,11 +3,17 @@
 #include <QString>
 #include <QJsonObject>
 #include <QKeyEvent>
+#include <QLabel>
+#include <QCoreApplication>
 #include "voutline.h"
 #include "vtoc.h"
+#include "utils/vutils.h"
+#include "vnote.h"
+
+extern VNote *g_vnote;
 
 VOutline::VOutline(QWidget *parent)
-    : QTreeWidget(parent)
+    : QTreeWidget(parent), VNavigationMode()
 {
     setColumnCount(1);
     setHeaderHidden(true);
@@ -178,11 +184,150 @@ bool VOutline::selectLineNumberOne(QTreeWidgetItem *item, int lineNumber)
 
 void VOutline::keyPressEvent(QKeyEvent *event)
 {
-    if (event->key() == Qt::Key_Return) {
+    int key = event->key();
+    int modifiers = event->modifiers();
+
+    switch (key) {
+    case Qt::Key_Return:
+    {
         QTreeWidgetItem *item = currentItem();
         if (item) {
             item->setExpanded(!item->isExpanded());
         }
+        break;
     }
+
+    case Qt::Key_J:
+    {
+        if (modifiers == Qt::ControlModifier) {
+            event->accept();
+            QKeyEvent *downEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down,
+                                                 Qt::NoModifier);
+            QCoreApplication::postEvent(this, downEvent);
+            return;
+        }
+        break;
+    }
+
+    case Qt::Key_K:
+    {
+        if (modifiers == Qt::ControlModifier) {
+            event->accept();
+            QKeyEvent *upEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up,
+                                               Qt::NoModifier);
+            QCoreApplication::postEvent(this, upEvent);
+            return;
+        }
+        break;
+    }
+
+    default:
+        break;
+    }
+
     QTreeWidget::keyPressEvent(event);
 }
+
+void VOutline::registerNavigation(QChar p_majorKey)
+{
+    m_majorKey = p_majorKey;
+    V_ASSERT(m_keyMap.empty());
+    V_ASSERT(m_naviLabels.empty());
+}
+
+void VOutline::showNavigation()
+{
+    // Clean up.
+    m_keyMap.clear();
+    for (auto label : m_naviLabels) {
+        delete label;
+    }
+    m_naviLabels.clear();
+
+    if (!isVisible()) {
+        return;
+    }
+
+    // Generate labels for visible items.
+    auto items = getVisibleItems();
+    for (int i = 0; i < 26 && i < items.size(); ++i) {
+        QChar key('a' + i);
+        m_keyMap[key] = items[i];
+
+        QString str = QString(m_majorKey) + key;
+        QLabel *label = new QLabel(str, this);
+        label->setStyleSheet(g_vnote->getNavigationLabelStyle(str));
+        label->move(visualItemRect(items[i]).topLeft());
+        label->show();
+        m_naviLabels.append(label);
+    }
+}
+
+void VOutline::hideNavigation()
+{
+    m_keyMap.clear();
+    for (auto label : m_naviLabels) {
+        delete label;
+    }
+    m_naviLabels.clear();
+}
+
+bool VOutline::handleKeyNavigation(int p_key, bool &p_succeed)
+{
+    static bool secondKey = false;
+    bool ret = false;
+    p_succeed = false;
+    QChar keyChar = VUtils::keyToChar(p_key);
+    if (secondKey && !keyChar.isNull()) {
+        secondKey = false;
+        p_succeed = true;
+        ret = true;
+        auto it = m_keyMap.find(keyChar);
+        if (it != m_keyMap.end()) {
+            setCurrentItem(it.value());
+            setFocus();
+        }
+    } else if (keyChar == m_majorKey) {
+        // Major key pressed.
+        // Need second key if m_keyMap is not empty.
+        if (m_keyMap.isEmpty()) {
+            p_succeed = true;
+        } else {
+            secondKey = true;
+        }
+        ret = true;
+    }
+    return ret;
+}
+
+QList<QTreeWidgetItem *> VOutline::getVisibleItems() const
+{
+    QList<QTreeWidgetItem *> items;
+    for (int i = 0; i < topLevelItemCount(); ++i) {
+        QTreeWidgetItem *item = topLevelItem(i);
+        if (!item->isHidden()) {
+            items.append(item);
+            if (item->isExpanded()) {
+                items.append(getVisibleChildItems(item));
+            }
+        }
+    }
+    return items;
+}
+
+QList<QTreeWidgetItem *> VOutline::getVisibleChildItems(const QTreeWidgetItem *p_item) const
+{
+    QList<QTreeWidgetItem *> items;
+    if (p_item && !p_item->isHidden() && p_item->isExpanded()) {
+        for (int i = 0; i < p_item->childCount(); ++i) {
+            QTreeWidgetItem *child = p_item->child(i);
+            if (!child->isHidden()) {
+                items.append(child);
+                if (child->isExpanded()) {
+                    items.append(getVisibleChildItems(child));
+                }
+            }
+        }
+    }
+    return items;
+}

+ 20 - 1
src/voutline.h

@@ -2,14 +2,26 @@
 #define VOUTLINE_H
 
 #include <QTreeWidget>
+#include <QVector>
+#include <QMap>
+#include <QChar>
 #include "vtoc.h"
+#include "vnavigationmode.h"
 
-class VOutline : public QTreeWidget
+class QLabel;
+
+class VOutline : public QTreeWidget, public VNavigationMode
 {
     Q_OBJECT
 public:
     VOutline(QWidget *parent = 0);
 
+    // Implementations for VNavigationMode.
+    void registerNavigation(QChar p_majorKey);
+    void showNavigation();
+    void hideNavigation();
+    bool handleKeyNavigation(int p_key, bool &p_succeed);
+
 signals:
     void outlineItemActivated(const VAnchor &anchor);
 
@@ -32,9 +44,16 @@ private:
     bool selectAnchorOne(QTreeWidgetItem *item, const QString &anchor);
     void selectLineNumber(int lineNumber);
     bool selectLineNumberOne(QTreeWidgetItem *item, int lineNumber);
+    QList<QTreeWidgetItem *> getVisibleItems() const;
+    QList<QTreeWidgetItem *> getVisibleChildItems(const QTreeWidgetItem *p_item) const;
 
     VToc outline;
     VAnchor curHeader;
+
+    // Navigation Mode.
+    // Map second key to QTreeWidgetItem.
+    QMap<QChar, QTreeWidgetItem *> m_keyMap;
+    QVector<QLabel *> m_naviLabels;
 };
 
 #endif // VOUTLINE_H