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