From ab824946e8bbb418adbe8b429d54297b3dd43673 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Thu, 6 Apr 2017 19:39:52 +0800 Subject: [PATCH] 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; --- src/vcaptain.cpp | 1 + src/vdirectorytree.cpp | 19 +++-- src/veditarea.cpp | 76 ++++++++++++++++++- src/veditarea.h | 14 +++- src/vfilelist.cpp | 18 +++-- src/vmainwindow.cpp | 2 + src/vnote.cpp | 71 +++++++++++++----- src/vnote.h | 2 + src/vnotebookselector.cpp | 24 +++--- src/voutline.cpp | 149 +++++++++++++++++++++++++++++++++++++- src/voutline.h | 21 +++++- 11 files changed, 353 insertions(+), 44 deletions(-) diff --git a/src/vcaptain.cpp b/src/vcaptain.cpp index b01b4d0f..f0e19496 100644 --- a/src/vcaptain.cpp +++ b/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: diff --git a/src/vdirectorytree.cpp b/src/vdirectorytree.cpp index e64a87b0..d0ac7c81 100644 --- a/src/vdirectorytree.cpp +++ b/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; diff --git a/src/veditarea.cpp b/src/veditarea.cpp index c61f71d2..5d1d528b 100644 --- a/src/veditarea.cpp +++ b/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; +} + diff --git a/src/veditarea.h b/src/veditarea.h index 1663aeb7..f4b8a892 100644 --- a/src/veditarea.h +++ b/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 m_keyMap; + QVector m_naviLabels; }; inline VEditWindow* VEditArea::getWindow(int windowIndex) const diff --git a/src/vfilelist.cpp b/src/vfilelist.cpp index b7bb2c2b..6790ceae 100644 --- a/src/vfilelist.cpp +++ b/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; diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 44ade597..e5fe4d25 100644 --- a/src/vmainwindow.cpp +++ b/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() diff --git a/src/vnote.cpp b/src/vnote.cpp index 61462746..bc012286 100644 --- a/src/vnote.cpp +++ b/src/vnote.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include "vnote.h" #include "utils/vutils.h" #include "vconfigmanager.h" @@ -176,25 +178,56 @@ QVector &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 fontFamily = getMonospacedFont(); - 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; + 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; } diff --git a/src/vnote.h b/src/vnote.h index ce9f53dc..4f7bb6ce 100644 --- a/src/vnote.h +++ b/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 m_notebooks; QVector > m_palette; diff --git a/src/vnotebookselector.cpp b/src/vnotebookselector.cpp index 36c07665..cf6c5e09 100644 --- a/src/vnotebookselector.cpp +++ b/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; } diff --git a/src/voutline.cpp b/src/voutline.cpp index 07d1dfb0..e5bffc43 100644 --- a/src/voutline.cpp +++ b/src/voutline.cpp @@ -3,11 +3,17 @@ #include #include #include +#include +#include #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 VOutline::getVisibleItems() const +{ + QList 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 VOutline::getVisibleChildItems(const QTreeWidgetItem *p_item) const +{ + QList 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; +} diff --git a/src/voutline.h b/src/voutline.h index 465336fd..24012089 100644 --- a/src/voutline.h +++ b/src/voutline.h @@ -2,14 +2,26 @@ #define VOUTLINE_H #include +#include +#include +#include #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 getVisibleItems() const; + QList getVisibleChildItems(const QTreeWidgetItem *p_item) const; VToc outline; VAnchor curHeader; + + // Navigation Mode. + // Map second key to QTreeWidgetItem. + QMap m_keyMap; + QVector m_naviLabels; }; #endif // VOUTLINE_H