QGIS API Documentation  2.14.0-Essen
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 nullptr;
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  QgsDebugMsg( "Entered" );
414  if ( !item )
415  return;
416  QModelIndex idx = findItem( item );
417  if ( !idx.isValid() )
418  return;
419  QgsDebugMsg( QString( "item %1 state changed %2 -> %3" ).arg( item->path() ).arg( oldState ).arg( item->state() ) );
420  emit stateChanged( idx, oldState );
421 }
423 {
424  connect( item, SIGNAL( beginInsertItems( QgsDataItem*, int, int ) ),
425  this, SLOT( beginInsertItems( QgsDataItem*, int, int ) ) );
426  connect( item, SIGNAL( endInsertItems() ),
427  this, SLOT( endInsertItems() ) );
428  connect( item, SIGNAL( beginRemoveItems( QgsDataItem*, int, int ) ),
429  this, SLOT( beginRemoveItems( QgsDataItem*, int, int ) ) );
430  connect( item, SIGNAL( endRemoveItems() ),
431  this, SLOT( endRemoveItems() ) );
432  connect( item, SIGNAL( dataChanged( QgsDataItem* ) ),
433  this, SLOT( itemDataChanged( QgsDataItem* ) ) );
434  connect( item, SIGNAL( stateChanged( QgsDataItem*, QgsDataItem::State ) ),
435  this, SLOT( itemStateChanged( QgsDataItem*, QgsDataItem::State ) ) );
436 }
437 
439 {
440  QStringList types;
441  // In theory the mime type convention is: application/x-vnd.<vendor>.<application>.<type>
442  // but it seems a bit over formalized. Would be an application/x-qgis-uri better?
443  types << "application/x-vnd.qgis.qgis.uri";
444  return types;
445 }
446 
447 QMimeData * QgsBrowserModel::mimeData( const QModelIndexList &indexes ) const
448 {
450  Q_FOREACH ( const QModelIndex &index, indexes )
451  {
452  if ( index.isValid() )
453  {
454  QgsDataItem* ptr = reinterpret_cast< QgsDataItem* >( index.internalPointer() );
455  if ( ptr->type() == QgsDataItem::Project )
456  {
457  QMimeData *mimeData = new QMimeData();
458  QUrl url = QUrl::fromLocalFile( ptr->path() );
459  QList<QUrl> urls;
460  urls << url;
461  mimeData->setUrls( urls );
462  return mimeData;
463  }
464 
465  if ( ptr->type() != QgsDataItem::Layer ) continue;
466  QgsLayerItem *layer = static_cast< QgsLayerItem* >( ptr );
467  lst.append( QgsMimeDataUtils::Uri( layer ) );
468  }
469  }
470  return QgsMimeDataUtils::encodeUriList( lst );
471 }
472 
473 bool QgsBrowserModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent )
474 {
475  Q_UNUSED( row );
476  Q_UNUSED( column );
477 
478  QgsDataItem* destItem = dataItem( parent );
479  if ( !destItem )
480  {
481  QgsDebugMsg( "DROP PROBLEM!" );
482  return false;
483  }
484 
485  return destItem->handleDrop( data, action );
486 }
487 
489 {
490  void *v = idx.internalPointer();
491  QgsDataItem *d = reinterpret_cast<QgsDataItem*>( v );
492  Q_ASSERT( !v || d );
493  return d;
494 }
495 
497 {
498  QgsDataItem* item = dataItem( parent );
499  // if ( item )
500  // QgsDebugMsg( QString( "path = %1 canFetchMore = %2" ).arg( item->path() ).arg( item && ! item->isPopulated() ) );
501  return ( item && item->state() == QgsDataItem::NotPopulated );
502 }
503 
505 {
506  QgsDebugMsg( "Entered" );
507  QgsDataItem* item = dataItem( parent );
508 
509  if ( !item || item->state() == QgsDataItem::Populating || item->state() == QgsDataItem::Populated )
510  return;
511 
512  QgsDebugMsg( "path = " + item->path() );
513 
514  item->populate();
515 }
516 
517 /* Refresh dir path */
519 {
520  QModelIndex index = findPath( path );
521  refresh( index );
522 }
523 
524 /* Refresh item */
525 void QgsBrowserModel::refresh( const QModelIndex& theIndex )
526 {
527  QgsDataItem *item = dataItem( theIndex );
528  if ( !item || item->state() == QgsDataItem::Populating )
529  return;
530 
531  QgsDebugMsg( "Refresh " + item->path() );
532 
533  item->refresh();
534 }
535 
537 {
538  Q_ASSERT( mFavourites );
539  mFavourites->addDirectory( favDir );
540 }
541 
543 {
544  QgsDirectoryItem *item = dynamic_cast<QgsDirectoryItem *>( dataItem( index ) );
545  if ( !item )
546  return;
547 
548  mFavourites->removeDirectory( item );
549 }
550 
552 {
553  QSettings settings;
554  QStringList hiddenItems = settings.value( "/browser/hiddenPaths",
556  int idx = hiddenItems.indexOf( item->path() );
557  if ( idx != -1 )
558  {
559  hiddenItems.removeAt( idx );
560  }
561  else
562  {
563  hiddenItems << item->path();
564  }
565  settings.setValue( "/browser/hiddenPaths", hiddenItems );
566  if ( item->parent() )
567  {
568  item->parent()->deleteChildItem( item );
569  }
570  else
571  {
572  int i = mRootItems.indexOf( item );
573  emit beginRemoveRows( QModelIndex(), i, i );
574  mRootItems.remove( i );
575  item->deleteLater();
576  emit endRemoveRows();
577  }
578 }
Contains various Favourites directories.
Definition: qgsdataitem.h:497
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
static void deleteLater(QVector< QgsDataItem * > &items)
int localeAwareCompare(const QString &other) const
QVector< QgsDataItem * > children() const
Definition: qgsdataitem.h:199
QString name() const
Definition: qgsdataitem.h:201
void hidePath(QgsDataItem *item)
Hide the given path in the browser model.
QList< T > values() const
QgsBrowserModel(QObject *parent=nullptr)
QgsDataItem * parent() const
Get item parent.
Definition: qgsdataitem.h:195
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.
#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:162
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)
State state() const
static QMimeData * encodeUriList(const UriList &layers)
void setIcon(const QIcon &icon)
Definition: qgsdataitem.h:211
void itemStateChanged(QgsDataItem *item, QgsDataItem::State oldState)
virtual QStringList mimeTypes() const override
Returns a list of mime that can describe model indexes.
QString homePath() const
Return project&#39;s home path.
QString homePath()
virtual void refresh(QVector< QgsDataItem * > children)
QString tr(const char *sourceText, const char *disambiguation, int n)
bool hasChildren(const QModelIndex &parent=QModelIndex()) const override
int size() const
bool isNull() const
T value(int i) const
void setValue(const QString &key, const QVariant &value)
QgsDirectoryItem * mProjectHome
void clear()
bool isValid() const
void append(const T &value)
iterator insertMulti(const Key &key, const T &value)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:34
QgsDataItem * dataItem(const QModelIndex &idx) const
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:105
Creating children in separate thread (populating or refreshing)
Definition: qgsdataitem.h:106
QString path() const
Definition: qgsdataitem.h:203
void * internalPointer() const
Type type() const
Definition: qgsdataitem.h:191
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:392
QModelIndex createIndex(int row, int column, void *ptr) const
Base class for all items in the model.
Definition: qgsdataitem.h:75
iterator end()
void addDirectory(const QString &favIcon)
void beginInsertRows(const QModelIndex &parent, int first, int last)
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)
virtual void populate(const QVector< QgsDataItem * > &children)
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...
virtual QString comments() const
Returns comments of the layer.
Definition: qgsdataitem.h:349
static QgsProject * instance()
access to canonical QgsProject instance
Definition: qgsproject.cpp:381
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:88
Item that represents a layer that can be opened with one of the providers.
Definition: qgsdataitem.h:301
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)
QString toolTip() const
Definition: qgsdataitem.h:215
virtual Qt::ItemFlags flags(const QModelIndex &index) const override
Used by other components to obtain information about each item provided by the model.
children created
Definition: qgsdataitem.h:107
QString absolutePath() const
virtual bool acceptDrop()
Returns whether the item accepts drag and dropped layers - e.g.
Definition: qgsdataitem.h:156
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
QModelIndex findItem(QgsDataItem *item, QgsDataItem *parent=nullptr) const
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() const
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