Skip to content

Fuzzy text search, part 0: Allow subclassing filters and sorters of SortFilterProxyModel#33052

Open
juli27 wants to merge 7 commits intomusescore:masterfrom
juli27:refactorSFPM
Open

Fuzzy text search, part 0: Allow subclassing filters and sorters of SortFilterProxyModel#33052
juli27 wants to merge 7 commits intomusescore:masterfrom
juli27:refactorSFPM

Conversation

@juli27
Copy link
Copy Markdown
Contributor

@juli27 juli27 commented Apr 18, 2026

I realized that using another model (FuzzySortFilterProxyModel) in part 1 (#32634) makes it unnecessarily hard to use in a lot of places where a SortFilterProxyModel is used right now. So instead of that approach, the fuzzy search will be implemented via a FuzzyFilter and FuzzyScoreSorter. This PR provides the foundation for such an implementation, by allowing subclassing the filters and sorters.

  • I signed the CLA
  • The title of the PR describes the problem it addresses
  • Each commit's message describes its purpose and effects, and references the issue it resolves
  • If changes are extensive, there is a sequence of easily reviewable commits
  • The code in the PR follows the coding rules
  • There are no unnecessary changes
  • The code compiles and runs on my machine, preferably after each commit individually
  • I created a unit test or vtest to verify the changes I made (if applicable)

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 18, 2026

📝 Walkthrough

Walkthrough

Adds new QML-exposed base types Filter and Sorter (headers and implementations) and registers their sources in the Muse.UiComponents QML module. Refactors FilterValue and SorterValue to derive from these bases and implement row-acceptance (acceptsRow) and comparison (lessThan) methods. Reworks SortFilterProxyModel to delegate filtering/sorting to Filter/Sorter instances, changes QQmlListProperty types to Filter/Sorter, adds roleFromRoleName(...), and removes prior role/value mapping logic. Adds unit tests for SortFilterProxyModel and a small UI include reorder.

đŸšĨ Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage âš ī¸ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Fuzzy text search, part 0: Allow subclassing filters and sorters of SortFilterProxyModel' directly summarizes the main change: enabling subclassing of filters and sorters to support fuzzy search implementation.
Description check ✅ Passed The PR description follows the template structure with all checkboxes checked, providing clear motivation (moving from separate model to subclassable components approach), issue resolution context, and rationale for the implementation strategy.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

âœī¸ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
đŸ§Ē Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

â¤ī¸ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/framework/uicomponents/qml/Muse/UiComponents/filter.h`:
- Around line 34-47: Add a focused unit test that exercises FilterValue
instances when accessed through the base-typed SortFilterProxyModel::filters
API: create a SortFilterProxyModel, append multiple FilterValue-derived filters
targeting the same role via the model's filters property, and assert the
combined accept/reject behavior by calling acceptsRow (or using the proxy
filtering) to ensure multiple filters on the same role behave correctly; also
verify inherited property behavior by toggling the base Filter properties
enabled and async (Filter::enabled, Filter::async) on parent/base-typed filters
and asserting those settings propagate/affect the FilterValue instances as
expected. Ensure the test constructs filters only through the public
SortFilterProxyModel::filters interface (not concrete types), covers both
enabling/disabling and async on/off permutations, and asserts the final filtered
row set matches the expected outcome.

In `@src/framework/uicomponents/qml/Muse/UiComponents/sorter.h`:
- Around line 34-46: Add a small unit/regression test that exercises the new
abstract QML API by inserting a concrete SorterValue into the
SortFilterProxyModel::sorters list and verifying sorting behavior and properties
propagate through the base type; specifically, instantiate a SorterValue (or
equivalent concrete subclass), append it to SortFilterProxyModel::sorters,
toggle the Sorter::enabled and Sorter::sortOrder properties and assert that the
proxy model's ordering changes accordingly and that inherited enabled/sortOrder
values are observed when sorting (use the Sorter class/interface and
SortFilterProxyModel APIs to locate where to add the test).

In `@src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.cpp`:
- Around line 36-49: In SorterValue::lessThan, avoid calling Val::fromQVariant
for unsupported QVariant types on every comparison: first call
proxyModel.roleFromRoleName(m_roleName) and compare to a named invalid constant
(use SortFilterProxyModel::INVALID_KEY if made public, or define a local const
int INVALID_ROLE = -1) instead of a bare -1; then check the
QVariant::typeId()/type() from sourceModel->data(...) and if the type is
unsupported return a stable fallback (e.g., compare sourceLeft < sourceRight or
compare row/column) without invoking Val::fromQVariant; additionally, to prevent
log flooding, implement a one-time warning mechanism keyed by (role,
qMetaTypeId) (e.g., a static thread-safe set of logged pairs) and only call the
error log once per pair when encountering an unsupported type.

In `@src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp`:
- Around line 38-58: The filter invalidation sequence is duplicated in the
onFilterChanged lambda; extract the common beginFilterChange() + Qt-versioned
endFilterChange(...)/invalidateFilter() block into a single helper (e.g., a
private method named performFilterInvalidation or scheduleFilterInvalidation)
and call that helper from both branches of onFilterChanged (the async branch
should QTimer::singleShot(0, this, [this]{ helper(); }) and the sync branch
should call helper() directly); also replace other occurrences in
setAlwaysIncludeIndices and setAlwaysExcludeIndices to call the same helper so
the invalidation logic is centralized and cannot drift.
đŸĒ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„šī¸ Review info
âš™ī¸ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: aad8c111-2e17-4a93-a2d4-54b6f60d377d

đŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between b669ca8 and 08aa73f.

📒 Files selected for processing (13)
  • src/framework/ui/internal/uiconfiguration.cpp
  • src/framework/ui/internal/uicontextconfiguration.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/CMakeLists.txt
  • src/framework/uicomponents/qml/Muse/UiComponents/filter.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/filter.h
  • src/framework/uicomponents/qml/Muse/UiComponents/filtervalue.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/filtervalue.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sorter.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sorter.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.h
💤 Files with no reviewable changes (1)
  • src/framework/ui/internal/uiconfiguration.cpp

Comment thread src/framework/uicomponents/qml/Muse/UiComponents/filter.h
Comment thread src/framework/uicomponents/qml/Muse/UiComponents/sorter.h
Comment thread src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/framework/uicomponents/qml/Muse/UiComponents/sorter.h`:
- Around line 40-41: Replace the empty-message and stray semicolon: remove the
trailing semicolon after the QML_ELEMENT macro and supply a meaningful reason
string to QML_UNCREATABLE (e.g., a short explanatory message describing why
instances are not creatable from QML and how to obtain one) so QML error
messages are actionable; update the line containing QML_ELEMENT and the
QML_UNCREATABLE("") usage in sorter.h (referenced symbols: QML_ELEMENT,
QML_UNCREATABLE) accordingly.

In `@src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.cpp`:
- Around line 34-48: The less-than logic in SorterValue::lessThan is wrong:
instead of falling back to comparing sourceLeft < sourceRight for all non-Less
results, only use the source-index tiebreaker when the QVariant comparison is
Equivalent or Unordered. Change SorterValue::lessThan so it returns true if
ordering == QPartialOrdering::Less, false if ordering ==
QPartialOrdering::Greater, and only when ordering is Equivalent or Unordered
evaluate and return sourceLeft < sourceRight; this preserves the required
antisymmetry for QSortFilterProxyModel.
đŸĒ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„šī¸ Review info
âš™ī¸ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9315a1f5-4177-4526-b02b-5a9dc353eeb2

đŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 08aa73f and d683a63.

📒 Files selected for processing (7)
  • src/framework/uicomponents/qml/Muse/UiComponents/CMakeLists.txt
  • src/framework/uicomponents/qml/Muse/UiComponents/sorter.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sorter.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.h

Comment thread src/framework/uicomponents/qml/Muse/UiComponents/sorter.h
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp`:
- Around line 78-82: The sorter-append lambda currently calls onSortersChanged()
unconditionally causing unnecessary invalidate()/sort() for newly appended
disabled sorters; change the lambda to fetch the appended Sorter via
m_sorters.at(index), check its enabled state (e.g., call sorter->enabled() or
equivalent), and only call onSortersChanged() if enabled, while still connecting
the Sorter::dataChanged signal to onSortersChanged so future enablement or data
changes trigger updates; reference the m_sorters.notifier()->appended handler,
m_sorters.at(index), onSortersChanged, and the
Sorter::dataChanged/Sorter::enabled symbols.
- Around line 143-146: roleFromRoleName currently does an O(n) reverse lookup
via roleNames().key() and is called per-row/per-comparison
(FilterValue::acceptsRow and SorterValue::lessThan), causing quadratic-like
costs; fix by avoiding repeated scans: either cache the resolved role id inside
FilterValue and SorterValue (store an int member, compute once on first use and
reset when sourceModelRoleNamesChanged fires) or add a lazy reverse map
(QHash<QByteArray,int>) inside SortFilterProxyModel that roleFromRoleName
consults and that is rebuilt/cleared when sourceModelRoleNamesChanged is
emitted; update the existing sourceModelRoleNamesChanged handler to invalidate
the cached id(s) or reverse map accordingly.
đŸĒ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„šī¸ Review info
âš™ī¸ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bc537158-d0d5-47d1-aa95-5c484967aa59

đŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between d683a63 and cdce00f.

📒 Files selected for processing (11)
  • src/framework/uicomponents/qml/Muse/UiComponents/CMakeLists.txt
  • src/framework/uicomponents/qml/Muse/UiComponents/filter.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/filter.h
  • src/framework/uicomponents/qml/Muse/UiComponents/filtervalue.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/filtervalue.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sorter.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sorter.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.h

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

âš ī¸ Outside diff range comments (1)
src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp (1)

38-90: 🧹 Nitpick | đŸ”ĩ Trivial

LGTM — wiring is correct and previous review feedback is addressed.

invalidateRows centralizes the Qt-versioned filter invalidation, and the append handlers now gate the initial onFilterChanged / onSortersChanged dispatch on enabled(), which avoids the spurious invalidations flagged previously. Delegating filterAcceptsRow/lessThan to Filter/Sorter keeps the proxy free of role-type logic, exactly as intended by the refactor.

One optional note: the same beginFilterChange() / versioned endFilterChange(Direction::Rows) / invalidateFilter() block is still open-coded in setAlwaysIncludeIndices (Lines 113-119) and setAlwaysExcludeIndices (Lines 135-141). Promoting invalidateRows to a private member function and reusing it there would eliminate the remaining duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp`
around lines 38 - 90, The duplicate Qt-versioned filter invalidation logic
(beginFilterChange()/endFilterChange(Direction::Rows)/invalidateFilter()) is
implemented in the local invalidateRows lambda and also repeated in
setAlwaysIncludeIndices and setAlwaysExcludeIndices; refactor by promoting
invalidateRows into a private member function (e.g., void invalidateRows()) that
encapsulates the versioned logic using beginFilterChange(),
endFilterChange(Direction::Rows) and invalidateFilter(), then replace the lambda
with a call to this new member and update setAlwaysIncludeIndices and
setAlwaysExcludeIndices to call the same member to remove the duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp`:
- Around line 38-90: The duplicate Qt-versioned filter invalidation logic
(beginFilterChange()/endFilterChange(Direction::Rows)/invalidateFilter()) is
implemented in the local invalidateRows lambda and also repeated in
setAlwaysIncludeIndices and setAlwaysExcludeIndices; refactor by promoting
invalidateRows into a private member function (e.g., void invalidateRows()) that
encapsulates the versioned logic using beginFilterChange(),
endFilterChange(Direction::Rows) and invalidateFilter(), then replace the lambda
with a call to this new member and update setAlwaysIncludeIndices and
setAlwaysExcludeIndices to call the same member to remove the duplication.

â„šī¸ Review info
âš™ī¸ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9c467f75-4df1-4267-b537-afd23a532aeb

đŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between cdce00f and e7cca3f.

📒 Files selected for processing (7)
  • src/framework/uicomponents/qml/Muse/UiComponents/CMakeLists.txt
  • src/framework/uicomponents/qml/Muse/UiComponents/sorter.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sorter.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.h

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

âš ī¸ Outside diff range comments (2)
src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp (2)

64-76: 🧹 Nitpick | đŸ”ĩ Trivial

Minor: invalidate() is called unconditionally on every sorter change, even no-op transitions.

onSortersChanged always calls invalidate() before dispatching sort(...). For the common case of toggling sortOrder on the already-active sorter, sort(0, newOrder) is sufficient on its own — the extra invalidate() causes an unnecessary full filter re-evaluation. Low impact in practice (filters are usually cheap), but worth considering if you ever hit large models with expensive Filter::acceptsRow implementations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp`
around lines 64 - 76, onSortersChanged currently always calls invalidate() which
forces a full re-filter even for no-op transitions (e.g., toggling the sortOrder
on the same Sorter); change the logic so invalidate() is only called when the
active Sorter itself changes (not when only its sortOrder toggles). Implement
this by tracking the previous active sorter pointer and its last sortOrder in
member variables (e.g., m_activeSorter, m_activeSortOrder), compare them inside
onSortersChanged (use currentSorter() and sorter->sortOrder()), and only call
invalidate() when the sorter pointer differs; always call sort(0, newOrder) when
sourceModel() exists to update ordering without invalidating when only the order
changed.

54-85: âš ī¸ Potential issue | 🟠 Major

Handle cleared() signal to disconnect stale filter/sorter listeners.

QmlListPropertyNotifier exposes a cleared() signal (lines 40–42 of qmllistproperty.h) that is emitted when the list is cleared via QmlListProperty<T>::clear(). This method is wired into the QQmlListProperty callbacks (line 62) and can be invoked from QML or C++.

Currently, only appended is connected in lines 54–85. When cleared() fires, the existing Filter::dataChanged and Sorter::dataChanged signal connections remain active. Removed-but-alive filter and sorter objects continue to trigger onFilterChanged and onSortersChanged, causing the proxy model to invalidate or re-sort based on stale items no longer in the active list.

Connect to the cleared() signal and disconnect all per-item listeners, or re-initialize the filter/sorter connections after a clear to prevent orphaned listeners from corrupting model state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp`
around lines 54 - 85, The appended-only handlers leave per-item listeners
attached when the list is cleared; connect m_filters.notifier()->cleared and
m_sorters.notifier()->cleared and in those lambdas iterate the removed items (or
iterate current m_filters/m_sorters before they are cleared) and call
QObject::disconnect(filter, &Filter::dataChanged, this, nullptr) and
QObject::disconnect(sorter, &Sorter::dataChanged, this, nullptr) respectively
(or otherwise remove stored QMetaObject::Connection handles) to ensure
Filter::dataChanged and Sorter::dataChanged handlers (the connections created in
the appended lambdas that call onFilterChanged/onSortersChanged) are
disconnected when the lists are cleared.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.cpp`:
- Around line 34-51: The call site in SorterValue::lessThan currently compares
role == -1 which re-encodes the file-static sentinel from
SortFilterProxyModel::roleFromRoleName; replace the magic -1 with the public
symbol (export INVALID_KEY from sortfilterproxymodel.h and use that constant
here) or update roleFromRoleName to return std::optional<int> and check
has_value() instead; also add a short assertion or comment before using
proxyModel.sourceModel() in SorterValue::lessThan to ensure the returned
QAbstractItemModel* is non-null (use the same function names:
SorterValue::lessThan, SortFilterProxyModel::roleFromRoleName, and the
INVALID_KEY symbol) to make the contract explicit and avoid silent breakage.

---

Outside diff comments:
In `@src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp`:
- Around line 64-76: onSortersChanged currently always calls invalidate() which
forces a full re-filter even for no-op transitions (e.g., toggling the sortOrder
on the same Sorter); change the logic so invalidate() is only called when the
active Sorter itself changes (not when only its sortOrder toggles). Implement
this by tracking the previous active sorter pointer and its last sortOrder in
member variables (e.g., m_activeSorter, m_activeSortOrder), compare them inside
onSortersChanged (use currentSorter() and sorter->sortOrder()), and only call
invalidate() when the sorter pointer differs; always call sort(0, newOrder) when
sourceModel() exists to update ordering without invalidating when only the order
changed.
- Around line 54-85: The appended-only handlers leave per-item listeners
attached when the list is cleared; connect m_filters.notifier()->cleared and
m_sorters.notifier()->cleared and in those lambdas iterate the removed items (or
iterate current m_filters/m_sorters before they are cleared) and call
QObject::disconnect(filter, &Filter::dataChanged, this, nullptr) and
QObject::disconnect(sorter, &Sorter::dataChanged, this, nullptr) respectively
(or otherwise remove stored QMetaObject::Connection handles) to ensure
Filter::dataChanged and Sorter::dataChanged handlers (the connections created in
the appended lambdas that call onFilterChanged/onSortersChanged) are
disconnected when the lists are cleared.
đŸĒ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„šī¸ Review info
âš™ī¸ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 39a0806a-15a5-417f-bae8-9fc075a019f8

đŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between e7cca3f and eee767c.

📒 Files selected for processing (8)
  • src/framework/uicomponents/qml/Muse/UiComponents/CMakeLists.txt
  • src/framework/uicomponents/qml/Muse/UiComponents/ValueList.qml
  • src/framework/uicomponents/qml/Muse/UiComponents/sorter.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sorter.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.h

Comment thread src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.cpp
juli27 added 5 commits April 21, 2026 21:56
- Remove unused private method `reset()`
- Remove unused invokable method `refresh()`
- Remove unused signal `filtersChanged`
  - it is never emitted
- Simplify collection iteration
This looks unnecessarily complicated to me. It also prevents multiple
filters to operate on the same role.
@juli27
Copy link
Copy Markdown
Contributor Author

juli27 commented Apr 21, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

✅ Actions performed

Reviews resumed.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

âš ī¸ Outside diff range comments (2)
src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp (2)

169-179: âš ī¸ Potential issue | 🟠 Major

Refresh the role cache when a chained proxy’s roles change.

m_roles is rebuilt only during setSourceModel(). When the source is another SortFilterProxyModel, lines 177-179 forward sourceModelRoleNamesChanged without rebuilding this cache, so roleFromRoleName() can return stale role IDs after the upstream proxy changes source roles.

Suggested fix
-    m_roles.clear();
-    for (const auto& [role, roleName] : roleNames().asKeyValueRange()) {
-        const auto[it, didInsert] = m_roles.try_emplace(roleName, role);
-        DO_ASSERT(didInsert);
-    }
+    const auto updateRoleCache = [this]() {
+        m_roles.clear();
+        for (const auto& [role, roleName] : roleNames().asKeyValueRange()) {
+            const auto[it, didInsert] = m_roles.try_emplace(roleName, role);
+            DO_ASSERT(didInsert);
+        }
+    };
+
+    updateRoleCache();
 
     emit sourceModelRoleNamesChanged();
 
     if (auto sourceSortFilterModel = qobject_cast<SortFilterProxyModel*>(sourceModel)) {
         m_subSourceModelConnection = connect(sourceSortFilterModel, &SortFilterProxyModel::sourceModelRoleNamesChanged,
-                                             this, &SortFilterProxyModel::sourceModelRoleNamesChanged);
+                                             this, [this, updateRoleCache]() {
+            updateRoleCache();
+            emit sourceModelRoleNamesChanged();
+        });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp`
around lines 169 - 179, When chaining another SortFilterProxyModel, the local
role cache m_roles is not refreshed when the upstream proxy emits
sourceModelRoleNamesChanged, leading roleFromRoleName() to return stale IDs;
update the handler connected in setSourceModel (the lambda or the slot bound to
SortFilterProxyModel::sourceModelRoleNamesChanged) to rebuild m_roles the same
way setSourceModel does: clear m_roles, repopulate it from
roleNames().asKeyValueRange() using try_emplace (preserving DO_ASSERT on
didInsert), then emit sourceModelRoleNamesChanged so downstream users see the
updated roles.

55-85: âš ī¸ Potential issue | 🟡 Minor

Handle cleared filter/sorter lists.

The append handlers update the proxy when items are added, but QmlListProperty::clear() also fires a clear notification. If QML clears filters or sorters, the proxy can keep stale filtering/sorting until some later change triggers invalidation.

Suggested fix
     connect(m_filters.notifier(), &QmlListPropertyNotifier::appended, this, [this, onFilterChanged](int index) {
         Filter* filter = m_filters.at(index);
 
         if (filter->enabled()) {
             onFilterChanged(filter);
         }
 
         connect(filter, &Filter::dataChanged, this, [onFilterChanged, filter] { onFilterChanged(filter); });
     });
+    connect(m_filters.notifier(), &QmlListPropertyNotifier::cleared, this, invalidateRows);
 
     auto onSortersChanged = [this] {
         Sorter* sorter = currentSorter();
         invalidate();
@@
         connect(sorter, &Sorter::dataChanged, this, onSortersChanged);
     });
+    connect(m_sorters.notifier(), &QmlListPropertyNotifier::cleared, this, onSortersChanged);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp`
around lines 55 - 85, The append-only handlers miss the
QmlListPropertyNotifier::cleared case, leaving stale state when QML calls
clear(); add connections to m_filters.notifier() and m_sorters.notifier() for
the cleared signal and in those cleared handlers call the same update logic as
on append: for filters simply invalidate() (so Filter-related filtering is
recomputed) and for sorters call the onSortersChanged logic (or replicate it:
invalidate(), then if no currentSorter() call sort(-1, Qt::AscendingOrder) else
if sourceModel() call sort(0, sorter->sortOrder())). Use the existing symbols
(m_filters.notifier(), m_sorters.notifier(), onFilterChanged, onSortersChanged,
invalidate(), currentSorter(), sort(), sourceModel()) so the cleared handlers
match the append behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/framework/ui/internal/uicontextconfiguration.cpp`:
- Around line 25-30: The added/ reordered includes (QApplication, QScreen) and
the new Windows-guarded include QOperatingSystemVersion (with Q_OS_WIN) in
uicontextconfiguration.cpp are unrelated to the SortFilterProxyModel refactor;
either remove/revert these include edits from this branch and restore the
original includes, or extract them into a separate cleanup PR; if the
QOperatingSystemVersion addition is required for the fuzzy-search change, add a
short justification to this PR description explaining why that transitive
include is necessary.

---

Outside diff comments:
In `@src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp`:
- Around line 169-179: When chaining another SortFilterProxyModel, the local
role cache m_roles is not refreshed when the upstream proxy emits
sourceModelRoleNamesChanged, leading roleFromRoleName() to return stale IDs;
update the handler connected in setSourceModel (the lambda or the slot bound to
SortFilterProxyModel::sourceModelRoleNamesChanged) to rebuild m_roles the same
way setSourceModel does: clear m_roles, repopulate it from
roleNames().asKeyValueRange() using try_emplace (preserving DO_ASSERT on
didInsert), then emit sourceModelRoleNamesChanged so downstream users see the
updated roles.
- Around line 55-85: The append-only handlers miss the
QmlListPropertyNotifier::cleared case, leaving stale state when QML calls
clear(); add connections to m_filters.notifier() and m_sorters.notifier() for
the cleared signal and in those cleared handlers call the same update logic as
on append: for filters simply invalidate() (so Filter-related filtering is
recomputed) and for sorters call the onSortersChanged logic (or replicate it:
invalidate(), then if no currentSorter() call sort(-1, Qt::AscendingOrder) else
if sourceModel() call sort(0, sorter->sortOrder())). Use the existing symbols
(m_filters.notifier(), m_sorters.notifier(), onFilterChanged, onSortersChanged,
invalidate(), currentSorter(), sort(), sourceModel()) so the cleared handlers
match the append behavior.
đŸĒ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„šī¸ Review info
âš™ī¸ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9ddcbba5-dfea-4ec8-8176-f6b402bb890c

đŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between e7cca3f and 6b5f4d3.

📒 Files selected for processing (16)
  • src/framework/ui/internal/uiconfiguration.cpp
  • src/framework/ui/internal/uicontextconfiguration.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/CMakeLists.txt
  • src/framework/uicomponents/qml/Muse/UiComponents/ValueList.qml
  • src/framework/uicomponents/qml/Muse/UiComponents/filter.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/filter.h
  • src/framework/uicomponents/qml/Muse/UiComponents/filtervalue.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/filtervalue.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sorter.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sorter.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sortervalue.h
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.h
  • src/framework/uicomponents/qml/Muse/UiComponents/tests/CMakeLists.txt
  • src/framework/uicomponents/qml/Muse/UiComponents/tests/sortfilterproxymodel_tests.cpp
💤 Files with no reviewable changes (1)
  • src/framework/ui/internal/uiconfiguration.cpp

Comment thread src/framework/ui/internal/uicontextconfiguration.cpp
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

Only repository collaborators, contributors, or members can run CodeRabbit commands.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

âš ī¸ Outside diff range comments (1)
src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp (1)

65-76: âš ī¸ Potential issue | 🟠 Major

Call sort(0, ...) even before the source model is attached to preserve sorter state.

When an enabled sorter is appended before setSourceModel() is called, line 74's guard skips the sort(0, sorter->sortOrder()) call. Later, when the source model is assigned, setSourceModel() only updates role mappings and invalidates—it does not restore sorting. Since QSortFilterProxyModel::sort() records the sort column/order internally even with a null sourceModel, removing the guard allows that state to be captured and reapplied when invalidate() is triggered by sourceModelRoleNamesChanged.

Proposed fix
         if (!sorter) {
             sort(-1, Qt::AscendingOrder);
             return;
         }
 
-        if (sourceModel()) {
-            sort(0, sorter->sortOrder());
-        }
+        sort(0, sorter->sortOrder());
     };

Add a test case that enables a SorterValue before setSourceModel() and verifies the model is sorted correctly once the source is assigned.

Also applies to: 161–171

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp`
around lines 65 - 76, The onSortersChanged lambda currently skips calling
sort(0, sorter->sortOrder()) when sourceModel() is null, so enabled sorter state
set before setSourceModel() is lost; remove the guard in onSortersChanged (the
lambda using currentSorter(), invalidate(), and sort()) so that sort(0,
sorter->sortOrder()) is called unconditionally when a sorter exists, ensuring
QSortFilterProxyModel records the sort column/order even with a null source
model; after this change add a unit test that enables a SorterValue before
calling setSourceModel() and then assigns the source model to verify sorting is
applied once sourceModelRoleNamesChanged triggers invalidate().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp`:
- Around line 65-76: The onSortersChanged lambda currently skips calling sort(0,
sorter->sortOrder()) when sourceModel() is null, so enabled sorter state set
before setSourceModel() is lost; remove the guard in onSortersChanged (the
lambda using currentSorter(), invalidate(), and sort()) so that sort(0,
sorter->sortOrder()) is called unconditionally when a sorter exists, ensuring
QSortFilterProxyModel records the sort column/order even with a null source
model; after this change add a unit test that enables a SorterValue before
calling setSourceModel() and then assigns the source model to verify sorting is
applied once sourceModelRoleNamesChanged triggers invalidate().

â„šī¸ Review info
âš™ī¸ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: de1e9c3e-247a-4b63-ad1d-51c8f508ec1b

đŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 6b5f4d3 and 7e0a67e.

📒 Files selected for processing (4)
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.cpp
  • src/framework/uicomponents/qml/Muse/UiComponents/sortfilterproxymodel.h
  • src/framework/uicomponents/qml/Muse/UiComponents/tests/CMakeLists.txt
  • src/framework/uicomponents/qml/Muse/UiComponents/tests/sortfilterproxymodel_tests.cpp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants