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