forked from openkylin/qt5-ukui-platformtheme
386 lines
15 KiB
C++
386 lines
15 KiB
C++
/*
|
|
* Qt5-UKUI's Library
|
|
*
|
|
* Copyright (C) 2023, KylinSoft Co., Ltd.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this library. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors: xibowen <xibowen@kylinos.cn>
|
|
*
|
|
*/
|
|
|
|
#include "ukui-config-style.h"
|
|
#include <QListView>
|
|
#include <QTreeView>
|
|
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);
|
|
}
|
|
|
|
|
|
QString UKUIConfigStyle::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
|
|
{
|
|
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) {
|
|
if (elideLastVisibleLine) {
|
|
if (text.endsWith(QChar::LineSeparator))
|
|
text.chop(1);
|
|
text += QChar(0x2026);
|
|
}
|
|
const QStackTextEngine engine(text, font);
|
|
ret += engine.elidedText(textElideMode, textRect.width(), flags);
|
|
|
|
// 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;
|
|
}
|
|
|
|
|
|
|
|
void UKUIConfigStyle::viewItemDrawText(QPainter *painter, const QStyleOptionViewItem *option, const QRect &rect) const
|
|
{
|
|
const QWidget *widget = option->widget;
|
|
const int textMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1;
|
|
|
|
QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding
|
|
const bool wrapText = option->features & QStyleOptionViewItem::WrapText;
|
|
QTextOption textOption;
|
|
textOption.setWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::ManualWrap);
|
|
//FIX ME,bug106749::QTablewidget text cannot be aligned, forced changed it wrap mode to wrapanywhere
|
|
if(wrapText && (widget->inherits("QTableWidget") || widget->inherits("QTableView")))
|
|
{
|
|
textOption.setWrapMode(QTextOption::WrapAnywhere);
|
|
}
|
|
textOption.setTextDirection(option->direction);
|
|
textOption.setAlignment(QStyle::visualAlignment(option->direction, option->displayAlignment));
|
|
|
|
QPointF paintPosition;
|
|
const QString newText = calculateElidedText(option->text, textOption,
|
|
option->font, textRect, option->displayAlignment,
|
|
option->textElideMode, 0,
|
|
true, &paintPosition);
|
|
|
|
QTextLayout textLayout(newText, option->font);
|
|
textLayout.setTextOption(textOption);
|
|
viewItemTextLayout(textLayout, textRect.width());
|
|
textLayout.draw(painter, paintPosition);
|
|
}
|
|
|
|
|
|
|
|
void UKUIConfigStyle::viewItemLayout(const QStyleOptionViewItem *option, QRect *checkRect, QRect *pixmapRect, QRect *textRect, bool sizehint) const
|
|
{
|
|
Q_ASSERT(checkRect && pixmapRect && textRect);
|
|
*pixmapRect = QRect(QPoint(0, 0), viewItemSize(option, Qt::DecorationRole));
|
|
*textRect = QRect(QPoint(0, 0), viewItemSize(option, Qt::DisplayRole));
|
|
*checkRect = QRect(QPoint(0, 0), viewItemSize(option, Qt::CheckStateRole));
|
|
|
|
int Margin_Width = 2;
|
|
int Margin_Height = 0;
|
|
|
|
const QWidget *widget = option->widget;
|
|
const bool hasCheck = checkRect->isValid();
|
|
const bool hasPixmap = pixmapRect->isValid();
|
|
const bool hasText = textRect->isValid();
|
|
const bool hasMargin = (hasText | hasPixmap | hasCheck);
|
|
const int frameHMargin = hasMargin ?
|
|
proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin, option, widget) + 1 : 0;
|
|
const int textMargin = hasText ? frameHMargin : 0;
|
|
const int pixmapMargin = hasPixmap ? frameHMargin : 0;
|
|
const int checkMargin = hasCheck ? frameHMargin : 0;
|
|
const int x = option->rect.left() + Margin_Width;
|
|
const int y = option->rect.top() + Margin_Height;
|
|
int w, h;
|
|
|
|
if (textRect->height() == 0 && (!hasPixmap || !sizehint)) {
|
|
//if there is no text, we still want to have a decent height for the item sizeHint and the editor size
|
|
textRect->setHeight(option->fontMetrics.height());
|
|
}
|
|
|
|
QSize pm(0, 0);
|
|
if (hasPixmap) {
|
|
pm = pixmapRect->size();
|
|
pm.rwidth() += 2 * pixmapMargin;
|
|
}
|
|
if (sizehint) {
|
|
h = qMax(checkRect->height(), qMax(textRect->height(), pm.height()));
|
|
if (option->decorationPosition == QStyleOptionViewItem::Left || option->decorationPosition == QStyleOptionViewItem::Right) {
|
|
w = textRect->width() + pm.width();
|
|
} else {
|
|
w = qMax(textRect->width(), pm.width());
|
|
}
|
|
} else {
|
|
w = option->rect.width() - Margin_Width * 2;
|
|
h = option->rect.height() - Margin_Height * 2;
|
|
}
|
|
|
|
int cw = 0;
|
|
QRect check;
|
|
if (hasCheck) {
|
|
cw = checkRect->width() + 2 * checkMargin;
|
|
if (sizehint) w += cw;
|
|
if (option->direction == Qt::RightToLeft) {
|
|
check.setRect(x + w - cw, y, cw, h);
|
|
} else {
|
|
check.setRect(x, y, cw, h);
|
|
}
|
|
}
|
|
|
|
QRect display;
|
|
QRect decoration;
|
|
switch (option->decorationPosition) {
|
|
case QStyleOptionViewItem::Top:
|
|
{
|
|
h = sizehint ? textRect->height() : h - pm.height() - pixmapMargin;
|
|
|
|
if (option->direction == Qt::RightToLeft) {
|
|
decoration.setRect(x, y, w - cw, pm.height());
|
|
display.setRect(x, y + pm.height() + pixmapMargin, w - cw, h);
|
|
} else {
|
|
decoration.setRect(x + cw, y, w - cw, pm.height());
|
|
display.setRect(x + cw, y + pm.height() + pixmapMargin, w - cw, h);
|
|
}
|
|
break;
|
|
}
|
|
case QStyleOptionViewItem::Bottom:
|
|
{
|
|
h = sizehint ? textRect->height() + textMargin + pm.height() : h;
|
|
|
|
if (option->direction == Qt::RightToLeft) {
|
|
display.setRect(x, y, w - cw, textRect->height());
|
|
decoration.setRect(x, y + textRect->height() + textMargin, w - cw, h - textRect->height() - textMargin);
|
|
} else {
|
|
display.setRect(x + cw, y, w - cw, textRect->height());
|
|
decoration.setRect(x + cw, y + textRect->height() + textMargin, w - cw, h - textRect->height() - textMargin);
|
|
}
|
|
break;
|
|
}
|
|
case QStyleOptionViewItem::Left:
|
|
{
|
|
if (option->direction == Qt::LeftToRight) {
|
|
decoration.setRect(x + cw, y, pm.width(), h);
|
|
display.setRect(decoration.right() + 1, y, w - pm.width() - cw, h);
|
|
} else {
|
|
display.setRect(x, y, w - pm.width() - cw, h);
|
|
decoration.setRect(display.right() + 1, y, pm.width(), h);
|
|
}
|
|
break;
|
|
}
|
|
case QStyleOptionViewItem::Right:
|
|
{
|
|
if (option->direction == Qt::LeftToRight) {
|
|
display.setRect(x + cw, y, w - pm.width() - cw, h);
|
|
decoration.setRect(display.right() + 1, y, pm.width(), h);
|
|
} else {
|
|
decoration.setRect(x, y, pm.width(), h);
|
|
display.setRect(decoration.right() + 1, y, w - pm.width() - cw, h);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
qWarning("doLayout: decoration position is invalid");
|
|
decoration = *pixmapRect;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!sizehint) { // we only need to do the internal layout if we are going to paint
|
|
*checkRect = QStyle::alignedRect(option->direction, Qt::AlignCenter,
|
|
checkRect->size(), check);
|
|
*pixmapRect = QStyle::alignedRect(option->direction, option->decorationAlignment,
|
|
pixmapRect->size(), decoration);
|
|
// the text takes up all available space, unless the decoration is not shown as selected
|
|
if (option->showDecorationSelected)
|
|
*textRect = display;
|
|
else
|
|
*textRect = QStyle::alignedRect(option->direction, option->displayAlignment,
|
|
textRect->size().boundedTo(display.size()), display);
|
|
} else {
|
|
*checkRect = check;
|
|
*pixmapRect = decoration;
|
|
*textRect = display;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
QSize UKUIConfigStyle::viewItemSize(const QStyleOptionViewItem *option, int role) const
|
|
{
|
|
const QWidget *widget = option->widget;
|
|
switch (role) {
|
|
case Qt::CheckStateRole:
|
|
{
|
|
if (option->features & QStyleOptionViewItem::HasCheckIndicator)
|
|
return QSize(proxy()->pixelMetric(QStyle::PM_IndicatorWidth, option, widget),
|
|
proxy()->pixelMetric(QStyle::PM_IndicatorHeight, option, widget));
|
|
break;
|
|
}
|
|
|
|
case Qt::DisplayRole:
|
|
{
|
|
if (option->features & QStyleOptionViewItem::HasDisplay) {
|
|
QTextOption textOption;
|
|
textOption.setWrapMode(QTextOption::WordWrap);
|
|
QTextLayout textLayout(option->text, option->font);
|
|
textLayout.setTextOption(textOption);
|
|
const bool wrapText = option->features & QStyleOptionViewItem::WrapText;
|
|
const int textMargin = proxy()->pixelMetric(QStyle::PM_FocusFrameHMargin, option, widget) + 1;
|
|
QRect bounds = option->rect;
|
|
switch (option->decorationPosition) {
|
|
case QStyleOptionViewItem::Left:
|
|
case QStyleOptionViewItem::Right: {
|
|
if (wrapText && bounds.isValid()) {
|
|
int width = bounds.width() - 2 * textMargin;
|
|
if (option->features & QStyleOptionViewItem::HasDecoration)
|
|
width -= option->decorationSize.width() + 2 * textMargin;
|
|
bounds.setWidth(width);
|
|
} else
|
|
bounds.setWidth(QFIXED_MAX);
|
|
break;
|
|
}
|
|
case QStyleOptionViewItem::Top:
|
|
case QStyleOptionViewItem::Bottom:
|
|
if (wrapText)
|
|
bounds.setWidth(bounds.isValid() ? bounds.width() - 2 * textMargin : option->decorationSize.width());
|
|
else
|
|
bounds.setWidth(QFIXED_MAX);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (wrapText && option->features & QStyleOptionViewItem::HasCheckIndicator)
|
|
bounds.setWidth(bounds.width() - proxy()->pixelMetric(QStyle::PM_IndicatorWidth) - 2 * textMargin);
|
|
|
|
const int lineWidth = bounds.width();
|
|
const QSizeF size = viewItemTextLayout(textLayout, lineWidth);
|
|
return QSize(qCeil(size.width()) + 2 * textMargin, qCeil(size.height()));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Qt::DecorationRole:
|
|
{
|
|
if (option->features & QStyleOptionViewItem::HasDecoration) {
|
|
if(widget){
|
|
if(qobject_cast<const QListView *>(widget)){
|
|
QSize size = qobject_cast<const QListView *>(widget)->iconSize();
|
|
return QSize(qMax(option->decorationSize.width(), size.width()),
|
|
qMax(option->decorationSize.height(), size.height()));
|
|
}
|
|
if(qobject_cast<const QTreeView *>(widget)){
|
|
QSize size = qobject_cast<const QTreeView *>(widget)->iconSize();
|
|
return QSize(qMax(option->decorationSize.width(), size.width()),
|
|
qMax(option->decorationSize.height(), size.height()));
|
|
}
|
|
}
|
|
return option->decorationSize;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return QSize(0, 0);
|
|
}
|
|
|
|
|