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: FreeCAD#13760
  • Loading branch information
kadet1090 committed May 2, 2024
1 parent a02ff7a commit 6958f10
Showing 1 changed file with 81 additions and 29 deletions.
110 changes: 81 additions & 29 deletions src/Gui/Tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@


#include "PreCompiled.h"
#include <qabstractitemmodel.h>
#include <qabstractitemview.h>
#include <qlogging.h>
#include <qnamespace.h>
#include <qstyle.h>
#include <qstyleoption.h>
#include <qt/QtWidgets/qproxystyle.h>
#include <qtreeview.h>

#ifndef _PreComp_
# include <QAction>
Expand Down Expand Up @@ -371,6 +379,11 @@ namespace Gui {
*/
class TreeWidgetItemDelegate: public QStyledItemDelegate {
typedef QStyledItemDelegate inherited;

QTreeView *artificial;

QRect calculateItemRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;

public:
explicit TreeWidgetItemDelegate(QObject* parent=nullptr);

Expand All @@ -389,6 +402,42 @@ class TreeWidgetItemDelegate: public QStyledItemDelegate {
TreeWidgetItemDelegate::TreeWidgetItemDelegate(QObject* parent)
: QStyledItemDelegate(parent)
{
artificial = new QTreeView(qobject_cast<QWidget*>(parent));
artificial->setObjectName("DocumentTreeItems");
artificial->setFixedSize(0, 0);
}


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, tree) + 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()
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ TreeParams::getItemBackgroundPadding() // required only for Qt5 build, Qt6 properly calculates layout
#endif
;

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,62 @@ 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;
}

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

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

QWidget* TreeWidgetItemDelegate::createEditor(
Expand Down

0 comments on commit 6958f10

Please sign in to comment.