Browse Source

LocationList: fix the recently introduced regression when highlighting segments of text

Le Tan 4 years ago
parent
commit
9cf015a676

+ 2 - 0
src/core/global.h

@@ -133,6 +133,8 @@ namespace vnotex
         int m_length = -1;
     };
 
+    Q_DECLARE_METATYPE(Segment);
+
 } // ns vnotex
 
 Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::FindOptions);

+ 3 - 0
src/core/historymgr.cpp

@@ -8,6 +8,7 @@
 #include "vnotex.h"
 #include "notebookmgr.h"
 #include <notebook/notebook.h>
+#include <notebookbackend/inotebookbackend.h>
 
 using namespace vnotex;
 
@@ -59,9 +60,11 @@ void HistoryMgr::loadHistory()
         const auto &notebooks = VNoteX::getInst().getNotebookMgr().getNotebooks();
         for (const auto &nb : notebooks) {
             const auto &history = nb->getHistory();
+            const auto &backend = nb->getBackend();
             for (const auto &item : history) {
                 auto fullItem = QSharedPointer<HistoryItemFull>::create();
                 fullItem->m_item = item;
+                fullItem->m_item.m_path = backend->getFullPath(item.m_path);
                 fullItem->m_notebookName = nb->getName();
                 m_history.push_back(fullItem);
             }

+ 4 - 4
src/core/iconfig.h

@@ -96,8 +96,8 @@ namespace vnotex
             auto arr = read(p_default, p_user, p_key).toArray();
             QStringList res;
             res.reserve(arr.size());
-            for (const auto &ele : arr) {
-                res.push_back(ele.toString());
+            for (int i = 0; i < arr.size(); ++i) {
+                res.push_back(arr[i].toString());
             }
             return res;
         }
@@ -108,8 +108,8 @@ namespace vnotex
             auto arr = p_obj.value(p_key).toArray();
             QStringList res;
             res.reserve(arr.size());
-            for (const auto &ele : arr) {
-                res.push_back(ele.toString());
+            for (int i = 0; i < arr.size(); ++i) {
+                res.push_back(arr[i].toString());
             }
             return res;
         }

+ 3 - 3
src/core/location.h

@@ -39,7 +39,7 @@ namespace vnotex
         {
             Line() = default;
 
-            Line(int p_lineNumber, const QString &p_text, const QVector<Segment> &p_segments)
+            Line(int p_lineNumber, const QString &p_text, const QList<Segment> &p_segments)
                 : m_lineNumber(p_lineNumber),
                   m_text(p_text),
                   m_segments(p_segments)
@@ -51,10 +51,10 @@ namespace vnotex
 
             QString m_text;
 
-            QVector<Segment> m_segments;
+            QList<Segment> m_segments;
         };
 
-        void addLine(int p_lineNumber, const QString &p_text, const QVector<Segment> &p_segments)
+        void addLine(int p_lineNumber, const QString &p_text, const QList<Segment> &p_segments)
         {
             m_lines.push_back(Line(p_lineNumber, p_text, p_segments));
         }

+ 1 - 1
src/core/notebook/bundlenotebook.cpp

@@ -70,7 +70,7 @@ void BundleNotebook::addHistory(const HistoryItem &p_item)
 {
     HistoryItem item(p_item);
     item.m_path = getBackend()->getRelativePath(item.m_path);
-    HistoryMgr::insertHistoryItem(m_history, p_item);
+    HistoryMgr::insertHistoryItem(m_history, item);
 
     updateNotebookConfig();
 }

+ 10 - 4
src/data/extra/themes/moonlight/palette.json

@@ -236,10 +236,6 @@
         "locationlist" : {
             "node_icon" : {
                 "fg" : "@base#icon#fg"
-            },
-            "text_highlight" : {
-                "fg" : "@base#master#fg",
-                "bg" : "@base#master#bg"
             }
         },
         "viewsplit" : {
@@ -621,6 +617,16 @@
                 "fg" : "@palette#bg2_9",
                 "border" : "@palette#bg2_9"
             }
+        },
+        "styleditemdelegate" : {
+            "separator" : {
+                "fg" : "@base#normal#fg",
+                "bg" : "@widgets#separator#bg"
+            },
+            "highlight" : {
+                "fg" : "@base#master#fg",
+                "bg" : "@base#master#bg"
+            }
         }
     }
 }

+ 10 - 4
src/data/extra/themes/native/palette.json

@@ -99,10 +99,6 @@
         "locationlist" : {
             "node_icon" : {
                 "fg" : "@base#icon#fg"
-            },
-            "text_highlight" : {
-                "fg" : "@base#master#fg",
-                "bg" : "@base#master#bg"
             }
         },
         "viewsplit" : {
@@ -134,6 +130,16 @@
                 "fg" : "@base#master#bg",
                 "border" : "@base#master#bg"
             }
+        },
+        "styleditemdelegate" : {
+            "separator" : {
+                "fg" : "@base#normal#fg",
+                "bg" : "@base#normal#border"
+            },
+            "highlight" : {
+                "fg" : "@base#master#fg",
+                "bg" : "@base#master#bg"
+            }
         }
     }
 }

+ 10 - 4
src/data/extra/themes/pure/palette.json

@@ -232,10 +232,6 @@
         "locationlist" : {
             "node_icon" : {
                 "fg" : "@base#icon#fg"
-            },
-            "text_highlight" : {
-                "fg" : "@base#master#fg",
-                "bg" : "@base#master#bg"
             }
         },
         "viewsplit" : {
@@ -617,6 +613,16 @@
                 "fg" : "@base#master#bg",
                 "border" : "@base#master#bg"
             }
+        },
+        "styleditemdelegate" : {
+            "separator" : {
+                "fg" : "@base#normal#fg",
+                "bg" : "@widgets#separator#bg"
+            },
+            "highlight" : {
+                "fg" : "@base#master#fg",
+                "bg" : "@base#master#bg"
+            }
         }
     }
 }

+ 4 - 1
src/search/filesearchengine.cpp

@@ -97,7 +97,7 @@ void FileSearchEngineWorker::searchFile(const QString &p_filePath, const QString
 
         const auto lineText = ins.readLine();
         bool matched = false;
-        QVector<Segment> segments;
+        QList<Segment> segments;
         if (!shouldStartBatchMode) {
             matched = m_token.matched(lineText, &segments);
         } else {
@@ -172,6 +172,9 @@ void FileSearchEngine::search(const QSharedPointer<SearchOption> &p_option,
     const int step = totalSize / numThread;
     int remain = totalSize % numThread;
     int start = 0;
+
+    qDebug() << "start async file search" << totalSize << numThread;
+
     for (int i = 0; i < numThread && start < totalSize; ++i) {
         int len = step;
         if (remain) {

+ 6 - 2
src/search/searcher.cpp

@@ -294,7 +294,7 @@ bool Searcher::searchContent(const File *p_file)
         if (idx > pos) {
             QString lineText = content.mid(pos, idx - pos);
             bool matched = false;
-            QVector<Segment> segments;
+            QList<Segment> segments;
             if (!shouldStartBatchMode) {
                 matched = m_token.matched(lineText, &segments);
             } else {
@@ -480,17 +480,21 @@ bool Searcher::firstPhaseSearch(Notebook *p_notebook, QVector<SearchSecondPhaseI
 bool Searcher::secondPhaseSearch(const QVector<SearchSecondPhaseItem> &p_secondPhaseItems)
 {
     Q_ASSERT(!p_secondPhaseItems.isEmpty());
+
+    emit logRequested(tr("Start second-phase search: %n files(s)", "", p_secondPhaseItems.size()));
     qDebug() << "secondPhaseSearch" << p_secondPhaseItems.size();
 
     createSearchEngine();
 
-    m_engine->search(m_option, m_token, p_secondPhaseItems);
     connect(m_engine.data(), &ISearchEngine::finished,
             this, &Searcher::finished);
     connect(m_engine.data(), &ISearchEngine::logRequested,
             this, &Searcher::logRequested);
     connect(m_engine.data(), &ISearchEngine::resultItemsAdded,
             this, &Searcher::resultItemsAdded);
+
+    m_engine->search(m_option, m_token, p_secondPhaseItems);
+
     return true;
 }
 

+ 3 - 3
src/search/searchresultitem.cpp

@@ -6,7 +6,7 @@ QSharedPointer<SearchResultItem> SearchResultItem::createBufferItem(const QStrin
                                                                     const QString &p_displayPath,
                                                                     int p_lineNumber,
                                                                     const QString &p_text,
-                                                                    const QVector<Segment> &p_segments)
+                                                                    const QList<Segment> &p_segments)
 {
     auto item = createBufferItem(p_targetPath, p_displayPath);
     item->m_location.addLine(p_lineNumber, p_text, p_segments);
@@ -27,7 +27,7 @@ QSharedPointer<SearchResultItem> SearchResultItem::createFileItem(const QString
                                                                   const QString &p_displayPath,
                                                                   int p_lineNumber,
                                                                   const QString &p_text,
-                                                                  const QVector<Segment> &p_segments)
+                                                                  const QList<Segment> &p_segments)
 {
     auto item = createFileItem(p_targetPath, p_displayPath);
     item->m_location.addLine(p_lineNumber, p_text, p_segments);
@@ -64,7 +64,7 @@ QSharedPointer<SearchResultItem> SearchResultItem::createNotebookItem(const QStr
     return item;
 }
 
-void SearchResultItem::addLine(int p_lineNumber, const QString &p_text, const QVector<Segment> &p_segments)
+void SearchResultItem::addLine(int p_lineNumber, const QString &p_text, const QList<Segment> &p_segments)
 {
     m_location.addLine(p_lineNumber, p_text, p_segments);
 }

+ 3 - 3
src/search/searchresultitem.h

@@ -17,13 +17,13 @@ namespace vnotex
             return p_dbg;
         }
 
-        void addLine(int p_lineNumber, const QString &p_text, const QVector<Segment> &p_segments);
+        void addLine(int p_lineNumber, const QString &p_text, const QList<Segment> &p_segments);
 
         static QSharedPointer<SearchResultItem> createBufferItem(const QString &p_targetPath,
                                                                  const QString &p_displayPath,
                                                                  int p_lineNumber,
                                                                  const QString &p_text,
-                                                                 const QVector<Segment> &p_segments);
+                                                                 const QList<Segment> &p_segments);
 
         static QSharedPointer<SearchResultItem> createBufferItem(const QString &p_targetPath,
                                                                  const QString &p_displayPath);
@@ -32,7 +32,7 @@ namespace vnotex
                                                                const QString &p_displayPath,
                                                                int p_lineNumber,
                                                                const QString &p_text,
-                                                               const QVector<Segment> &p_segments);
+                                                               const QList<Segment> &p_segments);
 
         static QSharedPointer<SearchResultItem> createFileItem(const QString &p_targetPath,
                                                                const QString &p_displayPath);

+ 2 - 2
src/search/searchtoken.cpp

@@ -31,7 +31,7 @@ void SearchToken::append(const QRegularExpression &p_regExp)
     m_regularExpressions.append(p_regExp);
 }
 
-bool SearchToken::matched(const QString &p_text, QVector<Segment> *p_segments) const
+bool SearchToken::matched(const QString &p_text, QList<Segment> *p_segments) const
 {
     const int consSize = constraintSize();
     if (consSize == 0) {
@@ -90,7 +90,7 @@ void SearchToken::startBatchMode()
     m_matchedConstraintsCountInBatchMode = 0;
 }
 
-bool SearchToken::matchedInBatchMode(const QString &p_text, QVector<Segment> *p_segments)
+bool SearchToken::matchedInBatchMode(const QString &p_text, QList<Segment> *p_segments)
 {
     bool isMatched = false;
     const int consSize = m_matchedConstraintsInBatchMode.size();

+ 2 - 2
src/search/searchtoken.h

@@ -35,7 +35,7 @@ namespace vnotex
         void append(const QRegularExpression &p_regExp);
 
         // Whether @p_text is matched.
-        bool matched(const QString &p_text, QVector<Segment> *p_segments = nullptr) const;
+        bool matched(const QString &p_text, QList<Segment> *p_segments = nullptr) const;
 
         int constraintSize() const;
 
@@ -48,7 +48,7 @@ namespace vnotex
 
         // Match one string in batch mode.
         // Return true if @p_text is matched.
-        bool matchedInBatchMode(const QString &p_text, QVector<Segment> *p_segments = nullptr);
+        bool matchedInBatchMode(const QString &p_text, QList<Segment> *p_segments = nullptr);
 
         bool readyToEndBatchMode() const;
 

+ 0 - 15
src/widgets/dialogs/selectdialog.cpp

@@ -118,21 +118,6 @@ void SelectDialog::keyPressEvent(QKeyEvent *p_event)
         return;
     }
 
-    // On Mac OS X, it is `Command+O` to activate an item, instead of Return.
-#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
-    {
-        const int key = p_event->key();
-        if (key == Qt::Key_Return || key == Qt::Key_Enter) {
-            p_event->accept();
-            if (auto item = m_list->currentItem()) {
-                selectionChosen(item);
-            }
-
-            return;
-        }
-    }
-#endif
-
     QDialog::keyPressEvent(p_event);
 }
 

+ 38 - 8
src/widgets/editors/statuswidget.cpp

@@ -2,25 +2,26 @@
 
 #include <QTimer>
 #include <QLabel>
-#include <QHBoxLayout>
+#include <QStackedLayout>
+#include <QDebug>
 
 using namespace vnotex;
 
 StatusWidget::StatusWidget(QWidget *p_parent)
     : QWidget(p_parent)
 {
-    auto layout = new QHBoxLayout(this);
-    layout->setContentsMargins(0, 0, 0, 0);
-    layout->setSpacing(0);
+    m_mainLayout = new QStackedLayout(this);
+    m_mainLayout->setContentsMargins(0, 0, 0, 0);
+    m_mainLayout->setSpacing(0);
 
     m_messageLabel = new QLabel(this);
     m_messageLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
-    layout->addWidget(m_messageLabel);
+    m_mainLayout->addWidget(m_messageLabel);
 
     m_messageTimer = new QTimer(this);
     m_messageTimer->setSingleShot(true);
     connect(m_messageTimer, &QTimer::timeout,
-            m_messageLabel, &QLabel::clear);
+            this, &StatusWidget::clearMessage);
 }
 
 StatusWidget::~StatusWidget()
@@ -32,13 +33,42 @@ StatusWidget::~StatusWidget()
 
 void StatusWidget::showMessage(const QString &p_msg, int p_milliseconds)
 {
+    if (p_msg.isEmpty()) {
+        clearMessage();
+        return;
+    }
+
     m_messageLabel->setText(p_msg);
-    m_messageTimer->start(p_milliseconds);
+    m_mainLayout->setCurrentWidget(m_messageLabel);
+
+    if (p_milliseconds > 0) {
+        m_messageTimer->start(p_milliseconds);
+    }
 }
 
 void StatusWidget::setEditorStatusWidget(const QSharedPointer<QWidget> &p_editorWidget)
 {
     Q_ASSERT(!m_editorWidget);
     m_editorWidget = p_editorWidget;
-    layout()->addWidget(m_editorWidget.data());
+    m_mainLayout->addWidget(m_editorWidget.data());
+    m_mainLayout->setCurrentWidget(m_editorWidget.data());
+}
+
+void StatusWidget::resizeEvent(QResizeEvent *p_event)
+{
+    QWidget::resizeEvent(p_event);
+
+    int maxWidth = width() - 10;
+    if (maxWidth <= 0) {
+        maxWidth = width();
+    }
+    m_messageLabel->setMaximumWidth(maxWidth);
+}
+
+void StatusWidget::clearMessage()
+{
+    m_messageLabel->clear();
+    if (m_editorWidget) {
+        m_mainLayout->setCurrentWidget(m_editorWidget.data());
+    }
 }

+ 8 - 0
src/widgets/editors/statuswidget.h

@@ -6,6 +6,7 @@
 
 class QLabel;
 class QTimer;
+class QStackedLayout;
 
 namespace vnotex
 {
@@ -22,7 +23,14 @@ namespace vnotex
 
         void setEditorStatusWidget(const QSharedPointer<QWidget> &p_editorWidget);
 
+    protected:
+        void resizeEvent(QResizeEvent *p_event) Q_DECL_OVERRIDE;
+
     private:
+        void clearMessage();
+
+        QStackedLayout *m_mainLayout = nullptr;
+
         QLabel *m_messageLabel = nullptr;
 
         QTimer *m_messageTimer = nullptr;

+ 1 - 2
src/widgets/historypanel.cpp

@@ -14,7 +14,6 @@
 #include <core/notebookmgr.h>
 #include <core/fileopenparameters.h>
 
-
 #include "titlebar.h"
 #include "listwidget.h"
 #include "mainwindow.h"
@@ -40,7 +39,7 @@ void HistoryPanel::setupUI()
         mainLayout->addWidget(m_titleBar);
     }
 
-    m_historyList = new ListWidget(this);
+    m_historyList = new ListWidget(true, this);
     m_historyList->setContextMenuPolicy(Qt::CustomContextMenu);
     m_historyList->setSelectionMode(QAbstractItemView::ExtendedSelection);
     connect(m_historyList, &QListWidget::customContextMenuRequested,

+ 257 - 0
src/widgets/itemproxystyle.cpp

@@ -0,0 +1,257 @@
+#include "itemproxystyle.h"
+
+#include <QDebug>
+#include <QPainter>
+#include <QStyleOptionViewItem>
+#include <QTextOption>
+#include <QTextLayout>
+
+#include "styleditemdelegate.h"
+
+using namespace vnotex;
+
+ItemProxyStyle::ItemProxyStyle(QStyle *p_style)
+    : QProxyStyle(p_style)
+{
+}
+
+void ItemProxyStyle::drawControl(QStyle::ControlElement p_element,
+                                 const QStyleOption *p_option,
+                                 QPainter *p_painter,
+                                 const QWidget *p_widget) const
+{
+    if (p_element == QStyle::CE_ItemViewItem) {
+        if (drawItemViewItem(p_option, p_painter, p_widget)) {
+            return;
+        }
+    }
+
+    QProxyStyle::drawControl(p_element, p_option, p_painter, p_widget);
+}
+
+bool ItemProxyStyle::drawItemViewItem(const QStyleOption *p_option, QPainter *p_painter, const QWidget *p_widget) const
+{
+    const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(p_option);
+    if (!vopt) {
+        return false;
+    }
+
+    const auto value = vopt->index.data(HighlightsRole);
+    if (!value.canConvert<QList<Segment>>()) {
+        return false;
+    }
+
+    auto segments = value.value<QList<Segment>>();
+    if (segments.isEmpty()) {
+        return false;
+    }
+
+    // Copied from qtbase/src/widgets/styles/qcommonstyle.cpp.
+
+    p_painter->save();
+    p_painter->setClipRect(vopt->rect);
+    QRect checkRect = proxy()->subElementRect(SE_ItemViewItemCheckIndicator, vopt, p_widget);
+    QRect iconRect = proxy()->subElementRect(SE_ItemViewItemDecoration, vopt, p_widget);
+    QRect textRect = proxy()->subElementRect(SE_ItemViewItemText, vopt, p_widget);
+
+    // Draw the background.
+    proxy()->drawPrimitive(PE_PanelItemViewItem, vopt, p_painter, p_widget);
+
+    // Draw the check mark.
+    if (vopt->features & QStyleOptionViewItem::HasCheckIndicator) {
+        QStyleOptionViewItem option(*vopt);
+        option.rect = checkRect;
+        option.state = option.state & ~QStyle::State_HasFocus;
+        switch (vopt->checkState) {
+        case Qt::Unchecked:
+            option.state |= QStyle::State_Off;
+            break;
+        case Qt::PartiallyChecked:
+            option.state |= QStyle::State_NoChange;
+            break;
+        case Qt::Checked:
+            option.state |= QStyle::State_On;
+            break;
+        }
+        proxy()->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &option, p_painter, p_widget);
+    }
+
+    // Draw the icon.
+    QIcon::Mode mode = QIcon::Normal;
+    if (!(vopt->state & QStyle::State_Enabled)) {
+        mode = QIcon::Disabled;
+    } else if (vopt->state & QStyle::State_Selected) {
+        mode = QIcon::Selected;
+    }
+    QIcon::State state = vopt->state & QStyle::State_Open ? QIcon::On : QIcon::Off;
+    vopt->icon.paint(p_painter, iconRect, vopt->decorationAlignment, mode, state);
+
+    // Draw the text.
+    if (!vopt->text.isEmpty()) {
+        QPalette::ColorGroup cg = vopt->state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
+        if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active)) {
+            cg = QPalette::Inactive;
+        }
+        if (vopt->state & QStyle::State_Selected) {
+            p_painter->setPen(vopt->palette.color(cg, QPalette::HighlightedText));
+        } else {
+            p_painter->setPen(vopt->palette.color(cg, QPalette::Text));
+        }
+        if (vopt->state & QStyle::State_Editing) {
+            p_painter->setPen(vopt->palette.color(cg, QPalette::Text));
+            p_painter->drawRect(textRect.adjusted(0, 0, -1, -1));
+        }
+
+        viewItemDrawText(p_painter, vopt, textRect);
+    }
+
+    // Draw the focus rect.
+    if (vopt->state & QStyle::State_HasFocus) {
+        QStyleOptionFocusRect o;
+        o.QStyleOption::operator=(*vopt);
+        o.rect = proxy()->subElementRect(SE_ItemViewItemFocusRect, vopt, p_widget);
+        o.state |= QStyle::State_KeyboardFocusChange;
+        o.state |= QStyle::State_Item;
+        QPalette::ColorGroup cg = (vopt->state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
+        o.backgroundColor = vopt->palette.color(cg, (vopt->state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Window);
+        proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, p_painter, p_widget);
+    }
+
+    p_painter->restore();
+
+    return true;
+}
+
+static QSizeF viewItemTextLayout(QTextLayout &textLayout, int lineWidth, int maxHeight = -1, int *lastVisibleLine = nullptr)
+{
+    if (lastVisibleLine)
+        *lastVisibleLine = -1;
+    qreal height = 0;
+    qreal widthUsed = 0;
+    textLayout.beginLayout();
+    int i = 0;
+    while (true) {
+        QTextLine line = textLayout.createLine();
+        if (!line.isValid())
+            break;
+        line.setLineWidth(lineWidth);
+        line.setPosition(QPointF(0, height));
+        height += line.height();
+        widthUsed = qMax(widthUsed, line.naturalTextWidth());
+        // we assume that the height of the next line is the same as the current one
+        if (maxHeight > 0 && lastVisibleLine && height + line.height() > maxHeight) {
+            const QTextLine nextLine = textLayout.createLine();
+            *lastVisibleLine = nextLine.isValid() ? i : -1;
+            break;
+        }
+        ++i;
+    }
+    textLayout.endLayout();
+    return QSizeF(widthUsed, height);
+}
+
+void ItemProxyStyle::viewItemDrawText(QPainter *p_painter, const QStyleOptionViewItem *p_option, const QRect &p_rect) const
+{
+    // Copied from qtbase/src/widgets/styles/qcommonstyle.cpp.
+
+    const QWidget *widget = p_option->widget;
+    const int textMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1;
+    // Remove width padding.
+    QRect textRect = p_rect.adjusted(textMargin, 0, -textMargin, 0);
+    const bool wrapText = p_option->features & QStyleOptionViewItem::WrapText;
+    QTextOption textOption;
+    textOption.setWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::ManualWrap);
+    textOption.setTextDirection(p_option->direction);
+    textOption.setAlignment(QStyle::visualAlignment(p_option->direction, p_option->displayAlignment));
+    QPointF paintPosition;
+    const QString newText = calculateElidedText(p_option->text,
+                                                textOption,
+                                                p_option->font,
+                                                textRect,
+                                                p_option->displayAlignment,
+                                                p_option->textElideMode,
+                                                0,
+                                                true,
+                                                &paintPosition);
+    QTextLayout textLayout(newText, p_option->font);
+    textLayout.setTextOption(textOption);
+    viewItemTextLayout(textLayout, textRect.width());
+    textLayout.draw(p_painter, paintPosition);
+}
+
+QString ItemProxyStyle::calculateElidedText(const QString &text, const QTextOption &textOption,
+                                            const QFont &font, const QRect &textRect, const Qt::Alignment valign,
+                                            Qt::TextElideMode textElideMode, int flags,
+                                            bool lastVisibleLineShouldBeElided, QPointF *paintStartPosition) const
+{
+    // Copied from qtbase/src/widgets/styles/qcommonstyle.cpp.
+
+    QTextLayout textLayout(text, font);
+    textLayout.setTextOption(textOption);
+    // In AlignVCenter mode when more than one line is displayed and the height only allows
+    // some of the lines it makes no sense to display those. From a users perspective it makes
+    // more sense to see the start of the text instead something inbetween.
+    const bool vAlignmentOptimization = paintStartPosition && valign.testFlag(Qt::AlignVCenter);
+    int lastVisibleLine = -1;
+    viewItemTextLayout(textLayout, textRect.width(), vAlignmentOptimization ? textRect.height() : -1, &lastVisibleLine);
+    const QRectF boundingRect = textLayout.boundingRect();
+    // don't care about LTR/RTL here, only need the height
+    const QRect layoutRect = QStyle::alignedRect(Qt::LayoutDirectionAuto, valign,
+                                                 boundingRect.size().toSize(), textRect);
+    if (paintStartPosition)
+        *paintStartPosition = QPointF(textRect.x(), layoutRect.top());
+    QString ret;
+    qreal height = 0;
+    const int lineCount = textLayout.lineCount();
+    for (int i = 0; i < lineCount; ++i) {
+        const QTextLine line = textLayout.lineAt(i);
+        height += line.height();
+        // above visible rect
+        if (height + layoutRect.top() <= textRect.top()) {
+            if (paintStartPosition)
+                paintStartPosition->ry() += line.height();
+            continue;
+        }
+        const int start = line.textStart();
+        const int length = line.textLength();
+        const bool drawElided = line.naturalTextWidth() > textRect.width();
+        bool elideLastVisibleLine = lastVisibleLine == i;
+        if (!drawElided && i + 1 < lineCount && lastVisibleLineShouldBeElided) {
+            const QTextLine nextLine = textLayout.lineAt(i + 1);
+            const int nextHeight = height + nextLine.height() / 2;
+            // elide when less than the next half line is visible
+            if (nextHeight + layoutRect.top() > textRect.height() + textRect.top())
+                elideLastVisibleLine = true;
+        }
+        QString text = textLayout.text().mid(start, length);
+        if (drawElided || elideLastVisibleLine) {
+            Q_ASSERT(false);
+            if (elideLastVisibleLine) {
+                if (text.endsWith(QChar::LineSeparator))
+                    text.chop(1);
+                text += QChar(0x2026);
+            }
+            /* TODO: QStackTextEngine is a private class.
+            const QStackTextEngine engine(text, font);
+            ret += engine.elidedText(textElideMode, textRect.width(), flags);
+            */
+            Q_UNUSED(flags);
+            Q_UNUSED(textElideMode);
+            ret += text;
+            // no newline for the last line (last visible or real)
+            // sometimes drawElided is true but no eliding is done so the text ends
+            // with QChar::LineSeparator - don't add another one. This happened with
+            // arabic text in the testcase for QTBUG-72805
+            if (i < lineCount - 1 &&
+                !ret.endsWith(QChar::LineSeparator))
+                ret += QChar::LineSeparator;
+        } else {
+            ret += text;
+        }
+        // below visible text, can stop
+        if ((height + layoutRect.top() >= textRect.bottom()) ||
+                (lastVisibleLine >= 0 && lastVisibleLine == i))
+            break;
+    }
+    return ret;
+}

+ 35 - 0
src/widgets/itemproxystyle.h

@@ -0,0 +1,35 @@
+#ifndef ITEMPROXYSTYLE_H
+#define ITEMPROXYSTYLE_H
+
+#include <QProxyStyle>
+
+class QStyleOptionViewItem;
+class QTextOption;
+
+namespace vnotex
+{
+    // Draw item with text segments highlighted.
+    class ItemProxyStyle : public QProxyStyle
+    {
+        Q_OBJECT
+    public:
+        explicit ItemProxyStyle(QStyle *p_style = nullptr);
+
+        void drawControl(QStyle::ControlElement p_element,
+                         const QStyleOption *p_option,
+                         QPainter *p_painter,
+                         const QWidget *p_widget = nullptr) const Q_DECL_OVERRIDE;
+
+    private:
+        bool drawItemViewItem(const QStyleOption *p_option, QPainter *p_painter, const QWidget *p_widget) const;
+
+        void viewItemDrawText(QPainter *p_painter, const QStyleOptionViewItem *p_option, const QRect &p_rect) const;
+
+        QString calculateElidedText(const QString &text, const QTextOption &textOption,
+                                    const QFont &font, const QRect &textRect, const Qt::Alignment valign,
+                                    Qt::TextElideMode textElideMode, int flags,
+                                    bool lastVisibleLineShouldBeElided, QPointF *paintStartPosition) const;
+    };
+}
+
+#endif // ITEMPROXYSTYLE_H

+ 18 - 4
src/widgets/lineedit.cpp

@@ -115,9 +115,23 @@ void LineEdit::setInputMethodEnabled(bool p_enabled)
     if (m_inputMethodEnabled != p_enabled) {
         m_inputMethodEnabled = p_enabled;
 
-        QInputMethod *im = QGuiApplication::inputMethod();
-        im->reset();
-        // Ask input method to query current state, which will call inputMethodQuery().
-        im->update(Qt::ImEnabled);
+        updateInputMethod();
     }
 }
+
+void LineEdit::showEvent(QShowEvent *p_event)
+{
+    QLineEdit::showEvent(p_event);
+
+    if (!m_inputMethodEnabled) {
+        updateInputMethod();
+    }
+}
+
+void LineEdit::updateInputMethod() const
+{
+    QInputMethod *im = QGuiApplication::inputMethod();
+    im->reset();
+    // Ask input method to query current state, which will call inputMethodQuery().
+    im->update(Qt::ImEnabled);
+}

+ 4 - 0
src/widgets/lineedit.h

@@ -23,7 +23,11 @@ namespace vnotex
     protected:
         void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
 
+        void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE;
+
     private:
+        void updateInputMethod() const;
+
         // Whether enable input method.
         bool m_inputMethodEnabled = true;
     };

+ 48 - 2
src/widgets/listwidget.cpp

@@ -1,13 +1,47 @@
 #include "listwidget.h"
 
 #include <QKeyEvent>
+
+#include <core/vnotex.h>
+#include <core/thememgr.h>
 #include <utils/widgetutils.h>
+#include "styleditemdelegate.h"
 
 using namespace vnotex;
 
+QBrush ListWidget::s_separatorForeground;
+
+QBrush ListWidget::s_separatorBackground;
+
 ListWidget::ListWidget(QWidget *p_parent)
     : QListWidget(p_parent)
 {
+    initialize();
+}
+
+ListWidget::ListWidget(bool p_enhancedStyle, QWidget *p_parent)
+    : QListWidget(p_parent)
+{
+    initialize();
+
+    if (p_enhancedStyle) {
+        auto delegate = new StyledItemDelegate(QSharedPointer<StyledItemDelegateListWidget>::create(this),
+                                               StyledItemDelegate::None,
+                                               this);
+        setItemDelegate(delegate);
+    }
+}
+
+void ListWidget::initialize()
+{
+    static bool initialized = false;
+    if (!initialized) {
+        initialized = true;
+
+        const auto &themeMgr = VNoteX::getInst().getThemeMgr();
+        s_separatorForeground = QColor(themeMgr.paletteColor(QStringLiteral("widgets#styleditemdelegate#separator#fg")));
+        s_separatorBackground = QColor(themeMgr.paletteColor(QStringLiteral("widgets#styleditemdelegate#separator#bg")));
+    }
 }
 
 void ListWidget::keyPressEvent(QKeyEvent *p_event)
@@ -16,6 +50,16 @@ void ListWidget::keyPressEvent(QKeyEvent *p_event)
         return;
     }
 
+    // On Mac OS X, it is `Command+O` to activate an item, instead of Return.
+#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
+    if (p_event->key() == Qt::Key_Return) {
+        if (auto item = currentItem()) {
+            emit itemActivated(item);
+        }
+        return;
+    }
+#endif
+
     QListWidget::keyPressEvent(p_event);
 }
 
@@ -44,12 +88,14 @@ QVector<QListWidgetItem *> ListWidget::getVisibleItems(const QListWidget *p_widg
 
 QListWidgetItem *ListWidget::createSeparatorItem(const QString &p_text)
 {
-    QListWidgetItem *item = new QListWidgetItem(p_text, nullptr, c_separatorType);
+    QListWidgetItem *item = new QListWidgetItem(p_text, nullptr, ItemTypeSeparator);
+    item->setData(Qt::ForegroundRole, s_separatorForeground);
+    item->setData(Qt::BackgroundRole, s_separatorBackground);
     item->setFlags(Qt::NoItemFlags);
     return item;
 }
 
 bool ListWidget::isSeparatorItem(const QListWidgetItem *p_item)
 {
-    return p_item->type() == c_separatorType;
+    return p_item->type() == ItemTypeSeparator;
 }

+ 12 - 1
src/widgets/listwidget.h

@@ -12,6 +12,8 @@ namespace vnotex
     public:
         explicit ListWidget(QWidget *p_parent = nullptr);
 
+        ListWidget(bool p_enhancedStyle, QWidget *p_parent = nullptr);
+
         static QVector<QListWidgetItem *> getVisibleItems(const QListWidget *p_widget);
 
         static QListWidgetItem *createSeparatorItem(const QString &p_text);
@@ -22,7 +24,16 @@ namespace vnotex
         void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
 
     private:
-        static const int c_separatorType = 2000;
+        enum
+        {
+            ItemTypeSeparator = 2000
+        };
+
+        void initialize();
+
+        static QBrush s_separatorForeground;
+
+        static QBrush s_separatorBackground;
     };
 }
 

+ 10 - 42
src/widgets/locationlist.cpp

@@ -8,6 +8,7 @@
 #include "treewidget.h"
 #include "widgetsfactory.h"
 #include "titlebar.h"
+#include "styleditemdelegate.h"
 
 #include <core/vnotex.h>
 #include <core/thememgr.h>
@@ -24,20 +25,10 @@ QIcon LocationList::s_folderIcon;
 
 QIcon LocationList::s_notebookIcon;
 
-QString LocationList::s_textHighlightForeground;
-
-QString LocationList::s_textHighlightBackground;
-
 LocationList::LocationList(QWidget *p_parent)
     : QFrame(p_parent)
 {
     setupUI();
-
-    if (s_textHighlightForeground.isEmpty()) {
-        const auto &themeMgr = VNoteX::getInst().getThemeMgr();
-        s_textHighlightForeground = themeMgr.paletteColor(QStringLiteral("widgets#locationlist#text_highlight#fg"));
-        s_textHighlightBackground = themeMgr.paletteColor(QStringLiteral("widgets#locationlist#text_highlight#bg"));
-    }
 }
 
 void LocationList::setupUI()
@@ -50,11 +41,10 @@ void LocationList::setupUI()
         mainLayout->addWidget(m_titleBar);
     }
 
-    m_tree = new TreeWidget(TreeWidget::Flag::None, this);
+    m_tree = new TreeWidget(TreeWidget::Flag::EnhancedStyle, this);
     // When updated, pay attention to the Columns enum.
     m_tree->setHeaderLabels(QStringList() << tr("Path") << tr("Line") << tr("Text"));
     TreeWidget::showHorizontalScrollbar(m_tree);
-    m_tree->header()->setSectionResizeMode(QHeaderView::Interactive);
     connect(m_tree, &QTreeWidget::itemActivated,
             this, [this](QTreeWidgetItem *p_item, int p_col) {
                 Q_UNUSED(p_col);
@@ -135,36 +125,15 @@ void LocationList::setItemLocationLineAndText(QTreeWidgetItem *p_item, const Com
         p_item->setText(Columns::LineColumn, QString::number(p_line.m_lineNumber + 1));
     }
 
-    if (p_line.m_segments.isEmpty()) {
-        p_item->setText(Columns::TextColumn, p_line.m_text);
+    // Truncate the text.
+    if (p_line.m_text.size() > 500) {
+        p_item->setText(Columns::TextColumn, p_line.m_text.left(500));
     } else {
-        auto segments = p_line.m_segments;
-        std::sort(segments.begin(), segments.end());
-
-        // Use \n as a marker for < and use \r for >.
-        QString text(p_line.m_text);
-        int lastOffset = text.size();
-        for (int i = segments.size() - 1; i >= 0; --i) {
-            Q_ASSERT(segments[i].m_length > 0);
-            if (segments[i].m_offset + segments[i].m_length > lastOffset) {
-                // Interset.
-                continue;
-            }
-
-            lastOffset = segments[i].m_offset;
-            text.insert(segments[i].m_offset + segments[i].m_length, QStringLiteral("\n/span\r"));
-            text.insert(segments[i].m_offset,
-                        QString("\nspan style='color:%1;background-color:%2'\r").arg(s_textHighlightForeground, s_textHighlightBackground));
-        }
-
-        text = text.toHtmlEscaped();
-        text.replace(QLatin1Char('\n'), QLatin1Char('<'));
-        text.replace(QLatin1Char('\r'), QLatin1Char('>'));
+        p_item->setText(Columns::TextColumn, p_line.m_text);
+    }
 
-        auto label = new QLabel(m_tree);
-        label->setTextFormat(Qt::RichText);
-        label->setText(text);
-        m_tree->setItemWidget(p_item, Columns::TextColumn, label);
+    if (!p_line.m_segments.isEmpty()) {
+        p_item->setData(Columns::TextColumn, HighlightsRole, QVariant::fromValue(p_line.m_segments));
     }
 }
 
@@ -172,9 +141,8 @@ void LocationList::addLocation(const ComplexLocation &p_location)
 {
     auto item = new QTreeWidgetItem(m_tree);
     item->setText(Columns::PathColumn, p_location.m_displayPath);
-    item->setData(Columns::PathColumn, Qt::UserRole, p_location.m_path);
-
     item->setIcon(Columns::PathColumn, getItemIcon(p_location.m_type));
+    item->setData(Columns::PathColumn, Qt::UserRole, p_location.m_path);
 
     if (p_location.m_lines.size() == 1) {
         setItemLocationLineAndText(item, p_location.m_lines[0]);

+ 0 - 4
src/widgets/locationlist.h

@@ -68,10 +68,6 @@ namespace vnotex
         static QIcon s_folderIcon;
 
         static QIcon s_notebookIcon;
-
-        static QString s_textHighlightForeground;
-
-        static QString s_textHighlightBackground;
     };
 }
 

+ 0 - 1
src/widgets/markdownviewwindow.cpp

@@ -250,7 +250,6 @@ void MarkdownViewWindow::setupToolBar()
     auto toolBar = createToolBar(this);
 
     const auto &editorConfig = ConfigMgr::getInst().getEditorConfig();
-    const auto &markdownEditorConfig = editorConfig.getMarkdownEditorConfig();
 
     const int iconSize = editorConfig.getToolBarIconSize();
     toolBar->setIconSize(QSize(iconSize, iconSize));

+ 0 - 2
src/widgets/notebooknodeexplorer.cpp

@@ -1,9 +1,7 @@
 #include "notebooknodeexplorer.h"
 
-#include <QTreeWidget>
 #include <QVBoxLayout>
 #include <QSplitter>
-#include <QTreeWidget>
 #include <QMenu>
 #include <QAction>
 #include <QSet>

+ 2 - 0
src/widgets/searchpanel.cpp

@@ -313,6 +313,8 @@ void SearchPanel::startSearch()
 
 void SearchPanel::handleSearchFinished(SearchState p_state)
 {
+    qDebug() << "handleSearchFinished" << (int)p_state;
+
     Q_ASSERT(m_searchOngoing);
     Q_ASSERT(p_state != SearchState::Idle);
 

+ 35 - 0
src/widgets/simplesegmenthighlighter.cpp

@@ -0,0 +1,35 @@
+#include "simplesegmenthighlighter.h"
+
+#include <QTextDocument>
+
+using namespace vnotex;
+
+SimpleSegmentHighlighter::SimpleSegmentHighlighter(QTextDocument *p_parent)
+    : QSyntaxHighlighter(p_parent)
+{
+}
+
+void SimpleSegmentHighlighter::highlightBlock(const QString &p_text)
+{
+    if (m_segments.isEmpty() || !m_highlightFormat.isValid()) {
+        return;
+    }
+
+    const int len = p_text.size();
+    for (const auto &seg : m_segments) {
+        if (seg.m_offset >= 0 && seg.m_offset < len) {
+            setFormat(seg.m_offset, qMin(seg.m_length, len - seg.m_offset), m_highlightFormat);
+        }
+    }
+}
+
+void SimpleSegmentHighlighter::setSegments(const QList<Segment> &p_segments)
+{
+    m_segments = p_segments;
+}
+
+void SimpleSegmentHighlighter::setHighlightFormat(const QBrush &p_foreground, const QBrush &p_background)
+{
+    m_highlightFormat.setForeground(p_foreground);
+    m_highlightFormat.setBackground(p_background);
+}

+ 31 - 0
src/widgets/simplesegmenthighlighter.h

@@ -0,0 +1,31 @@
+#ifndef SIMPLESEGMENTHIGHLIGHTER_H
+#define SIMPLESEGMENTHIGHLIGHTER_H
+
+#include <QSyntaxHighlighter>
+#include <QList>
+
+#include <core/global.h>
+
+namespace vnotex
+{
+    class SimpleSegmentHighlighter : public QSyntaxHighlighter
+    {
+        Q_OBJECT
+    public:
+        explicit SimpleSegmentHighlighter(QTextDocument *p_parent);
+
+        void setSegments(const QList<Segment> &p_segments);
+
+        void setHighlightFormat(const QBrush &p_foreground, const QBrush &p_background);
+
+    protected:
+        void highlightBlock(const QString &p_text) Q_DECL_OVERRIDE;
+
+    private:
+        QTextCharFormat m_highlightFormat;
+
+        QList<Segment> m_segments;
+    };
+}
+
+#endif // SIMPLESEGMENTHIGHLIGHTER_H

+ 158 - 0
src/widgets/styleditemdelegate.cpp

@@ -0,0 +1,158 @@
+#include "styleditemdelegate.h"
+
+#include <QPainter>
+#include <QListWidgetItem>
+#include <QTextDocument>
+#include <QApplication>
+#include <QStyle>
+#include <QAbstractTextDocumentLayout>
+
+#include <core/vnotex.h>
+#include <core/thememgr.h>
+#include "listwidget.h"
+#include "treewidget.h"
+#include "simplesegmenthighlighter.h"
+
+using namespace vnotex;
+
+StyledItemDelegateListWidget::StyledItemDelegateListWidget(const ListWidget *p_listWidget)
+    : m_listWidget(p_listWidget)
+{
+}
+
+
+StyledItemDelegateTreeWidget::StyledItemDelegateTreeWidget(const TreeWidget *p_treeWidget)
+    : m_treeWidget(p_treeWidget)
+{
+}
+
+
+QBrush StyledItemDelegate::s_highlightForeground;
+
+QBrush StyledItemDelegate::s_highlightBackground;
+
+StyledItemDelegate::StyledItemDelegate(const QSharedPointer<StyledItemDelegateInterface> &p_interface,
+                                       DelegateFlags p_flags,
+                                       QObject *p_parent)
+    : QStyledItemDelegate(p_parent),
+      m_interface(p_interface),
+      m_flags(p_flags)
+{
+    initialize();
+
+    if (m_flags & DelegateFlag::Highlights) {
+        m_document = new QTextDocument(this);
+        m_highlighter = new SimpleSegmentHighlighter(m_document);
+        m_highlighter->setHighlightFormat(s_highlightForeground, s_highlightBackground);
+    }
+}
+
+void StyledItemDelegate::initialize()
+{
+    static bool initialized = false;
+    if (!initialized) {
+        initialized = true;
+
+        const auto &themeMgr = VNoteX::getInst().getThemeMgr();
+        s_highlightForeground = QColor(themeMgr.paletteColor(QStringLiteral("widgets#styleditemdelegate#highlight#fg")));
+        s_highlightBackground = QColor(themeMgr.paletteColor(QStringLiteral("widgets#styleditemdelegate#highlight#bg")));
+    }
+}
+
+void StyledItemDelegate::paint(QPainter *p_painter,
+                               const QStyleOptionViewItem &p_option,
+                               const QModelIndex &p_index) const
+{
+    // [Qt's BUG] Qt does not draw the background from Qt::BackgroundRole. Do it manually.
+    auto bgBrushVal = p_index.data(Qt::BackgroundRole);
+    if (bgBrushVal.canConvert<QBrush>()) {
+        auto brush = qvariant_cast<QBrush>(bgBrushVal);
+        if (brush.style() != Qt::NoBrush) {
+            p_painter->fillRect(p_option.rect, brush);
+        }
+    }
+
+    if (m_flags & DelegateFlag::Highlights) {
+        const auto value = p_index.data(HighlightsRole);
+        if (value.canConvert<QList<Segment>>()) {
+            auto segments = value.value<QList<Segment>>();
+            if (!segments.isEmpty()) {
+                paintWithHighlights(p_painter, p_option, p_index, segments);
+                return;
+            }
+        }
+    }
+
+    QStyledItemDelegate::paint(p_painter, p_option, p_index);
+}
+
+static void drawContents(const QStyleOptionViewItem &p_option,
+                         QTextDocument *p_doc,
+                         QPainter *p_painter,
+                         const QRectF &p_rect)
+{
+    // From qtbase/src/gui/text/qtextdocument.cpp.
+
+    p_painter->save();
+
+    QAbstractTextDocumentLayout::PaintContext ctx;
+    if (p_rect.isValid()) {
+        p_painter->setClipRect(p_rect);
+        ctx.clip = p_rect;
+    }
+
+    // Update palette.
+    ctx.palette.setBrush(QPalette::Text, p_option.palette.brush(QPalette::Text));
+
+    p_doc->documentLayout()->draw(p_painter, ctx);
+
+    p_painter->restore();
+}
+
+void StyledItemDelegate::paintWithHighlights(QPainter *p_painter,
+                                             const QStyleOptionViewItem &p_option,
+                                             const QModelIndex &p_index,
+                                             const QList<Segment> &p_segments) const
+{
+    QStyleOptionViewItem opt(p_option);
+    initStyleOption(&opt, p_index);
+
+    m_highlighter->setSegments(p_segments);
+    m_document->clear();
+    m_document->setDefaultFont(opt.font);
+    m_document->setPlainText(opt.text);
+
+    p_painter->save();
+
+    // Draw the item without text.
+    opt.text = "";
+    auto style = opt.widget ? opt.widget->style() : QApplication::style();
+    style->drawControl(QStyle::CE_ItemViewItem, &opt, p_painter, opt.widget);
+
+    // Draw the text via QTextDocument.
+    p_painter->translate(opt.rect.left(), opt.rect.top());
+    const QRect clip(0, 0, opt.rect.width(), opt.rect.height());
+    drawContents(opt, m_document, p_painter, clip);
+
+    p_painter->restore();
+}
+
+QSize StyledItemDelegate::sizeHint(const QStyleOptionViewItem &p_option, const QModelIndex &p_index) const
+{
+    if (m_flags & DelegateFlag::Highlights) {
+        const auto value = p_index.data(HighlightsRole);
+        if (value.canConvert<QList<Segment>>()) {
+            auto segments = value.value<QList<Segment>>();
+            if (!segments.isEmpty()) {
+                QStyleOptionViewItem opt(p_option);
+                initStyleOption(&opt, p_index);
+
+                m_document->setPlainText(opt.text);
+                return QSize(m_document->idealWidth(), m_document->size().height());
+
+            }
+        }
+    }
+
+    return QStyledItemDelegate::sizeHint(p_option, p_index);
+}

+ 98 - 0
src/widgets/styleditemdelegate.h

@@ -0,0 +1,98 @@
+#ifndef STYLEDITEMDELEGATE_H
+#define STYLEDITEMDELEGATE_H
+
+#include <QStyledItemDelegate>
+#include <QSharedPointer>
+#include <QBrush>
+#include <QList>
+
+#include <core/global.h>
+
+class QTextDocument;
+
+namespace vnotex
+{
+    class ListWidget;
+    class TreeWidget;
+    class SimpleSegmentHighlighter;
+
+    enum
+    {
+        HighlightsRole = 0x0101
+    };
+
+
+    class StyledItemDelegateInterface
+    {
+    public:
+        virtual ~StyledItemDelegateInterface() = default;
+    };
+
+
+    class StyledItemDelegateListWidget : public StyledItemDelegateInterface
+    {
+    public:
+        explicit StyledItemDelegateListWidget(const ListWidget *p_listWidget);
+
+    private:
+        const ListWidget *m_listWidget = nullptr;
+    };
+
+
+    class StyledItemDelegateTreeWidget : public StyledItemDelegateInterface
+    {
+    public:
+        explicit StyledItemDelegateTreeWidget(const TreeWidget *p_treeWidget);
+
+    private:
+        const TreeWidget *m_treeWidget = nullptr;
+    };
+
+
+    // Template is not supported with QObject.
+    class StyledItemDelegate : public QStyledItemDelegate
+    {
+        Q_OBJECT
+    public:
+        enum DelegateFlag
+        {
+            None = 0,
+            Highlights = 0x1
+        };
+        Q_DECLARE_FLAGS(DelegateFlags, DelegateFlag);
+
+        StyledItemDelegate(const QSharedPointer<StyledItemDelegateInterface> &p_interface,
+                           DelegateFlags p_flags = DelegateFlag::None,
+                           QObject *p_parent = nullptr);
+
+        void paint(QPainter *p_painter,
+                   const QStyleOptionViewItem &p_option,
+                   const QModelIndex &p_index) const Q_DECL_OVERRIDE;
+
+        QSize sizeHint(const QStyleOptionViewItem &p_option, const QModelIndex &p_index) const Q_DECL_OVERRIDE;
+
+    private:
+        void initialize();
+
+        void paintWithHighlights(QPainter *p_painter,
+                                 const QStyleOptionViewItem &p_option,
+                                 const QModelIndex &p_index,
+                                 const QList<Segment> &p_segments) const;
+
+        QSharedPointer<StyledItemDelegateInterface> m_interface;
+
+        DelegateFlags m_flags = DelegateFlag::None;
+
+        QTextDocument *m_document = nullptr;
+
+        SimpleSegmentHighlighter *m_highlighter = nullptr;
+
+        static QBrush s_highlightForeground;
+
+        static QBrush s_highlightBackground;
+    };
+}
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::StyledItemDelegate::DelegateFlags)
+
+#endif // STYLEDITEMDELEGATE_H

+ 13 - 14
src/widgets/treewidget.cpp

@@ -6,6 +6,7 @@
 #include <QDropEvent>
 
 #include <utils/widgetutils.h>
+#include "styleditemdelegate.h"
 
 using namespace vnotex;
 
@@ -18,6 +19,11 @@ TreeWidget::TreeWidget(TreeWidget::Flags p_flags, QWidget *p_parent)
     : QTreeWidget(p_parent),
       m_flags(p_flags)
 {
+    if (m_flags & Flag::EnhancedStyle) {
+        auto interface = QSharedPointer<StyledItemDelegateTreeWidget>::create(this);
+        auto delegate = new StyledItemDelegate(interface, StyledItemDelegate::Highlights, this);
+        setItemDelegate(delegate);
+    }
 }
 
 void TreeWidget::mousePressEvent(QMouseEvent *p_event)
@@ -92,22 +98,15 @@ void TreeWidget::keyPressEvent(QKeyEvent *p_event)
         return;
     }
 
-    switch (p_event->key()) {
-    case Qt::Key_Return:
-        Q_FALLTHROUGH();
-    case Qt::Key_Enter:
-    {
-        auto item = currentItem();
-        if (item && item->childCount() > 0) {
-            item->setExpanded(!item->isExpanded());
+    // On Mac OS X, it is `Command+O` to activate an item, instead of Return.
+#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
+    if (p_event->key() == Qt::Key_Return) {
+        if (auto item = currentItem()) {
+            emit itemActivated(item, currentColumn());
         }
-
-        break;
-    }
-
-    default:
-        break;
+        return;
     }
+#endif
 
     QTreeWidget::keyPressEvent(p_event);
 }

+ 2 - 1
src/widgets/treewidget.h

@@ -13,7 +13,8 @@ namespace vnotex
         enum Flag
         {
             None = 0,
-            ClickSpaceToClearSelection = 0x1
+            ClickSpaceToClearSelection = 0x1,
+            EnhancedStyle = 0x2
         };
         Q_DECLARE_FLAGS(Flags, Flag)
 

+ 7 - 6
src/widgets/viewwindow.h

@@ -170,6 +170,13 @@ namespace vnotex
 
         virtual void handleFindAndReplaceWidgetOpened();
 
+    protected:
+        bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE;
+
+        void wheelEvent(QWheelEvent *p_event) Q_DECL_OVERRIDE;
+
+        void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
+
     protected:
         void setCentralWidget(QWidget *p_widget);
 
@@ -181,12 +188,6 @@ namespace vnotex
 
         void setStatusWidget(const QSharedPointer<StatusWidget> &p_widget);
 
-        bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE;
-
-        void wheelEvent(QWheelEvent *p_event) Q_DECL_OVERRIDE;
-
-        void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
-
         // Provide some common actions of tool bar for ViewWindow.
         QAction *addAction(QToolBar *p_toolBar, ViewWindowToolBarHelper::Action p_action);
 

+ 6 - 0
src/widgets/widgets.pri

@@ -50,6 +50,7 @@ SOURCES += \
     $$PWD/floatingwidget.cpp \
     $$PWD/fullscreentoggleaction.cpp \
     $$PWD/historypanel.cpp \
+    $$PWD/itemproxystyle.cpp \
     $$PWD/lineedit.cpp \
     $$PWD/lineeditdelegate.cpp \
     $$PWD/lineeditwithsnippet.cpp \
@@ -67,7 +68,9 @@ SOURCES += \
     $$PWD/quickselector.cpp \
     $$PWD/searchinfoprovider.cpp \
     $$PWD/searchpanel.cpp \
+    $$PWD/simplesegmenthighlighter.cpp \
     $$PWD/snippetpanel.cpp \
+    $$PWD/styleditemdelegate.cpp \
     $$PWD/systemtrayhelper.cpp \
     $$PWD/textviewwindow.cpp \
     $$PWD/toolbarhelper.cpp \
@@ -157,6 +160,7 @@ HEADERS += \
     $$PWD/floatingwidget.h \
     $$PWD/fullscreentoggleaction.h \
     $$PWD/historypanel.h \
+    $$PWD/itemproxystyle.h \
     $$PWD/lineedit.h \
     $$PWD/lineeditdelegate.h \
     $$PWD/lineeditwithsnippet.h \
@@ -175,7 +179,9 @@ HEADERS += \
     $$PWD/quickselector.h \
     $$PWD/searchinfoprovider.h \
     $$PWD/searchpanel.h \
+    $$PWD/simplesegmenthighlighter.h \
     $$PWD/snippetpanel.h \
+    $$PWD/styleditemdelegate.h \
     $$PWD/systemtrayhelper.h \
     $$PWD/textviewwindow.h \
     $$PWD/textviewwindowhelper.h \