QSFPM: fix mapping corruption in mapFromSource() on hidden row child
source_to_proxy (called by mapFromSource) was unconditionally creating a mapping for the parent of the given source index, even if that parent is in fact filtered out in the proxy. That mapping would then just say "-1" for the (also filtered out) child row. Afterwards, inserting a visible child (which makes the parent visible, in a recursive QSFPM) would see that the mapping for the parent already exists and fail to update it properly. Various symptoms could ensue, such as an out-of-bounds access in gm->proxy_rows.at(source_parent.row()) in a subsequent call to can_create_mapping (as I had on KMail startup for years, randomly) or a visible child that fails to appear (as in the unittest). Fixed by using create_mapping_recursive() which returns 'end' if the row is filtered out, going up the tree if necessary to create mapping for parents on demand (so that the call to filterAcceptsRow is cached). Fixes: QTBUG-76976 Pick-to: 6.8 Change-Id: I25a3bf6b147092c1231ab6441e636264fd0c4222 Reviewed-by: Santhosh Kumar <santhosh.kumar.selvaraj@qt.io> (cherry picked from commit 90a82ad8e59792fb430a1a11c9a0c2f94878303e) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
a81f33eff3
commit
ba9fa54b92
@ -572,7 +572,9 @@ QModelIndex QSortFilterProxyModelPrivate::source_to_proxy(const QModelIndex &sou
|
||||
return QModelIndex();
|
||||
}
|
||||
QModelIndex source_parent = source_index.parent();
|
||||
IndexMap::const_iterator it = create_mapping(source_parent);
|
||||
IndexMap::const_iterator it = create_mapping_recursive(source_parent);
|
||||
if (it == source_index_mapping.constEnd())
|
||||
return QModelIndex();
|
||||
Mapping *m = it.value();
|
||||
if ((source_index.row() >= m->proxy_rows.size()) || (source_index.column() >= m->proxy_columns.size()))
|
||||
return QModelIndex();
|
||||
|
@ -722,9 +722,53 @@ private Q_SLOTS:
|
||||
QCOMPARE(treeAsString(proxy), noParentProxyStr);
|
||||
}
|
||||
|
||||
void testMapFromSourceOnHiddenRow()
|
||||
{
|
||||
// Given a source model and a filter proxy model which hides a subtree
|
||||
QStandardItemModel sourceModel;
|
||||
const QString sourceStr = QStringLiteral("[KDAB[INBOX* shared[hidden]]]");
|
||||
fillModel(sourceModel, sourceStr);
|
||||
QCOMPARE(treeAsString(sourceModel), sourceStr);
|
||||
|
||||
TestModel proxyModel(&sourceModel);
|
||||
QCOMPARE(treeAsString(proxyModel), QStringLiteral("[KDAB[INBOX*]]"));
|
||||
|
||||
// STEP 1: call mapFromSource on a hidden row with a hidden parent
|
||||
// This used to create the mapping for the parent, which would then
|
||||
// become out of date after insertion below.
|
||||
QStandardItem *shared = sourceModel.item(0)->child(1);
|
||||
QCOMPARE(shared->text(), "shared");
|
||||
QStandardItem *hiddenChild = shared->child(0);
|
||||
QCOMPARE(hiddenChild->text(), "hidden");
|
||||
const QModelIndex idx = proxyModel.mapFromSource(hiddenChild->index());
|
||||
QVERIFY(!idx.isValid()); // invalid, but the call used to create the mapping, erroneously
|
||||
|
||||
// STEP 2: inserting a visible child, which makes the parent appear too
|
||||
const auto insertVisibleChild = [&]() {
|
||||
QStandardItem *visibleChild = new QStandardItem(QStringLiteral("visible"));
|
||||
visibleChild->setData(true, s_filterRole);
|
||||
shared->insertRow(0, visibleChild);
|
||||
};
|
||||
insertVisibleChild();
|
||||
QCOMPARE(treeAsString(sourceModel), QStringLiteral("[KDAB[INBOX* shared[visible* hidden]]]"));
|
||||
|
||||
// THEN
|
||||
QCOMPARE(treeAsString(proxyModel), QStringLiteral("[KDAB[INBOX* shared[visible*]]]"));
|
||||
|
||||
// For good measure, test removing and re-inserting the visible child again
|
||||
shared->removeRow(0);
|
||||
QCOMPARE(treeAsString(sourceModel), QStringLiteral("[KDAB[INBOX* shared[hidden]]]"));
|
||||
QCOMPARE(treeAsString(proxyModel), QStringLiteral("[KDAB[INBOX*]]"));
|
||||
|
||||
insertVisibleChild();
|
||||
QCOMPARE(treeAsString(sourceModel), QStringLiteral("[KDAB[INBOX* shared[visible* hidden]]]"));
|
||||
QCOMPARE(treeAsString(proxyModel), QStringLiteral("[KDAB[INBOX* shared[visible*]]]"));
|
||||
}
|
||||
|
||||
private:
|
||||
QStandardItem *itemByText(const QStandardItemModel& model, const QString &text) const {
|
||||
QModelIndexList list = model.match(model.index(0, 0), Qt::DisplayRole, text, 1, Qt::MatchRecursive);
|
||||
QModelIndexList list = model.match(model.index(0, 0), Qt::DisplayRole, text, 1,
|
||||
Qt::MatchRecursive);
|
||||
return list.isEmpty() ? 0 : model.itemFromIndex(list.first());
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user