Fix QHeaderView auto section resize corner case

In some situations where the model is set sufficient late
an auto resize would never be applied. See bug report for
further details.

This is solved by moving to the normal memory model when
the global resize mode is changed to resizeToContents or stretch.

Fixes: QTBUG-136453
Pick-to: 6.9
Change-Id: Ie0448042a7d23b059eaf7464a662f0f551d89fd9
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
(cherry picked from commit 0d61e8ec5341b1642b63c34d5b2ce96ada54bd73)
Reviewed-by: Thorbjørn Lindeijer <bjorn@lindeijer.nl>
This commit is contained in:
Thorbjørn Lund Martsum 2025-05-09 16:23:12 +02:00
parent 63093b0410
commit adda61e87b
2 changed files with 114 additions and 0 deletions

View File

@ -1234,6 +1234,14 @@ void QHeaderView::setSectionResizeMode(ResizeMode mode)
initializeSections();
d->stretchSections = (mode == Stretch ? count() : 0);
d->contentsSections = (mode == ResizeToContents ? count() : 0);
if (d->noSectionMemoryUsage() && (mode == Stretch || mode == ResizeToContents)) {
// Stretch can/could *_maybe_* in the future be used to switch back to low memory mode
// (if no sections are moved or swapped), but for now we simply instantly switch
// to normal memory usage on auto resize.
d->switchToFlexibleModeWithSectionMemoryUsage();
}
d->setGlobalHeaderResizeMode(mode);
if (d->hasAutoResizeSections())
d->doDelayedResizeSections(); // section sizes may change as a result of the new mode

View File

@ -11,6 +11,7 @@
#include <QTableView>
#include <QTest>
#include <QTreeWidget>
#include <QStyledItemDelegate>
#include <QtWidgets/private/qheaderview_p.h>
#include <QtWidgets/private/qapplication_p.h>
@ -237,6 +238,7 @@ private slots:
void normalMemoryUsageOnHide();
void storeRestoreLowMemoryMode();
void setSectionResizeModeWithSectionWillTakeMemory();
void setModelWithAutoSizeWillSwitchToMemoryMode();
void setDefaultSectionSizeRespectsColumnWidth();
@ -3854,6 +3856,110 @@ void tst_QHeaderView::setSectionResizeModeWithSectionWillTakeMemory()
QVERIFY(!tv2.hasLowMemoryUsage());
}
class SpecialResizeModeTestDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
SpecialResizeModeTestDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent)
{}
QSize sizeHint(const QStyleOptionViewItem &/*option*/, const QModelIndex &/*index*/) const override
{
return QSize(m_cellWidth, m_cellWidth);
}
int cellWidth() const { return m_cellWidth; }
void setCellWidth(int width) { m_cellWidth = width; }
private:
int m_cellWidth{100};
};
class SpecialResizeModeTestModel : public QAbstractTableModel
{
public:
SpecialResizeModeTestModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {}
int rowCount(const QModelIndex & = {}) const override { return 105; }
int columnCount(const QModelIndex & = {}) const override { return 15; }
QVariant data(const QModelIndex &i, int role) const override
{
return (role == Qt::DisplayRole) ? QString("R: %1, C: %2").arg(i.row()).arg(i.column()) : QVariant();
}
QVariant headerData(int /*section*/, Qt::Orientation /*orientation*/, int role = Qt::DisplayRole) const override
{
return (role == Qt::SizeHintRole) ? QSize(1, 1) : QVariant();
}
};
// Custom table view that sets the cell sizes based on a property
class SpecialResizeModeTableView : public QTableView
{
Q_OBJECT
public:
SpecialResizeModeTableView(QWidget *parent = nullptr) : QTableView(parent)
{
QHeaderView *hHeader = horizontalHeader();
QHeaderView *vHeader = verticalHeader();
// Hide the headers, otherwise it appears their size will be used
hHeader->hide();
vHeader->hide();
hHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
vHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
hHeader->setMinimumSectionSize(1);
vHeader->setMinimumSectionSize(1);
setItemDelegate(&delegate_);
}
int cellWidth() const { return delegate_.cellWidth(); }
void setCellWidth(int width)
{
delegate_.setCellWidth(width);
scheduleDelayedItemsLayout();
}
// The following are overridden for optimization purposes but not relevant to the example
int sizeHintForRow(int) const override { return cellWidth(); }
int sizeHintForColumn(int) const override { return cellWidth(); }
QSize sizeHint() const override
{
return QSize(720, 480); // Fixed size for the example
}
private:
SpecialResizeModeTestDelegate delegate_;
};
void tst_QHeaderView::setModelWithAutoSizeWillSwitchToMemoryMode()
{
SpecialResizeModeTableView v;
const int defaultWidth = 20;
QByteArray emptyState = v.horizontalHeader()->saveState();
v.horizontalHeader()->setDefaultSectionSize(defaultWidth);
v.horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
v.show();
QVERIFY(QTest::qWaitForWindowExposed(&v));
auto *model = new SpecialResizeModeTestModel(&v);
v.setModel(model);
const int headerLength = v.horizontalHeader()->length();
const int unexpectedLength = defaultWidth * model->columnCount();
// The length of the header is not the default section size times sections.
// If it fails we like to see this.
QVERIFY(headerLength != unexpectedLength);
// A secondary test is that obviously the header length is the bigger one.
QCOMPARE_GT(headerLength, unexpectedLength);
// and finally we should have switched memory model.
QByteArray nonEmptyState = v.horizontalHeader()->saveState();
const int delta = model->columnCount() * 8;
// even with delta help the nonEmptyState should now be bigger.
QCOMPARE_GT(nonEmptyState.size(), emptyState.size() + delta);
}
void tst_QHeaderView::setDefaultSectionSizeRespectsColumnWidth()
{
QTreeWidget tree;