diff --git a/src/src.pro b/src/src.pro index 97b8e60d..552d193e 100644 --- a/src/src.pro +++ b/src/src.pro @@ -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 \ diff --git a/src/vconstants.h b/src/vconstants.h index d78f37df..ad8e710b 100644 --- a/src/vconstants.h +++ b/src/vconstants.h @@ -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 diff --git a/src/veditwindow.cpp b/src/veditwindow.cpp index 8d22eaff..42b78fe0 100644 --- a/src/veditwindow.cpp +++ b/src/veditwindow.cpp @@ -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 file = action->data().value>(); - 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 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 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(widget(tabIndex)); +} + diff --git a/src/veditwindow.h b/src/veditwindow.h index 6265f301..7a265a9c 100644 --- a/src/veditwindow.h +++ b/src/veditwindow.h @@ -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(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); } diff --git a/src/vnote.cpp b/src/vnote.cpp index 73c298db..0eb36276 100644 --- a/src/vnote.cpp +++ b/src/vnote.cpp @@ -52,6 +52,7 @@ void VNote::initPalette(QPalette palette) m_palette.append(QPair("Teal2", "#80CBC4")); m_palette.append(QPair("Teal3", "#4DB6AC")); m_palette.append(QPair("Teal4", "#26A69A")); + m_palette.append(QPair("Teal5", "#009688")); m_palette.append(QPair("Indigo0", "#E8EAF6")); m_palette.append(QPair("Indigo1", "#C5CAE9")); diff --git a/src/vopenedlistmenu.cpp b/src/vopenedlistmenu.cpp new file mode 100644 index 00000000..a2f8471d --- /dev/null +++ b/src/vopenedlistmenu.cpp @@ -0,0 +1,285 @@ +#include "vopenedlistmenu.h" +#include +#include +#include +#include +#include +#include +#include + +#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 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 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 file = p_action->data().value>(); + 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 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 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(); + } +} diff --git a/src/vopenedlistmenu.h b/src/vopenedlistmenu.h new file mode 100644 index 00000000..2efc02e5 --- /dev/null +++ b/src/vopenedlistmenu.h @@ -0,0 +1,48 @@ +#ifndef VOPENEDLISTMENU_H +#define VOPENEDLISTMENU_H + +#include +#include + +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 m_seqActionMap; +}; + +#endif // VOPENEDLISTMENU_H