vcl/inc/qt5/QtInstanceTreeView.hxx |   11 +++++-
 vcl/qt5/QtBuilder.cxx              |   10 +++--
 vcl/qt5/QtInstanceTreeView.cxx     |   65 ++++++++++++++++++++++++-------------
 3 files changed, 60 insertions(+), 26 deletions(-)

New commits:
commit 3418a3648fe05169e96532039d01f20e959bacb6
Author:     Michael Weghorn <m.wegh...@posteo.de>
AuthorDate: Tue Apr 22 13:11:51 2025 +0200
Commit:     Michael Weghorn <m.wegh...@posteo.de>
CommitDate: Tue Apr 22 15:16:31 2025 +0200

    tdf#130857 qt weld: Actually implement tree view sorting
    
    Just calling QTreeView::setSortingEnabled
    in QtInstanceTreeView::make_sorted isn't sufficient
    for the tree view entires to be and remain sorted.
    
    It also requires that the tree view uses a model
    that supports sorting. While QStandardItemModel
    has a QStandardItemModel::sort method that can
    be used to sort the model, it doesn't ensure the
    entries remain sorted when adding new ones, i.e.
    it would have to be called every time an entry is
    inserted.
    
    Instead, switch to using QSortFilterProxyModel
    for the QTreeView's model to "wrap" the
    QStandardItemModel, which ensures that items
    remain sorted in the tree view once sorting has
    been enabled.
    
    In addition, QSortFilterProxyModel::sort needs
    to be called once so that the model knows what
    column to sort by. Quoting the
    QSortFilterProxyModel::sortColumn doc [1]
    
    > Returns the column currently used for sorting
    >
    > This returns the most recently used sort column. The default
    > value is -1, which means that this proxy model does not sort.
    >
    > See also sort().
    
    Use the first "normal" column (i.e. not the
    special toggle button column, if that one exists).
    A different sort column could explicitly be set by calling
    weld::TreeView::set_sort_column.
    
    The drawback of switching to QSortFilterProxyModel instead
    of using QStandardItemModel directly is that QSortFilterProxyModel
    only derives from QAbstractItemModel, so some
    QStandardItemModel/QStandardItem convenience methods cannot
    be used directly any more.
    
    Because of that, introduce a QtInstanceTreeView::itemFromIndex
    helper method instead of calling QStandardItemModel::itemFromIndex
    directly.
    In QtInstanceTreeView::swap, convert between the
    proxy and source model row indices and work with the
    underlying QStandardItemModel directly in order to
    be able to continue using QStandardItemModel::takeRow
    and QStandardItemModel::insertRow.
    Similar in QtInstanceTreeView::find_text, where
    QStandardItemModel::findItems is used.
    
    This commit e.g. causes the entries in the "Document" tab in
    Writer's "Insert" -> "Field" -> "More Fields..." dialog to
    be sorted as expected with SAL_VCL_QT_USE_WELDED_WIDGETS=1
    in a WIP branch where support for native Qt widgets for that
    dialog is declared while they were unsorted previously despite
    the SwFieldDokPage ctor calling weld::TreeView::make_sorted.
    
    [1] https://doc.qt.io/qt-6/qsortfilterproxymodel.html#sortColumn
    
    Change-Id: I5849f66caf78bc23059c8b52d45742f444578452
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/184434
    Tested-by: Jenkins
    Reviewed-by: Michael Weghorn <m.wegh...@posteo.de>

diff --git a/vcl/inc/qt5/QtInstanceTreeView.hxx 
b/vcl/inc/qt5/QtInstanceTreeView.hxx
index 86c64a606d10..cbf83c755969 100644
--- a/vcl/inc/qt5/QtInstanceTreeView.hxx
+++ b/vcl/inc/qt5/QtInstanceTreeView.hxx
@@ -11,6 +11,7 @@
 
 #include "QtInstanceWidget.hxx"
 
+#include <QtCore/QSortFilterProxyModel>
 #include <QtGui/QStandardItemModel>
 #include <QtWidgets/QTreeView>
 
@@ -19,7 +20,14 @@ class QtInstanceTreeView : public QtInstanceWidget, public 
virtual weld::TreeVie
     Q_OBJECT
 
     QTreeView* m_pTreeView;
-    QStandardItemModel* m_pModel;
+
+    /** The model displayed in the tree view. The proxy model takes care of 
sorting
+     *  if sorting is enabled. */
+    QSortFilterProxyModel* m_pModel;
+
+    /** The QStandardItemModel used as the source model for `m_pModel`. */
+    QStandardItemModel* m_pSourceModel;
+
     QItemSelectionModel* m_pSelectionModel;
 
     bool m_bExtraToggleButtonColumnEnabled = false;
@@ -195,6 +203,7 @@ private:
     QModelIndex modelIndex(int nRow, int nCol = 0) const;
     QModelIndex modelIndex(const weld::TreeIter& rIter, int nCol = 0) const;
     static int rowIndex(const weld::TreeIter& rIter);
+    QStandardItem* itemFromIndex(const QModelIndex& rIndex) const;
     QModelIndex toggleButtonModelIndex(int nRow) const;
     QModelIndex firstTextColumnModelIndex(int nRow) const;
     static QAbstractItemView::SelectionMode mapSelectionMode(SelectionMode 
eMode);
diff --git a/vcl/qt5/QtBuilder.cxx b/vcl/qt5/QtBuilder.cxx
index 564436dd307e..b96c12263cd5 100644
--- a/vcl/qt5/QtBuilder.cxx
+++ b/vcl/qt5/QtBuilder.cxx
@@ -20,6 +20,7 @@
 #include <rtl/ustrbuf.hxx>
 #include <vcl/qt/QtUtils.hxx>
 
+#include <QtCore/QSortFilterProxyModel>
 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
 #include <QtGui/QActionGroup>
 #endif
@@ -365,7 +366,10 @@ QObject* QtBuilder::makeObject(QObject* pParent, 
std::u16string_view sName, std:
     else if (sName == u"GtkTreeView")
     {
         QTreeView* pTreeView = new QTreeView(pParentWidget);
-        pTreeView->setModel(new QStandardItemModel(pTreeView));
+        QStandardItemModel* pItemModel = new QStandardItemModel(pTreeView);
+        QSortFilterProxyModel* pProxyModel = new 
QSortFilterProxyModel(pTreeView);
+        pProxyModel->setSourceModel(pItemModel);
+        pTreeView->setModel(pProxyModel);
         pTreeView->setHeaderHidden(!extractHeadersVisible(rMap));
         pTreeView->setRootIsDecorated(extractShowExpanders(rMap));
         pObject = pTreeView;
@@ -374,8 +378,8 @@ QObject* QtBuilder::makeObject(QObject* pParent, 
std::u16string_view sName, std:
     {
         QTreeView* pTreeView = qobject_cast<QTreeView*>(pParentWidget);
         assert(pTreeView && "Tree view column doesn't have a tree view 
parent");
-        QStandardItemModel* pModel = 
qobject_cast<QStandardItemModel*>(pTreeView->model());
-        assert(pModel && "Tree view doesn't have QStandardItemModel set");
+        QAbstractItemModel* pModel = pTreeView->model();
+        assert(pModel && "Tree view doesn't have model set");
         const int nCol = pModel->columnCount();
         pModel->insertColumn(nCol);
         pModel->setHeaderData(nCol, Qt::Horizontal, 
toQString(extractTitle(rMap)));
diff --git a/vcl/qt5/QtInstanceTreeView.cxx b/vcl/qt5/QtInstanceTreeView.cxx
index 83cf9869084b..d44726b5a657 100644
--- a/vcl/qt5/QtInstanceTreeView.cxx
+++ b/vcl/qt5/QtInstanceTreeView.cxx
@@ -25,8 +25,11 @@ QtInstanceTreeView::QtInstanceTreeView(QTreeView* pTreeView)
 {
     assert(m_pTreeView);
 
-    m_pModel = qobject_cast<QStandardItemModel*>(m_pTreeView->model());
-    assert(m_pModel && "tree view doesn't have expected item model set");
+    m_pModel = qobject_cast<QSortFilterProxyModel*>(m_pTreeView->model());
+    assert(m_pModel && "tree view doesn't have expected QSortFilterProxyModel 
set");
+
+    m_pSourceModel = 
qobject_cast<QStandardItemModel*>(m_pModel->sourceModel());
+    assert(m_pSourceModel && "proxy model doesn't have expected source model");
 
     m_pSelectionModel = m_pTreeView->selectionModel();
     assert(m_pSelectionModel);
@@ -57,7 +60,7 @@ void QtInstanceTreeView::insert(const weld::TreeIter* 
pParent, int nPos, const O
         m_pModel->insertRow(nPos);
 
         const QModelIndex aIndex = modelIndex(nPos);
-        QStandardItem* pItem = m_pModel->itemFromIndex(aIndex);
+        QStandardItem* pItem = itemFromIndex(aIndex);
         if (pStr)
             pItem->setText(toQString(*pStr));
         if (pId)
@@ -69,7 +72,7 @@ void QtInstanceTreeView::insert(const weld::TreeIter* 
pParent, int nPos, const O
             pItem->setIcon(toQPixmap(*pImageSurface));
 
         if (m_bExtraToggleButtonColumnEnabled)
-            
m_pModel->itemFromIndex(toggleButtonModelIndex(nPos))->setCheckable(true);
+            itemFromIndex(toggleButtonModelIndex(nPos))->setCheckable(true);
 
         if (pRet)
             static_cast<QtInstanceTreeIter*>(pRet)->setModelIndex(aIndex);
@@ -91,7 +94,7 @@ OUString QtInstanceTreeView::get_selected_text() const
         if (aSelectedIndexes.empty())
             return;
 
-        sText = 
toOUString(m_pModel->itemFromIndex(aSelectedIndexes.first())->text());
+        sText = toOUString(itemFromIndex(aSelectedIndexes.first())->text());
     });
 
     return sText;
@@ -208,7 +211,7 @@ void QtInstanceTreeView::set_sensitive(int nRow, bool 
bSensitive, int nCol)
             return;
         }
 
-        QStandardItem* pItem = m_pModel->item(nRow, nCol);
+        QStandardItem* pItem = itemFromIndex(modelIndex(nRow, nCol));
         if (pItem)
         {
             if (bSensitive)
@@ -225,7 +228,7 @@ bool QtInstanceTreeView::get_sensitive(int nRow, int nCol) 
const
 
     bool bSensitive = false;
     GetQtInstance().RunInMainThread([&] {
-        QStandardItem* pItem = m_pModel->item(nRow, nCol);
+        QStandardItem* pItem = itemFromIndex(modelIndex(nRow, nCol));
         if (pItem)
             bSensitive = pItem->flags() & Qt::ItemIsEnabled;
     });
@@ -249,7 +252,7 @@ void QtInstanceTreeView::set_toggle(int nRow, TriState 
eState, int nCol)
 
     GetQtInstance().RunInMainThread([&] {
         QModelIndex aIndex = nCol == -1 ? toggleButtonModelIndex(nRow) : 
modelIndex(nRow, nCol);
-        m_pModel->itemFromIndex(aIndex)->setCheckState(toQtCheckState(eState));
+        itemFromIndex(aIndex)->setCheckState(toQtCheckState(eState));
     });
 }
 
@@ -260,7 +263,7 @@ TriState QtInstanceTreeView::get_toggle(int nRow, int nCol) 
const
     TriState eState = TRISTATE_INDET;
     GetQtInstance().RunInMainThread([&] {
         QModelIndex aIndex = nCol == -1 ? toggleButtonModelIndex(nRow) : 
modelIndex(nRow, nCol);
-        eState = toVclTriState(m_pModel->itemFromIndex(aIndex)->checkState());
+        eState = toVclTriState(itemFromIndex(aIndex)->checkState());
     });
 
     return eState;
@@ -333,18 +336,21 @@ void QtInstanceTreeView::swap(int nPos1, int nPos2)
         const bool bPos1Selected = m_pSelectionModel->isRowSelected(nPos1);
         const bool bPos2Selected = m_pSelectionModel->isRowSelected(nPos2);
 
-        const int nMin = std::min(nPos1, nPos2);
-        const int nMax = std::max(nPos1, nPos2);
-        QList<QStandardItem*> aMaxRow = m_pModel->takeRow(nMax);
-        QList<QStandardItem*> aMinRow = m_pModel->takeRow(nMin);
-        m_pModel->insertRow(nMin, aMaxRow);
-        m_pModel->insertRow(nMax, aMinRow);
+        const int nSourceModelPos1 = 
m_pModel->mapToSource(modelIndex(nPos1)).row();
+        const int nSourceModelPos2 = 
m_pModel->mapToSource(modelIndex(nPos2)).row();
+
+        const int nMin = std::min(nSourceModelPos1, nSourceModelPos2);
+        const int nMax = std::max(nSourceModelPos1, nSourceModelPos2);
+        QList<QStandardItem*> aMaxRow = m_pSourceModel->takeRow(nMax);
+        QList<QStandardItem*> aMinRow = m_pSourceModel->takeRow(nMin);
+        m_pSourceModel->insertRow(nMin, aMaxRow);
+        m_pSourceModel->insertRow(nMax, aMinRow);
 
         // restore selection
         if (bPos1Selected)
-            select(nPos2);
+            
select(m_pModel->mapFromSource(m_pSourceModel->index(nSourceModelPos2, 
0)).row());
         if (bPos2Selected)
-            select(nPos1);
+            
select(m_pModel->mapFromSource(m_pSourceModel->index(nSourceModelPos1, 
0)).row());
     });
 }
 
@@ -413,9 +419,10 @@ int QtInstanceTreeView::find_text(const OUString& rText) 
const
 
     int nIndex = -1;
     GetQtInstance().RunInMainThread([&] {
-        const QList<QStandardItem*> aItems = 
m_pModel->findItems(toQString(rText));
+        // search in underlying QStandardItemModel and map index
+        const QList<QStandardItem*> aItems = 
m_pSourceModel->findItems(toQString(rText));
         if (!aItems.empty())
-            nIndex = aItems.at(0)->index().row();
+            nIndex = m_pModel->mapFromSource(aItems.at(0)->index()).row();
     });
 
     return nIndex;
@@ -766,14 +773,22 @@ int QtInstanceTreeView::n_children() const
     SolarMutexGuard g;
 
     int nChildCount;
-    GetQtInstance().RunInMainThread(
-        [&] { nChildCount = 
m_pModel->rowCount(m_pModel->invisibleRootItem()->index()); });
+    GetQtInstance().RunInMainThread([&] {
+        const QModelIndex aRootIndex
+            = 
m_pModel->mapFromSource(m_pSourceModel->invisibleRootItem()->index());
+        nChildCount = m_pModel->rowCount(aRootIndex);
+    });
     return nChildCount;
 }
 
 void QtInstanceTreeView::make_sorted()
 {
-    GetQtInstance().RunInMainThread([&] { 
m_pTreeView->setSortingEnabled(true); });
+    GetQtInstance().RunInMainThread([&] {
+        m_pTreeView->setSortingEnabled(true);
+        // sort by first "normal" column
+        const int nSortColumn = m_bExtraToggleButtonColumnEnabled ? 1 : 0;
+        m_pModel->sort(nSortColumn);
+    });
 }
 
 void QtInstanceTreeView::make_unsorted()
@@ -961,6 +976,12 @@ int QtInstanceTreeView::rowIndex(const weld::TreeIter& 
rIter)
     return aModelIndex.row();
 }
 
+QStandardItem* QtInstanceTreeView::itemFromIndex(const QModelIndex& rIndex) 
const
+{
+    const QModelIndex aSourceIndex = m_pModel->mapToSource(rIndex);
+    return m_pSourceModel->itemFromIndex(aSourceIndex);
+}
+
 QModelIndex QtInstanceTreeView::toggleButtonModelIndex(int nRow) const
 {
     assert(m_bExtraToggleButtonColumnEnabled && "Special toggle button column 
is not enabled");

Reply via email to