Skip to content

Commit

Permalink
Gui: Fix document tree background rendering with overlay (Qt6)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
kadet1090 committed May 5, 2024
1 parent 15b612e commit 805c34b
Showing 1 changed file with 82 additions and 30 deletions.
112 changes: 82 additions & 30 deletions src/Gui/Tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,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);

Expand All @@ -389,6 +404,40 @@ class TreeWidgetItemDelegate: public QStyledItemDelegate {
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

Check warning on line 413 in src/Gui/Tree.cpp

View workflow job for this annotation

GitHub Actions / Lint / Lint

unused parameter 'index' [-Wunused-parameter]
{
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,
Expand All @@ -397,59 +446,61 @@ 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))
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,
const QModelIndex &index) const
{
inherited::initStyleOption(option, index);

TreeWidget * tree = static_cast<TreeWidget*>(parent());
QTreeWidgetItem * item = tree->itemFromIndex(index);
if (!item || item->type() != TreeWidget::ObjectType)
auto tree = static_cast<TreeWidget*>(parent());
auto item = tree->itemFromIndex(index);

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

QSize size;
size = option->icon.actualSize(QSize(0xffff, 0xffff));
if (size.height())
option->decorationSize = QSize(size.width()*TreeWidget::iconSize()/size.height(),
TreeWidget::iconSize());
if (size.height() > 0) {
option->decorationSize = QSize(
size.width() * TreeWidget::iconSize() / size.height(),
TreeWidget::iconSize()
);
}

if (TreeParams::getHideColumn()) {
option->rect = calculateItemRect(*option, index);
}
}

QWidget* TreeWidgetItemDelegate::createEditor(
Expand Down Expand Up @@ -509,6 +560,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);
Expand Down Expand Up @@ -586,7 +638,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);
Expand Down

0 comments on commit 805c34b

Please sign in to comment.