QGIS API Documentation  2.99.0-Master (75367e4)
qgsbrowsermodel.cpp
Go to the documentation of this file.
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 
21 #include "qgis.h"
22 #include "qgsapplication.h"
23 #include "qgsdataitemprovider.h"
25 #include "qgsdataprovider.h"
26 #include "qgsmimedatautils.h"
27 #include "qgslogger.h"
28 #include "qgsproviderregistry.h"
29 
30 #include "qgsbrowsermodel.h"
31 #include "qgsproject.h"
32 
33 #include <QSettings>
34 
36  : mItem( item )
37 {
38 }
39 
40 // sort function for QList<QgsDataItem*>, e.g. sorted/grouped provider listings
42 {
43  return QString::localeAwareCompare( a->name(), b->name() ) < 0;
44 }
45 
47  : QAbstractItemModel( parent )
48  , mFavorites( nullptr )
49  , mProjectHome( nullptr )
50 {
51  connect( QgsProject::instance(), SIGNAL( readProject( const QDomDocument & ) ), this, SLOT( updateProjectHome() ) );
52  connect( QgsProject::instance(), SIGNAL( writeProject( QDomDocument & ) ), this, SLOT( updateProjectHome() ) );
53  addRootItems();
54 }
55 
57 {
59 }
60 
62 {
63  QString home = QgsProject::instance()->homePath();
64  if ( mProjectHome && mProjectHome->path() == home )
65  return;
66 
67  int idx = mRootItems.indexOf( mProjectHome );
68 
69  // using layoutAboutToBeChanged() was messing expanded items
70  if ( idx >= 0 )
71  {
72  beginRemoveRows( QModelIndex(), idx, idx );
73  mRootItems.remove( idx );
74  endRemoveRows();
75  }
76  delete mProjectHome;
77  mProjectHome = home.isNull() ? nullptr : new QgsDirectoryItem( nullptr, tr( "Project home" ), home, "project:" + home );
78  if ( mProjectHome )
79  {
81 
82  beginInsertRows( QModelIndex(), 0, 0 );
83  mRootItems.insert( 0, mProjectHome );
84  endInsertRows();
85  }
86 }
87 
89 {
91 
92  // give the home directory a prominent second place
93  QgsDirectoryItem *item = new QgsDirectoryItem( nullptr, tr( "Home" ), QDir::homePath(), "home:" + QDir::homePath() );
94  QStyle *style = QApplication::style();
95  QIcon homeIcon( style->standardPixmap( QStyle::SP_DirHomeIcon ) );
96  item->setIcon( homeIcon );
97  connectItem( item );
98  mRootItems << item;
99 
100  // add favorite directories
101  mFavorites = new QgsFavoritesItem( nullptr, tr( "Favorites" ) );
102  if ( mFavorites )
103  {
106  }
107 
108  // add drives
109  Q_FOREACH ( const QFileInfo& drive, QDir::drives() )
110  {
111  QString path = drive.absolutePath();
112 
113  if ( QgsDirectoryItem::hiddenPath( path ) )
114  continue;
115 
116  QgsDirectoryItem *item = new QgsDirectoryItem( nullptr, path, path );
117 
118  connectItem( item );
119  mRootItems << item;
120  }
121 
122 #ifdef Q_OS_MAC
123  QString path = QString( "/Volumes" );
124  QgsDirectoryItem *vols = new QgsDirectoryItem( nullptr, path, path );
125  connectItem( vols );
126  mRootItems << vols;
127 #endif
128 
129  // container for displaying providers as sorted groups (by QgsDataProvider::DataCapability enum)
130  QMap<int, QgsDataItem *> providerMap;
131 
132  Q_FOREACH ( QgsDataItemProvider* pr, QgsApplication::dataItemProviderRegistry()->providers() )
133  {
134  int capabilities = pr->capabilities();
135  if ( capabilities == QgsDataProvider::NoDataCapabilities )
136  {
137  QgsDebugMsg( pr->name() + " does not have any dataCapabilities" );
138  continue;
139  }
140 
141  QgsDataItem *item = pr->createDataItem( QLatin1String( "" ), nullptr ); // empty path -> top level
142  if ( item )
143  {
144  QgsDebugMsg( "Add new top level item : " + item->name() );
145  connectItem( item );
146  providerMap.insertMulti( capabilities, item );
147  }
148  }
149 
150  // add as sorted groups by QgsDataProvider::DataCapability enum
151  Q_FOREACH ( int key, providerMap.uniqueKeys() )
152  {
153  QList<QgsDataItem *> providerGroup = providerMap.values( key );
154  if ( providerGroup.size() > 1 )
155  {
156  std::sort( providerGroup.begin(), providerGroup.end(), cmpByDataItemName_ );
157  }
158 
159  Q_FOREACH ( QgsDataItem * ditem, providerGroup )
160  {
161  mRootItems << ditem;
162  }
163  }
164 }
165 
167 {
168  Q_FOREACH ( QgsDataItem* item, mRootItems )
169  {
170  delete item;
171  }
172 
173  mRootItems.clear();
174 }
175 
176 
177 Qt::ItemFlags QgsBrowserModel::flags( const QModelIndex & index ) const
178 {
179  if ( !index.isValid() )
180  return Qt::ItemFlags();
181 
182  Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
183 
184  QgsDataItem* ptr = reinterpret_cast< QgsDataItem* >( index.internalPointer() );
185  if ( ptr->hasDragEnabled() )
186  flags |= Qt::ItemIsDragEnabled;
187 
188  if ( ptr->acceptDrop() )
189  flags |= Qt::ItemIsDropEnabled;
190  return flags;
191 }
192 
193 QVariant QgsBrowserModel::data( const QModelIndex &index, int role ) const
194 {
195  if ( !index.isValid() )
196  return QVariant();
197 
198  QgsDataItem *item = dataItem( index );
199  if ( !item )
200  {
201  return QVariant();
202  }
203  else if ( role == Qt::DisplayRole )
204  {
205  return item->name();
206  }
207  else if ( role == Qt::ToolTipRole )
208  {
209  return item->toolTip();
210  }
211  else if ( role == Qt::DecorationRole && index.column() == 0 )
212  {
213  return item->icon();
214  }
215  else if ( role == QgsBrowserModel::PathRole )
216  {
217  return item->path();
218  }
219  else if ( role == QgsBrowserModel::CommentRole )
220  {
221  if ( item->type() == QgsDataItem::Layer )
222  {
223  QgsLayerItem* lyrItem = qobject_cast<QgsLayerItem*>( item );
224  return lyrItem->comments();
225  }
226  return QVariant();
227  }
228  else
229  {
230  // unsupported role
231  return QVariant();
232  }
233 }
234 
235 QVariant QgsBrowserModel::headerData( int section, Qt::Orientation orientation, int role ) const
236 {
237  Q_UNUSED( section );
238  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole )
239  {
240  return QVariant( "header" );
241  }
242 
243  return QVariant();
244 }
245 
246 int QgsBrowserModel::rowCount( const QModelIndex &parent ) const
247 {
248  //QgsDebugMsg(QString("isValid = %1 row = %2 column = %3").arg(parent.isValid()).arg(parent.row()).arg(parent.column()));
249 
250  if ( !parent.isValid() )
251  {
252  // root item: its children are top level items
253  return mRootItems.count(); // mRoot
254  }
255  else
256  {
257  // ordinary item: number of its children
258  QgsDataItem *item = dataItem( parent );
259  //if ( item ) QgsDebugMsg(QString("path = %1 rowCount = %2").arg(item->path()).arg(item->rowCount()) );
260  return item ? item->rowCount() : 0;
261  }
262 }
263 
264 bool QgsBrowserModel::hasChildren( const QModelIndex &parent ) const
265 {
266  if ( !parent.isValid() )
267  return true; // root item: its children are top level items
268 
269  QgsDataItem *item = dataItem( parent );
270  return item && item->hasChildren();
271 }
272 
273 int QgsBrowserModel::columnCount( const QModelIndex &parent ) const
274 {
275  Q_UNUSED( parent );
276  return 1;
277 }
278 
279 QModelIndex QgsBrowserModel::findPath( const QString& path, Qt::MatchFlag matchFlag )
280 {
281  return findPath( this, path, matchFlag );
282 }
283 
284 QModelIndex QgsBrowserModel::findPath( QAbstractItemModel *model, const QString& path, Qt::MatchFlag matchFlag )
285 {
286  if ( !model )
287  return QModelIndex();
288 
289  QModelIndex theIndex; // starting from root
290  bool foundChild = true;
291 
292  while ( foundChild )
293  {
294  foundChild = false; // assume that the next child item will not be found
295 
296  for ( int i = 0; i < model->rowCount( theIndex ); i++ )
297  {
298  QModelIndex idx = model->index( i, 0, theIndex );
299 
300  QString itemPath = model->data( idx, PathRole ).toString();
301  if ( itemPath == path )
302  {
303  QgsDebugMsg( "Arrived " + itemPath );
304  return idx; // we have found the item we have been looking for
305  }
306 
307  // paths are slash separated identifier
308  if ( path.startsWith( itemPath + '/' ) )
309  {
310  foundChild = true;
311  theIndex = idx;
312  break;
313  }
314  }
315  }
316 
317  if ( matchFlag == Qt::MatchStartsWith )
318  return theIndex;
319 
320  QgsDebugMsg( "path not found" );
321  return QModelIndex(); // not found
322 }
323 
325 {
326  // TODO: put items creating currently children in threads to deleteLater (does not seem urget because reload() is not used in QGIS)
327  beginResetModel();
328  removeRootItems();
329  addRootItems();
330  endResetModel();
331 }
332 
333 QModelIndex QgsBrowserModel::index( int row, int column, const QModelIndex &parent ) const
334 {
335  if ( column < 0 || column >= columnCount() || row < 0 )
336  return QModelIndex();
337 
338  QgsDataItem *p = dataItem( parent );
339  const QVector<QgsDataItem*> &items = p ? p->children() : mRootItems;
340  QgsDataItem *item = items.value( row, nullptr );
341  return item ? createIndex( row, column, item ) : QModelIndex();
342 }
343 
344 QModelIndex QgsBrowserModel::parent( const QModelIndex &index ) const
345 {
346  QgsDataItem *item = dataItem( index );
347  if ( !item )
348  return QModelIndex();
349 
350  return findItem( item->parent() );
351 }
352 
354 {
355  const QVector<QgsDataItem*> &items = parent ? parent->children() : mRootItems;
356 
357  for ( int i = 0; i < items.size(); i++ )
358  {
359  if ( items[i] == item )
360  return createIndex( i, 0, item );
361 
362  QModelIndex childIndex = findItem( item, items[i] );
363  if ( childIndex.isValid() )
364  return childIndex;
365  }
366 
367  return QModelIndex();
368 }
369 
371 {
372  QgsDebugMsgLevel( "parent mPath = " + parent->path(), 3 );
373  QModelIndex idx = findItem( parent );
374  if ( !idx.isValid() )
375  return;
376  QgsDebugMsgLevel( "valid", 3 );
377  beginInsertRows( idx, first, last );
378  QgsDebugMsgLevel( "end", 3 );
379 }
381 {
382  QgsDebugMsgLevel( "Entered", 3 );
383  endInsertRows();
384 }
386 {
387  QgsDebugMsgLevel( "parent mPath = " + parent->path(), 3 );
388  QModelIndex idx = findItem( parent );
389  if ( !idx.isValid() )
390  return;
391  beginRemoveRows( idx, first, last );
392 }
394 {
395  QgsDebugMsgLevel( "Entered", 3 );
396  endRemoveRows();
397 }
399 {
400  QgsDebugMsgLevel( "Entered", 3 );
401  QModelIndex idx = findItem( item );
402  if ( !idx.isValid() )
403  return;
404  emit dataChanged( idx, idx );
405 }
407 {
408  if ( !item )
409  return;
410  QModelIndex idx = findItem( item );
411  if ( !idx.isValid() )
412  return;
413  QgsDebugMsg( QString( "item %1 state changed %2 -> %3" ).arg( item->path() ).arg( oldState ).arg( item->state() ) );
414  emit stateChanged( idx, oldState );
415 }
417 {
418  connect( item, SIGNAL( beginInsertItems( QgsDataItem*, int, int ) ),
419  this, SLOT( beginInsertItems( QgsDataItem*, int, int ) ) );
420  connect( item, SIGNAL( endInsertItems() ),
421  this, SLOT( endInsertItems() ) );
422  connect( item, SIGNAL( beginRemoveItems( QgsDataItem*, int, int ) ),
423  this, SLOT( beginRemoveItems( QgsDataItem*, int, int ) ) );
424  connect( item, SIGNAL( endRemoveItems() ),
425  this, SLOT( endRemoveItems() ) );
426  connect( item, SIGNAL( dataChanged( QgsDataItem* ) ),
427  this, SLOT( itemDataChanged( QgsDataItem* ) ) );
428  connect( item, SIGNAL( stateChanged( QgsDataItem*, QgsDataItem::State ) ),
429  this, SLOT( itemStateChanged( QgsDataItem*, QgsDataItem::State ) ) );
430 }
431 
432 QStringList QgsBrowserModel::mimeTypes() const
433 {
434  QStringList types;
435  // In theory the mime type convention is: application/x-vnd.<vendor>.<application>.<type>
436  // but it seems a bit over formalized. Would be an application/x-qgis-uri better?
437  types << QStringLiteral( "application/x-vnd.qgis.qgis.uri" );
438  return types;
439 }
440 
441 QMimeData * QgsBrowserModel::mimeData( const QModelIndexList &indexes ) const
442 {
444  Q_FOREACH ( const QModelIndex &index, indexes )
445  {
446  if ( index.isValid() )
447  {
448  QgsDataItem* ptr = reinterpret_cast< QgsDataItem* >( index.internalPointer() );
449  if ( ptr->type() == QgsDataItem::Project )
450  {
451  QMimeData *mimeData = new QMimeData();
452  QUrl url = QUrl::fromLocalFile( ptr->path() );
453  QList<QUrl> urls;
454  urls << url;
455  mimeData->setUrls( urls );
456  return mimeData;
457  }
458 
459  QgsMimeDataUtils::Uri uri = ptr->mimeUri();
460  if ( uri.isValid() )
461  lst.append( uri );
462  }
463  }
464  return QgsMimeDataUtils::encodeUriList( lst );
465 }
466 
467 bool QgsBrowserModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent )
468 {
469  Q_UNUSED( row );
470  Q_UNUSED( column );
471 
472  QgsDataItem* destItem = dataItem( parent );
473  if ( !destItem )
474  {
475  QgsDebugMsg( "DROP PROBLEM!" );
476  return false;
477  }
478 
479  return destItem->handleDrop( data, action );
480 }
481 
482 QgsDataItem *QgsBrowserModel::dataItem( const QModelIndex &idx ) const
483 {
484  void *v = idx.internalPointer();
485  QgsDataItem *d = reinterpret_cast<QgsDataItem*>( v );
486  Q_ASSERT( !v || d );
487  return d;
488 }
489 
490 bool QgsBrowserModel::canFetchMore( const QModelIndex & parent ) const
491 {
492  QgsDataItem* item = dataItem( parent );
493  // if ( item )
494  // QgsDebugMsg( QString( "path = %1 canFetchMore = %2" ).arg( item->path() ).arg( item && ! item->isPopulated() ) );
495  return ( item && item->state() == QgsDataItem::NotPopulated );
496 }
497 
498 void QgsBrowserModel::fetchMore( const QModelIndex & parent )
499 {
500  QgsDataItem* item = dataItem( parent );
501 
502  if ( !item || item->state() == QgsDataItem::Populating || item->state() == QgsDataItem::Populated )
503  return;
504 
505  QgsDebugMsg( "path = " + item->path() );
506 
507  item->populate();
508 }
509 
510 /* Refresh dir path */
511 void QgsBrowserModel::refresh( const QString& path )
512 {
513  QModelIndex index = findPath( path );
514  refresh( index );
515 }
516 
517 /* Refresh item */
518 void QgsBrowserModel::refresh( const QModelIndex& theIndex )
519 {
520  QgsDataItem *item = dataItem( theIndex );
521  if ( !item || item->state() == QgsDataItem::Populating )
522  return;
523 
524  QgsDebugMsg( "Refresh " + item->path() );
525 
526  item->refresh();
527 }
528 
529 void QgsBrowserModel::addFavoriteDirectory( const QString& directory )
530 {
531  Q_ASSERT( mFavorites );
532  mFavorites->addDirectory( directory );
533 }
534 
535 void QgsBrowserModel::removeFavorite( const QModelIndex &index )
536 {
537  QgsDirectoryItem *item = dynamic_cast<QgsDirectoryItem *>( dataItem( index ) );
538  if ( !item )
539  return;
540 
541  mFavorites->removeDirectory( item );
542 }
543 
545 {
546  QSettings settings;
547  QStringList hiddenItems = settings.value( QStringLiteral( "/browser/hiddenPaths" ),
548  QStringList() ).toStringList();
549  int idx = hiddenItems.indexOf( item->path() );
550  if ( idx != -1 )
551  {
552  hiddenItems.removeAt( idx );
553  }
554  else
555  {
556  hiddenItems << item->path();
557  }
558  settings.setValue( QStringLiteral( "/browser/hiddenPaths" ), hiddenItems );
559  if ( item->parent() )
560  {
561  item->parent()->deleteChildItem( item );
562  }
563  else
564  {
565  int i = mRootItems.indexOf( item );
566  emit beginRemoveRows( QModelIndex(), i, i );
567  mRootItems.remove( i );
568  item->deleteLater();
569  emit endRemoveRows();
570  }
571 }
QString path() const
Definition: qgsdataitem.h:224
static bool cmpByDataItemName_(QgsDataItem *a, QgsDataItem *b)
bool canFetchMore(const QModelIndex &parent) const override
QString name() const
Definition: qgsdataitem.h:222
QString toolTip() const
Definition: qgsdataitem.h:236
void hidePath(QgsDataItem *item)
Hide the given path in the browser model.
QgsBrowserModel(QObject *parent=nullptr)
virtual QgsDataItem * createDataItem(const QString &path, QgsDataItem *parentItem)=0
Create a new instance of QgsDataItem (or null) for given path and parent item.
void addDirectory(const QString &directory)
Adds a new directory to the favorites group.
virtual int rowCount(const QModelIndex &parent=QModelIndex()) const override
Provides the number of rows of data exposed by the model.
void fetchMore(const QModelIndex &parent) override
virtual QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Used to supply item data to views and delegates.
QVector< QgsDataItem * > children() const
Definition: qgsdataitem.h:220
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
virtual bool handleDrop(const QMimeData *, Qt::DropAction)
Attempts to process the mime data dropped on this item.
Definition: qgsdataitem.h:164
QModelIndex findPath(const QString &path, Qt::MatchFlag matchFlag=Qt::MatchExactly)
Return index of item with given path.
void beginRemoveItems(QgsDataItem *parent, int first, int last)
virtual QIcon icon()
void connectItem(QgsDataItem *item)
Type type() const
Definition: qgsdataitem.h:211
static QMimeData * encodeUriList(const UriList &layers)
void setIcon(const QIcon &icon)
Definition: qgsdataitem.h:232
bool isValid() const
Returns whether the object contains valid data.
void itemStateChanged(QgsDataItem *item, QgsDataItem::State oldState)
virtual QStringList mimeTypes() const override
Returns a list of mime that can describe model indexes.
bool hasChildren(const QModelIndex &parent=QModelIndex()) const override
State state() const
Item path used to access path in the tree, see QgsDataItem::mPath.
QString homePath
Definition: qgsproject.h:79
QgsDirectoryItem * mProjectHome
static void deleteLater(QVector< QgsDataItem *> &items)
QgsDataItem * parent() const
Get item parent.
Definition: qgsdataitem.h:215
QgsFavoritesItem * mFavorites
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:37
virtual QModelIndex parent(const QModelIndex &index) const override
Returns the parent of the model item with the given index.
Children not yet created.
Definition: qgsdataitem.h:110
Creating children in separate thread (populating or refreshing)
Definition: qgsdataitem.h:111
QgsDataItem * dataItem(const QModelIndex &idx) const
void removeFavorite(const QModelIndex &index)
Removes a favorite directory from its corresponding model index.
virtual bool hasDragEnabled() const
Returns true if the item may be dragged.
Definition: qgsdataitem.h:172
virtual int capabilities()=0
Return combination of flags from QgsDataProvider::DataCapabilities.
virtual QgsMimeDataUtils::Uri mimeUri() const
Return mime URI for the data item.
Definition: qgsdataitem.h:179
QgsBrowserWatcher(QgsDataItem *item)
bool hasChildren()
static bool hiddenPath(const QString &path)
Check if the given path is hidden from the browser model.
void reload()
Reload the whole model.
A directory: contains subdirectories and layers.
Definition: qgsdataitem.h:421
Base class for all items in the model.
Definition: qgsdataitem.h:80
QModelIndex findItem(QgsDataItem *item, QgsDataItem *parent=nullptr) const
virtual QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
Provides views with information to show in their headers.
void beginInsertItems(QgsDataItem *parent, int first, int last)
Contains various Favorites directories.
Definition: qgsdataitem.h:526
void removeDirectory(QgsDirectoryItem *item)
Removes an existing directory from the favorites group.
virtual QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
Returns the index of the item in the model specified by the given row, column and parent index...
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:355
virtual int columnCount(const QModelIndex &parent=QModelIndex()) const override
Provides the number of columns of data exposed by the model.
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override
Handles the data supplied by a drag and drop operation that ended with the given action.
void stateChanged(const QModelIndex &index, QgsDataItem::State oldState)
Emitted when item children fetch was finished.
void refresh(const QString &path)
Refresh item specified by path.
virtual QMimeData * mimeData(const QModelIndexList &indexes) const override
Returns an object that contains serialized items of data corresponding to the list of indexes specifi...
Represents a QGIS project.
Definition: qgsdataitem.h:93
Item that represents a layer that can be opened with one of the providers.
Definition: qgsdataitem.h:322
virtual void deleteChildItem(QgsDataItem *child)
Removes and deletes a child item, emitting relevant signals to the model.
virtual QString name()=0
Human-readable name of the provider name.
void itemDataChanged(QgsDataItem *item)
void addFavoriteDirectory(const QString &directory)
Adds a directory to the favorites group.
virtual Qt::ItemFlags flags(const QModelIndex &index) const override
Used by other components to obtain information about each item provided by the model.
virtual void populate(const QVector< QgsDataItem *> &children)
Children created.
Definition: qgsdataitem.h:112
virtual bool acceptDrop()
Returns whether the item accepts drag and dropped layers - e.g.
Definition: qgsdataitem.h:158
QList< Uri > UriList
virtual QString comments() const
Returns comments of the layer.
Definition: qgsdataitem.h:374
void addRootItems()
Populates the model.
static QgsDataItemProviderRegistry * dataItemProviderRegistry()
Returns the application&#39;s data item provider registry, which keeps a list of data item providers that...
virtual void refresh(const QVector< QgsDataItem *> &children)
Refresh the items from a specified list of child items.
This is the interface for those who want to add custom data items to the browser tree.
QVector< QgsDataItem * > mRootItems