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