refine tab list menu by adding VOpenedListMenu

1. Group opened files by notebook and directory;
2. Type the sequence number to activate a certain item in the popup menu;
3. Ctrl+[, Ctrl+J, Ctrl+K
This commit is contained in:
Le Tan 2017-04-03 14:34:31 +08:00
parent 0a91037f71
commit f7f4bb1569
7 changed files with 358 additions and 57 deletions

View File

@ -56,7 +56,8 @@ SOURCES += main.cpp\
dialog/vsettingsdialog.cpp \
dialog/vdeletenotebookdialog.cpp \
dialog/vselectdialog.cpp \
vcaptain.cpp
vcaptain.cpp \
vopenedlistmenu.cpp
HEADERS += vmainwindow.h \
vdirectorytree.h \
@ -98,7 +99,8 @@ HEADERS += vmainwindow.h \
dialog/vsettingsdialog.h \
dialog/vdeletenotebookdialog.h \
dialog/vselectdialog.h \
vcaptain.h
vcaptain.h \
vopenedlistmenu.h
RESOURCES += \
vnote.qrc \

View File

@ -8,4 +8,5 @@ enum class OpenFileMode {Read = 0, Edit};
static const qreal c_webZoomFactorMax = 5;
static const qreal c_webZoomFactorMin = 0.25;
static const int c_tabSequenceBase = 1;
#endif

View File

@ -8,6 +8,7 @@
#include "vfile.h"
#include "vmainwindow.h"
#include "veditarea.h"
#include "vopenedlistmenu.h"
extern VConfigManager vconfig;
@ -63,17 +64,14 @@ void VEditWindow::initTabActions()
void VEditWindow::setupCornerWidget()
{
// Left corner button
tabListAct = new QActionGroup(this);
connect(tabListAct, &QActionGroup::triggered,
this, &VEditWindow::tabListJump);
leftBtn = new QPushButton(QIcon(":/resources/icons/corner_tablist.svg"),
"", this);
leftBtn->setProperty("CornerBtn", true);
QMenu *leftMenu = new QMenu(this);
VOpenedListMenu *leftMenu = new VOpenedListMenu(this);
connect(leftMenu, &VOpenedListMenu::fileTriggered,
this, &VEditWindow::tabListJump);
leftBtn->setMenu(leftMenu);
setCornerWidget(leftBtn, Qt::TopLeftCorner);
connect(leftMenu, &QMenu::aboutToShow,
this, &VEditWindow::updateTabListMenu);
// Right corner button
// Actions
@ -453,49 +451,18 @@ void VEditWindow::tabbarContextMenuRequested(QPoint p_pos)
menu.exec(bar->mapToGlobal(p_pos));
}
void VEditWindow::tabListJump(QAction *action)
void VEditWindow::tabListJump(VFile *p_file)
{
if (!action) {
if (!p_file) {
return;
}
QPointer<VFile> file = action->data().value<QPointer<VFile>>();
int idx = findTabByFile(file);
int idx = findTabByFile(p_file);
Q_ASSERT(idx >= 0);
setCurrentIndex(idx);
noticeStatus(idx);
}
void VEditWindow::updateTabListMenu()
{
// Re-generate the tab list menu
QMenu *menu = leftBtn->menu();
QList<QAction *> actions = menu->actions();
int nrActions = actions.size();
for (int i = 0; i < nrActions; ++i) {
QAction *tmpAct = actions.at(i);
menu->removeAction(tmpAct);
tabListAct->removeAction(tmpAct);
delete tmpAct;
}
int curTab = currentIndex();
int nrTab = count();
for (int i = 0; i < nrTab; ++i) {
VEditTab *editor = getTab(i);
QPointer<VFile> file = editor->getFile();
QAction *action = new QAction(tabIcon(i), tabText(i), tabListAct);
action->setStatusTip(generateTooltip(file));
action->setData(QVariant::fromValue(file));
if (i == curTab) {
QFont font;
font.setBold(true);
action->setFont(font);
}
menu->addAction(action);
}
}
void VEditWindow::updateSplitMenu()
{
if (canRemoveSplit()) {
@ -757,11 +724,10 @@ bool VEditWindow::showOpenedFileList()
bool VEditWindow::activateTab(int p_sequence)
{
const int base = 1;
if (p_sequence < base || p_sequence >= (base + count())) {
if (p_sequence < c_tabSequenceBase || p_sequence >= (c_tabSequenceBase + count())) {
return false;
}
setCurrentIndex(p_sequence - base);
setCurrentIndex(p_sequence - c_tabSequenceBase);
return true;
}
@ -777,3 +743,9 @@ bool VEditWindow::alternateTab()
}
return false;
}
VEditTab* VEditWindow::getTab(int tabIndex) const
{
return dynamic_cast<VEditTab *>(widget(tabIndex));
}

View File

@ -9,6 +9,7 @@
#include "vnotebook.h"
#include "vedittab.h"
#include "vtoc.h"
#include "vconstants.h"
class VNote;
class QPushButton;
@ -53,6 +54,7 @@ public:
bool activateTab(int p_sequence);
// Switch to previous activated tab.
bool alternateTab();
VEditTab *getTab(int tabIndex) const;
protected:
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
@ -73,11 +75,10 @@ private slots:
void handleTabbarClicked(int p_index);
void handleCurrentIndexChanged(int p_index);
void contextMenuRequested(QPoint pos);
void tabListJump(QAction *action);
void tabListJump(VFile *p_file);
void handleOutlineChanged(const VToc &p_toc);
void handleCurHeaderChanged(const VAnchor &p_anchor);
void handleTabStatusChanged();
void updateTabListMenu();
void updateSplitMenu();
void tabbarContextMenuRequested(QPoint p_pos);
void handleLocateAct();
@ -91,7 +92,6 @@ private:
int insertEditTab(int p_index, VFile *p_file, QWidget *p_page);
int appendEditTab(VFile *p_file, QWidget *p_page);
int openFileInTab(VFile *p_file, OpenFileMode p_mode);
inline VEditTab *getTab(int tabIndex) const;
void noticeTabStatus(int p_index);
void noticeStatus(int index);
inline QString generateTooltip(const VFile *p_file) const;
@ -118,18 +118,12 @@ private:
// Actions
QAction *splitAct;
QAction *removeSplitAct;
QActionGroup *tabListAct;
// Locate current note in the directory and file list
QAction *m_locateAct;
QAction *m_moveLeftAct;
QAction *m_moveRightAct;
};
inline VEditTab* VEditWindow::getTab(int tabIndex) const
{
return dynamic_cast<VEditTab *>(widget(tabIndex));
}
inline QString VEditWindow::generateTooltip(const VFile *p_file) const
{
if (!p_file) {
@ -142,9 +136,7 @@ inline QString VEditWindow::generateTooltip(const VFile *p_file) const
inline QString VEditWindow::generateTabText(int p_index, const QString &p_name,
bool p_modified) const
{
// Based on 1.
const int base = 1;
QString seq = QString::number(p_index + base, 10);
QString seq = QString::number(p_index + c_tabSequenceBase, 10);
return seq + ". " + (p_modified ? (p_name + "*") : p_name);
}

View File

@ -52,6 +52,7 @@ void VNote::initPalette(QPalette palette)
m_palette.append(QPair<QString, QString>("Teal2", "#80CBC4"));
m_palette.append(QPair<QString, QString>("Teal3", "#4DB6AC"));
m_palette.append(QPair<QString, QString>("Teal4", "#26A69A"));
m_palette.append(QPair<QString, QString>("Teal5", "#009688"));
m_palette.append(QPair<QString, QString>("Indigo0", "#E8EAF6"));
m_palette.append(QPair<QString, QString>("Indigo1", "#C5CAE9"));

285
src/vopenedlistmenu.cpp Normal file
View File

@ -0,0 +1,285 @@
#include "vopenedlistmenu.h"
#include <QActionGroup>
#include <QAction>
#include <QFont>
#include <QVector>
#include <QTimer>
#include <QString>
#include <QStyleFactory>
#include "veditwindow.h"
#include "vfile.h"
#include "vedittab.h"
#include "vdirectory.h"
#include "utils/vutils.h"
static const int c_cmdTime = 1 * 1000;
static bool fileComp(const VOpenedListMenu::ItemInfo &a,
const VOpenedListMenu::ItemInfo &b)
{
QString notebooka = a.file->getNotebookName().toLower();
QString notebookb = b.file->getNotebookName().toLower();
if (notebooka < notebookb) {
return true;
} else if (notebooka > notebookb) {
return false;
} else {
QString patha = a.file->retriveRelativePath().toLower();
QString pathb = b.file->retriveRelativePath().toLower();
return patha < pathb;
}
}
VOpenedListMenu::VOpenedListMenu(VEditWindow *p_editWin)
: QMenu(p_editWin), m_editWin(p_editWin), m_cmdNum(0)
{
// Force to display separator text on Windows and macOS.
setStyle(QStyleFactory::create("Fusion"));
setStyleSheet("::separator { color: #009688; height: 15px; padding-top: 5px; }");
setToolTipsVisible(true);
m_cmdTimer = new QTimer(this);
m_cmdTimer->setSingleShot(true);
m_cmdTimer->setInterval(c_cmdTime);
connect(m_cmdTimer, &QTimer::timeout,
this, &VOpenedListMenu::cmdTimerTimeout);
connect(this, &QMenu::aboutToShow,
this, &VOpenedListMenu::updateOpenedList);
connect(this, &QMenu::triggered,
this, &VOpenedListMenu::handleItemTriggered);
}
void VOpenedListMenu::updateOpenedList()
{
// Regenerate the opened list.
m_seqActionMap.clear();
clear();
int curTab = m_editWin->currentIndex();
int nrTab = m_editWin->count();
QVector<ItemInfo> files(nrTab);
for (int i = 0; i < nrTab; ++i) {
files[i].file = m_editWin->getTab(i)->getFile();
files[i].index = i;
}
std::sort(files.begin(), files.end(), fileComp);
QString notebook;
const VDirectory *directory = NULL;
QFont sepFont;
sepFont.setItalic(true);
for (int i = 0; i < nrTab; ++i) {
QPointer<VFile> file = files[i].file;
int index = files[i].index;
// Whether add separator.
QString curNotebook = file->getNotebookName();
if (curNotebook != notebook || file->getDirectory() != directory) {
notebook = curNotebook;
directory = file->getDirectory();
QString text = QString("[%1] %2").arg(notebook).arg(directory->getName());
QAction *sepAct = addSection(text);
sepAct->setFont(sepFont);
}
QAction *action = new QAction(m_editWin->tabIcon(index),
m_editWin->tabText(index));
action->setToolTip(generateDescription(file));
action->setData(QVariant::fromValue(file));
if (index == curTab) {
QFont boldFont;
boldFont.setBold(true);
action->setFont(boldFont);
}
addAction(action);
m_seqActionMap[index + c_tabSequenceBase] = action;
}
}
QString VOpenedListMenu::generateDescription(const VFile *p_file) const
{
if (!p_file) {
return "";
}
// [Notebook]path
return QString("[%1] %2").arg(p_file->getNotebookName()).arg(p_file->retrivePath());
}
void VOpenedListMenu::handleItemTriggered(QAction *p_action)
{
if (!p_action) {
return;
}
QPointer<VFile> file = p_action->data().value<QPointer<VFile>>();
emit fileTriggered(file);
}
void VOpenedListMenu::keyPressEvent(QKeyEvent *p_event)
{
int key = p_event->key();
int modifiers = p_event->modifiers();
switch (key) {
case Qt::Key_0:
case Qt::Key_1:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_4:
case Qt::Key_5:
case Qt::Key_6:
case Qt::Key_7:
case Qt::Key_8:
case Qt::Key_9:
{
addDigit(key - Qt::Key_0);
return;
}
case Qt::Key_BracketLeft:
{
m_cmdTimer->stop();
m_cmdNum = 0;
if (modifiers == Qt::ControlModifier) {
hide();
return;
}
break;
}
case Qt::Key_J:
{
m_cmdTimer->stop();
m_cmdNum = 0;
if (modifiers == Qt::ControlModifier) {
QList<QAction *> acts = actions();
if (acts.size() == 0) {
return;
}
int idx = 0;
QAction *act = activeAction();
if (act) {
for (int i = 0; i < acts.size(); ++i) {
if (acts.at(i) == act) {
idx = i + 1;
break;
}
}
}
while (true) {
if (idx >= acts.size()) {
idx = 0;
}
act = acts.at(idx);
if (act->isSeparator() || !act->isVisible()) {
++idx;
} else {
break;
}
}
setActiveAction(act);
return;
}
break;
}
case Qt::Key_K:
{
m_cmdTimer->stop();
m_cmdNum = 0;
if (modifiers == Qt::ControlModifier) {
QList<QAction *> acts = actions();
if (acts.size() == 0) {
return;
}
int idx = acts.size() - 1;
QAction *act = activeAction();
if (act) {
for (int i = 0; i < acts.size(); ++i) {
if (acts.at(i) == act) {
idx = i - 1;
break;
}
}
}
while (true) {
if (idx < 0) {
idx = acts.size() - 1;
}
act = acts.at(idx);
if (act->isSeparator() || !act->isVisible()) {
--idx;
} else {
break;
}
}
setActiveAction(act);
return;
}
break;
}
default:
m_cmdTimer->stop();
m_cmdNum = 0;
break;
}
QMenu::keyPressEvent(p_event);
}
void VOpenedListMenu::cmdTimerTimeout()
{
if (m_cmdNum > 0) {
triggerItem(m_cmdNum);
m_cmdNum = 0;
}
}
void VOpenedListMenu::addDigit(int p_digit)
{
V_ASSERT(p_digit >= 0 && p_digit <= 9);
m_cmdTimer->stop();
m_cmdNum = m_cmdNum * 10 + p_digit;
int totalItem = m_seqActionMap.size();
// Try to trigger it ASAP.
if (m_cmdNum > 0) {
if (getNumOfDigit(m_cmdNum) == getNumOfDigit(totalItem)) {
triggerItem(m_cmdNum);
m_cmdNum = 0;
return;
}
// Set active action to the candidate.
auto it = m_seqActionMap.find(m_cmdNum);
if (it != m_seqActionMap.end()) {
QAction *act = it.value();
setActiveAction(act);
}
}
m_cmdTimer->start();
}
int VOpenedListMenu::getNumOfDigit(int p_num)
{
int nrDigit = 1;
while (true) {
p_num /= 10;
if (p_num == 0) {
return nrDigit;
} else {
++nrDigit;
}
}
}
void VOpenedListMenu::triggerItem(int p_seq)
{
auto it = m_seqActionMap.find(p_seq);
if (it != m_seqActionMap.end()) {
QAction *act = it.value();
act->trigger();
hide();
}
}

48
src/vopenedlistmenu.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef VOPENEDLISTMENU_H
#define VOPENEDLISTMENU_H
#include <QMenu>
#include <QMap>
class VEditWindow;
class VFile;
class QAction;
class QKeyEvent;
class QTimer;
class VOpenedListMenu : public QMenu
{
Q_OBJECT
public:
struct ItemInfo {
VFile *file;
int index;
};
explicit VOpenedListMenu(VEditWindow *p_editWin);
protected:
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
signals:
void fileTriggered(VFile *p_file);
private slots:
void updateOpenedList();
void handleItemTriggered(QAction *p_action);
void cmdTimerTimeout();
private:
QString generateDescription(const VFile *p_file) const;
void addDigit(int p_digit);
int getNumOfDigit(int p_num);
void triggerItem(int p_seq);
VEditWindow *m_editWin;
// The number user pressed.
int m_cmdNum;
QTimer *m_cmdTimer;
QMap<int, QAction*> m_seqActionMap;
};
#endif // VOPENEDLISTMENU_H