Browse Source

implement Find/Replace logics

Supports Find/Replace in both edit and preview modes.

Signed-off-by: Le Tan <[email protected]>
Le Tan 9 years ago
parent
commit
3005d9bf5c

+ 201 - 15
src/dialog/vfindreplacedialog.cpp

@@ -2,7 +2,7 @@
 #include <QtWidgets>
 
 VFindReplaceDialog::VFindReplaceDialog(QWidget *p_parent)
-    : QWidget(p_parent)
+    : QWidget(p_parent), m_options(0), m_replaceAvailable(true)
 {
     setupUI();
 }
@@ -24,25 +24,42 @@ void VFindReplaceDialog::setupUI()
     // Find
     QLabel *findLabel = new QLabel(tr("&Find:"));
     m_findEdit = new QLineEdit();
+    m_findEdit->setPlaceholderText(tr("Enter text to search"));
     findLabel->setBuddy(m_findEdit);
-    m_findNextBtn = new QPushButton(tr("Find Next"));
+    m_findNextBtn = new QPushButton(tr("Find &Next"));
     m_findNextBtn->setProperty("FlatBtn", true);
     m_findNextBtn->setDefault(true);
-    m_findPrevBtn = new QPushButton(tr("Find Previous"));
+    m_findPrevBtn = new QPushButton(tr("Find &Previous"));
     m_findPrevBtn->setProperty("FlatBtn", true);
 
     // Replace
     QLabel *replaceLabel = new QLabel(tr("&Replace with:"));
     m_replaceEdit = new QLineEdit();
+    m_replaceEdit->setPlaceholderText(tr("Enter text to replace with"));
     replaceLabel->setBuddy(m_replaceEdit);
-    m_replaceBtn = new QPushButton(tr("Replace"));
+    m_replaceBtn = new QPushButton(tr("R&eplace"));
     m_replaceBtn->setProperty("FlatBtn", true);
-    m_replaceFindBtn = new QPushButton(tr("Replace && Find"));
+    m_replaceFindBtn = new QPushButton(tr("Replace && Fin&d"));
     m_replaceFindBtn->setProperty("FlatBtn", true);
-    m_replaceAllBtn = new QPushButton(tr("Replace All"));
+    m_replaceAllBtn = new QPushButton(tr("Replace A&ll"));
     m_replaceAllBtn->setProperty("FlatBtn", true);
-    m_advancedBtn = new QPushButton(tr("Advanced"));
+    m_advancedBtn = new QPushButton(tr("&Advanced >>"));
     m_advancedBtn->setProperty("FlatBtn", true);
+    m_advancedBtn->setCheckable(true);
+
+    // Options
+    m_caseSensitiveCheck = new QCheckBox(tr("&Case sensitive"), this);
+    connect(m_caseSensitiveCheck, &QCheckBox::stateChanged,
+            this, &VFindReplaceDialog::optionBoxToggled);
+    m_wholeWordOnlyCheck = new QCheckBox(tr("&Whole word only"), this);
+    connect(m_wholeWordOnlyCheck, &QCheckBox::stateChanged,
+            this, &VFindReplaceDialog::optionBoxToggled);
+    m_regularExpressionCheck = new QCheckBox(tr("Re&gular expression"), this);
+    connect(m_regularExpressionCheck, &QCheckBox::stateChanged,
+            this, &VFindReplaceDialog::optionBoxToggled);
+    m_incrementalSearchCheck = new QCheckBox(tr("&Incremental search"), this);
+    connect(m_incrementalSearchCheck, &QCheckBox::stateChanged,
+            this, &VFindReplaceDialog::optionBoxToggled);
 
     QGridLayout *gridLayout = new QGridLayout();
     gridLayout->addWidget(findLabel, 0, 0);
@@ -55,6 +72,10 @@ void VFindReplaceDialog::setupUI()
     gridLayout->addWidget(m_replaceFindBtn, 1, 3);
     gridLayout->addWidget(m_replaceAllBtn, 1, 4);
     gridLayout->addWidget(m_advancedBtn, 1, 5);
+    gridLayout->addWidget(m_caseSensitiveCheck, 2, 1);
+    gridLayout->addWidget(m_wholeWordOnlyCheck, 2, 2);
+    gridLayout->addWidget(m_regularExpressionCheck, 3, 1);
+    gridLayout->addWidget(m_incrementalSearchCheck, 3, 2);
     gridLayout->setColumnStretch(0, 0);
     gridLayout->setColumnStretch(1, 4);
     gridLayout->setColumnStretch(2, 1);
@@ -62,6 +83,7 @@ void VFindReplaceDialog::setupUI()
     gridLayout->setColumnStretch(4, 1);
     gridLayout->setColumnStretch(5, 1);
     gridLayout->setColumnStretch(6, 3);
+
     QMargins margin = gridLayout->contentsMargins();
     margin.setLeft(3);
     gridLayout->setContentsMargins(margin);
@@ -73,31 +95,195 @@ void VFindReplaceDialog::setupUI()
 
     setLayout(mainLayout);
 
+    m_caseSensitiveCheck->hide();
+    m_wholeWordOnlyCheck->hide();
+    m_regularExpressionCheck->hide();
+    m_incrementalSearchCheck->hide();
+
     // Signals
     connect(m_closeBtn, &QPushButton::clicked,
             this, &VFindReplaceDialog::closeDialog);
+    connect(m_findEdit, &QLineEdit::textChanged,
+            this, &VFindReplaceDialog::handleFindTextChanged);
+    connect(m_advancedBtn, &QPushButton::toggled,
+            this, &VFindReplaceDialog::advancedBtnToggled);
+    connect(m_findNextBtn, SIGNAL(clicked(bool)),
+            this, SLOT(findNext()));
+    connect(m_findPrevBtn, SIGNAL(clicked(bool)),
+            this, SLOT(findPrevious()));
+    connect(m_replaceBtn, SIGNAL(clicked(bool)),
+            this, SLOT(replace()));
+    connect(m_replaceFindBtn, SIGNAL(clicked(bool)),
+            this, SLOT(replaceFind()));
+    connect(m_replaceAllBtn, SIGNAL(clicked(bool)),
+            this, SLOT(replaceAll()));
 }
 
 void VFindReplaceDialog::closeDialog()
 {
     if (this->isVisible()) {
-        this->hide();
+        hide();
         emit dialogClosed();
     }
 }
 
-void VFindReplaceDialog::showEvent(QShowEvent *event)
-{
-    m_findEdit->selectAll();
-    QWidget::showEvent(event);
-}
-
 void VFindReplaceDialog::keyPressEvent(QKeyEvent *event)
 {
-    if (event->key() == Qt::Key_Escape) {
+    switch (event->key()) {
+    case Qt::Key_Escape:
         event->accept();
         closeDialog();
         return;
+
+    case Qt::Key_Return:
+    {
+        int modifiers = event->modifiers();
+        bool shift = false;
+        if (modifiers == Qt::ShiftModifier) {
+            shift = true;
+        } else if (modifiers != Qt::NoModifier) {
+            break;
+        }
+        if (!m_findEdit->hasFocus() && !m_replaceEdit->hasFocus()) {
+            break;
+        }
+        event->accept();
+        if (shift) {
+            findPrevious();
+        } else {
+            findNext();
+        }
+        return;
+    }
+
+    default:
+        break;
     }
     QWidget::keyPressEvent(event);
 }
+
+void VFindReplaceDialog::openDialog(QString p_text)
+{
+    show();
+    if (!p_text.isEmpty()) {
+        m_findEdit->setText(p_text);
+    }
+    m_findEdit->setFocus();
+    m_findEdit->selectAll();
+}
+
+void VFindReplaceDialog::handleFindTextChanged(const QString &p_text)
+{
+    emit findTextChanged(p_text, m_options);
+}
+
+void VFindReplaceDialog::advancedBtnToggled(bool p_checked)
+{
+    if (p_checked) {
+        m_advancedBtn->setText("B&asic <<");
+    } else {
+        m_advancedBtn->setText("&Advanced <<");
+    }
+
+    m_caseSensitiveCheck->setVisible(p_checked);
+    m_wholeWordOnlyCheck->setVisible(p_checked);
+    m_regularExpressionCheck->setVisible(p_checked);
+    m_incrementalSearchCheck->setVisible(p_checked);
+}
+
+void VFindReplaceDialog::optionBoxToggled(int p_state)
+{
+    QObject *obj = sender();
+    FindOption opt = FindOption::CaseSensitive;
+    if (obj == m_caseSensitiveCheck) {
+        opt = FindOption::CaseSensitive;
+    } else if (obj == m_wholeWordOnlyCheck) {
+        opt = FindOption::WholeWordOnly;
+    } else if (obj == m_regularExpressionCheck) {
+        opt = FindOption::RegularExpression;
+    } else {
+        opt = FindOption::IncrementalSearch;
+    }
+
+    if (p_state) {
+        m_options |= opt;
+    } else {
+        m_options &= ~opt;
+    }
+    emit findOptionChanged(m_options);
+}
+
+void VFindReplaceDialog::setOption(FindOption p_opt, bool p_enabled)
+{
+    if (p_opt == FindOption::CaseSensitive) {
+        m_caseSensitiveCheck->setChecked(p_enabled);
+    } else if (p_opt == FindOption::WholeWordOnly) {
+        m_wholeWordOnlyCheck->setChecked(p_enabled);
+    } else if (p_opt == FindOption::RegularExpression) {
+        m_regularExpressionCheck->setChecked(p_enabled);
+    } else if (p_opt == FindOption::IncrementalSearch) {
+        m_incrementalSearchCheck->setChecked(p_enabled);
+    } else {
+        Q_ASSERT(false);
+    }
+}
+
+void VFindReplaceDialog::findNext()
+{
+    QString text = m_findEdit->text();
+    if (text.isEmpty()) {
+        return;
+    }
+    emit findNext(text, m_options, true);
+}
+
+void VFindReplaceDialog::findPrevious()
+{
+    QString text = m_findEdit->text();
+    if (text.isEmpty()) {
+        return;
+    }
+    emit findNext(text, m_options, false);
+}
+
+void VFindReplaceDialog::replace()
+{
+    QString text = m_findEdit->text();
+    if (text.isEmpty() || !m_replaceAvailable) {
+        return;
+    }
+    QString replaceText = m_replaceEdit->text();
+    emit replace(text, m_options, replaceText, false);
+}
+
+void VFindReplaceDialog::replaceFind()
+{
+    QString text = m_findEdit->text();
+    if (text.isEmpty() || !m_replaceAvailable) {
+        return;
+    }
+    QString replaceText = m_replaceEdit->text();
+    emit replace(text, m_options, replaceText, true);
+}
+
+void VFindReplaceDialog::replaceAll()
+{
+    QString text = m_findEdit->text();
+    if (text.isEmpty() || !m_replaceAvailable) {
+        return;
+    }
+    QString replaceText = m_replaceEdit->text();
+    emit replaceAll(text, m_options, replaceText);
+}
+
+void VFindReplaceDialog::updateState(DocType p_docType, bool p_editMode)
+{
+    if (p_editMode || p_docType == DocType::Html) {
+        m_wholeWordOnlyCheck->setEnabled(true);
+        m_regularExpressionCheck->setEnabled(true);
+    } else {
+        m_wholeWordOnlyCheck->setEnabled(false);
+        m_regularExpressionCheck->setEnabled(false);
+    }
+    m_replaceAvailable = p_editMode;
+}

+ 41 - 1
src/dialog/vfindreplacedialog.h

@@ -2,28 +2,64 @@
 #define VFINDREPLACEDIALOG_H
 
 #include <QWidget>
+#include <QString>
+#include "vconstants.h"
 
 class QLineEdit;
 class QPushButton;
+class QCheckBox;
+
+enum FindOption
+{
+    CaseSensitive = 0x1U,
+    WholeWordOnly = 0x2U,
+    RegularExpression = 0x4U,
+    IncrementalSearch = 0x8U
+};
 
 class VFindReplaceDialog : public QWidget
 {
     Q_OBJECT
 public:
     explicit VFindReplaceDialog(QWidget *p_parent = 0);
+    void setOption(FindOption p_opt, bool p_enabled);
+    // Update the options enabled/disabled state according to current
+    // edit tab.
+    void updateState(DocType p_docType, bool p_editMode);
 
 signals:
     void dialogClosed();
+    void findTextChanged(const QString &p_text, uint p_options);
+    void findOptionChanged(uint p_options);
+    void findNext(const QString &p_text, uint p_options, bool p_forward);
+    void replace(const QString &p_text, uint p_options,
+                 const QString &p_replaceText, bool p_findNext);
+    void replaceAll(const QString &p_text, uint p_options,
+                    const QString &p_replaceText);
 
 protected:
-    void showEvent(QShowEvent *event) Q_DECL_OVERRIDE;
     void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;
 
 public slots:
     void closeDialog();
+    void openDialog(QString p_text = "");
+    void findNext();
+    void findPrevious();
+    void replace();
+    void replaceFind();
+    void replaceAll();
+
+private slots:
+    void handleFindTextChanged(const QString &p_text);
+    void advancedBtnToggled(bool p_checked);
+    void optionBoxToggled(int p_state);
 
 private:
     void setupUI();
+    // Bit OR of FindOption
+    uint m_options;
+    bool m_replaceAvailable;
+
     QLineEdit *m_findEdit;
     QLineEdit *m_replaceEdit;
     QPushButton *m_findNextBtn;
@@ -33,6 +69,10 @@ private:
     QPushButton *m_replaceAllBtn;
     QPushButton *m_advancedBtn;
     QPushButton *m_closeBtn;
+    QCheckBox *m_caseSensitiveCheck;
+    QCheckBox *m_wholeWordOnlyCheck;
+    QCheckBox *m_regularExpressionCheck;
+    QCheckBox *m_incrementalSearchCheck;
 };
 
 #endif // VFINDREPLACEDIALOG_H

+ 10 - 0
src/resources/icons/find_replace.svg

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<path d="M445,386.7l-84.8-85.9c13.8-24.1,21-50.9,21-77.9c0-87.6-71.2-158.9-158.6-158.9C135.2,64,64,135.3,64,222.9
+	c0,87.6,71.2,158.9,158.6,158.9c27.9,0,55.5-7.7,80.1-22.4l84.4,85.6c1.9,1.9,4.6,3.1,7.3,3.1c2.7,0,5.4-1.1,7.3-3.1l43.3-43.8
+	C449,397.1,449,390.7,445,386.7z M222.6,125.9c53.4,0,96.8,43.5,96.8,97c0,53.5-43.4,97-96.8,97c-53.4,0-96.8-43.5-96.8-97
+	C125.8,169.4,169.2,125.9,222.6,125.9z"/>
+</svg>

+ 9 - 0
src/vconfigmanager.cpp

@@ -68,6 +68,15 @@ void VConfigManager::initialize()
     m_mainSplitterState = getConfigFromSettings("session", "main_splitter_state").toByteArray();
 
     updateMarkdownEditStyle();
+
+    m_findCaseSensitive = getConfigFromSettings("global",
+                                                "find_case_sensitive").toBool();
+    m_findWholeWordOnly = getConfigFromSettings("global",
+                                                "find_whole_word_only").toBool();
+    m_findRegularExpression = getConfigFromSettings("global",
+                                                    "find_regular_expression").toBool();
+    m_findIncrementalSearch = getConfigFromSettings("global",
+                                                    "find_incremental_search").toBool();
 }
 
 void VConfigManager::readPredefinedColorsFromSettings()

+ 76 - 0
src/vconfigmanager.h

@@ -99,6 +99,18 @@ public:
     inline const QByteArray &getMainSplitterState() const;
     inline void setMainSplitterState(const QByteArray &p_state);
 
+    inline bool getFindCaseSensitive() const;
+    inline void setFindCaseSensitive(bool p_enabled);
+
+    inline bool getFindWholeWordOnly() const;
+    inline void setFindWholeWordOnly(bool p_enabled);
+
+    inline bool getFindRegularExpression() const;
+    inline void setFindRegularExpression(bool p_enabled);
+
+    inline bool getFindIncrementalSearch() const;
+    inline void setFindIncrementalSearch(bool p_enabled);
+
 private:
     void updateMarkdownEditStyle();
     QVariant getConfigFromSettings(const QString &section, const QString &key);
@@ -144,6 +156,12 @@ private:
     QByteArray m_mainWindowState;
     QByteArray m_mainSplitterState;
 
+    // Find/Replace dialog options
+    bool m_findCaseSensitive;
+    bool m_findWholeWordOnly;
+    bool m_findRegularExpression;
+    bool m_findIncrementalSearch;
+
     // The name of the config file in each directory
     static const QString dirConfigFileName;
     // The name of the default configuration file
@@ -373,4 +391,62 @@ inline void VConfigManager::setMainSplitterState(const QByteArray &p_state)
     setConfigToSettings("session", "main_splitter_state", m_mainSplitterState);
 }
 
+inline bool VConfigManager::getFindCaseSensitive() const
+{
+    return m_findCaseSensitive;
+}
+
+inline void VConfigManager::setFindCaseSensitive(bool p_enabled)
+{
+    if (m_findCaseSensitive == p_enabled) {
+        return;
+    }
+    m_findCaseSensitive = p_enabled;
+    setConfigToSettings("global", "find_case_sensitive", m_findCaseSensitive);
+}
+
+inline bool VConfigManager::getFindWholeWordOnly() const
+{
+    return m_findWholeWordOnly;
+}
+
+inline void VConfigManager::setFindWholeWordOnly(bool p_enabled)
+{
+    if (m_findWholeWordOnly == p_enabled) {
+        return;
+    }
+    m_findWholeWordOnly = p_enabled;
+    setConfigToSettings("global", "find_whole_word_only", m_findWholeWordOnly);
+}
+
+inline bool VConfigManager::getFindRegularExpression() const
+{
+    return m_findRegularExpression;
+}
+
+inline void VConfigManager::setFindRegularExpression(bool p_enabled)
+{
+    if (m_findRegularExpression == p_enabled) {
+        return;
+    }
+    m_findRegularExpression = p_enabled;
+    setConfigToSettings("global", "find_regular_expression",
+                        m_findRegularExpression);
+}
+
+inline bool VConfigManager::getFindIncrementalSearch() const
+{
+    return m_findIncrementalSearch;
+}
+
+inline void VConfigManager::setFindIncrementalSearch(bool p_enabled)
+{
+    if (m_findIncrementalSearch == p_enabled) {
+        return;
+    }
+    m_findIncrementalSearch = p_enabled;
+    setConfigToSettings("global", "find_incremental_search",
+                        m_findIncrementalSearch);
+}
+
 #endif // VCONFIGMANAGER_H

+ 164 - 0
src/vedit.cpp

@@ -1,11 +1,13 @@
 #include <QtWidgets>
 #include <QVector>
+#include <QDebug>
 #include "vedit.h"
 #include "vnote.h"
 #include "vconfigmanager.h"
 #include "vtoc.h"
 #include "utils/vutils.h"
 #include "veditoperations.h"
+#include "dialog/vfindreplacedialog.h"
 
 extern VConfigManager vconfig;
 
@@ -82,3 +84,165 @@ void VEdit::insertImage()
         m_editOps->insertImage();
     }
 }
+
+bool VEdit::findText(const QString &p_text, uint p_options, bool p_peek,
+                     bool p_forward)
+{
+    static int startPos = textCursor().selectionStart();
+    static int lastPos = startPos;
+    bool found = false;
+
+    if (p_text.isEmpty() && p_peek) {
+        // Clear previous selection
+        QTextCursor cursor = textCursor();
+        cursor.clearSelection();
+        cursor.setPosition(startPos);
+        setTextCursor(cursor);
+    } else {
+        QTextCursor cursor = textCursor();
+        if (p_peek) {
+            int curPos = cursor.selectionStart();
+            if (curPos != lastPos) {
+                // Cursor has been moved. Just start at current position.
+                startPos = curPos;
+                lastPos = curPos;
+            } else {
+                // Move cursor to startPos to search.
+                cursor.setPosition(startPos);
+                setTextCursor(cursor);
+            }
+        }
+
+        // Options
+        QTextDocument::FindFlags flags;
+        if (p_options & FindOption::CaseSensitive) {
+            flags |= QTextDocument::FindCaseSensitively;
+        }
+        if (p_options & FindOption::WholeWordOnly) {
+            flags |= QTextDocument::FindWholeWords;
+        }
+        if (!p_forward) {
+            flags |= QTextDocument::FindBackward;
+        }
+        // Use regular expression
+        if (p_options & FindOption::RegularExpression) {
+            QRegExp exp(p_text,
+                        p_options & FindOption::CaseSensitive ?
+                        Qt::CaseSensitive : Qt::CaseInsensitive);
+            found = find(exp, flags);
+        } else {
+            found = find(p_text, flags);
+        }
+        cursor = textCursor();
+        if (!p_peek) {
+            startPos = cursor.selectionStart();
+        }
+        lastPos = cursor.selectionStart();
+    }
+    return found;
+}
+
+void VEdit::replaceText(const QString &p_text, uint p_options,
+                        const QString &p_replaceText, bool p_findNext)
+{
+    // Options
+    QTextDocument::FindFlags flags;
+    if (p_options & FindOption::CaseSensitive) {
+        flags |= QTextDocument::FindCaseSensitively;
+    }
+    if (p_options & FindOption::WholeWordOnly) {
+        flags |= QTextDocument::FindWholeWords;
+    }
+
+    bool useRegExp = false;
+    QRegExp exp;
+    if (p_options & FindOption::RegularExpression) {
+        useRegExp = true;
+        exp = QRegExp(p_text,
+                      p_options & FindOption::CaseSensitive ?
+                      Qt::CaseSensitive : Qt::CaseInsensitive);
+    }
+
+    QTextCursor cursor = textCursor();
+    if (cursor.hasSelection()) {
+        // Replace occurs only if the selected text matches @p_text with @p_options.
+        QTextCursor matchCursor;
+        if (useRegExp) {
+            matchCursor = document()->find(exp, cursor.selectionStart(),
+                                           flags);
+        } else {
+            matchCursor = document()->find(p_text, cursor.selectionStart(),
+                                           flags);
+        }
+        bool matched = (cursor.selectionStart() == matchCursor.selectionStart())
+                       && (cursor.selectionEnd() == matchCursor.selectionEnd());
+        if (matched) {
+            cursor.beginEditBlock();
+            cursor.removeSelectedText();
+            cursor.insertText(p_replaceText);
+            cursor.endEditBlock();
+            setTextCursor(cursor);
+        }
+    }
+    if (p_findNext) {
+        findText(p_text, p_options, false, true);
+    }
+}
+
+void VEdit::replaceTextAll(const QString &p_text, uint p_options,
+                           const QString &p_replaceText)
+{
+    // Options
+    QTextDocument::FindFlags flags;
+    if (p_options & FindOption::CaseSensitive) {
+        flags |= QTextDocument::FindCaseSensitively;
+    }
+    if (p_options & FindOption::WholeWordOnly) {
+        flags |= QTextDocument::FindWholeWords;
+    }
+
+    bool useRegExp = false;
+    QRegExp exp;
+    if (p_options & FindOption::RegularExpression) {
+        useRegExp = true;
+        exp = QRegExp(p_text,
+                      p_options & FindOption::CaseSensitive ?
+                      Qt::CaseSensitive : Qt::CaseInsensitive);
+    }
+
+    QTextCursor cursor = textCursor();
+    int pos = cursor.position();
+    int nrReplaces = 0;
+    int startPos = 0;
+    int lastMatch = -1;
+    while (true) {
+        QTextCursor matchCursor;
+        if (useRegExp) {
+            matchCursor = document()->find(exp, startPos, flags);
+        } else {
+            matchCursor = document()->find(p_text, startPos, flags);
+        }
+        if (matchCursor.isNull()) {
+            break;
+        } else {
+            if (matchCursor.selectionStart() <= lastMatch) {
+                // Wrap back.
+                break;
+            } else {
+                lastMatch = matchCursor.selectionStart();
+            }
+            nrReplaces++;
+            matchCursor.beginEditBlock();
+            matchCursor.removeSelectedText();
+            matchCursor.insertText(p_replaceText);
+            matchCursor.endEditBlock();
+            setTextCursor(matchCursor);
+            startPos = matchCursor.position();
+        }
+    }
+    // Restore cursor position.
+    cursor.setPosition(pos);
+    setTextCursor(cursor);
+    qDebug() << "replace all" << nrReplaces << "occurencs";
+}
+

+ 6 - 0
src/vedit.h

@@ -26,6 +26,12 @@ public:
     virtual void scrollToLine(int p_lineNumber);
     // User requests to insert an image.
     virtual void insertImage();
+    virtual bool findText(const QString &p_text, uint p_options, bool p_peek,
+                          bool p_forward);
+    virtual void replaceText(const QString &p_text, uint p_options,
+                             const QString &p_replaceText, bool p_findNext);
+    virtual void replaceTextAll(const QString &p_text, uint p_options,
+                                const QString &p_replaceText);
 
 protected:
     QPointer<VFile> m_file;

+ 103 - 0
src/veditarea.cpp

@@ -7,6 +7,8 @@
 #include "vfile.h"
 #include "dialog/vfindreplacedialog.h"
 
+extern VConfigManager vconfig;
+
 VEditArea::VEditArea(VNote *vnote, QWidget *parent)
     : QWidget(parent), vnote(vnote), curWindowIndex(-1)
 {
@@ -20,6 +22,14 @@ void VEditArea::setupUI()
 {
     splitter = new QSplitter(this);
     m_findReplace = new VFindReplaceDialog(this);
+    m_findReplace->setOption(FindOption::CaseSensitive,
+                             vconfig.getFindCaseSensitive());
+    m_findReplace->setOption(FindOption::WholeWordOnly,
+                             vconfig.getFindWholeWordOnly());
+    m_findReplace->setOption(FindOption::RegularExpression,
+                             vconfig.getFindRegularExpression());
+    m_findReplace->setOption(FindOption::IncrementalSearch,
+                             vconfig.getFindIncrementalSearch());
 
     QVBoxLayout *mainLayout = new QVBoxLayout();
     mainLayout->addWidget(splitter);
@@ -31,6 +41,22 @@ void VEditArea::setupUI()
 
     setLayout(mainLayout);
 
+    connect(m_findReplace, &VFindReplaceDialog::findTextChanged,
+            this, &VEditArea::handleFindTextChanged);
+    connect(m_findReplace, &VFindReplaceDialog::findOptionChanged,
+            this, &VEditArea::handleFindOptionChanged);
+    connect(m_findReplace, SIGNAL(findNext(const QString &, uint, bool)),
+            this, SLOT(handleFindNext(const QString &, uint, bool)));
+    connect(m_findReplace,
+            SIGNAL(replace(const QString &, uint, const QString &, bool)),
+            this,
+            SLOT(handleReplace(const QString &, uint, const QString &, bool)));
+    connect(m_findReplace,
+            SIGNAL(replaceAll(const QString &, uint, const QString &)),
+            this,
+            SLOT(handleReplaceAll(const QString &, uint, const QString &)));
+    connect(m_findReplace, &VFindReplaceDialog::dialogClosed,
+            this, &VEditArea::handleFindDialogClosed);
     m_findReplace->hide();
 }
 
@@ -422,3 +448,80 @@ void VEditArea::moveTab(QWidget *p_widget, int p_fromIdx, int p_toIdx)
         delete p_widget;
     }
 }
+
+// Only propogate the search in the IncrementalSearch case.
+void VEditArea::handleFindTextChanged(const QString &p_text, uint p_options)
+{
+    VEditTab *tab = currentEditTab();
+    if (tab) {
+        if (p_options & FindOption::IncrementalSearch) {
+            tab->findText(p_text, p_options, true);
+        }
+    }
+}
+
+void VEditArea::handleFindOptionChanged(uint p_options)
+{
+    qDebug() << "find option changed" << p_options;
+    vconfig.setFindCaseSensitive(p_options & FindOption::CaseSensitive);
+    vconfig.setFindWholeWordOnly(p_options & FindOption::WholeWordOnly);
+    vconfig.setFindRegularExpression(p_options & FindOption::RegularExpression);
+    vconfig.setFindIncrementalSearch(p_options & FindOption::IncrementalSearch);
+}
+
+void VEditArea::handleFindNext(const QString &p_text, uint p_options,
+                               bool p_forward)
+{
+    qDebug() << "find next" << p_text << p_options << p_forward;
+    VEditTab *tab = currentEditTab();
+    if (tab) {
+        tab->findText(p_text, p_options, false, p_forward);
+    }
+}
+
+void VEditArea::handleReplace(const QString &p_text, uint p_options,
+                              const QString &p_replaceText, bool p_findNext)
+{
+    qDebug() << "replace" << p_text << p_options << "with" << p_replaceText
+             << p_findNext;
+    VEditTab *tab = currentEditTab();
+    if (tab) {
+        tab->replaceText(p_text, p_options, p_replaceText, p_findNext);
+    }
+}
+
+void VEditArea::handleReplaceAll(const QString &p_text, uint p_options,
+                                 const QString &p_replaceText)
+{
+    qDebug() << "replace all" << p_text << p_options << "with" << p_replaceText;
+    VEditTab *tab = currentEditTab();
+    if (tab) {
+        tab->replaceTextAll(p_text, p_options, p_replaceText);
+    }
+}
+
+// Let VEditArea get focus after VFindReplaceDialog is closed.
+void VEditArea::handleFindDialogClosed()
+{
+    if (curWindowIndex == -1) {
+        setFocus();
+    } else {
+        getWindow(curWindowIndex)->focusWindow();
+    }
+
+    // Clear all the selection in Web view.
+    int nrWin = splitter->count();
+    for (int i = 0; i < nrWin; ++i) {
+        getWindow(i)->clearFindSelectionInWebView();
+    }
+}
+
+QString VEditArea::getSelectedText()
+{
+    VEditTab *tab = currentEditTab();
+    if (tab) {
+        return tab->getSelectedText();
+    } else {
+        return QString();
+    }
+}

+ 10 - 0
src/veditarea.h

@@ -40,6 +40,8 @@ public:
     // If fail, just delete the p_widget.
     void moveTab(QWidget *p_widget, int p_fromIdx, int p_toIdx);
     inline VFindReplaceDialog *getFindReplaceDialog() const;
+    // Return selected text of current edit tab.
+    QString getSelectedText();
 
 signals:
     void curTabStatusChanged(const VFile *p_file, const VEditTab *p_editTab, bool p_editMode);
@@ -66,6 +68,14 @@ private slots:
     void handleWindowFocused();
     void handleOutlineChanged(const VToc &toc);
     void handleCurHeaderChanged(const VAnchor &anchor);
+    void handleFindTextChanged(const QString &p_text, uint p_options);
+    void handleFindOptionChanged(uint p_options);
+    void handleFindNext(const QString &p_text, uint p_options, bool p_forward);
+    void handleReplace(const QString &p_text, uint p_options,
+                       const QString &p_replaceText, bool p_findNext);
+    void handleReplaceAll(const QString &p_text, uint p_options,
+                          const QString &p_replaceText);
+    void handleFindDialogClosed();
 
 private:
     void setupUI();

+ 60 - 0
src/vedittab.cpp

@@ -15,6 +15,7 @@
 #include "vnotebook.h"
 #include "vtoc.h"
 #include "vmdedit.h"
+#include "dialog/vfindreplacedialog.h"
 
 extern VConfigManager vconfig;
 
@@ -107,6 +108,7 @@ void VEditTab::showFileReadMode()
             previewByConverter();
         }
         setCurrentWidget(webPreviewer);
+        clearFindSelectionInWebView();
         scrollPreviewToHeader(outlineIndex);
         break;
     default:
@@ -276,6 +278,7 @@ void VEditTab::setupMarkdownPreview()
 void VEditTab::focusTab()
 {
     currentWidget()->setFocus();
+    emit getFocused();
 }
 
 void VEditTab::handleFocusChanged(QWidget * /* old */, QWidget *now)
@@ -455,3 +458,60 @@ void VEditTab::insertImage()
     }
     m_textEditor->insertImage();
 }
+
+void VEditTab::findText(const QString &p_text, uint p_options, bool p_peek,
+                        bool p_forward)
+{
+    if (isEditMode || !webPreviewer) {
+        m_textEditor->findText(p_text, p_options, p_peek, p_forward);
+    } else {
+        findTextInWebView(p_text, p_options, p_peek, p_forward);
+    }
+}
+
+void VEditTab::replaceText(const QString &p_text, uint p_options,
+                           const QString &p_replaceText, bool p_findNext)
+{
+    if (isEditMode) {
+        m_textEditor->replaceText(p_text, p_options, p_replaceText, p_findNext);
+    }
+}
+
+void VEditTab::replaceTextAll(const QString &p_text, uint p_options,
+                              const QString &p_replaceText)
+{
+    if (isEditMode) {
+        m_textEditor->replaceTextAll(p_text, p_options, p_replaceText);
+    }
+}
+
+void VEditTab::findTextInWebView(const QString &p_text, uint p_options,
+                                 bool p_peek, bool p_forward)
+{
+    Q_ASSERT(webPreviewer);
+    QWebEnginePage::FindFlags flags;
+    if (p_options & FindOption::CaseSensitive) {
+        flags |= QWebEnginePage::FindCaseSensitively;
+    }
+    if (!p_forward) {
+        flags |= QWebEnginePage::FindBackward;
+    }
+    webPreviewer->findText(p_text, flags);
+}
+
+QString VEditTab::getSelectedText() const
+{
+    if (isEditMode || !webPreviewer) {
+        QTextCursor cursor = m_textEditor->textCursor();
+        return cursor.selectedText();
+    } else {
+        return webPreviewer->selectedText();
+    }
+}
+
+void VEditTab::clearFindSelectionInWebView()
+{
+    if (webPreviewer) {
+        webPreviewer->findText("");
+    }
+}

+ 12 - 0
src/vedittab.h

@@ -38,6 +38,16 @@ public:
     void scrollToAnchor(const VAnchor& anchor);
     inline VFile *getFile();
     void insertImage();
+    // Search @p_text in current note.
+    void findText(const QString &p_text, uint p_options, bool p_peek,
+                  bool p_forward = true);
+    // Replace @p_text with @p_replaceText in current note.
+    void replaceText(const QString &p_text, uint p_options,
+                     const QString &p_replaceText, bool p_findNext);
+    void replaceTextAll(const QString &p_text, uint p_options,
+                        const QString &p_replaceText);
+    QString getSelectedText() const;
+    void clearFindSelectionInWebView();
 
 signals:
     void getFocused();
@@ -65,6 +75,8 @@ private:
     void parseTocUl(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
     void parseTocLi(QXmlStreamReader &xml, QVector<VHeader> &headers, int level);
     void scrollPreviewToHeader(int p_outlineIndex);
+    void findTextInWebView(const QString &p_text, uint p_options, bool p_peek,
+                           bool p_forward);
 
     QPointer<VFile> m_file;
     bool isEditMode;

+ 10 - 1
src/veditwindow.cpp

@@ -362,6 +362,7 @@ void VEditWindow::focusWindow()
     int idx = currentIndex();
     if (idx == -1) {
         setFocus();
+        emit getFocused();
         return;
     }
     getTab(idx)->focusTab();
@@ -375,7 +376,7 @@ void VEditWindow::handleTabbarClicked(int /* index */)
 
 void VEditWindow::mousePressEvent(QMouseEvent *event)
 {
-    emit getFocused();
+    focusWindow();
     QTabWidget::mousePressEvent(event);
 }
 
@@ -705,3 +706,11 @@ void VEditWindow::setCurrentWindow(bool p_current)
         leftBtn->setIcon(QIcon(":/resources/icons/corner_tablist.svg"));
     }
 }
+
+void VEditWindow::clearFindSelectionInWebView()
+{
+    int nrTab = count();
+    for (int i = 0; i < nrTab; ++i) {
+        getTab(i)->clearFindSelectionInWebView();
+    }
+}

+ 1 - 0
src/veditwindow.h

@@ -45,6 +45,7 @@ public:
     bool addEditTab(QWidget *p_widget);
     // Set whether it is the current window.
     void setCurrentWindow(bool p_current);
+    void clearFindSelectionInWebView();
 
 protected:
     void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;

+ 123 - 29
src/vmainwindow.cpp

@@ -48,6 +48,7 @@ void VMainWindow::setupUI()
 
     editArea = new VEditArea(vnote);
     editArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+    m_findReplaceDialog = editArea->getFindReplaceDialog();
     fileList->setEditArea(editArea);
     directoryTree->setEditArea(editArea);
     notebookSelector->setEditArea(editArea);
@@ -79,10 +80,12 @@ void VMainWindow::setupUI()
             editArea, &VEditArea::handleFileUpdated);
     connect(editArea, &VEditArea::curTabStatusChanged,
             this, &VMainWindow::handleCurTabStatusChanged);
+    connect(m_findReplaceDialog, &VFindReplaceDialog::findTextChanged,
+            this, &VMainWindow::handleFindDialogTextChanged);
 
     setCentralWidget(mainSplitter);
-    // TODO: Create and show the status bar
-    // statusBar();
+    // Create and show the status bar
+    statusBar();
 }
 
 QWidget *VMainWindow::setupDirectoryPanel()
@@ -179,7 +182,7 @@ void VMainWindow::initFileToolBar()
     newNoteAct = new QAction(QIcon(":/resources/icons/create_note_tb.svg"),
                              tr("New &Note"), this);
     newNoteAct->setStatusTip(tr("Create a note in current directory"));
-    newNoteAct->setShortcut(QKeySequence("Ctrl+N"));
+    newNoteAct->setShortcut(QKeySequence::New);
     connect(newNoteAct, &QAction::triggered,
             fileList, &VFileList::newFile);
 
@@ -325,13 +328,49 @@ void VMainWindow::initEditMenu()
 {
     QMenu *editMenu = menuBar()->addMenu(tr("&Edit"));
 
-    // Inser image.
-    QAction *insertImageAct = new QAction(QIcon(":/resources/icons/insert_image.svg"),
-                                          tr("Insert &Image"), this);
-    insertImageAct->setStatusTip(tr("Insert an image from file in current note"));
-    connect(insertImageAct, &QAction::triggered,
+    // Insert image.
+    m_insertImageAct = new QAction(QIcon(":/resources/icons/insert_image.svg"),
+                                   tr("Insert &Image"), this);
+    m_insertImageAct->setStatusTip(tr("Insert an image from file in current note"));
+    connect(m_insertImageAct, &QAction::triggered,
             this, &VMainWindow::insertImage);
 
+    // Find/Replace.
+    m_findReplaceAct = new QAction(QIcon(":/resources/icons/find_replace.svg"),
+                                   tr("Find/Replace"), this);
+    m_findReplaceAct->setStatusTip(tr("Open Find/Replace dialog to search in current note"));
+    m_findReplaceAct->setShortcut(QKeySequence::Find);
+    connect(m_findReplaceAct, &QAction::triggered,
+            this, &VMainWindow::openFindDialog);
+
+    m_findNextAct = new QAction(tr("Find Next"), this);
+    m_findNextAct->setStatusTip(tr("Find next occurence"));
+    m_findNextAct->setShortcut(QKeySequence::FindNext);
+    connect(m_findNextAct, SIGNAL(triggered(bool)),
+            m_findReplaceDialog, SLOT(findNext()));
+
+    m_findPreviousAct = new QAction(tr("Find Previous"), this);
+    m_findPreviousAct->setStatusTip(tr("Find previous occurence"));
+    m_findPreviousAct->setShortcut(QKeySequence::FindPrevious);
+    connect(m_findPreviousAct, SIGNAL(triggered(bool)),
+            m_findReplaceDialog, SLOT(findPrevious()));
+
+    m_replaceAct = new QAction(tr("Replace"), this);
+    m_replaceAct->setStatusTip(tr("Replace current occurence"));
+    m_replaceAct->setShortcut(QKeySequence::Replace);
+    connect(m_replaceAct, SIGNAL(triggered(bool)),
+            m_findReplaceDialog, SLOT(replace()));
+
+    m_replaceFindAct = new QAction(tr("Replace && Find"), this);
+    m_replaceFindAct->setStatusTip(tr("Replace current occurence and find the next one"));
+    connect(m_replaceFindAct, SIGNAL(triggered(bool)),
+            m_findReplaceDialog, SLOT(replaceFind()));
+
+    m_replaceAllAct = new QAction(tr("Replace All"), this);
+    m_replaceAllAct->setStatusTip(tr("Replace all occurences in current note"));
+    connect(m_replaceAllAct, SIGNAL(triggered(bool)),
+            m_findReplaceDialog, SLOT(replaceAll()));
+
     // Expand Tab into spaces.
     QAction *expandTabAct = new QAction(tr("&Expand Tab"), this);
     expandTabAct->setStatusTip(tr("Expand entered tab to spaces"));
@@ -364,8 +403,25 @@ void VMainWindow::initEditMenu()
             this, &VMainWindow::changeHighlightCursorLine);
 
 
-    editMenu->addAction(insertImageAct);
+    editMenu->addAction(m_insertImageAct);
+    editMenu->addSeparator();
+    m_insertImageAct->setEnabled(false);
+
+    QMenu *findReplaceMenu = editMenu->addMenu(tr("Find/Replace"));
+    findReplaceMenu->addAction(m_findReplaceAct);
+    findReplaceMenu->addAction(m_findNextAct);
+    findReplaceMenu->addAction(m_findPreviousAct);
+    findReplaceMenu->addAction(m_replaceAct);
+    findReplaceMenu->addAction(m_replaceFindAct);
+    findReplaceMenu->addAction(m_replaceAllAct);
     editMenu->addSeparator();
+    m_findReplaceAct->setEnabled(false);
+    m_findNextAct->setEnabled(false);
+    m_findPreviousAct->setEnabled(false);
+    m_replaceAct->setEnabled(false);
+    m_replaceFindAct->setEnabled(false);
+    m_replaceAllAct->setEnabled(false);
+
     editMenu->addAction(expandTabAct);
     if (vconfig.getIsExpandTab()) {
         expandTabAct->setChecked(true);
@@ -588,36 +644,56 @@ void VMainWindow::setRenderBackgroundColor(QAction *action)
     vnote->updateTemplate();
 }
 
-void VMainWindow::updateToolbarFromTabChage(const VFile *p_file, bool p_editMode)
+void VMainWindow::updateActionStateFromTabStatusChange(const VFile *p_file,
+                                                       bool p_editMode)
 {
-    qDebug() << "update toolbar";
-    if (!p_file) {
+    if (p_file) {
+        if (p_editMode) {
+            editNoteAct->setVisible(false);
+            saveExitAct->setVisible(true);
+            saveNoteAct->setVisible(true);
+            deleteNoteAct->setEnabled(true);
+
+            m_insertImageAct->setEnabled(true);
+        } else {
+            editNoteAct->setVisible(true);
+            saveExitAct->setVisible(false);
+            saveNoteAct->setVisible(false);
+            deleteNoteAct->setEnabled(true);
+
+            m_insertImageAct->setEnabled(false);
+            m_replaceAct->setEnabled(false);
+            m_replaceFindAct->setEnabled(false);
+            m_replaceAllAct->setEnabled(false);
+        }
+        noteInfoAct->setEnabled(true);
+
+        m_findReplaceAct->setEnabled(true);
+    } else {
         editNoteAct->setVisible(false);
         saveExitAct->setVisible(false);
         saveNoteAct->setVisible(false);
         deleteNoteAct->setEnabled(false);
-    } else if (p_editMode) {
-        editNoteAct->setVisible(false);
-        saveExitAct->setVisible(true);
-        saveNoteAct->setVisible(true);
-        deleteNoteAct->setEnabled(true);
-    } else {
-        editNoteAct->setVisible(true);
-        saveExitAct->setVisible(false);
-        saveNoteAct->setVisible(false);
-        deleteNoteAct->setEnabled(true);
-    }
-
-    if (p_file) {
-        noteInfoAct->setEnabled(true);
-    } else {
         noteInfoAct->setEnabled(false);
+
+        m_insertImageAct->setEnabled(false);
+        // Find/Replace
+        m_findReplaceAct->setEnabled(false);
+        m_findNextAct->setEnabled(false);
+        m_findPreviousAct->setEnabled(false);
+        m_replaceAct->setEnabled(false);
+        m_replaceFindAct->setEnabled(false);
+        m_replaceAllAct->setEnabled(false);
+        m_findReplaceDialog->closeDialog();
     }
 }
 
 void VMainWindow::handleCurTabStatusChanged(const VFile *p_file, const VEditTab *p_editTab, bool p_editMode)
 {
-    updateToolbarFromTabChage(p_file, p_editMode);
+    updateActionStateFromTabStatusChange(p_file, p_editMode);
+    if (p_file) {
+        m_findReplaceDialog->updateState(p_file->getDocType(), p_editMode);
+    }
 
     QString title;
     if (p_file) {
@@ -761,7 +837,7 @@ void VMainWindow::resizeEvent(QResizeEvent *event)
 void VMainWindow::keyPressEvent(QKeyEvent *event)
 {
     if (event->key() == Qt::Key_Escape) {
-        editArea->getFindReplaceDialog()->closeDialog();
+        m_findReplaceDialog->closeDialog();
         event->accept();
         return;
     }
@@ -809,3 +885,21 @@ void VMainWindow::locateFile(VFile *p_file) const
     }
 }
 
+void VMainWindow::handleFindDialogTextChanged(const QString &p_text, uint /* p_options */)
+{
+    bool enabled = true;
+    if (p_text.isEmpty()) {
+        enabled = false;
+    }
+    m_findNextAct->setEnabled(enabled);
+    m_findPreviousAct->setEnabled(enabled);
+    m_replaceAct->setEnabled(enabled);
+    m_replaceFindAct->setEnabled(enabled);
+    m_replaceAllAct->setEnabled(enabled);
+}
+
+void VMainWindow::openFindDialog()
+{
+    m_findReplaceDialog->openDialog(editArea->getSelectedText());
+}
+

+ 14 - 1
src/vmainwindow.h

@@ -27,6 +27,7 @@ class QToolBox;
 class VOutline;
 class VNotebookSelector;
 class VAvatar;
+class VFindReplaceDialog;
 
 class VMainWindow : public QMainWindow
 {
@@ -55,6 +56,8 @@ private slots:
     void handleCurrentDirectoryChanged(const VDirectory *p_dir);
     void handleCurrentNotebookChanged(const VNotebook *p_notebook);
     void insertImage();
+    void handleFindDialogTextChanged(const QString &p_text, uint p_options);
+    void openFindDialog();
 
 protected:
     void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
@@ -83,7 +86,8 @@ private:
     void initEditorBackgroundMenu(QMenu *menu);
     void changeSplitterView(int nrPanel);
     void updateWindowTitle(const QString &str);
-    void updateToolbarFromTabChage(const VFile *p_file, bool p_editMode);
+    void updateActionStateFromTabStatusChange(const VFile *p_file,
+                                              bool p_editMode);
     void saveStateAndGeometry();
     void restoreStateAndGeometry();
     void repositionAvatar();
@@ -103,6 +107,7 @@ private:
     QToolBox *toolBox;
     VOutline *outline;
     VAvatar *m_avatar;
+    VFindReplaceDialog *m_findReplaceDialog;
 
     // Actions
     QAction *newRootDirAct;
@@ -115,6 +120,14 @@ private:
     QAction *discardExitAct;
     QAction *expandViewAct;
 
+    QAction *m_insertImageAct;
+    QAction *m_findReplaceAct;
+    QAction *m_findNextAct;
+    QAction *m_findPreviousAct;
+    QAction *m_replaceAct;
+    QAction *m_replaceFindAct;
+    QAction *m_replaceAllAct;
+
     // Menus
     QMenu *viewMenu;
 

+ 6 - 2
src/vmdeditoperations.cpp

@@ -287,6 +287,7 @@ bool VMdEditOperations::handleKeyBracketLeft(QKeyEvent *p_event)
     // 1. If it is not in Normal state, just go back to Normal state;
     // 2. If it is already Normal state, try to clear selection;
     // 3. Anyway, we accept this event.
+    bool accept = false;
     if (p_event->modifiers() == Qt::ControlModifier) {
         if (m_keyState != KeyState::Normal) {
             m_pendingTimer->stop();
@@ -299,9 +300,12 @@ bool VMdEditOperations::handleKeyBracketLeft(QKeyEvent *p_event)
                 m_editor->setTextCursor(cursor);
             }
         }
+        accept = true;
     }
-    p_event->accept();
-    return true;
+    if (accept) {
+        p_event->accept();
+    }
+    return accept;
 }
 
 bool VMdEditOperations::handleKeyTab(QKeyEvent *p_event)

+ 1 - 0
src/vnote.qrc

@@ -82,5 +82,6 @@
         <file>resources/icons/corner_menu_cur.svg</file>
         <file>resources/icons/corner_tablist_cur.svg</file>
         <file>resources/icons/close.svg</file>
+        <file>resources/icons/find_replace.svg</file>
     </qresource>
 </RCC>