diff --git a/src/resources/icons/current_tab.svg b/src/resources/icons/arrow_dropup.svg similarity index 50% rename from src/resources/icons/current_tab.svg rename to src/resources/icons/arrow_dropup.svg index b51f8169..5cdfca0c 100644 --- a/src/resources/icons/current_tab.svg +++ b/src/resources/icons/arrow_dropup.svg @@ -2,7 +2,8 @@ - + width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve"> + + + diff --git a/src/resources/vnote.qss b/src/resources/vnote.qss index 75cb0a16..1a26ad5e 100644 --- a/src/resources/vnote.qss +++ b/src/resources/vnote.qss @@ -17,6 +17,22 @@ QPushButton[CornerBtn="true"]::focus { background-color: @focus-color; } +QPushButton[StatusBtn="true"] { + font: bold; + padding: 0px 2px 0px 2px; + margin: 0px; + border: none; + background-color: transparent; +} + +QPushButton[StatusBtn="true"]::hover { + background-color: @hover-color; +} + +QPushButton[StatusBtn="true"]::focus { + background-color: @focus-color; +} + QPushButton[FlatBtn="true"] { padding: 4px; margin: 0px; diff --git a/src/src.pro b/src/src.pro index f1bae6f3..db15b44e 100644 --- a/src/src.pro +++ b/src/src.pro @@ -66,7 +66,9 @@ SOURCES += main.cpp\ vmdtab.cpp \ vhtmltab.cpp \ utils/vvim.cpp \ - utils/veditutils.cpp + utils/veditutils.cpp \ + vvimindicator.cpp \ + vbuttonwithwidget.cpp HEADERS += vmainwindow.h \ vdirectorytree.h \ @@ -119,7 +121,9 @@ HEADERS += vmainwindow.h \ vmdtab.h \ vhtmltab.h \ utils/vvim.h \ - utils/veditutils.h + utils/veditutils.h \ + vvimindicator.h \ + vbuttonwithwidget.h RESOURCES += \ vnote.qrc \ diff --git a/src/utils/vvim.cpp b/src/utils/vvim.cpp index a9c22998..3a7197ca 100644 --- a/src/utils/vvim.cpp +++ b/src/utils/vvim.cpp @@ -290,12 +290,16 @@ bool VVim::handleKeyPressEvent(QKeyEvent *p_event) goto accept; } + m_pendingKeys.append(keyInfo); + if (expectingRegisterName()) { // Expecting a register name. QChar reg = keyToRegisterName(keyInfo); if (!reg.isNull()) { - resetState(); - m_regName = reg; + // We should keep m_pendingKeys. + m_keys.clear(); + m_tokens.clear(); + setRegister(reg); if (m_registers[reg].isNamedRegister()) { m_registers[reg].m_append = (modifiers == Qt::ShiftModifier); } else { @@ -1281,6 +1285,7 @@ accept: exit: m_resetPositionInBlock = resetPositionInBlock; + emit vimStatusUpdated(this); return ret; } @@ -1288,7 +1293,8 @@ void VVim::resetState() { m_keys.clear(); m_tokens.clear(); - m_regName = c_unnamedRegister; + m_pendingKeys.clear(); + setRegister(c_unnamedRegister); m_resetPositionInBlock = true; } @@ -1305,6 +1311,7 @@ void VVim::setMode(VimMode p_mode) resetState(); emit modeChanged(m_mode); + emit vimStatusUpdated(this); } } @@ -3380,3 +3387,28 @@ void VVim::message(const QString &p_msg) qDebug() << "vim msg:" << p_msg; emit vimMessage(p_msg); } + +const QMap &VVim::getRegisters() const +{ + return m_registers; +} + +QChar VVim::getCurrentRegisterName() const +{ + return m_regName; +} + +QString VVim::getPendingKeys() const +{ + QString str; + for (auto const & key : m_pendingKeys) { + str.append(keyToChar(key.m_key, key.m_modifiers)); + } + + return str; +} + +void VVim::setRegister(QChar p_reg) +{ + m_regName = p_reg; +} diff --git a/src/utils/vvim.h b/src/utils/vvim.h index 51537c0e..0ea24415 100644 --- a/src/utils/vvim.h +++ b/src/utils/vvim.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include "vutils.h" @@ -28,6 +28,69 @@ class VVim : public QObject public: explicit VVim(VEdit *p_editor); + struct Register + { + Register(QChar p_name, const QString &p_value) + : m_name(p_name), m_value(p_value), m_append(false) + { + } + + Register(QChar p_name) + : m_name(p_name), m_append(false) + { + } + + Register() + : m_append(false) + { + } + + // Register a-z. + bool isNamedRegister() const + { + char ch = m_name.toLatin1(); + return ch >= 'a' && ch <= 'z'; + } + + bool isUnnamedRegister() const + { + return m_name == c_unnamedRegister; + } + + bool isBlackHoleRegister() const + { + return m_name == c_blackHoleRegister; + } + + bool isSelectionRegister() const + { + return m_name == c_selectionRegister; + } + + bool isBlock() const + { + return m_value.endsWith('\n'); + } + + // @p_value is the content to update. + // If @p_value ends with \n, then it is a block. + // When @p_value is a block, we need to add \n at the end if necessary. + // If @m_append is true and @p_value is a block, we need to add \n between + // them if necessary. + void update(const QString &p_value); + + // Read the value of this register. + const QString &read(); + + QChar m_name; + QString m_value; + + // This is not info of Register itself, but a hint to the handling logics + // whether we need to append the content to this register. + // Only valid for a-z registers. + bool m_append; + }; + // Handle key press event. // Returns true if the event is consumed and need no more handling. bool handleKeyPressEvent(QKeyEvent *p_event); @@ -38,6 +101,18 @@ public: // Set current mode. void setMode(VimMode p_mode); + // Set current register. + void setRegister(QChar p_reg); + + // Get m_registers. + const QMap &getRegisters() const; + + QChar getCurrentRegisterName() const; + + // Get pending keys. + // Turn m_pendingKeys to a string. + QString getPendingKeys() const; + signals: // Emit when current mode has been changed. void modeChanged(VimMode p_mode); @@ -45,6 +120,9 @@ signals: // Emit when VVim want to display some messages. void vimMessage(const QString &p_msg); + // Emit when current status updated. + void vimStatusUpdated(const VVim *p_vim); + private slots: // When user use mouse to select texts in Normal mode, we should change to // Visual mode. @@ -265,69 +343,6 @@ private: Key m_key; }; - struct Register - { - Register(QChar p_name, const QString &p_value) - : m_name(p_name), m_value(p_value), m_append(false) - { - } - - Register(QChar p_name) - : m_name(p_name), m_append(false) - { - } - - Register() - : m_append(false) - { - } - - // Register a-z. - bool isNamedRegister() const - { - char ch = m_name.toLatin1(); - return ch >= 'a' && ch <= 'z'; - } - - bool isUnnamedRegister() const - { - return m_name == c_unnamedRegister; - } - - bool isBlackHoleRegister() const - { - return m_name == c_blackHoleRegister; - } - - bool isSelectionRegister() const - { - return m_name == c_selectionRegister; - } - - bool isBlock() const - { - return m_value.endsWith('\n'); - } - - // @p_value is the content to update. - // If @p_value ends with \n, then it is a block. - // When @p_value is a block, we need to add \n at the end if necessary. - // If @m_append is true and @p_value is a block, we need to add \n between - // them if necessary. - void update(const QString &p_value); - - // Read the value of this register. - const QString &read(); - - QChar m_name; - QString m_value; - - // This is not info of Register itself, but a hint to the handling logics - // whether we need to append the content to this register. - // Only valid for a-z registers. - bool m_append; - }; - // Reset all key state info. void resetState(); @@ -468,10 +483,13 @@ private: QList m_keys; QList m_tokens; + // Keys for status indication. + QList m_pendingKeys; + // Whether reset the position in block when moving cursor. bool m_resetPositionInBlock; - QHash m_registers; + QMap m_registers; // Currently used register. QChar m_regName; diff --git a/src/vbuttonwithwidget.cpp b/src/vbuttonwithwidget.cpp new file mode 100644 index 00000000..30dfe558 --- /dev/null +++ b/src/vbuttonwithwidget.cpp @@ -0,0 +1,98 @@ +#include "vbuttonwithwidget.h" + +#include +#include +#include + +VButtonWithWidget::VButtonWithWidget(QWidget *p_parent) + : QPushButton(p_parent), m_popupWidget(NULL) +{ + init(); +} + +VButtonWithWidget::VButtonWithWidget(const QString &p_text, QWidget *p_parent) + : QPushButton(p_text, p_parent), m_popupWidget(NULL) +{ + init(); +} + +VButtonWithWidget::VButtonWithWidget(const QIcon &p_icon, + const QString &p_text, + QWidget *p_parent) + : QPushButton(p_icon, p_text, p_parent), m_popupWidget(NULL) +{ + init(); +} + +VButtonWithWidget::~VButtonWithWidget() +{ + if (m_popupWidget) { + delete m_popupWidget; + } +} + +void VButtonWithWidget::init() +{ + connect(this, &QPushButton::clicked, + this, &VButtonWithWidget::showPopupWidget); +} + +void VButtonWithWidget::setPopupWidget(QWidget *p_widget) +{ + if (m_popupWidget) { + delete m_popupWidget; + } + + m_popupWidget = p_widget; + if (m_popupWidget) { + m_popupWidget->hide(); + m_popupWidget->setParent(NULL); + + Qt::WindowFlags flags = Qt::Popup; + m_popupWidget->setWindowFlags(flags); + m_popupWidget->setWindowModality(Qt::NonModal); + + // Let popup widget to hide itself if focus lost. + m_popupWidget->installEventFilter(this); + } +} + +QWidget *VButtonWithWidget::getPopupWidget() const +{ + return m_popupWidget; +} + +void VButtonWithWidget::showPopupWidget() +{ + if (m_popupWidget->isVisible()) { + m_popupWidget->hide(); + } else { + emit popupWidgetAboutToShow(m_popupWidget); + + // Calculate the position of the popup widget. + QPoint btnPos = mapToGlobal(QPoint(0, 0)); + int btnWidth = width(); + + int popupWidth = btnWidth * 10; + int popupHeight = height() * 10; + int popupX = btnPos.x() + btnWidth - popupWidth; + int popupY = btnPos.y() - popupHeight - 10; + + m_popupWidget->setGeometry(popupX, popupY, popupWidth, popupHeight); + m_popupWidget->show(); + } +} + +bool VButtonWithWidget::eventFilter(QObject *p_obj, QEvent *p_event) +{ + if (p_event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *eve = dynamic_cast(p_event); + QPoint clickPos = eve->pos(); + const QRect &rect = m_popupWidget->rect(); + if (!rect.contains(clickPos)) { + m_popupWidget->hide(); + } + } + + return QPushButton::eventFilter(p_obj, p_event); +} diff --git a/src/vbuttonwithwidget.h b/src/vbuttonwithwidget.h new file mode 100644 index 00000000..bd43b043 --- /dev/null +++ b/src/vbuttonwithwidget.h @@ -0,0 +1,42 @@ +#ifndef VBUTTONWITHWIDGET_H +#define VBUTTONWITHWIDGET_H + +#include +#include +#include + +// A QPushButton with popup widget. +class VButtonWithWidget : public QPushButton +{ + Q_OBJECT +public: + VButtonWithWidget(QWidget *p_parent = Q_NULLPTR); + VButtonWithWidget(const QString &p_text, QWidget *p_parent = Q_NULLPTR); + VButtonWithWidget(const QIcon &p_icon, + const QString &p_text, + QWidget *p_parent = Q_NULLPTR); + ~VButtonWithWidget(); + + // Set the widget which will transfer the ownership to VButtonWithWidget. + void setPopupWidget(QWidget *p_widget); + + QWidget *getPopupWidget() const; + +signals: + // Emit when popup widget is about to show. + void popupWidgetAboutToShow(QWidget *p_widget); + +protected: + bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE; + +private slots: + // Show the popup widget. + void showPopupWidget(); + +private: + void init(); + + QWidget *m_popupWidget; +}; + +#endif // VBUTTONWITHWIDGET_H diff --git a/src/vedit.cpp b/src/vedit.cpp index e8f9c467..3077666c 100644 --- a/src/vedit.cpp +++ b/src/vedit.cpp @@ -781,3 +781,12 @@ void VEdit::mouseMoveEvent(QMouseEvent *p_event) QTextEdit::mouseMoveEvent(p_event); } + +void VEdit::requestUpdateVimStatus() +{ + if (m_editOps) { + m_editOps->requestUpdateVimStatus(); + } else { + emit vimStatusUpdated(NULL); + } +} diff --git a/src/vedit.h b/src/vedit.h index b6b8f333..d0d725cd 100644 --- a/src/vedit.h +++ b/src/vedit.h @@ -15,6 +15,7 @@ class VEditOperations; class QLabel; class QTimer; +class VVim; enum class SelectionId { CurrentLine = 0, @@ -81,6 +82,9 @@ public: VEditConfig &getConfig(); + // Request to update Vim status. + void requestUpdateVimStatus(); + signals: void saveAndRead(); void discardAndRead(); @@ -92,6 +96,9 @@ signals: // Emit when want to show message in status bar. void statusMessage(const QString &p_msg); + // Emit when Vim status updated. + void vimStatusUpdated(const VVim *p_vim); + public slots: virtual void highlightCurrentLine(); diff --git a/src/veditarea.cpp b/src/veditarea.cpp index 3dd432af..ec4518b5 100644 --- a/src/veditarea.cpp +++ b/src/veditarea.cpp @@ -81,6 +81,8 @@ void VEditArea::insertSplitWindow(int idx) this, &VEditArea::handleCurHeaderChanged); connect(win, &VEditWindow::statusMessage, this, &VEditArea::handleWindowStatusMessage); + connect(win, &VEditWindow::vimStatusUpdated, + this, &VEditArea::handleWindowVimStatusUpdated); } void VEditArea::handleEditWindowStatusChanged(const VFile *p_file, @@ -99,6 +101,13 @@ void VEditArea::handleWindowStatusMessage(const QString &p_msg) } } +void VEditArea::handleWindowVimStatusUpdated(const VVim *p_vim) +{ + if (splitter->widget(curWindowIndex) == sender()) { + emit vimStatusUpdated(p_vim); + } +} + void VEditArea::removeSplitWindow(VEditWindow *win) { if (!win) { diff --git a/src/veditarea.h b/src/veditarea.h index ffdedfd6..4a94f581 100644 --- a/src/veditarea.h +++ b/src/veditarea.h @@ -20,6 +20,7 @@ class VFile; class VDirectory; class VFindReplaceDialog; class QLabel; +class VVim; class VEditArea : public QWidget, public VNavigationMode { @@ -66,6 +67,9 @@ signals: // Emit when want to show message in status bar. void statusMessage(const QString &p_msg); + // Emit when Vim status updated. + void vimStatusUpdated(const VVim *p_vim); + protected: void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; @@ -101,6 +105,9 @@ private slots: // Handle the statusMessage signal of VEditWindow. void handleWindowStatusMessage(const QString &p_msg); + // Handle the vimStatusUpdated signal of VEditWindow. + void handleWindowVimStatusUpdated(const VVim *p_vim); + private: void setupUI(); QVector > findTabsByFile(const VFile *p_file); diff --git a/src/veditoperations.cpp b/src/veditoperations.cpp index 0dc2a047..c5aa40b7 100644 --- a/src/veditoperations.cpp +++ b/src/veditoperations.cpp @@ -20,6 +20,8 @@ VEditOperations::VEditOperations(VEdit *p_editor, VFile *p_file) this, &VEditOperations::handleVimModeChanged); connect(m_vim, &VVim::vimMessage, this, &VEditOperations::statusMessage); + connect(m_vim, &VVim::vimStatusUpdated, + this, &VEditOperations::vimStatusUpdated); } void VEditOperations::insertTextAtCurPos(const QString &p_text) @@ -80,3 +82,12 @@ void VEditOperations::handleVimModeChanged(VimMode p_mode) updateCursorLineBg(); } + +void VEditOperations::requestUpdateVimStatus() +{ + if (m_editConfig->m_enableVimMode) { + emit vimStatusUpdated(m_vim); + } else { + emit vimStatusUpdated(NULL); + } +} diff --git a/src/veditoperations.h b/src/veditoperations.h index f01804fb..f349e5f0 100644 --- a/src/veditoperations.h +++ b/src/veditoperations.h @@ -27,10 +27,16 @@ public: // processed. virtual bool handleKeyPressEvent(QKeyEvent *p_event) = 0; + // Request to propogate Vim status. + void requestUpdateVimStatus(); + signals: // Want to display a template message in status bar. void statusMessage(const QString &p_msg); + // Propogate Vim status. + void vimStatusUpdated(const VVim *p_vim); + protected slots: // Handle the update of VEditConfig of the editor. virtual void handleEditConfigUpdated(); diff --git a/src/vedittab.h b/src/vedittab.h index 9ab34f93..98af3a1e 100644 --- a/src/vedittab.h +++ b/src/vedittab.h @@ -6,6 +6,7 @@ #include #include "vtoc.h" #include "vfile.h" +#include "utils/vvim.h" class VEditArea; @@ -63,6 +64,9 @@ public: virtual void clearSearchedWordHighlight() = 0; + // Request current tab to propogate its status about Vim. + virtual void requestUpdateVimStatus() = 0; + public slots: // Enter edit mode virtual void editFile() = 0; @@ -96,6 +100,8 @@ signals: // Emit when want to show message in status bar. void statusMessage(const QString &p_msg); + void vimStatusUpdated(const VVim *p_vim); + private slots: // Called when app focus changed. void handleFocusChanged(QWidget *p_old, QWidget *p_now); diff --git a/src/veditwindow.cpp b/src/veditwindow.cpp index d8f8ac41..cad6a545 100644 --- a/src/veditwindow.cpp +++ b/src/veditwindow.cpp @@ -270,6 +270,8 @@ int VEditWindow::openFileInTab(VFile *p_file, OpenFileMode p_mode) this, &VEditWindow::handleTabStatusChanged); connect(editor, &VEditTab::statusMessage, this, &VEditWindow::handleTabStatusMessage); + connect(editor, &VEditTab::vimStatusUpdated, + this, &VEditWindow::handleTabVimStatusUpdated); int idx = appendEditTab(p_file, editor); return idx; @@ -586,6 +588,14 @@ void VEditWindow::handleTabStatusMessage(const QString &p_msg) } } +void VEditWindow::handleTabVimStatusUpdated(const VVim *p_vim) +{ + int idx = indexOf(dynamic_cast(sender())); + if (idx == currentIndex()) { + emit vimStatusUpdated(p_vim); + } +} + void VEditWindow::updateFileInfo(const VFile *p_file) { if (!p_file) { diff --git a/src/veditwindow.h b/src/veditwindow.h index db4ac331..91cb7b8f 100644 --- a/src/veditwindow.h +++ b/src/veditwindow.h @@ -71,6 +71,9 @@ signals: // Emit when want to show message in status bar. void statusMessage(const QString &p_msg); + // Emit when Vim mode status changed. + void vimStatusUpdated(const VVim *p_vim); + private slots: bool handleTabCloseRequest(int index); void splitWindow(); @@ -91,6 +94,9 @@ private slots: // Handle the statusMessage signal of VEditTab. void handleTabStatusMessage(const QString &p_msg); + // Handle the vimStatusUpdated() signal of VEditTab. + void handleTabVimStatusUpdated(const VVim *p_vim); + private: void initTabActions(); void setupCornerWidget(); diff --git a/src/vhtmltab.cpp b/src/vhtmltab.cpp index 797dcfac..c8396adf 100644 --- a/src/vhtmltab.cpp +++ b/src/vhtmltab.cpp @@ -41,6 +41,9 @@ void VHtmlTab::setupUI() this, &VHtmlTab::editFile); connect(m_editor, &VEdit::statusMessage, this, &VEditTab::statusMessage); + connect(m_editor, &VEdit::vimStatusUpdated, + this, &VEditTab::vimStatusUpdated); + m_editor->reloadFile(); QVBoxLayout *mainLayout = new QVBoxLayout(); @@ -250,3 +253,8 @@ void VHtmlTab::focusChild() { m_editor->setFocus(); } + +void VHtmlTab::requestUpdateVimStatus() +{ + m_editor->requestUpdateVimStatus(); +} diff --git a/src/vhtmltab.h b/src/vhtmltab.h index 3e08b53c..ea0a237f 100644 --- a/src/vhtmltab.h +++ b/src/vhtmltab.h @@ -46,6 +46,8 @@ public: void clearSearchedWordHighlight() Q_DECL_OVERRIDE; + void requestUpdateVimStatus() Q_DECL_OVERRIDE; + public slots: // Enter edit mode. void editFile() Q_DECL_OVERRIDE; diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index a01dd1a1..052b9739 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -20,6 +20,7 @@ #include "vwebview.h" #include "vexporter.h" #include "vmdtab.h" +#include "vvimindicator.h" extern VConfigManager vconfig; @@ -111,12 +112,17 @@ void VMainWindow::setupUI() this, &VMainWindow::handleCurTabStatusChanged); connect(editArea, &VEditArea::statusMessage, this, &VMainWindow::showStatusMessage); + connect(editArea, &VEditArea::vimStatusUpdated, + this, &VMainWindow::handleVimStatusUpdated); connect(m_findReplaceDialog, &VFindReplaceDialog::findTextChanged, this, &VMainWindow::handleFindDialogTextChanged); setCentralWidget(mainSplitter); + // Create and show the status bar - statusBar(); + m_vimIndicator = new VVimIndicator(this); + m_vimIndicator->hide(); + statusBar()->addPermanentWidget(m_vimIndicator); } QWidget *VMainWindow::setupDirectoryPanel() @@ -1121,9 +1127,12 @@ void VMainWindow::handleCurTabStatusChanged(const VFile *p_file, const VEditTab title.append('#'); } } + updateWindowTitle(title); m_curFile = const_cast(p_file); m_curTab = const_cast(p_editTab); + + updateStatusInfo(p_editMode); } void VMainWindow::onePanelView() @@ -1496,3 +1505,22 @@ void VMainWindow::showStatusMessage(const QString &p_msg) const int timeout = 3000; statusBar()->showMessage(p_msg, timeout); } + +void VMainWindow::updateStatusInfo(bool p_editMode) +{ + if (!p_editMode || !m_curTab) { + m_vimIndicator->hide(); + } else { + m_curTab->requestUpdateVimStatus(); + } +} + +void VMainWindow::handleVimStatusUpdated(const VVim *p_vim) +{ + if (!p_vim || !m_curTab) { + m_vimIndicator->hide(); + } else { + m_vimIndicator->update(p_vim); + m_vimIndicator->show(); + } +} diff --git a/src/vmainwindow.h b/src/vmainwindow.h index 1e0c68bb..332a0b51 100644 --- a/src/vmainwindow.h +++ b/src/vmainwindow.h @@ -29,6 +29,7 @@ class VNotebookSelector; class VAvatar; class VFindReplaceDialog; class VCaptain; +class VVimIndicator; class VMainWindow : public QMainWindow { @@ -88,6 +89,9 @@ private slots: // Show a temporary message in status bar. void showStatusMessage(const QString &p_msg); + // Handle Vim status updated. + void handleVimStatusUpdated(const VVim *p_vim); + protected: void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE; void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; @@ -127,6 +131,9 @@ private: void toggleOnePanelView(); void closeCurrentFile(); + // Update status bar information according to m_curTab and m_curFile. + void updateStatusInfo(bool p_editMode); + // Wrapper to create a QAction. QAction *newAction(const QIcon &p_icon, const QString &p_text, @@ -150,6 +157,7 @@ private: VOutline *outline; VAvatar *m_avatar; VFindReplaceDialog *m_findReplaceDialog; + VVimIndicator *m_vimIndicator; // Whether it is one panel or two panles. bool m_onePanel; diff --git a/src/vmdedit.cpp b/src/vmdedit.cpp index 97c67360..42f5c23b 100644 --- a/src/vmdedit.cpp +++ b/src/vmdedit.cpp @@ -53,6 +53,8 @@ VMdEdit::VMdEdit(VFile *p_file, VDocument *p_vdoc, MarkdownConverterType p_type, connect(m_editOps, &VEditOperations::statusMessage, this, &VEdit::statusMessage); + connect(m_editOps, &VEditOperations::vimStatusUpdated, + this, &VEdit::vimStatusUpdated); connect(this, &VMdEdit::cursorPositionChanged, this, &VMdEdit::updateCurHeader); diff --git a/src/vmdtab.cpp b/src/vmdtab.cpp index 5c24b6ef..adb09744 100644 --- a/src/vmdtab.cpp +++ b/src/vmdtab.cpp @@ -60,6 +60,8 @@ void VMdTab::setupUI() this, &VMdTab::discardAndRead); connect(m_editor, &VEdit::statusMessage, this, &VEditTab::statusMessage); + connect(m_editor, &VEdit::vimStatusUpdated, + this, &VEditTab::vimStatusUpdated); m_editor->reloadFile(); m_stacks->addWidget(m_editor); @@ -643,3 +645,12 @@ void VMdTab::focusChild() { m_stacks->currentWidget()->setFocus(); } + +void VMdTab::requestUpdateVimStatus() +{ + if (m_editor) { + m_editor->requestUpdateVimStatus(); + } else { + emit vimStatusUpdated(NULL); + } +} diff --git a/src/vmdtab.h b/src/vmdtab.h index ec1e7d6a..b750f338 100644 --- a/src/vmdtab.h +++ b/src/vmdtab.h @@ -55,6 +55,8 @@ public: MarkdownConverterType getMarkdownConverterType() const; + void requestUpdateVimStatus() Q_DECL_OVERRIDE; + public slots: // Enter edit mode. void editFile() Q_DECL_OVERRIDE; diff --git a/src/vnote.qrc b/src/vnote.qrc index 10147262..fd8a8357 100644 --- a/src/vnote.qrc +++ b/src/vnote.qrc @@ -66,7 +66,6 @@ resources/icons/dir_item.svg resources/icons/notebook_item.svg resources/icons/arrow_dropdown.svg - resources/icons/current_tab.svg resources/icons/vnote.png resources/icons/insert_image.svg resources/icons/import_note.svg @@ -108,5 +107,6 @@ utils/markdown-it/markdown-it-sub.min.js utils/markdown-it/markdown-it-sup.min.js utils/markdown-it/markdown-it-footnote.min.js + resources/icons/arrow_dropup.svg diff --git a/src/vvimindicator.cpp b/src/vvimindicator.cpp new file mode 100644 index 00000000..0bcd8c3d --- /dev/null +++ b/src/vvimindicator.cpp @@ -0,0 +1,174 @@ +#include "vvimindicator.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "vconfigmanager.h" +#include "vbuttonwithwidget.h" + +extern VConfigManager vconfig; + +VVimIndicator::VVimIndicator(QWidget *parent) + : QWidget(parent), m_vim(NULL) +{ + setupUI(); +} + +void VVimIndicator::setupUI() +{ + m_modeLabel = new QLabel(this); + + m_regBtn = new VButtonWithWidget(QIcon(":/resources/icons/arrow_dropup.svg"), + "\"", + this); + m_regBtn->setProperty("StatusBtn", true); + m_regBtn->setFocusPolicy(Qt::NoFocus); + QTreeWidget *regTree = new QTreeWidget(this); + regTree->setColumnCount(2); + regTree->header()->setStretchLastSection(true); + regTree->header()->setProperty("RegisterTree", true); + QStringList headers; + headers << tr("Register") << tr("Value"); + regTree->setHeaderLabels(headers); + m_regBtn->setPopupWidget(regTree); + connect(m_regBtn, &VButtonWithWidget::popupWidgetAboutToShow, + this, &VVimIndicator::updateRegistersTree); + + m_keyLabel = new QLabel(this); + QFontMetrics metric(font()); + m_keyLabel->setMinimumWidth(metric.width('A') * 5); + + QHBoxLayout *mainLayout = new QHBoxLayout(this); + mainLayout->addWidget(m_modeLabel); + mainLayout->addWidget(m_regBtn); + mainLayout->addWidget(m_keyLabel); + mainLayout->setContentsMargins(0, 0, 0, 0); + + setLayout(mainLayout); +} + +QString VVimIndicator::modeToString(VimMode p_mode) const +{ + QString str; + + switch (p_mode) { + case VimMode::Normal: + str = tr("Normal"); + break; + + case VimMode::Insert: + str = tr("Insert"); + break; + + case VimMode::Visual: + str = tr("Visual"); + break; + + case VimMode::VisualLine: + str = tr("VisualLine"); + break; + + case VimMode::Replace: + str = tr("Replace"); + break; + + default: + str = tr("Unknown"); + break; + } + + return str; +} + +static QString modeBackgroundColor(VimMode p_mode) +{ + QString color; + + switch (p_mode) { + case VimMode::Normal: + color = vconfig.getEditorVimNormalBg(); + break; + + case VimMode::Insert: + color = vconfig.getEditorVimInsertBg(); + break; + + case VimMode::Visual: + color = vconfig.getEditorVimVisualBg(); + break; + + case VimMode::VisualLine: + color = vconfig.getEditorVimVisualBg(); + break; + + case VimMode::Replace: + color = vconfig.getEditorVimReplaceBg(); + break; + + default: + color = "red"; + break; + } + + return color; +} + +static void fillTreeItemsWithRegisters(QTreeWidget *p_tree, + const QMap &p_regs) +{ + p_tree->clear(); + for (auto const ® : p_regs) { + if (reg.m_value.isEmpty()) { + continue; + } + + QStringList itemStr; + itemStr << reg.m_name << reg.m_value; + QTreeWidgetItem *item = new QTreeWidgetItem(p_tree, itemStr); + item->setFlags(item->flags() | Qt::ItemIsSelectable | Qt::ItemIsEditable); + } + + p_tree->resizeColumnToContents(0); + p_tree->resizeColumnToContents(1); +} + +void VVimIndicator::update(const VVim *p_vim) +{ + if (!p_vim) { + m_vim = p_vim; + return; + } + + m_vim = p_vim; + + VimMode mode = p_vim->getMode(); + QChar curRegName = p_vim->getCurrentRegisterName(); + + QString style = QString("QLabel { padding: 0px 2px 0px 2px; font: bold; background-color: %1; }") + .arg(modeBackgroundColor(mode)); + m_modeLabel->setStyleSheet(style); + m_modeLabel->setText(modeToString(mode)); + + m_regBtn->setText(curRegName); + + QString keyText = QString("%1") + .arg(p_vim->getPendingKeys()); + m_keyLabel->setText(keyText); +} + +void VVimIndicator::updateRegistersTree(QWidget *p_widget) +{ + QTreeWidget *regTree = dynamic_cast(p_widget); + if (!m_vim) { + regTree->clear(); + return; + } + + const QMap ®s = m_vim->getRegisters(); + fillTreeItemsWithRegisters(regTree, regs); +} diff --git a/src/vvimindicator.h b/src/vvimindicator.h new file mode 100644 index 00000000..5930ae66 --- /dev/null +++ b/src/vvimindicator.h @@ -0,0 +1,40 @@ +#ifndef VVIMINDICATOR_H +#define VVIMINDICATOR_H + +#include +#include "utils/vvim.h" + +class QLabel; +class VButtonWithWidget; + +class VVimIndicator : public QWidget +{ + Q_OBJECT + +public: + explicit VVimIndicator(QWidget *parent = 0); + +public slots: + void update(const VVim *p_vim); + +private slots: + void updateRegistersTree(QWidget *p_widget); + +private: + void setupUI(); + + QString modeToString(VimMode p_mode) const; + + // Indicate the mode. + QLabel *m_modeLabel; + + // Indicate the registers. + VButtonWithWidget *m_regBtn; + + // Indicate the pending keys. + QLabel *m_keyLabel; + + const VVim *m_vim; +}; + +#endif // VVIMINDICATOR_H