QGIS API Documentation  2.11.0-Master
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 
20 #include "qgis.h"
21 #include "qgsapplication.h"
22 #include "qgsdataitemprovider.h"
24 #include "qgsdataprovider.h"
25 #include "qgsmimedatautils.h"
26 #include "qgslogger.h"
27 #include "qgsproviderregistry.h"
28 
29 #include "qgsbrowsermodel.h"
30 #include "qgsproject.h"
31 
32 #include <QSettings>
33 
35  : mItem( item )
36 {
37 }
38 
40 {
41 }
42 
43 // sort function for QList<QgsDataItem*>, e.g. sorted/grouped provider listings
45 {
46  return QString::localeAwareCompare( a->name(), b->name() ) < 0;
47 }
48 
50  : QAbstractItemModel( parent )
51  , mFavourites( 0 )
52  , mProjectHome( 0 )
53 {
54  connect( QgsProject::instance(), SIGNAL( readProject( const QDomDocument & ) ), this, SLOT( updateProjectHome() ) );
55  connect( QgsProject::instance(), SIGNAL( writeProject( QDomDocument & ) ), this, SLOT( updateProjectHome() ) );
56  addRootItems();
57 }
58 
60 {
62 }
63 
65 {
67  if ( mProjectHome && mProjectHome->path() == home )
68  return;
69 
70  int idx = mRootItems.indexOf( mProjectHome );
71 
72  // using layoutAboutToBeChanged() was messing expanded items
73  if ( idx >= 0 )
74  {
75  beginRemoveRows( QModelIndex(), idx, idx );
76  mRootItems.remove( idx );
77  endRemoveRows();
78  }
79  delete mProjectHome;
80  mProjectHome = home.isNull() ? 0 : new QgsDirectoryItem( NULL, tr( "Project home" ), home, "project:" + home );
81  if ( mProjectHome )
82  {
84 
85  beginInsertRows( QModelIndex(), 0, 0 );
87  endInsertRows();
88  }
89 }
90 
92 {
94 
95  // give the home directory a prominent second place
96  QgsDirectoryItem *item = new QgsDirectoryItem( NULL, tr( "Home" ), QDir::homePath(), "home:" + QDir::homePath() );
97  QStyle *style = QApplication::style();
98  QIcon homeIcon( style->standardPixmap( QStyle::SP_DirHomeIcon ) );
99  item->setIcon( homeIcon );
100  connectItem( item );
101  mRootItems << item;
102 
103  // add favourite directories
104  mFavourites = new QgsFavouritesItem( NULL, tr( "Favourites" ) );
105  if ( mFavourites )
106  {
109  }
110 
111  // add drives
112  foreach ( QFileInfo drive, QDir::drives() )
113  {
114  QString path = drive.absolutePath();
115  QgsDirectoryItem *item = new QgsDirectoryItem( NULL, path, path );
116 
117  connectItem( item );
118  mRootItems << item;
119  }
120 
121 #ifdef Q_OS_MAC
122  QString path = QString( "/Volumes" );
123  QgsDirectoryItem *vols = new QgsDirectoryItem( NULL, path, path );
124  connectItem( vols );
125  mRootItems << vols;
126 #endif
127 
128  // container for displaying providers as sorted groups (by QgsDataProvider::DataCapability enum)
129  QMap<int, QgsDataItem *> providerMap;
130 
131  foreach ( QgsDataItemProvider* pr, QgsDataItemProviderRegistry::instance()->providers() )
132  {
133  int capabilities = pr->capabilities();
134  if ( capabilities == QgsDataProvider::NoDataCapabilities )
135  {
136  QgsDebugMsg( pr->name() + " does not have any dataCapabilities" );
137  continue;
138  }
139 
140  QgsDataItem *item = pr->createDataItem( "", NULL ); // empty path -> top level
141  if ( item )
142  {
143  QgsDebugMsg( "Add new top level item : " + item->name() );
144  connectItem( item );
145  providerMap.insertMulti( capabilities, item );
146  }
147  }
148 
149  // add as sorted groups by QgsDataProvider::DataCapability enum
150  foreach ( int key, providerMap.uniqueKeys() )
151  {
152  QList<QgsDataItem *> providerGroup = providerMap.values( key );
153  if ( providerGroup.size() > 1 )
154  {
155  qSort( providerGroup.begin(), providerGroup.end(), cmpByDataItemName_ );
156  }
157 
158  foreach ( QgsDataItem * ditem, providerGroup )
159  {
160  mRootItems << ditem;
161  }
162  }
163 }
164 
166 {
167  foreach ( QgsDataItem* item, mRootItems )
168  {
169  delete item;
170  }
171 
172  mRootItems.clear();
173 }
174 
175 
177 {
178  if ( !index.isValid() )
179  return 0;
180 
181  Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
182 
183  QgsDataItem* ptr = ( QgsDataItem* ) index.internalPointer();
184  if ( ptr->type() == QgsDataItem::Layer )
185  {
186  flags |= Qt::ItemIsDragEnabled;
187  }
188  if ( ptr->acceptDrop() )
189  flags |= Qt::ItemIsDropEnabled;
190  return flags;
191 }
192 
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
220  {
221  // unsupported role
222  return QVariant();
223  }
224 }
225 
226 QVariant QgsBrowserModel::headerData( int section, Qt::Orientation orientation, int role ) const
227 {
228  Q_UNUSED( section );
229  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole )
230  {
231  return QVariant( "header" );
232  }
233 
234  return QVariant();
235 }
236 
237 int QgsBrowserModel::rowCount( const QModelIndex &parent ) const
238 {
239  //QgsDebugMsg(QString("isValid = %1 row = %2 column = %3").arg(parent.isValid()).arg(parent.row()).arg(parent.column()));
240 
241  if ( !parent.isValid() )
242  {
243  // root item: its children are top level items
244  return mRootItems.count(); // mRoot
245  }
246  else
247  {
248  // ordinary item: number of its children
249  QgsDataItem *item = dataItem( parent );
250  //if ( item ) QgsDebugMsg(QString("path = %1 rowCount = %2").arg(item->path()).arg(item->rowCount()) );
251  return item ? item->rowCount() : 0;
252  }
253 }
254 
255 bool QgsBrowserModel::hasChildren( const QModelIndex &parent ) const
256 {
257  if ( !parent.isValid() )
258  return true; // root item: its children are top level items
259 
260  QgsDataItem *item = dataItem( parent );
261  return item && item->hasChildren();
262 }
263 
264 int QgsBrowserModel::columnCount( const QModelIndex &parent ) const
265 {
266  Q_UNUSED( parent );
267  return 1;
268 }
269 
270 QModelIndex QgsBrowserModel::findPath( QString path, Qt::MatchFlag matchFlag )
271 {
272  return findPath( this, path, matchFlag );
273 }
274 
275 QModelIndex QgsBrowserModel::findPath( QAbstractItemModel *model, QString path, Qt::MatchFlag matchFlag )
276 {
277  if ( !model )
278  return QModelIndex();
279 
280  QModelIndex theIndex; // starting from root
281  bool foundChild = true;
282 
283  while ( foundChild )
284  {
285  foundChild = false; // assume that the next child item will not be found
286 
287  for ( int i = 0; i < model->rowCount( theIndex ); i++ )
288  {
289  QModelIndex idx = model->index( i, 0, theIndex );
290 
291  QString itemPath = model->data( idx, PathRole ).toString();
292  if ( itemPath == path )
293  {
294  QgsDebugMsg( "Arrived " + itemPath );
295  return idx; // we have found the item we have been looking for
296  }
297 
298  // paths are slash separated identifier
299  if ( path.startsWith( itemPath + "/" ) )
300  {
301  foundChild = true;
302  theIndex = idx;
303  break;
304  }
305  }
306  }
307 
308  if ( matchFlag == Qt::MatchStartsWith )
309  return theIndex;
310 
311  QgsDebugMsg( "path not found" );
312  return QModelIndex(); // not found
313 }
314 
316 {
317  // TODO: put items creating currently children in threads to deleteLater (does not seem urget because reload() is not used in QGIS)
318  beginResetModel();
319  removeRootItems();
320  addRootItems();
321  endResetModel();
322 }
323 
324 QModelIndex QgsBrowserModel::index( int row, int column, const QModelIndex &parent ) const
325 {
326  QgsDataItem *p = dataItem( parent );
327  const QVector<QgsDataItem*> &items = p ? p->children() : mRootItems;
328  QgsDataItem *item = items.value( row, 0 );
329  return item ? createIndex( row, column, item ) : QModelIndex();
330 }
331 
333 {
334  QgsDataItem *item = dataItem( index );
335  if ( !item )
336  return QModelIndex();
337 
338  return findItem( item->parent() );
339 }
340 
342 {
343  const QVector<QgsDataItem*> &items = parent ? parent->children() : mRootItems;
344 
345  for ( int i = 0; i < items.size(); i++ )
346  {
347  if ( items[i] == item )
348  return createIndex( i, 0, item );
349 
350  QModelIndex childIndex = findItem( item, items[i] );
351  if ( childIndex.isValid() )
352  return childIndex;
353  }
354 
355  return QModelIndex();
356 }
357 
358 void QgsBrowserModel::beginInsertItems( QgsDataItem *parent, int first, int last )
359 {
360  QgsDebugMsgLevel( "parent mPath = " + parent->path(), 3 );
361  QModelIndex idx = findItem( parent );
362  if ( !idx.isValid() )
363  return;
364  QgsDebugMsgLevel( "valid", 3 );
365  beginInsertRows( idx, first, last );
366  QgsDebugMsgLevel( "end", 3 );
367 }
369 {
370  QgsDebugMsgLevel( "Entered", 3 );
371  endInsertRows();
372 }
373 void QgsBrowserModel::beginRemoveItems( QgsDataItem *parent, int first, int last )
374 {
375  QgsDebugMsgLevel( "parent mPath = " + parent->path(), 3 );
376  QModelIndex idx = findItem( parent );
377  if ( !idx.isValid() )
378  return;
379  beginRemoveRows( idx, first, last );
380 }
382 {
383  QgsDebugMsgLevel( "Entered", 3 );
384  endRemoveRows();
385 }
387 {
388  QgsDebugMsgLevel( "Entered", 3 );
389  QModelIndex idx = findItem( item );
390  if ( !idx.isValid() )
391  return;
392  emit dataChanged( idx, idx );
393 }
395 {
396  QgsDebugMsg( "Entered" );
397  if ( !item )
398  return;
399  QModelIndex idx = findItem( item );
400  if ( !idx.isValid() )
401  return;
402  QgsDebugMsg( QString( "item %1 state changed %2 -> %3" ).arg( item->path() ).arg( oldState ).arg( item->state() ) );
403  emit stateChanged( idx, oldState );
404 }
406 {
407  connect( item, SIGNAL( beginInsertItems( QgsDataItem*, int, int ) ),
408  this, SLOT( beginInsertItems( QgsDataItem*, int, int ) ) );
409  connect( item, SIGNAL( endInsertItems() ),
410  this, SLOT( endInsertItems() ) );
411  connect( item, SIGNAL( beginRemoveItems( QgsDataItem*, int, int ) ),
412  this, SLOT( beginRemoveItems( QgsDataItem*, int, int ) ) );
413  connect( item, SIGNAL( endRemoveItems() ),
414  this, SLOT( endRemoveItems() ) );
415  connect( item, SIGNAL( dataChanged( QgsDataItem* ) ),
416  this, SLOT( itemDataChanged( QgsDataItem* ) ) );
417  connect( item, SIGNAL( stateChanged( QgsDataItem*, QgsDataItem::State ) ),
418  this, SLOT( itemStateChanged( QgsDataItem*, QgsDataItem::State ) ) );
419 }
420 
422 {
423  QStringList types;
424  // In theory the mime type convention is: application/x-vnd.<vendor>.<application>.<type>
425  // but it seems a bit over formalized. Would be an application/x-qgis-uri better?
426  types << "application/x-vnd.qgis.qgis.uri";
427  return types;
428 }
429 
430 QMimeData * QgsBrowserModel::mimeData( const QModelIndexList &indexes ) const
431 {
433  foreach ( const QModelIndex &index, indexes )
434  {
435  if ( index.isValid() )
436  {
437  QgsDataItem* ptr = ( QgsDataItem* ) index.internalPointer();
438  if ( ptr->type() != QgsDataItem::Layer ) continue;
439  QgsLayerItem *layer = ( QgsLayerItem* ) ptr;
440  lst.append( QgsMimeDataUtils::Uri( layer ) );
441  }
442  }
443  return QgsMimeDataUtils::encodeUriList( lst );
444 }
445 
446 bool QgsBrowserModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent )
447 {
448  Q_UNUSED( row );
449  Q_UNUSED( column );
450 
451  QgsDataItem* destItem = dataItem( parent );
452  if ( !destItem )
453  {
454  QgsDebugMsg( "DROP PROBLEM!" );
455  return false;
456  }
457 
458  return destItem->handleDrop( data, action );
459 }
460 
462 {
463  void *v = idx.internalPointer();
464  QgsDataItem *d = reinterpret_cast<QgsDataItem*>( v );
465  Q_ASSERT( !v || d );
466  return d;
467 }
468 
469 bool QgsBrowserModel::canFetchMore( const QModelIndex & parent ) const
470 {
471  QgsDataItem* item = dataItem( parent );
472  // if ( item )
473  // QgsDebugMsg( QString( "path = %1 canFetchMore = %2" ).arg( item->path() ).arg( item && ! item->isPopulated() ) );
474  return ( item && item->state() == QgsDataItem::NotPopulated );
475 }
476 
478 {
479  QgsDebugMsg( "Entered" );
480  QgsDataItem* item = dataItem( parent );
481 
482  if ( !item || item->state() == QgsDataItem::Populating || item->state() == QgsDataItem::Populated )
483  return;
484 
485  QgsDebugMsg( "path = " + item->path() );
486 
487  item->populate();
488 }
489 
490 /* Refresh dir path */
492 {
493  QModelIndex index = findPath( path );
494  refresh( index );
495 }
496 
497 /* Refresh item */
498 void QgsBrowserModel::refresh( const QModelIndex& theIndex )
499 {
500  QgsDataItem *item = dataItem( theIndex );
501  if ( !item || item->state() == QgsDataItem::Populating )
502  return;
503 
504  QgsDebugMsg( "Refresh " + item->path() );
505 
506  item->refresh();
507 }
508 
510 {
511  Q_ASSERT( mFavourites );
512  mFavourites->addDirectory( favDir );
513 }
514 
516 {
517  QgsDirectoryItem *item = dynamic_cast<QgsDirectoryItem *>( dataItem( index ) );
518  if ( !item )
519  return;
520 
521  mFavourites->removeDirectory( item );
522 }
Contains various Favourites directories.
Definition: qgsdataitem.h:445
static unsigned index
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
int localeAwareCompare(const QString &other) const
QVector< QgsDataItem * > children() const
Definition: qgsdataitem.h:180
QString name() const
Definition: qgsdataitem.h:182
QList< T > values() const
QgsDataItem * parent() const
Get item parent.
Definition: qgsdataitem.h:176
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 addDirectory(QString favIcon)
void removeFavourite(const QModelIndex &index)
virtual bool handleDrop(const QMimeData *, Qt::DropAction)
Definition: qgsdataitem.h:143
QgsFavouritesItem * mFavourites
void beginRemoveItems(QgsDataItem *parent, int first, int last)
virtual QIcon icon()
void connectItem(QgsDataItem *item)
void insert(int i, const T &value)
State state() const
virtual void populate(QVector< QgsDataItem * > children)
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's home path.
void setIcon(QIcon icon)
Definition: qgsdataitem.h:192
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
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)
QgsBrowserModel(QObject *parent=0)
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:34
QgsDataItem * dataItem(const QModelIndex &idx) const
QModelIndex findPath(QString path, Qt::MatchFlag matchFlag=Qt::MatchExactly)
Return index of item with given path.
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:104
Creating children in separate thread (populating or refreshing)
Definition: qgsdataitem.h:105
QString path() const
Definition: qgsdataitem.h:184
void * internalPointer() const
Type type() const
Definition: qgsdataitem.h:172
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()
A directory: contains subdirectories and layers.
Definition: qgsdataitem.h:362
QModelIndex findItem(QgsDataItem *item, QgsDataItem *parent=0) const
QModelIndex createIndex(int row, int column, void *ptr) const
static QMimeData * encodeUriList(UriList layers)
Base class for all items in the model.
Definition: qgsdataitem.h:75
iterator end()
void beginInsertRows(const QModelIndex &parent, int first, int last)
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)
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...
void refresh(QString path)
static QgsProject * instance()
access to canonical QgsProject instance
Definition: qgsproject.cpp:352
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.
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...
Item that represents a layer that can be opened with one of the providers.
Definition: qgsdataitem.h:282
virtual QString name()=0
Human-readable name of the provider name.
void itemDataChanged(QgsDataItem *item)
QString toolTip() const
Definition: qgsdataitem.h:196
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:106
QString absolutePath() const
virtual bool acceptDrop()
Definition: qgsdataitem.h:140
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
iterator begin()
This is the interface for those who want to add custom data items to the browser tree.
QList< Key > uniqueKeys() const
typedef ItemFlags
QVector< QgsDataItem * > mRootItems
void addFavouriteDirectory(QString favDir)