QHeaderView: allow un-sorting of models
If one clicks on a QHeaderView's section, the header view will sort the view by the respective column/row. By clicking multiple times, one is able to toggle the sorting between ascending and descending. Something that is NOT possible to do however is to un-sort the view -- that is, to restore the model's original sorting. This must be done via code, by asking the header or the view to sort by section -1. This commit adds new property to QHeaderView to make it possible to unsort models. Basically, the sort indicator becomes a tri-state: sort ascending, sort descending, unsort (sort by column -1). [ChangeLog][QtWidgets][QHeaderView] Added the sortIndicatorClearable property. Setting this property allows the user to clear the sort indicator on a section, resetting the model to its default ordering. Change-Id: Ibf4e280b2086b75ccd64d619ea4d70816dc3529f Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io> Reviewed-by: David Faure <david.faure@kdab.com>bb10
parent
3cacf1d1bd
commit
d982fdfca4
|
|
@ -1466,6 +1466,41 @@ Qt::SortOrder QHeaderView::sortIndicatorOrder() const
|
|||
return d->sortIndicatorOrder;
|
||||
}
|
||||
|
||||
/*!
|
||||
\property QHeaderView::sortIndicatorClearable
|
||||
\brief Whether the sort indicator can be cleared by clicking on a section multiple times
|
||||
\since 6.1
|
||||
|
||||
This property controls whether the user is able to remove the
|
||||
sorting indicator on a given section by clicking on the section
|
||||
multiple times. Normally, clicking on a section will simply change
|
||||
the sorting order for that section. By setting this property to
|
||||
true, the sorting indicator will be cleared after alternating to
|
||||
ascending and descending; this will typically restore the original
|
||||
sorting of a model.
|
||||
|
||||
Setting this property to true has no effect unless
|
||||
sectionsClickable() is also true (which is the default for certain
|
||||
views, for instance QTableView, or is automatically set when making
|
||||
a view sortable, for instance by calling
|
||||
QTreeView::setSortingEnabled).
|
||||
*/
|
||||
|
||||
void QHeaderView::setSortIndicatorClearable(bool clearable)
|
||||
{
|
||||
Q_D(QHeaderView);
|
||||
if (d->sortIndicatorClearable == clearable)
|
||||
return;
|
||||
d->sortIndicatorClearable = clearable;
|
||||
emit sortIndicatorClearableChanged(clearable);
|
||||
}
|
||||
|
||||
bool QHeaderView::isSortIndicatorClearable() const
|
||||
{
|
||||
Q_D(const QHeaderView);
|
||||
return d->sortIndicatorClearable;
|
||||
}
|
||||
|
||||
/*!
|
||||
\property QHeaderView::stretchLastSection
|
||||
\brief whether the last visible section in the header takes up all the
|
||||
|
|
@ -3691,22 +3726,48 @@ void QHeaderViewPrivate::clear()
|
|||
}
|
||||
}
|
||||
|
||||
static Qt::SortOrder flipOrder(Qt::SortOrder order)
|
||||
{
|
||||
switch (order) {
|
||||
case Qt::AscendingOrder:
|
||||
return Qt::DescendingOrder;
|
||||
case Qt::DescendingOrder:
|
||||
return Qt::AscendingOrder;
|
||||
};
|
||||
Q_UNREACHABLE();
|
||||
return Qt::AscendingOrder;
|
||||
};
|
||||
|
||||
void QHeaderViewPrivate::flipSortIndicator(int section)
|
||||
{
|
||||
Q_Q(QHeaderView);
|
||||
Qt::SortOrder sortOrder;
|
||||
if (sortIndicatorSection == section) {
|
||||
sortOrder = (sortIndicatorOrder == Qt::DescendingOrder) ? Qt::AscendingOrder : Qt::DescendingOrder;
|
||||
if (sortIndicatorClearable) {
|
||||
const Qt::SortOrder defaultSortOrder = defaultSortOrderForSection(section);
|
||||
if (sortIndicatorOrder == defaultSortOrder) {
|
||||
sortOrder = flipOrder(sortIndicatorOrder);
|
||||
} else {
|
||||
section = -1;
|
||||
sortOrder = Qt::AscendingOrder;
|
||||
}
|
||||
} else {
|
||||
sortOrder = flipOrder(sortIndicatorOrder);
|
||||
}
|
||||
} else {
|
||||
const QVariant value = model->headerData(section, orientation, Qt::InitialSortOrderRole);
|
||||
if (value.canConvert<int>())
|
||||
sortOrder = static_cast<Qt::SortOrder>(value.toInt());
|
||||
else
|
||||
sortOrder = Qt::AscendingOrder;
|
||||
sortOrder = defaultSortOrderForSection(section);
|
||||
}
|
||||
q->setSortIndicator(section, sortOrder);
|
||||
}
|
||||
|
||||
Qt::SortOrder QHeaderViewPrivate::defaultSortOrderForSection(int section) const
|
||||
{
|
||||
const QVariant value = model->headerData(section, orientation, Qt::InitialSortOrderRole);
|
||||
if (value.canConvert<int>())
|
||||
return static_cast<Qt::SortOrder>(value.toInt());
|
||||
return Qt::AscendingOrder;
|
||||
}
|
||||
|
||||
void QHeaderViewPrivate::cascadingResize(int visual, int newSize)
|
||||
{
|
||||
Q_Q(QHeaderView);
|
||||
|
|
@ -4009,6 +4070,7 @@ void QHeaderViewPrivate::write(QDataStream &out) const
|
|||
out << resizeContentsPrecision;
|
||||
out << customDefaultSectionSize;
|
||||
out << lastSectionSize;
|
||||
out << int(sortIndicatorClearable);
|
||||
}
|
||||
|
||||
bool QHeaderViewPrivate::read(QDataStream &in)
|
||||
|
|
@ -4152,6 +4214,11 @@ bool QHeaderViewPrivate::read(QDataStream &in)
|
|||
doDelayedResizeSections();
|
||||
}
|
||||
|
||||
int inSortIndicatorClearable;
|
||||
in >> inSortIndicatorClearable;
|
||||
if (in.status() == QDataStream::Ok) // we haven't read past end
|
||||
sortIndicatorClearable = inSortIndicatorClearable;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ class Q_WIDGETS_EXPORT QHeaderView : public QAbstractItemView
|
|||
Q_PROPERTY(int minimumSectionSize READ minimumSectionSize WRITE setMinimumSectionSize)
|
||||
Q_PROPERTY(int maximumSectionSize READ maximumSectionSize WRITE setMaximumSectionSize)
|
||||
Q_PROPERTY(Qt::Alignment defaultAlignment READ defaultAlignment WRITE setDefaultAlignment)
|
||||
Q_PROPERTY(bool sortIndicatorClearable READ isSortIndicatorClearable WRITE setSortIndicatorClearable NOTIFY sortIndicatorClearableChanged)
|
||||
|
||||
public:
|
||||
|
||||
|
|
@ -140,6 +141,9 @@ public:
|
|||
int sortIndicatorSection() const;
|
||||
Qt::SortOrder sortIndicatorOrder() const;
|
||||
|
||||
void setSortIndicatorClearable(bool clearable);
|
||||
bool isSortIndicatorClearable() const;
|
||||
|
||||
bool stretchLastSection() const;
|
||||
void setStretchLastSection(bool stretch);
|
||||
|
||||
|
|
@ -186,6 +190,7 @@ Q_SIGNALS:
|
|||
void sectionHandleDoubleClicked(int logicalIndex);
|
||||
void geometriesChanged();
|
||||
void sortIndicatorChanged(int logicalIndex, Qt::SortOrder order);
|
||||
void sortIndicatorClearableChanged(bool clearable);
|
||||
|
||||
protected Q_SLOTS:
|
||||
void updateSection(int logicalIndex);
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ public:
|
|||
sortIndicatorOrder(Qt::DescendingOrder),
|
||||
sortIndicatorSection(0),
|
||||
sortIndicatorShown(false),
|
||||
sortIndicatorClearable(false),
|
||||
lastPos(-1),
|
||||
firstPos(-1),
|
||||
originalSize(-1),
|
||||
|
|
@ -248,6 +249,7 @@ public:
|
|||
|
||||
void clear();
|
||||
void flipSortIndicator(int section);
|
||||
Qt::SortOrder defaultSortOrderForSection(int section) const;
|
||||
void cascadingResize(int visual, int newSize);
|
||||
|
||||
enum State { NoState, ResizeSection, MoveSection, SelectSections, NoClear } state;
|
||||
|
|
@ -257,6 +259,7 @@ public:
|
|||
Qt::SortOrder sortIndicatorOrder;
|
||||
int sortIndicatorSection;
|
||||
bool sortIndicatorShown;
|
||||
bool sortIndicatorClearable;
|
||||
|
||||
mutable QList<int> visualIndices; // visualIndex = visualIndices.at(logicalIndex)
|
||||
mutable QList<int> logicalIndices; // logicalIndex = row or column in the model
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ private slots:
|
|||
void moveAndInsertSection();
|
||||
void highlightSections();
|
||||
void showSortIndicator();
|
||||
void clearSectionSorting();
|
||||
void sortIndicatorTracking();
|
||||
void removeAndInsertRow();
|
||||
void unhideSection();
|
||||
|
|
@ -1333,6 +1334,75 @@ void tst_QHeaderView::showSortIndicator()
|
|||
// Don't assert baby :)
|
||||
}
|
||||
|
||||
void tst_QHeaderView::clearSectionSorting()
|
||||
{
|
||||
QStandardItemModel m(4, 4);
|
||||
QHeaderView h(Qt::Horizontal);
|
||||
|
||||
QCOMPARE(h.sortIndicatorSection(), 0);
|
||||
QCOMPARE(h.sortIndicatorOrder(), Qt::DescendingOrder);
|
||||
|
||||
h.setModel(&m);
|
||||
h.setSectionsClickable(true);
|
||||
h.setSortIndicatorShown(true);
|
||||
h.setSortIndicator(-1, Qt::DescendingOrder);
|
||||
h.show();
|
||||
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&h));
|
||||
|
||||
QCOMPARE(h.sortIndicatorSection(), -1);
|
||||
QCOMPARE(h.sortIndicatorOrder(), Qt::DescendingOrder);
|
||||
|
||||
QSignalSpy sectionClickedSpy(&h, &QHeaderView::sectionClicked);
|
||||
QVERIFY(sectionClickedSpy.isValid());
|
||||
QCOMPARE(sectionClickedSpy.count(), 0);
|
||||
|
||||
QSignalSpy sortIndicatorChangedSpy(&h, &QHeaderView::sortIndicatorChanged);
|
||||
QVERIFY(sortIndicatorChangedSpy.isValid());
|
||||
QCOMPARE(sortIndicatorChangedSpy.count(), 0);
|
||||
|
||||
enum { Count = 30 };
|
||||
|
||||
// normal behavior: clicking multiple times will just toggle the sort indicator
|
||||
for (int i = 0; i < Count; ++i) {
|
||||
QTest::mouseClick(h.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
|
||||
QCOMPARE(sectionClickedSpy.count(), i + 1);
|
||||
QCOMPARE(sortIndicatorChangedSpy.count(), i + 1);
|
||||
QCOMPARE(h.sortIndicatorSection(), 0);
|
||||
const auto expectedOrder = (i % 2) == 0 ? Qt::AscendingOrder : Qt::DescendingOrder;
|
||||
QCOMPARE(h.sortIndicatorOrder(), expectedOrder);
|
||||
}
|
||||
|
||||
h.setSortIndicator(-1, Qt::DescendingOrder);
|
||||
h.setSortIndicatorClearable(true);
|
||||
QCOMPARE(h.sortIndicatorSection(), -1);
|
||||
QCOMPARE(h.sortIndicatorOrder(), Qt::DescendingOrder);
|
||||
|
||||
sectionClickedSpy.clear();
|
||||
sortIndicatorChangedSpy.clear();
|
||||
|
||||
// clearing behavior: clicking multiple times will be tristate (asc, desc, nothing)
|
||||
for (int i = 0; i < Count; ++i) {
|
||||
QTest::mouseClick(h.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(5, 5));
|
||||
QCOMPARE(sectionClickedSpy.count(), i + 1);
|
||||
QCOMPARE(sortIndicatorChangedSpy.count(), i + 1);
|
||||
switch (i % 3) {
|
||||
case 0:
|
||||
QCOMPARE(h.sortIndicatorSection(), 0);
|
||||
QCOMPARE(h.sortIndicatorOrder(), Qt::AscendingOrder);
|
||||
break;
|
||||
case 1:
|
||||
QCOMPARE(h.sortIndicatorSection(), 0);
|
||||
QCOMPARE(h.sortIndicatorOrder(), Qt::DescendingOrder);
|
||||
break;
|
||||
case 2:
|
||||
QCOMPARE(h.sortIndicatorSection(), -1);
|
||||
QCOMPARE(h.sortIndicatorOrder(), Qt::AscendingOrder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QHeaderView::sortIndicatorTracking()
|
||||
{
|
||||
QtTestModel model(10, 10);
|
||||
|
|
|
|||
Loading…
Reference in New Issue