From 4374f2d8f149ac30ff64a302208ce5856c36d1eb Mon Sep 17 00:00:00 2001 From: Le Tan Date: Fri, 7 Jul 2017 16:46:41 +0800 Subject: [PATCH] editor: add line number and editor-line-number config --- src/resources/styles/default.mdhl | 2 + src/resources/vnote.ini | 4 + src/vconfigmanager.cpp | 23 +++- src/vconfigmanager.h | 41 +++++++ src/vedit.cpp | 171 ++++++++++++++++++++++++++++++ src/vedit.h | 62 +++++++++++ src/vmainwindow.cpp | 47 ++++++++ src/vmainwindow.h | 4 + src/vstyleparser.cpp | 2 +- 9 files changed, 352 insertions(+), 4 deletions(-) diff --git a/src/resources/styles/default.mdhl b/src/resources/styles/default.mdhl index e77688bf..6af90e9b 100644 --- a/src/resources/styles/default.mdhl +++ b/src/resources/styles/default.mdhl @@ -15,6 +15,8 @@ font-family: Hiragino Sans GB, 冬青黑体, Microsoft YaHei, 微软雅黑, Micr # [VNote] Style for trailing space trailing-space: ffebee font-size: 12 +line-number-background: bdbdbd +line-number-foreground: 424242 editor-selection foreground: eeeeee diff --git a/src/resources/vnote.ini b/src/resources/vnote.ini index 0ecd53f4..8cb2df1c 100644 --- a/src/resources/vnote.ini +++ b/src/resources/vnote.ini @@ -53,6 +53,10 @@ enable_vim_mode=false ; Enable smart input method in Vim mode (disable IM in non-Insert modes) enable_smart_im_in_vim_mode=true +; Display an area besides the editor area to show line number +; 0 - None, 1 - Absolute, 2 - Relative +editor_line_number=0 + [session] tools_dock_checked=true diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp index 0c0ccd3d..aa02e694 100644 --- a/src/vconfigmanager.cpp +++ b/src/vconfigmanager.cpp @@ -150,6 +150,9 @@ void VConfigManager::initialize() m_enableSmartImInVimMode = getConfigFromSettings("global", "enable_smart_im_in_vim_mode").toBool(); + + m_editorLineNumber = getConfigFromSettings("global", + "editor_line_number").toInt(); } void VConfigManager::readPredefinedColorsFromSettings() @@ -336,6 +339,8 @@ void VConfigManager::updateMarkdownEditStyle() static const QString defaultVimVisualBg = "#90CAF9"; static const QString defaultVimReplaceBg = "#F8BBD0"; static const QString defaultTrailingSpaceBackground = "#FFEBEE"; + static const QString defaultLineNumberBg = "#BDBDBD"; + static const QString defaultLineNumberFg = "#424242"; // Read style file .mdhl QString file(getEditorStyleUrl()); @@ -392,11 +397,23 @@ void VConfigManager::updateMarkdownEditStyle() } m_editorTrailingSpaceBg = defaultTrailingSpaceBackground; + m_editorLineNumberBg = defaultLineNumberBg; + m_editorLineNumberFg = defaultLineNumberFg; auto editorIt = styles.find("editor"); if (editorIt != styles.end()) { - auto trailingIt = editorIt->find("trailing-space"); - if (trailingIt != editorIt->end()) { - m_editorTrailingSpaceBg = "#" + *trailingIt; + auto it = editorIt->find("trailing-space"); + if (it != editorIt->end()) { + m_editorTrailingSpaceBg = "#" + *it; + } + + it = editorIt->find("line-number-background"); + if (it != editorIt->end()) { + m_editorLineNumberBg = "#" + *it; + } + + it = editorIt->find("line-number-foreground"); + if (it != editorIt->end()) { + m_editorLineNumberFg = "#" + *it; } } } diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index 3725e735..048e77fa 100644 --- a/src/vconfigmanager.h +++ b/src/vconfigmanager.h @@ -199,6 +199,12 @@ public: bool getEnableSmartImInVimMode() const; void setEnableSmartImInVimMode(bool p_enabled); + int getEditorLineNumber() const; + void setEditorLineNumber(int p_mode); + + const QString &getEditorLineNumberBg() const; + const QString &getEditorLineNumberFg() const; + // Get the folder the ini file exists. QString getConfigFolder() const; @@ -364,6 +370,15 @@ private: // Enable smart input method in Vim mode. bool m_enableSmartImInVimMode; + // Editor line number mode. + int m_editorLineNumber; + + // The background color of the line number area. + QString m_editorLineNumberBg; + + // The foreground color of the line number area. + QString m_editorLineNumberFg; + // The name of the config file in each directory, obsolete. // Use c_dirConfigFile instead. static const QString c_obsoleteDirConfigFile; @@ -964,4 +979,30 @@ inline void VConfigManager::setEnableSmartImInVimMode(bool p_enabled) m_enableSmartImInVimMode); } +inline int VConfigManager::getEditorLineNumber() const +{ + return m_editorLineNumber; +} + +inline void VConfigManager::setEditorLineNumber(int p_mode) +{ + if (m_editorLineNumber == p_mode) { + return; + } + + m_editorLineNumber = p_mode; + setConfigToSettings("global", "editor_line_number", + m_editorLineNumber); +} + +inline const QString &VConfigManager::getEditorLineNumberBg() const +{ + return m_editorLineNumberBg; +} + +inline const QString &VConfigManager::getEditorLineNumberFg() const +{ + return m_editorLineNumberFg; +} + #endif // VCONFIGMANAGER_H diff --git a/src/vedit.cpp b/src/vedit.cpp index 460f9866..0594b4bd 100644 --- a/src/vedit.cpp +++ b/src/vedit.cpp @@ -84,6 +84,16 @@ VEdit::VEdit(VFile *p_file, QWidget *p_parent) connect(this, &VEdit::selectionChanged, this, &VEdit::highlightSelectedWord); + + m_lineNumberArea = new LineNumberArea(this); + connect(document(), &QTextDocument::blockCountChanged, + this, &VEdit::updateLineNumberAreaMargin); + connect(this, &QTextEdit::textChanged, + this, &VEdit::updateLineNumberArea); + connect(verticalScrollBar(), &QScrollBar::valueChanged, + this, &VEdit::updateLineNumberArea); + + updateLineNumberAreaMargin(); } VEdit::~VEdit() @@ -833,3 +843,164 @@ void VEdit::decorateText(TextDecoration p_decoration) m_editOps->decorateText(p_decoration); } } + +void VEdit::updateLineNumberAreaMargin() +{ + int width = 0; + if (vconfig.getEditorLineNumber()) { + width = m_lineNumberArea->calculateWidth(); + } + + setViewportMargins(width, 0, 0, 0); +} + +void VEdit::updateLineNumberArea() +{ + if (vconfig.getEditorLineNumber()) { + if (!m_lineNumberArea->isVisible()) { + updateLineNumberAreaMargin(); + m_lineNumberArea->show(); + } + + m_lineNumberArea->update(); + } else if (m_lineNumberArea->isVisible()) { + updateLineNumberAreaMargin(); + m_lineNumberArea->hide(); + } +} + +void VEdit::resizeEvent(QResizeEvent *p_event) +{ + QTextEdit::resizeEvent(p_event); + + if (vconfig.getEditorLineNumber()) { + QRect rect = contentsRect(); + m_lineNumberArea->setGeometry(QRect(rect.left(), + rect.top(), + m_lineNumberArea->calculateWidth(), + rect.height())); + } +} + +void VEdit::lineNumberAreaPaintEvent(QPaintEvent *p_event) +{ + if (!vconfig.getEditorLineNumber()) { + updateLineNumberAreaMargin(); + m_lineNumberArea->hide(); + return; + } + + QPainter painter(m_lineNumberArea); + painter.fillRect(p_event->rect(), vconfig.getEditorLineNumberBg()); + + QTextDocument *doc = document(); + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + + QTextBlock block = firstVisibleBlock(); + int blockNumber = block.blockNumber(); + int offsetY = contentOffsetY(); + QRectF rect = layout->blockBoundingRect(block); + int top = offsetY + (int)rect.y(); + int bottom = top + (int)rect.height(); + int eventTop = p_event->rect().top(); + int eventBtm = p_event->rect().bottom(); + const int digitHeight = m_lineNumberArea->getDigitHeight(); + const int curBlockNumber = textCursor().block().blockNumber(); + const bool relative = vconfig.getEditorLineNumber() == 2; + const QString &fg = vconfig.getEditorLineNumberFg(); + + while (block.isValid() && top <= eventBtm) { + if (block.isVisible() && bottom >= eventTop) { + QString number = QString::number(relative ? blockNumber - curBlockNumber + : blockNumber + 1); + painter.setPen(fg); + painter.drawText(0, top + 2, + m_lineNumberArea->width(), + digitHeight, Qt::AlignRight, number); + } + + block = block.next(); + top = bottom; + bottom = top + (int)layout->blockBoundingRect(block).height(); + ++blockNumber; + } +} + +int VEdit::contentOffsetY() +{ + int offsety = 0; + QScrollBar *sb = verticalScrollBar(); + offsety = sb->value(); + return -offsety; +} + +QTextBlock VEdit::firstVisibleBlock() +{ + QTextDocument *doc = document(); + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + int offsetY = contentOffsetY(); + + // Binary search. + int idx = -1; + int start = 0, end = doc->blockCount() - 1; + while (start <= end) { + int mid = start + (end - start) / 2; + QTextBlock block = doc->findBlockByNumber(mid); + if (!block.isValid()) { + break; + } + + int y = offsetY + (int)layout->blockBoundingRect(block).y(); + if (y == 0) { + return block; + } else if (y < 0) { + start = mid + 1; + } else { + if (idx == -1 || mid < idx) { + idx = mid; + } + + end = mid - 1; + } + } + + if (idx != -1) { + return doc->findBlockByNumber(idx); + } + + // Linear search. + qDebug() << "fall back to linear search for first visible block"; + QTextBlock block = doc->begin(); + while (block.isValid()) { + int y = offsetY + (int)layout->blockBoundingRect(block).y(); + if (y >= 0) { + return block; + } + + block = block.next(); + } + + Q_ASSERT(false); + return doc->begin(); +} + +int LineNumberArea::calculateWidth() const +{ + int bc = m_document->blockCount(); + if (m_blockCount == bc) { + return m_width; + } + + const_cast(this)->m_blockCount = bc; + int digits = 1; + int max = qMax(1, m_blockCount); + while (max >= 10) { + max /= 10; + ++digits; + } + + int width = 2 + m_digitWidth * digits; + const_cast(this)->m_width = width; + + return m_width; +} diff --git a/src/vedit.h b/src/vedit.h index ffa85007..460d41ab 100644 --- a/src/vedit.h +++ b/src/vedit.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "vconstants.h" #include "vtoc.h" @@ -16,6 +17,10 @@ class VEditOperations; class QLabel; class QTimer; class VVim; +class QPaintEvent; +class QResizeEvent; +class QSize; +class QWidget; enum class SelectionId { CurrentLine = 0, @@ -55,6 +60,8 @@ public: bool m_highlightWholeBlock; }; +class LineNumberArea; + class VEdit : public QTextEdit { Q_OBJECT @@ -95,6 +102,9 @@ public: // Insert decoration markers or decorate selected text. void decorateText(TextDecoration p_decoration); + // LineNumberArea will call this to request paint itself. + void lineNumberAreaPaintEvent(QPaintEvent *p_event); + signals: // Request VEditTab to save and exit edit mode. void saveAndRead(); @@ -137,6 +147,11 @@ private slots: void highlightTrailingSpace(); void handleCursorPositionChanged(); + // Update viewport margin to hold the line number area. + void updateLineNumberAreaMargin(); + + void updateLineNumberArea(); + protected: QPointer m_file; VEditOperations *m_editOps; @@ -151,6 +166,8 @@ protected: virtual void mouseReleaseEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE; virtual void mouseMoveEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE; + virtual void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE; + // Update m_config according to VConfigManager. void updateConfig(); @@ -177,6 +194,8 @@ private: // Whether enable input method. bool m_enableInputMethod; + LineNumberArea *m_lineNumberArea; + void showWrapLabel(); // Trigger the timer to request highlight. @@ -196,8 +215,51 @@ private: void highlightSearchedWord(const QString &p_text, uint p_options); bool wordInSearchedSelection(const QString &p_text); + + // Return the first visible block. + QTextBlock firstVisibleBlock(); + + // Return the y offset of the content. + int contentOffsetY(); }; +class LineNumberArea : public QWidget +{ +public: + LineNumberArea(VEdit *p_editor) + : QWidget(p_editor), m_editor(p_editor), + m_document(p_editor->document()), + m_width(0), m_blockCount(-1) + { + m_digitWidth = m_editor->fontMetrics().width(QLatin1Char('9')); + m_digitHeight = m_editor->fontMetrics().height(); + } + QSize sizeHint() const Q_DECL_OVERRIDE + { + return QSize(calculateWidth(), 0); + } + + int calculateWidth() const; + + int getDigitHeight() const + { + return m_digitHeight; + } + +protected: + void paintEvent(QPaintEvent *p_event) Q_DECL_OVERRIDE + { + m_editor->lineNumberAreaPaintEvent(p_event); + } + +private: + VEdit *m_editor; + const QTextDocument *m_document; + int m_width; + int m_blockCount; + int m_digitWidth; + int m_digitHeight; +}; #endif // VEDIT_H diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index e5774abd..c76b8f63 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -848,6 +848,8 @@ void VMainWindow::initEditMenu() initEditorBackgroundMenu(editMenu); + initEditorLineNumberMenu(editMenu); + editMenu->addAction(cursorLineAct); cursorLineAct->setChecked(vconfig.getHighlightCursorLine()); @@ -1130,6 +1132,51 @@ void VMainWindow::initEditorBackgroundMenu(QMenu *menu) } } +void VMainWindow::initEditorLineNumberMenu(QMenu *p_menu) +{ + QMenu *lineNumMenu = p_menu->addMenu(tr("Line Number")); + lineNumMenu->setToolTipsVisible(true); + + QActionGroup *lineNumAct = new QActionGroup(lineNumMenu); + connect(lineNumAct, &QActionGroup::triggered, + this, [this](QAction *p_action){ + if (!p_action) { + return; + } + + vconfig.setEditorLineNumber(p_action->data().toInt()); + }); + + int lineNumberMode = vconfig.getEditorLineNumber(); + + QAction *act = lineNumAct->addAction(tr("None")); + act->setToolTip(tr("Do not display line number in edit mode")); + act->setCheckable(true); + act->setData(0); + lineNumMenu->addAction(act); + if (lineNumberMode == 0) { + act->setChecked(true); + } + + act = lineNumAct->addAction(tr("Absolute")); + act->setToolTip(tr("Display absolute line number in edit mode")); + act->setCheckable(true); + act->setData(1); + lineNumMenu->addAction(act); + if (lineNumberMode == 1) { + act->setChecked(true); + } + + act = lineNumAct->addAction(tr("Relative")); + act->setToolTip(tr("Display line number relative to current cursor line in edit mode")); + act->setCheckable(true); + act->setData(2); + lineNumMenu->addAction(act); + if (lineNumberMode == 2) { + act->setChecked(true); + } +} + void VMainWindow::updateEditorStyleMenu() { QMenu *menu = dynamic_cast(sender()); diff --git a/src/vmainwindow.h b/src/vmainwindow.h index 02cb509f..9bd534dc 100644 --- a/src/vmainwindow.h +++ b/src/vmainwindow.h @@ -124,6 +124,10 @@ private: void initRenderBackgroundMenu(QMenu *menu); void initRenderStyleMenu(QMenu *p_menu); void initEditorBackgroundMenu(QMenu *menu); + + // Init the Line Number submenu in Edit menu. + void initEditorLineNumberMenu(QMenu *p_menu); + void initEditorStyleMenu(QMenu *p_emnu); void changeSplitterView(int nrPanel); void updateWindowTitle(const QString &str); diff --git a/src/vstyleparser.cpp b/src/vstyleparser.cpp index 0450aa8d..c15c40fb 100644 --- a/src/vstyleparser.cpp +++ b/src/vstyleparser.cpp @@ -234,7 +234,7 @@ void VStyleParser::fetchMarkdownEditorStyles(QPalette &palette, QFont &font, } // Get custom styles: - // trailing-space. + // trailing-space, line-number-background, line-number-foreground case pmh_attr_type_other: { QString attrName(editorStyles->name);