vnote/src/widgets/itemproxystyle.cpp

258 lines
10 KiB
C++

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