QHeaderView - fix broken stretchLastSection

In setStretchLastSection we restore a default section size
if we no longer use stretch. That size was however not
sufficient - we should restore the actual size.

Furthermore we should also always stretch the last section
(last visible index) - and not leave a section with a
huge size and stretch another.

This patch refactors stretch handling and keeps track
of the last section and its size in eg. moveSection,
swapSection, hideSection, showSection etc.

There is an auto test showing and guarding its behavior.

[ChangeLog][QtWidgets][QHeaderView] Fixed some issues
(e.g QTBUG-39010) with restoring of section size after
a section is no longer the last visible section
(in stretchLastSection mode).

Task-number: QTBUG-39010
Change-Id: Id47075b5a9dfeb250027374ecbd10eb8babbf9ef
Reviewed-by: David Faure <david.faure@kdab.com>
bb10
Thorbjørn Martsum 2015-06-26 06:36:54 +02:00 committed by Thorbjørn Lund Martsum
parent 9059931cd2
commit 024a52d0d1
3 changed files with 242 additions and 16 deletions

View File

@ -753,9 +753,6 @@ void QHeaderView::moveSection(int from, int to)
return;
}
if (stretchLastSection() && to == d->lastVisibleVisualIndex())
d->lastSectionSize = sectionSize(from);
d->initializeIndexMapping();
int *visualIndices = d->visualIndices.data();
@ -788,6 +785,12 @@ void QHeaderView::moveSection(int from, int to)
d->viewport->update();
emit sectionMoved(logical, from, to);
if (stretchLastSection()) {
const int lastSectionVisualIdx = visualIndex(d->lastSectionLogicalIdx);
if (from >= lastSectionVisualIdx || to >= lastSectionVisualIdx)
d->maybeRestorePrevLastSectionAndStretchLast();
}
}
/*!
@ -839,6 +842,12 @@ void QHeaderView::swapSections(int first, int second)
d->viewport->update();
emit sectionMoved(firstLogical, first, second);
emit sectionMoved(secondLogical, second, first);
if (stretchLastSection()) {
const int lastSectionVisualIdx = visualIndex(d->lastSectionLogicalIdx);
if (first >= lastSectionVisualIdx || second >= lastSectionVisualIdx)
d->maybeRestorePrevLastSectionAndStretchLast();
}
}
/*!
@ -877,7 +886,7 @@ void QHeaderView::resizeSection(int logical, int size)
d->executePostedLayout();
d->invalidateCachedSizeHint();
if (stretchLastSection() && visual == d->lastVisibleVisualIndex())
if (stretchLastSection() && logical == d->lastSectionLogicalIdx)
d->lastSectionSize = size;
d->createSectionItems(visual, visual, size, d->headerSectionResizeMode(visual));
@ -1000,11 +1009,16 @@ void QHeaderView::setSectionHidden(int logicalIndex, bool hide)
if (hide == d->isVisualIndexHidden(visual))
return;
if (hide) {
const bool isHidingLastSection = (stretchLastSection() && logicalIndex == d->lastSectionLogicalIdx);
if (isHidingLastSection)
d->restoreSizeOnPrevLastSection(); // Restore here/now to get the right restore size.
int size = d->headerSectionSize(visual);
if (!d->hasAutoResizeSections())
resizeSection(logicalIndex, 0);
d->hiddenSectionSize.insert(logicalIndex, size);
d->setVisualIndexHidden(visual, true);
if (isHidingLastSection)
d->setNewLastSection(d->lastVisibleVisualIndex());
if (d->hasAutoResizeSections())
d->doDelayedResizeSections();
} else {
@ -1012,6 +1026,12 @@ void QHeaderView::setSectionHidden(int logicalIndex, bool hide)
d->hiddenSectionSize.remove(logicalIndex);
d->setVisualIndexHidden(visual, false);
resizeSection(logicalIndex, size);
const bool newLastSection = (stretchLastSection() && visual > visualIndex(d->lastSectionLogicalIdx));
if (newLastSection) {
d->restoreSizeOnPrevLastSection();
d->setNewLastSection(visual);
}
}
}
@ -1473,13 +1493,17 @@ bool QHeaderView::stretchLastSection() const
void QHeaderView::setStretchLastSection(bool stretch)
{
Q_D(QHeaderView);
const bool changedStretchMode = (d->stretchLastSection != stretch);
d->stretchLastSection = stretch;
if (d->state != QHeaderViewPrivate::NoState)
return;
if (stretch)
if (stretch) {
d->setNewLastSection(d->lastVisibleVisualIndex());
resizeSections();
else if (count())
resizeSection(count() - 1, d->defaultSectionSize);
} else {
if (changedStretchMode)
d->restoreSizeOnPrevLastSection();
}
}
/*!
@ -1834,6 +1858,21 @@ void QHeaderView::sectionsInserted(const QModelIndex &parent,
int insertAt = logicalFirst;
int insertCount = logicalLast - logicalFirst + 1;
bool lastSectionActualChange = false;
if (stretchLastSection()) {
int visualIndexForStretch = d->lastSectionLogicalIdx;
if (d->lastSectionLogicalIdx >= 0 && d->lastSectionLogicalIdx < d->visualIndices.size())
visualIndexForStretch = d->visualIndices[d->lastSectionLogicalIdx]; // We cannot call visualIndex since it executes executePostedLayout()
// and it is likely to bypass initializeSections() and we may end up here again. Doing the insert twice.
if (d->lastSectionLogicalIdx < 0 || insertAt >= visualIndexForStretch)
lastSectionActualChange = true;
if (d->lastSectionLogicalIdx >= logicalFirst)
d->lastSectionLogicalIdx += insertCount; // We do not want to emit resize before we have fixed the count
}
QHeaderViewPrivate::SectionItem section(d->defaultSectionSize, d->globalResizeMode);
d->sectionStartposRecalc = true;
@ -1890,6 +1929,9 @@ void QHeaderView::sectionsInserted(const QModelIndex &parent,
d->doDelayedResizeSections();
emit sectionCountChanged(oldCount, count());
if (lastSectionActualChange)
d->maybeRestorePrevLastSectionAndStretchLast();
// if the new sections were not updated by resizing, we need to update now
if (!d->hasAutoResizeSections())
d->viewport->update();
@ -2000,6 +2042,16 @@ void QHeaderViewPrivate::_q_sectionsRemoved(const QModelIndex &parent,
clear();
invalidateCachedSizeHint();
emit q->sectionCountChanged(oldCount, q->count());
if (q->stretchLastSection()) {
const bool lastSectionRemoved = lastSectionLogicalIdx >= logicalFirst && lastSectionLogicalIdx <= logicalLast;
if (lastSectionRemoved)
setNewLastSection(lastVisibleVisualIndex());
else
lastSectionLogicalIdx = logicalIndex(lastVisibleVisualIndex()); // Just update the last log index.
doDelayedResizeSections();
}
viewport->update();
}
@ -2078,8 +2130,8 @@ void QHeaderView::initializeSections()
} else if (newCount != oldCount) {
const int min = qBound(0, oldCount, newCount - 1);
initializeSections(min, newCount - 1);
if (stretchLastSection()) // we've already gotten the size hint
d->lastSectionSize = sectionSize(logicalIndex(d->sectionCount() - 1));
if (stretchLastSection()) // we've already gotten the size hint
d->maybeRestorePrevLastSectionAndStretchLast();
//make sure we update the hidden sections
if (newCount < oldCount)
@ -3169,6 +3221,42 @@ int QHeaderViewPrivate::lastVisibleVisualIndex() const
return -1;
}
void QHeaderViewPrivate::restoreSizeOnPrevLastSection()
{
Q_Q(QHeaderView);
if (lastSectionLogicalIdx < 0)
return;
int resizeLogIdx = lastSectionLogicalIdx;
lastSectionLogicalIdx = -1; // We do not want resize to catch it as the last section.
q->resizeSection(resizeLogIdx, lastSectionSize);
}
void QHeaderViewPrivate::setNewLastSection(int visualIndexForLastSection)
{
Q_Q(QHeaderView);
lastSectionSize = -1;
lastSectionLogicalIdx = q->logicalIndex(visualIndexForLastSection);
lastSectionSize = headerSectionSize(visualIndexForLastSection); // pick size directly since ...
// q->sectionSize(lastSectionLogicalIdx) may do delayed resize and stretch it before we get the value.
}
void QHeaderViewPrivate::maybeRestorePrevLastSectionAndStretchLast()
{
Q_Q(const QHeaderView);
if (!q->stretchLastSection())
return;
int nowLastVisualSection = lastVisibleVisualIndex();
if (lastSectionLogicalIdx == q->logicalIndex(nowLastVisualSection))
return;
// restore old last section.
restoreSizeOnPrevLastSection();
setNewLastSection(nowLastVisualSection);
doDelayedResizeSections(); // Do stretch of last section soon (but not now).
}
/*!
\internal
Go through and resize all of the sections applying stretchLastSection,
@ -3197,13 +3285,12 @@ void QHeaderViewPrivate::resizeSections(QHeaderView::ResizeMode globalMode, bool
resizeRecursionBlock = true;
invalidateCachedSizeHint();
const int lastVisibleSection = lastVisibleVisualIndex();
const int lastSectionVisualIdx = q->visualIndex(lastSectionLogicalIdx);
// find stretchLastSection if we have it
int stretchSection = -1;
if (stretchLastSection && !useGlobalMode)
stretchSection = lastVisibleVisualIndex();
stretchSection = lastSectionVisualIdx;
// count up the number of stretched sections and how much space left for them
int lengthToStretch = (orientation == Qt::Horizontal ? viewport->width() : viewport->height());
@ -3272,7 +3359,7 @@ void QHeaderViewPrivate::resizeSections(QHeaderView::ResizeMode globalMode, bool
? QHeaderView::Stretch
: newSectionResizeMode);
if (resizeMode == QHeaderView::Stretch && stretchSectionLength != -1) {
if (i == lastVisibleSection)
if (i == lastSectionVisualIdx)
newSectionLength = qMax(stretchSectionLength, lastSectionSize);
else
newSectionLength = stretchSectionLength;
@ -3661,10 +3748,12 @@ void QHeaderViewPrivate::write(QDataStream &out) const
out << sectionItems;
out << resizeContentsPrecision;
out << customDefaultSectionSize;
out << lastSectionSize;
}
bool QHeaderViewPrivate::read(QDataStream &in)
{
Q_Q(QHeaderView);
int orient, order, align, global;
int sortIndicatorSectionIn;
bool sortIndicatorShownIn;
@ -3683,7 +3772,6 @@ bool QHeaderViewPrivate::read(QDataStream &in)
int minimumSectionSizeIn;
QVector<SectionItem> sectionItemsIn;
in >> orient;
in >> order;
@ -3776,6 +3864,18 @@ bool QHeaderViewPrivate::read(QDataStream &in)
updateDefaultSectionSizeFromStyle();
}
lastSectionSize = -1;
int inLastSectionSize;
in >> inLastSectionSize;
if (in.status() == QDataStream::Ok)
lastSectionSize = inLastSectionSize;
lastSectionLogicalIdx = -1;
if (stretchLastSection) {
lastSectionLogicalIdx = q->logicalIndex(lastVisibleVisualIndex());
doDelayedResizeSections();
}
return true;
}

View File

@ -90,6 +90,7 @@ public:
minimumSectionSize(-1),
maximumSectionSize(-1),
lastSectionSize(0),
lastSectionLogicalIdx(-1), // Only trust when we stretch last section
sectionIndicatorOffset(0),
sectionIndicator(0),
globalResizeMode(QHeaderView::Interactive),
@ -99,6 +100,9 @@ public:
int lastVisibleVisualIndex() const;
void restoreSizeOnPrevLastSection();
void setNewLastSection(int visualIndexForLastSection);
void maybeRestorePrevLastSectionAndStretchLast();
int sectionHandleAt(int position);
void setupSectionIndicator(int section, int position);
void updateSectionIndicator(int section, int position);
@ -281,7 +285,8 @@ public:
int defaultSectionSize;
int minimumSectionSize;
int maximumSectionSize;
int lastSectionSize; // $$$
int lastSectionSize;
int lastSectionLogicalIdx; // Only trust if we stretch LastSection
int sectionIndicatorOffset;
Qt::Alignment defaultAlignment;
QLabel *sectionIndicator;

View File

@ -47,6 +47,7 @@
#include <qitemdelegate.h>
#include <qtreewidget.h>
#include <qdebug.h>
#include <qscreen.h>
typedef QList<int> IntList;
@ -235,6 +236,7 @@ private slots:
void resizeToContentTest();
void testStreamWithHide();
void testStylePosition();
void stretchAndRestoreLastSection();
void sizeHintCrash();
@ -554,7 +556,7 @@ void tst_QHeaderView::hidden()
void tst_QHeaderView::stretch()
{
// Show before resize and setStrechLastSection
// Show before resize and setStretchLastSection
#if defined(Q_OS_WINCE)
QSize viewSize(200,300);
#else
@ -2891,5 +2893,124 @@ void tst_QHeaderView::sizeHintCrash()
treeView.header()->sizeHintForRow(0);
}
void tst_QHeaderView::stretchAndRestoreLastSection()
{
QStandardItemModel m(10, 10);
QTableView tv;
tv.setModel(&m);
tv.showMaximized();
const int defaultSectionSize = 30;
const int someOtherSectionSize = 40;
const int biggerSizeThanAnySection = 50;
QVERIFY(QTest::qWaitForWindowExposed(&tv));
QHeaderView &header = *tv.horizontalHeader();
header.setDefaultSectionSize(defaultSectionSize);
header.resizeSection(9, someOtherSectionSize);
header.setStretchLastSection(true);
// Default last section is larger
QCOMPARE(header.sectionSize(8), defaultSectionSize);
QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection);
// Moving last section away (restore old last section 9 - and make 8 larger)
header.swapSections(9, 8);
QCOMPARE(header.sectionSize(9), someOtherSectionSize);
QVERIFY(header.sectionSize(8) >= biggerSizeThanAnySection);
// Make section 9 the large one again
header.hideSection(8);
QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection);
// Show section 8 again - and make that one the last one.
header.showSection(8);
QVERIFY(header.sectionSize(8) > biggerSizeThanAnySection);
QCOMPARE(header.sectionSize(9), someOtherSectionSize);
// Swap the sections so the logical indexes are equal to visible indexes again.
header.moveSection(9, 8);
QCOMPARE(header.sectionSize(8), defaultSectionSize);
QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection);
// Append sections
m.setColumnCount(15);
QCOMPARE(header.sectionSize(9), someOtherSectionSize);
QVERIFY(header.sectionSize(14) >= biggerSizeThanAnySection);
// Truncate sections (remove sections with the last section)
m.setColumnCount(10);
QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection);
for (int u = 0; u < 9; ++u)
QCOMPARE(header.sectionSize(u), defaultSectionSize);
// Insert sections
m.insertColumns(2, 2);
QCOMPARE(header.sectionSize(9), defaultSectionSize);
QCOMPARE(header.sectionSize(10), defaultSectionSize);
QVERIFY(header.sectionSize(11) >= biggerSizeThanAnySection);
// Append an extra section and check restore
m.setColumnCount(m.columnCount() + 1);
QCOMPARE(header.sectionSize(11), someOtherSectionSize);
QVERIFY(header.sectionSize(12) >= biggerSizeThanAnySection);
// Remove some sections but not the last one.
m.removeColumns(2, 2);
QCOMPARE(header.sectionSize(9), someOtherSectionSize);
QVERIFY(header.sectionSize(10) >= biggerSizeThanAnySection);
for (int u = 0; u < 9; ++u)
QCOMPARE(header.sectionSize(u), defaultSectionSize);
// Empty the header and start over with some more tests
m.setColumnCount(0);
m.setColumnCount(10);
QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection);
// Check resize of the last section
header.resizeSection(9, someOtherSectionSize);
QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection); // It should still be stretched
header.swapSections(9, 8);
QCOMPARE(header.sectionSize(9), someOtherSectionSize);
// Restore the order
header.swapSections(9, 8);
QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection);
// Hide the last 3 sections and test stretch last section on swap/move
// when hidden sections with a larger visual index exists.
header.hideSection(7);
header.hideSection(8);
header.hideSection(9);
QVERIFY(header.sectionSize(6) >= biggerSizeThanAnySection);
header.moveSection(2, 7);
QVERIFY(header.sectionSize(2) >= biggerSizeThanAnySection);
header.swapSections(1, 8);
QCOMPARE(header.sectionSize(2), defaultSectionSize);
QVERIFY(header.sectionSize(1) >= biggerSizeThanAnySection);
// Inserting sections 2
m.setColumnCount(0);
m.setColumnCount(10);
header.resizeSection(1, someOtherSectionSize);
header.swapSections(1, 9);
m.insertColumns(9, 2);
header.swapSections(1, 11);
QCOMPARE(header.sectionSize(1), someOtherSectionSize);
// Test import/export of the original (not stretched) sectionSize.
m.setColumnCount(0);
m.setColumnCount(10);
header.resizeSection(9, someOtherSectionSize);
QVERIFY(header.sectionSize(9) >= biggerSizeThanAnySection);
QByteArray b = header.saveState();
m.setColumnCount(0);
m.setColumnCount(10);
header.restoreState(b);
header.setStretchLastSection(false);
QCOMPARE(header.sectionSize(9), someOtherSectionSize);
}
QTEST_MAIN(tst_QHeaderView)
#include "tst_qheaderview.moc"