1 /***************************************************************************
2     qgsbrowsermodel.cpp
3     ---------------------
4     begin                : July 2011
5     copyright            : (C) 2011 by Martin Dobias
6     email                : wonder dot sk at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 #include <QDir>
16 #include <QApplication>
17 #include <QStyle>
18 #include <QtConcurrentMap>
19 #include <QUrl>
20 #include <QStorageInfo>
21 #include <QFuture>
22 #include <QFutureWatcher>
23 
24 #include "qgis.h"
25 #include "qgsapplication.h"
26 #include "qgsdataitemprovider.h"
27 #include "qgsdataitemproviderregistry.h"
28 #include "qgsdataprovider.h"
29 #include "qgsmimedatautils.h"
30 #include "qgslogger.h"
31 #include "qgsproviderregistry.h"
32 #include "qgsbrowsermodel.h"
33 #include "qgsproject.h"
34 #include "qgssettings.h"
35 #include "qgsdirectoryitem.h"
36 #include "qgsprojectitem.h"
37 #include "qgslayeritem.h"
38 #include "qgsfavoritesitem.h"
39 
40 #define PROJECT_HOME_PREFIX "project:"
41 #define HOME_PREFIX "home:"
42 
43 /// @cond PRIVATE
44 class QgsBrowserWatcher : public QFutureWatcher<QVector <QgsDataItem *> >
45 {
46     Q_OBJECT
47 
48   public:
QgsBrowserWatcher(QgsDataItem * item)49     QgsBrowserWatcher( QgsDataItem *item )
50       : QFutureWatcher( nullptr )
51       , mItem( item )
52     {
53     }
54 
item() const55     QgsDataItem *item() const { return mItem; }
56 
57   signals:
58     void finished( QgsDataItem *item, const QVector <QgsDataItem *> &items );
59 
60   private:
61     QgsDataItem *mItem = nullptr;
62 };
63 ///@endcond
64 
65 // sort function for QList<QgsDataItem*>, e.g. sorted/grouped provider listings
cmpByDataItemName_(QgsDataItem * a,QgsDataItem * b)66 static bool cmpByDataItemName_( QgsDataItem *a, QgsDataItem *b )
67 {
68   return QString::localeAwareCompare( a->name(), b->name() ) < 0;
69 }
70 
QgsBrowserModel(QObject * parent)71 QgsBrowserModel::QgsBrowserModel( QObject *parent )
72   : QAbstractItemModel( parent )
73 
74 {
75   connect( QgsApplication::dataItemProviderRegistry(), &QgsDataItemProviderRegistry::providerAdded,
76            this, &QgsBrowserModel::dataItemProviderAdded );
77   connect( QgsApplication::dataItemProviderRegistry(), &QgsDataItemProviderRegistry::providerWillBeRemoved,
78            this, &QgsBrowserModel::dataItemProviderWillBeRemoved );
79 }
80 
~QgsBrowserModel()81 QgsBrowserModel::~QgsBrowserModel()
82 {
83   removeRootItems();
84 }
85 
updateProjectHome()86 void QgsBrowserModel::updateProjectHome()
87 {
88   QString home = QgsProject::instance()->homePath();
89   if ( mProjectHome && mProjectHome->path().mid( QStringLiteral( PROJECT_HOME_PREFIX ).length() ) == home )
90     return;
91 
92   int idx = mRootItems.indexOf( mProjectHome );
93 
94   // using layoutAboutToBeChanged() was messing expanded items
95   if ( idx >= 0 )
96   {
97     beginRemoveRows( QModelIndex(), idx, idx );
98     mRootItems.remove( idx );
99     endRemoveRows();
100   }
101   delete mProjectHome;
102   mProjectHome = home.isNull() ? nullptr : new QgsProjectHomeItem( nullptr, tr( "Project Home" ), home, QStringLiteral( PROJECT_HOME_PREFIX ) + home );
103   if ( mProjectHome )
104   {
105     setupItemConnections( mProjectHome );
106 
107     beginInsertRows( QModelIndex(), 0, 0 );
108     mRootItems.insert( 0, mProjectHome );
109     endInsertRows();
110   }
111 }
112 
addRootItems()113 void QgsBrowserModel::addRootItems()
114 {
115   updateProjectHome();
116 
117   // give the home directory a prominent third place
118   QgsDirectoryItem *item = new QgsDirectoryItem( nullptr, tr( "Home" ), QDir::homePath(),
119       QStringLiteral( HOME_PREFIX ) + QDir::homePath(),
120       QStringLiteral( "special:Home" ) );
121   item->setSortKey( QStringLiteral( " 2" ) );
122   setupItemConnections( item );
123   mRootItems << item;
124 
125   // add favorite directories
126   mFavorites = new QgsFavoritesItem( nullptr, tr( "Favorites" ) );
127   if ( mFavorites )
128   {
129     setupItemConnections( mFavorites );
130     mRootItems << mFavorites;
131   }
132 
133   // add drives
134   const auto drives { QDir::drives() };
135   for ( const QFileInfo &drive : drives )
136   {
137     const QString path = drive.absolutePath();
138 
139     if ( QgsDirectoryItem::hiddenPath( path ) )
140       continue;
141 
142     const QString driveName = QStorageInfo( path ).displayName();
143     const QString name = driveName.isEmpty() || driveName == path ? path : QStringLiteral( "%1 (%2)" ).arg( path, driveName );
144 
145     QgsDirectoryItem *item = new QgsDirectoryItem( nullptr, name, path, path, QStringLiteral( "special:Drives" ) );
146     item->setSortKey( QStringLiteral( " 3 %1" ).arg( path ) );
147     mDriveItems.insert( path, item );
148 
149     setupItemConnections( item );
150     mRootItems << item;
151   }
152 
153 #ifdef Q_OS_MAC
154   QString path = QString( "/Volumes" );
155   QgsDirectoryItem *vols = new QgsDirectoryItem( nullptr, path, path, path, QStringLiteral( "special:Volumes" ) );
156   setupItemConnections( vols );
157   mRootItems << vols;
158 #endif
159 
160   // container for displaying providers as sorted groups (by QgsDataProvider::DataCapability enum)
161   QMultiMap<int, QgsDataItem *> providerMap;
162 
163   const auto constProviders = QgsApplication::dataItemProviderRegistry()->providers();
164   for ( QgsDataItemProvider *pr : constProviders )
165   {
166     if ( QgsDataItem *item = addProviderRootItem( pr ) )
167     {
168       providerMap.insert( pr->capabilities(), item );
169     }
170   }
171 
172   // add as sorted groups by QgsDataProvider::DataCapability enum
173   const auto constUniqueKeys = providerMap.uniqueKeys();
174   for ( int key : constUniqueKeys )
175   {
176     QList<QgsDataItem *> providerGroup = providerMap.values( key );
177     if ( providerGroup.size() > 1 )
178     {
179       std::sort( providerGroup.begin(), providerGroup.end(), cmpByDataItemName_ );
180     }
181 
182     const auto constProviderGroup = providerGroup;
183     for ( QgsDataItem *ditem : constProviderGroup )
184     {
185       mRootItems << ditem;
186     }
187   }
188 }
189 
removeRootItems()190 void QgsBrowserModel::removeRootItems()
191 {
192   const auto constMRootItems = mRootItems;
193   for ( QgsDataItem *item : constMRootItems )
194   {
195     delete item;
196   }
197 
198   mRootItems.clear();
199   mDriveItems.clear();
200 }
201 
dataItemProviderAdded(QgsDataItemProvider * provider)202 void QgsBrowserModel::dataItemProviderAdded( QgsDataItemProvider *provider )
203 {
204   if ( !mInitialized )
205     return;
206 
207   if ( QgsDataItem *item = addProviderRootItem( provider ) )
208   {
209     beginInsertRows( QModelIndex(), rowCount(), rowCount() );
210     mRootItems << item;
211     endInsertRows();
212   }
213 }
214 
dataItemProviderWillBeRemoved(QgsDataItemProvider * provider)215 void QgsBrowserModel::dataItemProviderWillBeRemoved( QgsDataItemProvider *provider )
216 {
217   const auto constMRootItems = mRootItems;
218   for ( QgsDataItem *item : constMRootItems )
219   {
220     if ( item->providerKey() == provider->name() )
221     {
222       removeRootItem( item );
223       break;  // assuming there is max. 1 root item per provider
224     }
225   }
226 }
227 
onConnectionsChanged(const QString & providerKey)228 void QgsBrowserModel::onConnectionsChanged( const QString &providerKey )
229 {
230   // refresh the matching provider
231   for ( QgsDataItem *item : std::as_const( mRootItems ) )
232   {
233     if ( item->providerKey() == providerKey )
234     {
235       item->refresh();
236       break;  // assuming there is max. 1 root item per provider
237     }
238   }
239 
240   emit connectionsChanged( providerKey );
241 }
242 
driveItems() const243 QMap<QString, QgsDirectoryItem *> QgsBrowserModel::driveItems() const
244 {
245   return mDriveItems;
246 }
247 
248 
initialize()249 void QgsBrowserModel::initialize()
250 {
251   if ( ! mInitialized )
252   {
253     connect( QgsProject::instance(), &QgsProject::homePathChanged, this, &QgsBrowserModel::updateProjectHome );
254     addRootItems();
255     mInitialized = true;
256   }
257 }
258 
flags(const QModelIndex & index) const259 Qt::ItemFlags QgsBrowserModel::flags( const QModelIndex &index ) const
260 {
261   if ( !index.isValid() )
262     return Qt::ItemFlags();
263 
264   Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
265 
266   QgsDataItem *ptr = dataItem( index );
267 
268   if ( !ptr )
269   {
270     QgsDebugMsgLevel( QStringLiteral( "FLAGS PROBLEM!" ), 4 );
271     return Qt::ItemFlags();
272   }
273 
274   if ( ptr->hasDragEnabled() )
275     flags |= Qt::ItemIsDragEnabled;
276 
277   Q_NOWARN_DEPRECATED_PUSH
278   if ( ptr->acceptDrop() )
279     flags |= Qt::ItemIsDropEnabled;
280   Q_NOWARN_DEPRECATED_POP
281 
282   if ( ( ptr->capabilities2() & Qgis::BrowserItemCapability::Rename )
283        || ( ptr->capabilities2() & Qgis::BrowserItemCapability::ItemRepresentsFile ) )
284     flags |= Qt::ItemIsEditable;
285 
286   return flags;
287 }
288 
data(const QModelIndex & index,int role) const289 QVariant QgsBrowserModel::data( const QModelIndex &index, int role ) const
290 {
291   if ( !index.isValid() )
292     return QVariant();
293 
294   QgsDataItem *item = dataItem( index );
295   if ( !item )
296   {
297     return QVariant();
298   }
299   else if ( role == Qt::DisplayRole || role == Qt::EditRole )
300   {
301     return item->name();
302   }
303   else if ( role == QgsBrowserModel::SortRole )
304   {
305     return item->sortKey();
306   }
307   else if ( role == Qt::ToolTipRole )
308   {
309     return item->toolTip();
310   }
311   else if ( role == Qt::DecorationRole && index.column() == 0 )
312   {
313     return item->icon();
314   }
315   else if ( role == QgsBrowserModel::PathRole )
316   {
317     return item->path();
318   }
319   else if ( role == QgsBrowserModel::CommentRole )
320   {
321     if ( item->type() == Qgis::BrowserItemType::Layer )
322     {
323       QgsLayerItem *lyrItem = qobject_cast<QgsLayerItem *>( item );
324       return lyrItem->comments();
325     }
326     return QVariant();
327   }
328   else if ( role == QgsBrowserModel::ProviderKeyRole )
329   {
330     return item->providerKey();
331   }
332   else
333   {
334     // unsupported role
335     return QVariant();
336   }
337 }
338 
setData(const QModelIndex & index,const QVariant & value,int role)339 bool QgsBrowserModel::setData( const QModelIndex &index, const QVariant &value, int role )
340 {
341   if ( !index.isValid() )
342     return false;
343 
344 
345   QgsDataItem *item = dataItem( index );
346   if ( !item )
347   {
348     return false;
349   }
350 
351   if ( !( item->capabilities2() & Qgis::BrowserItemCapability::Rename )
352        && !( item->capabilities2() & Qgis::BrowserItemCapability::ItemRepresentsFile ) )
353     return false;
354 
355   switch ( role )
356   {
357     case Qt::EditRole:
358     {
359       Q_NOWARN_DEPRECATED_PUSH
360       return item->rename( value.toString() );
361       Q_NOWARN_DEPRECATED_POP
362     }
363   }
364   return false;
365 }
366 
headerData(int section,Qt::Orientation orientation,int role) const367 QVariant QgsBrowserModel::headerData( int section, Qt::Orientation orientation, int role ) const
368 {
369   Q_UNUSED( section )
370   if ( orientation == Qt::Horizontal && role == Qt::DisplayRole )
371   {
372     return QVariant( "header" );
373   }
374 
375   return QVariant();
376 }
377 
rowCount(const QModelIndex & parent) const378 int QgsBrowserModel::rowCount( const QModelIndex &parent ) const
379 {
380   //QgsDebugMsg(QString("isValid = %1 row = %2 column = %3").arg(parent.isValid()).arg(parent.row()).arg(parent.column()));
381 
382   if ( !parent.isValid() )
383   {
384     // root item: its children are top level items
385     return mRootItems.count(); // mRoot
386   }
387   else
388   {
389     // ordinary item: number of its children
390     QgsDataItem *item = dataItem( parent );
391     //if ( item ) QgsDebugMsg(QString("path = %1 rowCount = %2").arg(item->path()).arg(item->rowCount()) );
392     return item ? item->rowCount() : 0;
393   }
394 }
395 
hasChildren(const QModelIndex & parent) const396 bool QgsBrowserModel::hasChildren( const QModelIndex &parent ) const
397 {
398   if ( !parent.isValid() )
399     return !mRootItems.isEmpty(); // root item: its children are top level items
400 
401   QgsDataItem *item = dataItem( parent );
402   return item && item->hasChildren();
403 }
404 
columnCount(const QModelIndex & parent) const405 int QgsBrowserModel::columnCount( const QModelIndex &parent ) const
406 {
407   Q_UNUSED( parent )
408   return 1;
409 }
410 
findPath(const QString & path,Qt::MatchFlag matchFlag)411 QModelIndex QgsBrowserModel::findPath( const QString &path, Qt::MatchFlag matchFlag )
412 {
413   return findPath( this, path, matchFlag );
414 }
415 
findPath(QAbstractItemModel * model,const QString & path,Qt::MatchFlag matchFlag)416 QModelIndex QgsBrowserModel::findPath( QAbstractItemModel *model, const QString &path, Qt::MatchFlag matchFlag )
417 {
418   if ( !model )
419     return QModelIndex();
420 
421   QModelIndex index; // starting from root
422   bool foundChild = true;
423 
424   while ( foundChild )
425   {
426     foundChild = false; // assume that the next child item will not be found
427 
428     for ( int i = 0; i < model->rowCount( index ); i++ )
429     {
430       QModelIndex idx = model->index( i, 0, index );
431 
432       QString itemPath = model->data( idx, PathRole ).toString();
433       if ( itemPath == path )
434       {
435         QgsDebugMsgLevel( "Arrived " + itemPath, 4 );
436         return idx; // we have found the item we have been looking for
437       }
438 
439       // paths are slash separated identifier
440       if ( path.startsWith( itemPath + '/' ) )
441       {
442         foundChild = true;
443         index = idx;
444         break;
445       }
446     }
447   }
448 
449   if ( matchFlag == Qt::MatchStartsWith )
450     return index;
451 
452   QgsDebugMsgLevel( QStringLiteral( "path not found" ), 4 );
453   return QModelIndex(); // not found
454 }
455 
findUri(const QString & uri,QModelIndex index)456 QModelIndex QgsBrowserModel::findUri( const QString &uri, QModelIndex index )
457 {
458   for ( int i = 0; i < this->rowCount( index ); i++ )
459   {
460     QModelIndex idx = this->index( i, 0, index );
461 
462     if ( qobject_cast<QgsLayerItem *>( dataItem( idx ) ) )
463     {
464       QString itemUri = qobject_cast<QgsLayerItem *>( dataItem( idx ) )->uri();
465 
466       if ( itemUri == uri )
467       {
468         QgsDebugMsgLevel( "Arrived " + itemUri, 4 );
469         return idx; // we have found the item we have been looking for
470       }
471     }
472 
473     QModelIndex childIdx = findUri( uri, idx );
474     if ( childIdx.isValid() )
475       return childIdx;
476   }
477   return QModelIndex();
478 }
479 
connectItem(QgsDataItem *)480 void QgsBrowserModel::connectItem( QgsDataItem * )
481 {
482   // deprecated, no use
483 }
484 
reload()485 void QgsBrowserModel::reload()
486 {
487   // TODO: put items creating currently children in threads to deleteLater (does not seem urget because reload() is not used in QGIS)
488   beginResetModel();
489   removeRootItems();
490   addRootItems();
491   endResetModel();
492 }
493 
refreshDrives()494 void QgsBrowserModel::refreshDrives()
495 {
496   const QList< QFileInfo > drives = QDir::drives();
497   // remove any removed drives
498   const QStringList existingDrives = mDriveItems.keys();
499   for ( const QString &drivePath : existingDrives )
500   {
501     bool stillExists = false;
502     for ( const QFileInfo &drive : drives )
503     {
504       if ( drivePath == drive.absolutePath() )
505       {
506         stillExists = true;
507         break;
508       }
509     }
510 
511     if ( stillExists )
512       continue;
513 
514     // drive has been removed, remove corresponding item
515     if ( QgsDirectoryItem *driveItem = mDriveItems.value( drivePath ) )
516       removeRootItem( driveItem );
517   }
518 
519   for ( const QFileInfo &drive : drives )
520   {
521     const QString path = drive.absolutePath();
522 
523     if ( QgsDirectoryItem::hiddenPath( path ) )
524       continue;
525 
526     // does an item for this drive already exist?
527     if ( !mDriveItems.contains( path ) )
528     {
529       const QString driveName = QStorageInfo( path ).displayName();
530       const QString name = driveName.isEmpty() || driveName == path ? path : QStringLiteral( "%1 (%2)" ).arg( path, driveName );
531 
532       QgsDirectoryItem *item = new QgsDirectoryItem( nullptr, name, path, path, QStringLiteral( "special:Drives" ) );
533       item->setSortKey( QStringLiteral( " 3 %1" ).arg( path ) );
534 
535       mDriveItems.insert( path, item );
536       setupItemConnections( item );
537 
538       beginInsertRows( QModelIndex(), mRootItems.count(), mRootItems.count() );
539       mRootItems << item;
540       endInsertRows();
541     }
542   }
543 }
544 
index(int row,int column,const QModelIndex & parent) const545 QModelIndex QgsBrowserModel::index( int row, int column, const QModelIndex &parent ) const
546 {
547   if ( column < 0 || column >= columnCount() || row < 0 )
548     return QModelIndex();
549 
550   QgsDataItem *p = dataItem( parent );
551   const QVector<QgsDataItem *> &items = p ? p->children() : mRootItems;
552   QgsDataItem *item = items.value( row, nullptr );
553   return item ? createIndex( row, column, item ) : QModelIndex();
554 }
555 
parent(const QModelIndex & index) const556 QModelIndex QgsBrowserModel::parent( const QModelIndex &index ) const
557 {
558   QgsDataItem *item = dataItem( index );
559   if ( !item )
560     return QModelIndex();
561 
562   return findItem( item->parent() );
563 }
564 
findItem(QgsDataItem * item,QgsDataItem * parent) const565 QModelIndex QgsBrowserModel::findItem( QgsDataItem *item, QgsDataItem *parent ) const
566 {
567   const QVector<QgsDataItem *> &items = parent ? parent->children() : mRootItems;
568 
569   for ( int i = 0; i < items.size(); i++ )
570   {
571     if ( items[i] == item )
572       return createIndex( i, 0, item );
573 
574     QModelIndex childIndex = findItem( item, items[i] );
575     if ( childIndex.isValid() )
576       return childIndex;
577   }
578   return QModelIndex();
579 }
580 
beginInsertItems(QgsDataItem * parent,int first,int last)581 void QgsBrowserModel::beginInsertItems( QgsDataItem *parent, int first, int last )
582 {
583   QgsDebugMsgLevel( "parent mPath = " + parent->path(), 3 );
584   QModelIndex idx = findItem( parent );
585   if ( !idx.isValid() )
586     return;
587   QgsDebugMsgLevel( QStringLiteral( "valid" ), 3 );
588   beginInsertRows( idx, first, last );
589   QgsDebugMsgLevel( QStringLiteral( "end" ), 3 );
590 }
endInsertItems()591 void QgsBrowserModel::endInsertItems()
592 {
593   QgsDebugMsgLevel( QStringLiteral( "Entered" ), 3 );
594   endInsertRows();
595 }
beginRemoveItems(QgsDataItem * parent,int first,int last)596 void QgsBrowserModel::beginRemoveItems( QgsDataItem *parent, int first, int last )
597 {
598   QgsDebugMsgLevel( "parent mPath = " + parent->path(), 3 );
599   QModelIndex idx = findItem( parent );
600   if ( !idx.isValid() )
601     return;
602   beginRemoveRows( idx, first, last );
603 }
endRemoveItems()604 void QgsBrowserModel::endRemoveItems()
605 {
606   QgsDebugMsgLevel( QStringLiteral( "Entered" ), 3 );
607   endRemoveRows();
608 }
itemDataChanged(QgsDataItem * item)609 void QgsBrowserModel::itemDataChanged( QgsDataItem *item )
610 {
611   QgsDebugMsgLevel( QStringLiteral( "Entered" ), 3 );
612   QModelIndex idx = findItem( item );
613   if ( !idx.isValid() )
614     return;
615   emit dataChanged( idx, idx );
616 }
itemStateChanged(QgsDataItem * item,Qgis::BrowserItemState oldState)617 void QgsBrowserModel::itemStateChanged( QgsDataItem *item, Qgis::BrowserItemState oldState )
618 {
619   if ( !item )
620     return;
621   QModelIndex idx = findItem( item );
622   if ( !idx.isValid() )
623     return;
624   QgsDebugMsgLevel( QStringLiteral( "item %1 state changed %2 -> %3" ).arg( item->path() ).arg( qgsEnumValueToKey< Qgis::BrowserItemState >( oldState ) ).arg( qgsEnumValueToKey< Qgis::BrowserItemState >( item->state() ) ), 4 );
625   emit stateChanged( idx, oldState );
626 }
627 
setupItemConnections(QgsDataItem * item)628 void QgsBrowserModel::setupItemConnections( QgsDataItem *item )
629 {
630   connect( item, &QgsDataItem::beginInsertItems,
631            this, &QgsBrowserModel::beginInsertItems );
632   connect( item, &QgsDataItem::endInsertItems,
633            this, &QgsBrowserModel::endInsertItems );
634   connect( item, &QgsDataItem::beginRemoveItems,
635            this, &QgsBrowserModel::beginRemoveItems );
636   connect( item, &QgsDataItem::endRemoveItems,
637            this, &QgsBrowserModel::endRemoveItems );
638   connect( item, &QgsDataItem::dataChanged,
639            this, &QgsBrowserModel::itemDataChanged );
640   connect( item, &QgsDataItem::stateChanged,
641            this, &QgsBrowserModel::itemStateChanged );
642 
643   // if it's a collection item, also forwards connectionsChanged
644   QgsDataCollectionItem *collectionItem = qobject_cast<QgsDataCollectionItem *>( item );
645   if ( collectionItem )
646     connect( collectionItem, &QgsDataCollectionItem::connectionsChanged, this, &QgsBrowserModel::onConnectionsChanged );
647 }
648 
mimeTypes() const649 QStringList QgsBrowserModel::mimeTypes() const
650 {
651   QStringList types;
652   // In theory the mime type convention is: application/x-vnd.<vendor>.<application>.<type>
653   // but it seems a bit over formalized. Would be an application/x-qgis-uri better?
654   types << QStringLiteral( "application/x-vnd.qgis.qgis.uri" );
655   return types;
656 }
657 
mimeData(const QModelIndexList & indexes) const658 QMimeData *QgsBrowserModel::mimeData( const QModelIndexList &indexes ) const
659 {
660   QgsMimeDataUtils::UriList lst;
661   const auto constIndexes = indexes;
662   for ( const QModelIndex &index : constIndexes )
663   {
664     if ( index.isValid() )
665     {
666       QgsDataItem *ptr = reinterpret_cast< QgsDataItem * >( index.internalPointer() );
667       const QgsMimeDataUtils::UriList uris = ptr->mimeUris();
668       for ( QgsMimeDataUtils::Uri uri : std::as_const( uris ) )
669       {
670         if ( ptr->capabilities2() & Qgis::BrowserItemCapability::ItemRepresentsFile )
671         {
672           uri.filePath = ptr->path();
673         }
674         lst.append( uri );
675       }
676     }
677   }
678   return QgsMimeDataUtils::encodeUriList( lst );
679 }
680 
dropMimeData(const QMimeData * data,Qt::DropAction action,int,int,const QModelIndex & parent)681 bool QgsBrowserModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int, int, const QModelIndex &parent )
682 {
683   QgsDataItem *destItem = dataItem( parent );
684   if ( !destItem )
685   {
686     QgsDebugMsgLevel( QStringLiteral( "DROP PROBLEM!" ), 4 );
687     return false;
688   }
689 
690   Q_NOWARN_DEPRECATED_PUSH
691   return destItem->handleDrop( data, action );
692   Q_NOWARN_DEPRECATED_POP
693 }
694 
dataItem(const QModelIndex & idx) const695 QgsDataItem *QgsBrowserModel::dataItem( const QModelIndex &idx ) const
696 {
697   void *v = idx.internalPointer();
698   QgsDataItem *d = reinterpret_cast<QgsDataItem *>( v );
699   Q_ASSERT( !v || d );
700   return d;
701 }
702 
canFetchMore(const QModelIndex & parent) const703 bool QgsBrowserModel::canFetchMore( const QModelIndex &parent ) const
704 {
705   QgsDataItem *item = dataItem( parent );
706   // if ( item )
707   //   QgsDebugMsg( QStringLiteral( "path = %1 canFetchMore = %2" ).arg( item->path() ).arg( item && ! item->isPopulated() ) );
708   return ( item && item->state() == Qgis::BrowserItemState::NotPopulated );
709 }
710 
fetchMore(const QModelIndex & parent)711 void QgsBrowserModel::fetchMore( const QModelIndex &parent )
712 {
713   QgsDataItem *item = dataItem( parent );
714 
715   if ( !item || item->state() == Qgis::BrowserItemState::Populating || item->state() == Qgis::BrowserItemState::Populated )
716     return;
717 
718   QgsDebugMsgLevel( "path = " + item->path(), 4 );
719 
720   item->populate();
721 }
722 
723 /* Refresh dir path */
refresh(const QString & path)724 void QgsBrowserModel::refresh( const QString &path )
725 {
726   QModelIndex index = findPath( path );
727   refresh( index );
728 }
729 
730 /* Refresh item */
refresh(const QModelIndex & index)731 void QgsBrowserModel::refresh( const QModelIndex &index )
732 {
733   QgsDataItem *item = dataItem( index );
734   if ( !item || item->state() == Qgis::BrowserItemState::Populating )
735     return;
736 
737   QgsDebugMsgLevel( "Refresh " + item->path(), 4 );
738 
739   item->refresh();
740 }
741 
addFavoriteDirectory(const QString & directory,const QString & name)742 void QgsBrowserModel::addFavoriteDirectory( const QString &directory, const QString &name )
743 {
744   Q_ASSERT( mFavorites );
745   mFavorites->addDirectory( directory, name );
746 }
747 
removeFavorite(const QModelIndex & index)748 void QgsBrowserModel::removeFavorite( const QModelIndex &index )
749 {
750   QgsDirectoryItem *item = qobject_cast<QgsDirectoryItem *>( dataItem( index ) );
751   if ( !item )
752     return;
753 
754   mFavorites->removeDirectory( item );
755 }
756 
removeFavorite(QgsFavoriteItem * favorite)757 void QgsBrowserModel::removeFavorite( QgsFavoriteItem *favorite )
758 {
759   if ( !favorite )
760     return;
761 
762   mFavorites->removeDirectory( favorite );
763 }
764 
hidePath(QgsDataItem * item)765 void QgsBrowserModel::hidePath( QgsDataItem *item )
766 {
767   QgsSettings settings;
768   QStringList hiddenItems = settings.value( QStringLiteral( "browser/hiddenPaths" ),
769                             QStringList() ).toStringList();
770   int idx = hiddenItems.indexOf( item->path() );
771   if ( idx != -1 )
772   {
773     hiddenItems.removeAt( idx );
774   }
775   else
776   {
777     hiddenItems << item->path();
778   }
779   settings.setValue( QStringLiteral( "browser/hiddenPaths" ), hiddenItems );
780   if ( item->parent() )
781   {
782     item->parent()->deleteChildItem( item );
783   }
784   else
785   {
786     removeRootItem( item );
787   }
788 }
789 
790 
removeRootItem(QgsDataItem * item)791 void QgsBrowserModel::removeRootItem( QgsDataItem *item )
792 {
793   int i = mRootItems.indexOf( item );
794   beginRemoveRows( QModelIndex(), i, i );
795   mRootItems.remove( i );
796   QgsDirectoryItem *dirItem = qobject_cast< QgsDirectoryItem * >( item );
797   if ( !mDriveItems.key( dirItem ).isEmpty() )
798   {
799     mDriveItems.remove( mDriveItems.key( dirItem ) );
800   }
801   item->deleteLater();
802   endRemoveRows();
803 }
804 
addProviderRootItem(QgsDataItemProvider * pr)805 QgsDataItem *QgsBrowserModel::addProviderRootItem( QgsDataItemProvider *pr )
806 {
807   int capabilities = pr->capabilities();
808   if ( capabilities == QgsDataProvider::NoDataCapabilities )
809   {
810     QgsDebugMsgLevel( pr->name() + " does not have any dataCapabilities", 4 );
811     return nullptr;
812   }
813 
814   QgsDataItem *item = pr->createDataItem( QString(), nullptr );  // empty path -> top level
815   if ( item )
816   {
817     // make sure the top level key is always set
818     item->setProviderKey( pr->name() );
819     // Forward the signal from the root items to the model (and then to the app)
820     connect( item, &QgsDataItem::connectionsChanged, this, &QgsBrowserModel::onConnectionsChanged );
821     QgsDebugMsgLevel( "Add new top level item : " + item->name(), 4 );
822     setupItemConnections( item );
823   }
824   return item;
825 }
826 
827 // For QgsBrowserWatcher
828 #include "qgsbrowsermodel.moc"
829