From 58a53e70d1c7ffe9eaccbfb37fd0bce82a201c6b Mon Sep 17 00:00:00 2001
From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
Date: Mon, 4 Dec 2023 13:51:27 -0500
Subject: [PATCH] smartlist: prevent selection oscillation

The dataChanged signal should be queued to prevent the conversation selection callback from re-entering SelectableListProxyModel::updateSelection prior to sort/filter updates. This can cause desync of the source and filter indices.

Change-Id: I35b3b38512cf51b1dd7ca8110b143a9223740056
---
 src/app/conversationlistmodel.cpp    |  2 +-
 src/app/selectablelistproxymodel.cpp | 42 ++++++++++++----------------
 2 files changed, 19 insertions(+), 25 deletions(-)

diff --git a/src/app/conversationlistmodel.cpp b/src/app/conversationlistmodel.cpp
index 5b8135349..462703b82 100644
--- a/src/app/conversationlistmodel.cpp
+++ b/src/app/conversationlistmodel.cpp
@@ -58,7 +58,7 @@ ConversationListModel::ConversationListModel(LRCInstance* instance, QObject* par
     connect(model_, &ConversationModel::dataChanged, this, [this](int position) {
         const auto index = createIndex(position, 0);
         Q_EMIT ConversationListModel::dataChanged(index, index);
-    });
+    }, Qt::QueuedConnection);
 }
 
 int
diff --git a/src/app/selectablelistproxymodel.cpp b/src/app/selectablelistproxymodel.cpp
index 51410fdb0..0106d30e5 100644
--- a/src/app/selectablelistproxymodel.cpp
+++ b/src/app/selectablelistproxymodel.cpp
@@ -33,26 +33,15 @@ SelectableListProxyModel::bindSourceModel(QAbstractListModel* model)
     if (!model) {
         return;
     }
-    connect(sourceModel(),
-            &QAbstractListModel::dataChanged,
-            this,
-            &SelectableListProxyModel::onModelUpdated,
-            Qt::UniqueConnection);
-    connect(model,
-            &QAbstractListModel::rowsInserted,
-            this,
-            &SelectableListProxyModel::onModelUpdated,
-            Qt::UniqueConnection);
-    connect(model,
-            &QAbstractListModel::rowsRemoved,
-            this,
-            &SelectableListProxyModel::onModelTrimmed,
-            Qt::UniqueConnection);
-    connect(sourceModel(),
-            &QAbstractListModel::modelReset,
-            this,
-            &SelectableListProxyModel::deselect,
-            Qt::UniqueConnection);
+
+    auto connectModelSignal = [this, model](auto signal, auto slot) {
+        connect(model, signal, this, slot, Qt::UniqueConnection);
+    };
+
+    connectModelSignal(&QAbstractListModel::dataChanged, &SelectableListProxyModel::onModelUpdated);
+    connectModelSignal(&QAbstractListModel::rowsInserted, &SelectableListProxyModel::onModelUpdated);
+    connectModelSignal(&QAbstractListModel::rowsRemoved, &SelectableListProxyModel::onModelTrimmed);
+    connectModelSignal(&QAbstractListModel::modelReset, &SelectableListProxyModel::deselect);
 }
 
 void
@@ -120,11 +109,14 @@ SelectableListProxyModel::updateSelection(bool rowsRemoved)
 
     // if the source and filtered index is no longer valid
     // this would indicate that a mutation has occured,
-    // thus any arbritrary ux decision is okay here
+    // In this case, we need to update the selection to one
+    // row above the current one.
     if (!selectedSourceIndex_.isValid()) {
-        auto row = qMax(--currentFilteredRow_, 0);
+        auto row = qMax(currentFilteredRow_ - 1, 0);
         selectedSourceIndex_ = mapToSource(index(row, 0));
         filteredIndex = mapFromSource(selectedSourceIndex_);
+        // filteredIndex is not necessarily valid here, so we emit
+        // forcefully to ensure that the selection is updated
         currentFilteredRow_ = filteredIndex.row();
         Q_EMIT currentFilteredRowChanged();
         Q_EMIT validSelectionChanged();
@@ -139,9 +131,11 @@ SelectableListProxyModel::updateSelection(bool rowsRemoved)
     // want to force reselection of other ui components, as the
     // source index is still valid, in that case, or if the
     // row hasn't changed, don't notify
-    if (filteredIndex.isValid() && lastFilteredRow != currentFilteredRow_) {
-        Q_EMIT validSelectionChanged();
+    if (!filteredIndex.isValid() || lastFilteredRow == currentFilteredRow_) {
+        return;
     }
+
+    Q_EMIT validSelectionChanged();
 }
 
 void
-- 
GitLab