From 40daec02acebd6e4eb386eb854e3e804b1635ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Cie=C5=9Blak?= Date: Fri, 13 Sep 2024 12:11:07 +0200 Subject: [PATCH] Improve handling of QTBUG-57971 (ListModel initializing roles on first insertion) So far the provided workaround was missing some corner cases when there is more than one SFPM (or other proxies) in a chain, providing some custom roles like proxy roles in SFPM. Detailed explanation provided as a comment directly in the source. --- qqmlsortfilterproxymodel.cpp | 41 +++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/qqmlsortfilterproxymodel.cpp b/qqmlsortfilterproxymodel.cpp index 3ebca88..9bf44c3 100644 --- a/qqmlsortfilterproxymodel.cpp +++ b/qqmlsortfilterproxymodel.cpp @@ -380,10 +380,45 @@ void QQmlSortFilterProxyModel::resetInternalData() void QQmlSortFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { - if (sourceModel && sourceModel->roleNames().isEmpty()) { // workaround for when a model has no roles and roles are added when the model is populated (ListModel) - // QTBUG-57971 - connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &QQmlSortFilterProxyModel::initRoles); + // QML built-in type ListModel behaves in a specific way regarding roles + // initialization (QTBUG-57971). Empty model has no roles, they become + // available after first insertion. However modelAboutToBeReset/modelReset + // is not emited. It means that roles may change not only in between model + // resets but also on the first insertion. + // In a simple case, where ListModel (or other model behaving in that way) + // is direct source of SFPM, situation is relatively simple - if source + // has no roles, SFPM should try to initialize them on first insertion + // However this behavior has far-reaching consequences, because the + // ListModel-like model may not be a direct source for SFPM. E.g. there may + // by another SFPM in between, with proxy roles defined: + // + // ListModel -> SFPM 1 (with proxy roles) -> SFPM 2 + // + // In such scenario SFPM 2 will always have model with roles as it's source + // (at least proxy roles). It means that on first insertion right after + // SFPM creation or after source model reset it's necessary to re-initialize + // role names if the source was empty before insertion. + + if (auto currentSource = this->sourceModel()) { + disconnect(currentSource, &QAbstractItemModel::rowsInserted, this, + &QQmlSortFilterProxyModel::initRoles); + disconnect(currentSource, &QAbstractItemModel::modelReset, this, nullptr); } + + if (sourceModel && sourceModel->rowCount() == 0) + connect(sourceModel, &QAbstractItemModel::rowsInserted, this, + &QQmlSortFilterProxyModel::initRoles, Qt::UniqueConnection); + + if (sourceModel) { + connect(sourceModel, &QAbstractItemModel::modelReset, this, [sourceModel, this]() { + if (sourceModel->rowCount() != 0) + return; + + connect(sourceModel, &QAbstractItemModel::rowsInserted, this, + &QQmlSortFilterProxyModel::initRoles, Qt::UniqueConnection); + }); + } + QSortFilterProxyModel::setSourceModel(sourceModel); }