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

This commit is contained in:
Le Tan 2021-07-28 20:53:47 +08:00
parent 5ba425ae95
commit 9cf015a676
37 changed files with 846 additions and 135 deletions

View File

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

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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();
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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();

View File

@ -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;

View File

@ -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);
}

View File

@ -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());
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -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());
p_item->setText(Columns::TextColumn, p_line.m_text);
}
// 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('>'));
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]);

View File

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

View File

@ -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));

View File

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

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}

View File

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

View File

@ -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);

View File

@ -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 \