QGIS API Documentation  2.17.0-Master (973e4b0)
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 }
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
static void deleteLater(QVector< QgsDataItem * > &items)
int localeAwareCompare(const QString &other) const
QVector< QgsDataItem * > children() const
Definition: qgsdataitem.h:203
QString name() const
Definition: qgsdataitem.h:205
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:199
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: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)
State state() const
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()
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
QString homePath
Definition: qgsproject.h:76
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:109
Creating children in separate thread (populating or refreshing)
Definition: qgsdataitem.h:110
QString path() const
Definition: qgsdataitem.h:207
void * internalPointer() const
Type type() const
Definition: qgsdataitem.h:195
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)
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:355
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)
QString toolTip() const
Definition: qgsdataitem.h:219
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: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
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