From fec31f8da20de04fdaa241000abf1cca3b3e01c5 Mon Sep 17 00:00:00 2001
From: Nyall Dawson <nyall.dawson@gmail.com>
Date: Wed, 13 Dec 2017 15:06:12 +1000
Subject: [PATCH] Sort browser items

Implements a sort key for browser items, allowing them to be
correctly sorted.

Fixes #17591
---
 python/core/qgsbrowsermodel.sip    |  1 +
 python/core/qgsdataitem.sip        | 25 +++++++++++++++++++++++++
 src/core/qgsbrowsermodel.cpp       |  9 ++++++++-
 src/core/qgsbrowsermodel.h         |  1 +
 src/core/qgsdataitem.cpp           | 19 +++++++++++++++++++
 src/core/qgsdataitem.h             | 25 +++++++++++++++++++++++++
 src/gui/qgsbrowserdockwidget.cpp   |  2 ++
 src/gui/qgsbrowserdockwidget_p.cpp |  3 +++
 8 files changed, 84 insertions(+), 1 deletion(-)

diff --git a/python/core/qgsbrowsermodel.sip b/python/core/qgsbrowsermodel.sip
index 7ba4f01f17f..e1654396c7d 100644
--- a/python/core/qgsbrowsermodel.sip
+++ b/python/core/qgsbrowsermodel.sip
@@ -23,6 +23,7 @@ class QgsBrowserModel : QAbstractItemModel
     {
       PathRole,
       CommentRole,
+      SortRole,
     };
 
     virtual Qt::ItemFlags flags( const QModelIndex &index ) const;
diff --git a/python/core/qgsdataitem.sip b/python/core/qgsdataitem.sip
index 0cf12294cfb..20a80203469 100644
--- a/python/core/qgsdataitem.sip
+++ b/python/core/qgsdataitem.sip
@@ -271,6 +271,27 @@ Create path component replacing path separators
  :rtype: str
 %End
 
+    virtual QVariant sortKey() const;
+%Docstring
+ Returns the sorting key for the item. By default name() is returned,
+ but setSortKey() can be used to set a custom sort key for the item.
+
+ Alternatively subclasses can override this method to return a custom
+ sort key.
+
+.. seealso:: :py:func:`setSortKey()`
+.. versionadded:: 3.0
+ :rtype: QVariant
+%End
+
+    void setSortKey( const QVariant &key );
+%Docstring
+ Sets a custom sorting ``key`` for the item.
+.. seealso:: :py:func:`sortKey()`
+.. versionadded:: 3.0
+%End
+
+
     void setIcon( const QIcon &icon );
     void setIconName( const QString &iconName );
 
@@ -307,6 +328,7 @@ Move object and all its descendants to thread
 %End
 
 
+
   public slots:
 
     virtual void deleteLater();
@@ -669,6 +691,9 @@ Icon for favorites group
  :rtype: QIcon
 %End
 
+    virtual QVariant sortKey() const;
+
+
 };
 
 class QgsZipItem : QgsDataCollectionItem
diff --git a/src/core/qgsbrowsermodel.cpp b/src/core/qgsbrowsermodel.cpp
index 7e6842b66d8..984903d6bdc 100644
--- a/src/core/qgsbrowsermodel.cpp
+++ b/src/core/qgsbrowsermodel.cpp
@@ -72,6 +72,7 @@ void QgsBrowserModel::updateProjectHome()
   mProjectHome = home.isNull() ? nullptr : new QgsDirectoryItem( nullptr, tr( "Project home" ), home, "project:" + home );
   if ( mProjectHome )
   {
+    mProjectHome->setSortKey( QStringLiteral( " 1" ) );
     connectItem( mProjectHome );
 
     beginInsertRows( QModelIndex(), 0, 0 );
@@ -84,8 +85,9 @@ void QgsBrowserModel::addRootItems()
 {
   updateProjectHome();
 
-  // give the home directory a prominent second place
+  // give the home directory a prominent third place
   QgsDirectoryItem *item = new QgsDirectoryItem( nullptr, tr( "Home" ), QDir::homePath(), "home:" + QDir::homePath() );
+  item->setSortKey( QStringLiteral( " 2" ) );
   QStyle *style = QApplication::style();
   QIcon homeIcon( style->standardPixmap( QStyle::SP_DirHomeIcon ) );
   item->setIcon( homeIcon );
@@ -109,6 +111,7 @@ void QgsBrowserModel::addRootItems()
       continue;
 
     QgsDirectoryItem *item = new QgsDirectoryItem( nullptr, path, path );
+    item->setSortKey( QStringLiteral( " 3 %1" ).arg( path ) );
 
     connectItem( item );
     mRootItems << item;
@@ -212,6 +215,10 @@ QVariant QgsBrowserModel::data( const QModelIndex &index, int role ) const
   {
     return item->name();
   }
+  else if ( role == QgsBrowserModel::SortRole )
+  {
+    return item->sortKey();
+  }
   else if ( role == Qt::ToolTipRole )
   {
     return item->toolTip();
diff --git a/src/core/qgsbrowsermodel.h b/src/core/qgsbrowsermodel.h
index ddec807a024..db790d47215 100644
--- a/src/core/qgsbrowsermodel.h
+++ b/src/core/qgsbrowsermodel.h
@@ -64,6 +64,7 @@ class CORE_EXPORT QgsBrowserModel : public QAbstractItemModel
     {
       PathRole = Qt::UserRole, //!< Item path used to access path in the tree, see QgsDataItem::mPath
       CommentRole = Qt::UserRole + 1, //!< Item comment
+      SortRole, //!< Custom sort role, see QgsDataItem::sortKey()
     };
     // implemented methods from QAbstractItemModel for read-only access
 
diff --git a/src/core/qgsdataitem.cpp b/src/core/qgsdataitem.cpp
index 584d7c014d7..5d80da75304 100644
--- a/src/core/qgsdataitem.cpp
+++ b/src/core/qgsdataitem.cpp
@@ -102,6 +102,11 @@ QIcon QgsFavoritesItem::iconFavorites()
   return QgsApplication::getThemeIcon( QStringLiteral( "/mIconFavourites.png" ) );
 }
 
+QVariant QgsFavoritesItem::sortKey() const
+{
+  return QStringLiteral( " 0" );
+}
+
 QIcon QgsZipItem::iconZip()
 {
   return QgsApplication::getThemeIcon( QStringLiteral( "/mIconZip.png" ) );
@@ -150,6 +155,16 @@ QString QgsDataItem::pathComponent( const QString &string )
   return QString( string ).replace( QRegExp( "[\\\\/]" ), QStringLiteral( "|" ) );
 }
 
+QVariant QgsDataItem::sortKey() const
+{
+  return mSortKey.isValid() ? mSortKey : name();
+}
+
+void QgsDataItem::setSortKey( const QVariant &key )
+{
+  mSortKey = key;
+}
+
 void QgsDataItem::deleteLater()
 {
   QgsDebugMsgLevel( "path = " + path(), 3 );
@@ -753,6 +768,10 @@ QVector<QgsDataItem *> QgsDirectoryItem::createChildren()
       continue;
 
     QgsDirectoryItem *item = new QgsDirectoryItem( this, subdir, subdirPath, path );
+
+    // we want directories shown before files
+    item->setSortKey( QStringLiteral( "  %1" ).arg( subdir ) );
+
     // propagate signals up to top
 
     children.append( item );
diff --git a/src/core/qgsdataitem.h b/src/core/qgsdataitem.h
index 0e9405d1467..bf4796e9eee 100644
--- a/src/core/qgsdataitem.h
+++ b/src/core/qgsdataitem.h
@@ -255,6 +255,26 @@ class CORE_EXPORT QgsDataItem : public QObject
     //! Create path component replacing path separators
     static QString pathComponent( const QString &component );
 
+    /**
+     * Returns the sorting key for the item. By default name() is returned,
+     * but setSortKey() can be used to set a custom sort key for the item.
+     *
+     * Alternatively subclasses can override this method to return a custom
+     * sort key.
+     *
+     * \see setSortKey()
+     * \since QGIS 3.0
+     */
+    virtual QVariant sortKey() const;
+
+    /**
+     * Sets a custom sorting \a key for the item.
+     * \see sortKey()
+     * \since QGIS 3.0
+     */
+    void setSortKey( const QVariant &key );
+
+
     // Because QIcon (QPixmap) must not be used in outside the GUI thread, it is
     // not possible to set mIcon in constructor. Either use mIconName/setIconName()
     // or implement icon().
@@ -303,6 +323,9 @@ class CORE_EXPORT QgsDataItem : public QObject
     QIcon mIcon;
     QMap<QString, QIcon> mIconMap;
 
+    //! Custom sort key. If invalid, name() will be used for sorting instead.
+    QVariant mSortKey;
+
   public slots:
 
     /**
@@ -631,6 +654,8 @@ class CORE_EXPORT QgsFavoritesItem : public QgsDataCollectionItem
     //! Icon for favorites group
     static QIcon iconFavorites();
 
+    QVariant sortKey() const override;
+
   private:
     QVector<QgsDataItem *> createChildren( const QString &favDir );
 };
diff --git a/src/gui/qgsbrowserdockwidget.cpp b/src/gui/qgsbrowserdockwidget.cpp
index 39e9bebc6ae..9b5dd54f371 100644
--- a/src/gui/qgsbrowserdockwidget.cpp
+++ b/src/gui/qgsbrowserdockwidget.cpp
@@ -122,6 +122,8 @@ void QgsBrowserDockWidget::showEvent( QShowEvent *e )
     mBrowserView->setSettingsSection( objectName().toLower() ); // to distinguish 2 or more instances of the browser
     mBrowserView->setBrowserModel( mModel );
     mBrowserView->setModel( mProxyModel );
+    mBrowserView->setSortingEnabled( true );
+    mBrowserView->sortByColumn( 0, Qt::AscendingOrder );
     // provide a horizontal scroll bar instead of using ellipse (...) for longer items
     mBrowserView->setTextElideMode( Qt::ElideNone );
     mBrowserView->header()->setSectionResizeMode( 0, QHeaderView::ResizeToContents );
diff --git a/src/gui/qgsbrowserdockwidget_p.cpp b/src/gui/qgsbrowserdockwidget_p.cpp
index 454b3e9d4a9..625e590e60a 100644
--- a/src/gui/qgsbrowserdockwidget_p.cpp
+++ b/src/gui/qgsbrowserdockwidget_p.cpp
@@ -339,6 +339,9 @@ QgsBrowserTreeFilterProxyModel::QgsBrowserTreeFilterProxyModel( QObject *parent
   , mCaseSensitivity( Qt::CaseInsensitive )
 {
   setDynamicSortFilter( true );
+  setSortRole( QgsBrowserModel::SortRole );
+  setSortCaseSensitivity( Qt::CaseInsensitive );
+  sort( 0 );
 }
 
 void QgsBrowserTreeFilterProxyModel::setBrowserModel( QgsBrowserModel *model )