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;
This commit is contained in:
Le Tan 2017-04-06 19:39:52 +08:00
parent 611a695cb1
commit ab824946e8
11 changed files with 353 additions and 44 deletions

View File

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

View File

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

View File

@ -6,11 +6,14 @@
#include "vconfigmanager.h" #include "vconfigmanager.h"
#include "vfile.h" #include "vfile.h"
#include "dialog/vfindreplacedialog.h" #include "dialog/vfindreplacedialog.h"
#include "utils/vutils.h"
extern VConfigManager vconfig; extern VConfigManager vconfig;
extern VNote *g_vnote;
VEditArea::VEditArea(VNote *vnote, QWidget *parent) VEditArea::VEditArea(VNote *vnote, QWidget *parent)
: QWidget(parent), vnote(vnote), curWindowIndex(-1) : QWidget(parent), VNavigationMode(),
vnote(vnote), curWindowIndex(-1)
{ {
setupUI(); setupUI();
@ -570,3 +573,74 @@ VEditWindow *VEditArea::getCurrentWindow() const
} }
return getWindow(curWindowIndex); 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;
}

View File

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

View File

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

View File

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

View File

@ -5,6 +5,8 @@
#include <QDir> #include <QDir>
#include <QFont> #include <QFont>
#include <QFontMetrics> #include <QFontMetrics>
#include <QStringList>
#include <QFontDatabase>
#include "vnote.h" #include "vnote.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vconfigmanager.h" #include "vconfigmanager.h"
@ -176,25 +178,56 @@ QVector<VNotebook *> &VNote::getNotebooks()
QString VNote::getNavigationLabelStyle(const QString &p_str) const QString VNote::getNavigationLabelStyle(const QString &p_str) const
{ {
static int lastLen = -1;
static int pxWidth = 24;
int fontPt = 15; int fontPt = 15;
QString fontFamily("Monospace"); QString fontFamily = getMonospacedFont();
QFont font(fontFamily, fontPt);
font.setBold(true);
QFontMetrics fm(font);
int pxWidth = fm.width(p_str);
QString stylesheet = QString("background-color: %1;" if (p_str.size() != lastLen) {
"color: %2;" QFont font(fontFamily, fontPt);
"font-size: %3pt;" font.setBold(true);
"font: bold;" QFontMetrics fm(font);
"font-family: %4;" pxWidth = fm.width(p_str);
"border-radius: 3px;" lastLen = p_str.size();
"min-width: %5px;" }
"max-width: %5px;")
.arg(getColorFromPalette("logo-base")) return QString("background-color: %1;"
.arg(getColorFromPalette("logo-max")) "color: %2;"
.arg(fontPt) "font-size: %3pt;"
.arg(fontFamily) "font: bold;"
.arg(pxWidth); "font-family: %4;"
return stylesheet; "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;
} }

View File

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

View File

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

View File

@ -3,11 +3,17 @@
#include <QString> #include <QString>
#include <QJsonObject> #include <QJsonObject>
#include <QKeyEvent> #include <QKeyEvent>
#include <QLabel>
#include <QCoreApplication>
#include "voutline.h" #include "voutline.h"
#include "vtoc.h" #include "vtoc.h"
#include "utils/vutils.h"
#include "vnote.h"
extern VNote *g_vnote;
VOutline::VOutline(QWidget *parent) VOutline::VOutline(QWidget *parent)
: QTreeWidget(parent) : QTreeWidget(parent), VNavigationMode()
{ {
setColumnCount(1); setColumnCount(1);
setHeaderHidden(true); setHeaderHidden(true);
@ -178,11 +184,150 @@ bool VOutline::selectLineNumberOne(QTreeWidgetItem *item, int lineNumber)
void VOutline::keyPressEvent(QKeyEvent *event) 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(); QTreeWidgetItem *item = currentItem();
if (item) { if (item) {
item->setExpanded(!item->isExpanded()); 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); 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;
}

View File

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