Gui: Fix document tree background rendering with overlay (Qt6)

This aims to fix rendering of tree view items in Qt6. While I don't
belive that this is a good way to fix this, I am worried that it is the
only way to do ir.

BC BREAK: This change introduces artificial QTreeView widget that can be
targeted using QSS and can be used in the delegate for painting background of
items. `QTreeView::item` would now be used to render background for the
whole row, while each cell can be targeted using `#DocumentTreeItems`
selector.

More details on implementation:
https://stackoverflow.com/questions/78414383/qt6-disable-drawing-of-default-background-for-qtreeview-items/78421604#78421604

Fixes: #13760
This commit is contained in:
Kacper Donat
2024-05-02 23:12:42 +02:00
committed by Chris Hennes
parent cf31f0c7ae
commit b7ff904144

View File

@@ -398,6 +398,21 @@ namespace Gui {
*/
class TreeWidgetItemDelegate: public QStyledItemDelegate {
typedef QStyledItemDelegate inherited;
// Beware, big scary hack incoming!
//
// This is artificial QTreeWidget that is not rendered and its sole goal is to be the source
// of style information that can be manipulated using QSS. From Qt6.5 tree branches also
// have rendered background using ::item sub-control. Whole row also gets background from
// the same sub-control. Only way to prevent this is to disable background of ::item,
// this however limits our ability to style tree items. As solution we create this widget
// that will be for painter to read information and draw proper backgrounds only when asked.
//
// More information: https://github.com/FreeCAD/FreeCAD/pull/13807
QTreeView *artificial;
QRect calculateItemRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
public:
explicit TreeWidgetItemDelegate(QObject* parent=nullptr);
@@ -416,6 +431,40 @@ public:
TreeWidgetItemDelegate::TreeWidgetItemDelegate(QObject* parent)
: QStyledItemDelegate(parent)
{
artificial = new QTreeView(qobject_cast<QWidget*>(parent));
artificial->setObjectName(QString::fromLatin1("DocumentTreeItems"));
artificial->setFixedSize(0, 0); // ensure that it does not render
}
QRect TreeWidgetItemDelegate::calculateItemRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
auto tree = static_cast<TreeWidget*>(parent());
auto style = tree->style();
QRect rect = option.rect;
const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, artificial) + 1;
// 2 margin for text, 2 margin for decoration (icon) = 4 times margin
int width = 4 * margin
+ option.fontMetrics.boundingRect(option.text).width()
+ option.decorationSize.width()
+ TreeParams::getItemBackgroundPadding()
;
if (TreeParams::getCheckBoxesSelection()) {
// another 2 margin for checkbox
width += 2 * margin
+ style->pixelMetric(QStyle::PM_IndicatorWidth)
+ style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
}
if (width < rect.width()) {
rect.setWidth(width);
}
return rect;
}
void TreeWidgetItemDelegate::paint(QPainter *painter,
@@ -424,42 +473,29 @@ void TreeWidgetItemDelegate::paint(QPainter *painter,
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
TreeWidget * tree = static_cast<TreeWidget*>(parent());
auto tree = static_cast<TreeWidget*>(parent());
auto style = tree->style();
// If the second column is not shown, we'll trim the color background when
// rendering as transparent overlay.
bool trimBG = TreeParams::getHideColumn();
QRect rect = opt.rect;
if (index.column() == 0) {
if (tree->testAttribute(Qt::WA_NoSystemBackground)
&& (trimBG || (opt.backgroundBrush.style() == Qt::NoBrush
&& _TreeItemBackground.style() != Qt::NoBrush)))
{
const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, tree) + 1;
// 2 margin for text, 2 margin for decoration (icon)
int width = 4*margin + opt.fontMetrics.boundingRect(opt.text).width()
+ opt.decorationSize.width() + TreeParams::getItemBackgroundPadding();
if (TreeParams::getCheckBoxesSelection()) {
// another 2 margin for checkbox
width += 2*margin + style->pixelMetric(QStyle::PM_IndicatorWidth)
+ style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
}
if (width < rect.width())
rect.setWidth(width);
if (trimBG) {
rect.setWidth(rect.width() + 5);
opt.rect = rect;
if (opt.backgroundBrush.style() == Qt::NoBrush)
painter->fillRect(rect, _TreeItemBackground);
} else if (!opt.state.testFlag(QStyle::State_Selected))
painter->fillRect(rect, _TreeItemBackground);
}
QRect rect = calculateItemRect(option, index);
if (trimBG && opt.backgroundBrush.style() == Qt::NoBrush) {
painter->fillRect(rect, _TreeItemBackground);
} else if (!opt.state.testFlag(QStyle::State_Selected)) {
painter->fillRect(rect, _TreeItemBackground);
}
}
}
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, tree);
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, artificial);
}
void TreeWidgetItemDelegate::initStyleOption(QStyleOptionViewItem *option,
@@ -467,16 +503,35 @@ void TreeWidgetItemDelegate::initStyleOption(QStyleOptionViewItem *option,
{
inherited::initStyleOption(option, index);
TreeWidget * tree = static_cast<TreeWidget*>(parent());
QTreeWidgetItem * item = tree->itemFromIndex(index);
if (!item || item->type() != TreeWidget::ObjectType)
return;
auto tree = static_cast<TreeWidget*>(parent());
auto item = tree->itemFromIndex(index);
QSize size;
size = option->icon.actualSize(QSize(0xffff, 0xffff));
if (size.height())
option->decorationSize = QSize(size.width()*TreeWidget::iconSize()/size.height(),
TreeWidget::iconSize());
if (!item) {
return;
}
auto mousePos = option->widget->mapFromGlobal(QCursor::pos());
auto isHovered = option->rect.contains(mousePos);
if (!isHovered) {
option->state &= ~QStyle::State_MouseOver;
}
QSize size = option->icon.actualSize(QSize(0xffff, 0xffff));
if (size.height() > 0) {
option->decorationSize = QSize(
size.width() * TreeWidget::iconSize() / size.height(),
TreeWidget::iconSize()
);
}
if (TreeParams::getHideColumn()) {
option->rect = calculateItemRect(*option, index);
// we need to extend this shape a bit, 3px on each side
// this value was obtained experimentally
option->rect.setWidth(option->rect.width() + 3 * 2);
}
}
QWidget* TreeWidgetItemDelegate::createEditor(
@@ -536,6 +591,7 @@ TreeWidget::TreeWidget(const char* name, QWidget* parent)
this->setDragDropMode(QTreeWidget::InternalMove);
this->setColumnCount(2);
this->setItemDelegate(new TreeWidgetItemDelegate(this));
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
this->showHiddenAction = new QAction(this);
this->showHiddenAction->setCheckable(true);
@@ -613,7 +669,7 @@ TreeWidget::TreeWidget(const char* name, QWidget* parent)
//NOLINTEND
setupResizableColumn(this);
this->header()->setStretchLastSection(false);
this->header()->setStretchLastSection(true);
QObject::connect(this->header(), &QHeaderView::sectionResized, [](int idx, int, int newSize) {
if (idx)
TreeParams::setColumnSize2(newSize);