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