QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsdataitem.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsdataitem.cpp - Data items
3  -------------------
4  begin : 2011-04-01
5  copyright : (C) 2011 Radim Blazek
6  email : radim dot blazek at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include <QApplication>
19 #include <QtConcurrentMap>
20 #include <QtConcurrentRun>
21 #include <QDateTime>
22 #include <QDir>
23 #include <QFileInfo>
24 #include <QMenu>
25 #include <QMouseEvent>
26 #include <QTreeWidget>
27 #include <QTreeWidgetItem>
28 #include <QVector>
29 #include <QStyle>
30 #include <QSettings>
31 
32 #include "qgis.h"
33 #include "qgsdataitem.h"
34 
35 #include "qgsdataprovider.h"
36 #include "qgslogger.h"
37 #include "qgsproviderregistry.h"
38 #include "qgsconfig.h"
39 
40 // use GDAL VSI mechanism
41 #include "cpl_vsi.h"
42 #include "cpl_string.h"
43 
44 // shared icons
46 {
47  static QIcon icon;
48 
49  if ( icon.isNull() )
50  icon = QgsApplication::getThemeIcon( "/mIconPointLayer.svg" );
51 
52  return icon;
53 }
54 
55 const QIcon &QgsLayerItem::iconLine()
56 {
57  static QIcon icon;
58 
59  if ( icon.isNull() )
60  icon = QgsApplication::getThemeIcon( "/mIconLineLayer.svg" );
61 
62  return icon;
63 }
64 
66 {
67  static QIcon icon;
68 
69  if ( icon.isNull() )
70  icon = QgsApplication::getThemeIcon( "/mIconPolygonLayer.svg" );
71 
72  return icon;
73 }
74 
76 {
77  static QIcon icon;
78 
79  if ( icon.isNull() )
80  icon = QgsApplication::getThemeIcon( "/mIconTableLayer.png" );
81 
82  return icon;
83 }
84 
86 {
87  static QIcon icon;
88 
89  if ( icon.isNull() )
90  icon = QgsApplication::getThemeIcon( "/mIconRaster.svg" );
91 
92  return icon;
93 }
94 
96 {
97  static QIcon icon;
98 
99  if ( icon.isNull() )
100  icon = QgsApplication::getThemeIcon( "/mIconLayer.png" );
101 
102  return icon;
103 }
104 
106 {
107  static QIcon icon;
108 
109  if ( icon.isNull() )
110  icon = QgsApplication::getThemeIcon( "/mIconDbSchema.png" );
111 
112  return icon;
113 }
114 
116 {
117  static QIcon icon;
118 
119  if ( icon.isNull() )
120  {
121  // initialize shared icons
122  QStyle *style = QApplication::style();
123  icon = QIcon( style->standardPixmap( QStyle::SP_DirClosedIcon ) );
124  icon.addPixmap( style->standardPixmap( QStyle::SP_DirOpenIcon ),
125  QIcon::Normal, QIcon::On );
126  }
127 
128  return icon;
129 }
130 
132 {
133  static QIcon icon;
134 
135  if ( icon.isNull() )
136  icon = QgsApplication::getThemeIcon( "/mIconFavourites.png" );
137 
138  return icon;
139 }
140 
141 const QIcon &QgsZipItem::iconZip()
142 {
143  static QIcon icon;
144 
145  if ( icon.isNull() )
146  icon = QgsApplication::getThemeIcon( "/mIconZip.png" );
147 // icon from http://www.softicons.com/free-icons/application-icons/mega-pack-icons-1-by-nikolay-verin/winzip-folder-icon
148 
149  return icon;
150 }
151 
152 QMap<QString, QIcon> QgsDataItem::mIconMap = QMap<QString, QIcon>();
153 
154 int QgsDataItem::mPopulatingCount = 0;
155 QMovie * QgsDataItem::mPopulatingMovie = 0;
156 QIcon QgsDataItem::mPopulatingIcon = QIcon();
157 
158 QgsDataItem::QgsDataItem( QgsDataItem::Type type, QgsDataItem* parent, QString name, QString path )
159 // Do not pass parent to QObject, Qt would delete this when parent is deleted
160  : QObject()
161  , mType( type )
162  , mCapabilities( NoCapabilities )
163  , mParent( parent )
164  , mState( NotPopulated )
165  , mPopulated( false )
166  , mName( name )
167  , mPath( path )
168  , mDeferredDelete( false )
169  , mFutureWatcher( 0 )
170 {
171 }
172 
174 {
175  QgsDebugMsgLevel( QString( "mName = %1 mPath = %2 mChildren.size() = %3" ).arg( mName ).arg( mPath ).arg( mChildren.size() ), 2 );
176  foreach ( QgsDataItem *child, mChildren )
177  {
178  if ( !child ) // should not happen
179  continue;
180  child->deleteLater();
181  }
182  mChildren.clear();
183 
184  if ( mFutureWatcher && !mFutureWatcher->isFinished() )
185  {
186  // this should not usually happen (until the item was deleted directly when createChildren was running)
187  QgsDebugMsg( "mFutureWatcher not finished (should not happen) -> waitForFinished()" );
188  mDeferredDelete = true;
189  mFutureWatcher->waitForFinished();
190  }
191 }
192 
193 QString QgsDataItem::pathComponent( const QString &string )
194 {
195  return QString( string ).replace( QRegExp( "[\\\\/]" ), "|" );
196 }
197 
199 {
200  QgsDebugMsg( "path = " + path() );
201  setParent( 0 ); // also disconnects parent
202  foreach ( QgsDataItem *child, mChildren )
203  {
204  if ( !child ) // should not happen
205  continue;
206  child->deleteLater();
207  }
208  mChildren.clear();
209 
210  if ( mFutureWatcher && !mFutureWatcher->isFinished() )
211  {
212  QgsDebugMsg( "mFutureWatcher not finished -> schedule to delete later" );
213  mDeferredDelete = true;
214  }
215  else
216  {
218  }
219 }
220 
221 void QgsDataItem::deleteLater( QVector<QgsDataItem*> &items )
222 {
223  foreach ( QgsDataItem *item, items )
224  {
225  if ( !item ) // should not happen
226  continue;
227  item->deleteLater();
228  }
229  items.clear();
230 }
231 
232 void QgsDataItem::moveToThread( QThread * targetThread )
233 {
234  // QObject::moveToThread() cannot move objects with parent, but QgsDataItem is not using paren/children from QObject
235  foreach ( QgsDataItem* child, mChildren )
236  {
237  if ( !child ) // should not happen
238  continue;
239  QgsDebugMsg( "moveToThread child " + child->path() );
240  child->QObject::setParent( 0 ); // to be sure
241  child->moveToThread( targetThread );
242  }
243  QObject::moveToThread( targetThread );
244 }
245 
247 {
248  if ( state() == Populating )
249  return mPopulatingIcon;
250 
251  if ( !mIcon.isNull() )
252  return mIcon;
253 
254  if ( !mIconMap.contains( mIconName ) )
256 
257  return mIconMap.value( mIconName );
258 }
259 
260 void QgsDataItem::emitBeginInsertItems( QgsDataItem* parent, int first, int last )
261 {
262  emit beginInsertItems( parent, first, last );
263 }
265 {
266  emit endInsertItems();
267 }
268 void QgsDataItem::emitBeginRemoveItems( QgsDataItem* parent, int first, int last )
269 {
270  emit beginRemoveItems( parent, first, last );
271 }
273 {
274  emit endRemoveItems();
275 }
276 
278 {
279  emit dataChanged( item );
280 }
281 
283 {
284  emit dataChanged( this );
285 }
286 
288 {
289  if ( !item )
290  return;
291  QgsDebugMsg( QString( "item %1 state changed %2 -> %3" ).arg( item->path() ).arg( oldState ).arg( item->state() ) );
292  emit stateChanged( item, oldState );
293 }
294 
295 QVector<QgsDataItem*> QgsDataItem::createChildren()
296 {
297  return QVector<QgsDataItem*>();
298 }
299 
301 {
302  if ( state() == Populated || state() == Populating )
303  return;
304 
305  QgsDebugMsg( "mPath = " + mPath );
306 
308  {
310  }
311  else
312  {
313  setState( Populating );
314  // The watcher must not be created with item (in constructor) because the item may be created in thread and the watcher created in thread does not work correctly.
315  if ( !mFutureWatcher )
316  {
317  mFutureWatcher = new QFutureWatcher< QVector <QgsDataItem*> >( this );
318  }
319  connect( mFutureWatcher, SIGNAL( finished() ), SLOT( childrenCreated() ) );
320  mFutureWatcher->setFuture( QtConcurrent::run( runCreateChildren, this ) );
321  }
322 }
323 
324 // This is expected to be run in a separate thread
325 QVector<QgsDataItem*> QgsDataItem::runCreateChildren( QgsDataItem* item )
326 {
327  QgsDebugMsg( "path = " + item->path() );
328  QTime time;
329  time.start();
330  QVector <QgsDataItem*> children = item->createChildren();
331  QgsDebugMsg( QString( "%1 children created in %2 ms" ).arg( children.size() ).arg( time.elapsed() ) );
332  // Children objects must be pushed to main thread.
333  foreach ( QgsDataItem* child, children )
334  {
335  if ( !child ) // should not happen
336  continue;
337  QgsDebugMsg( "moveToThread child " + child->path() );
338  child->moveToThread( QApplication::instance()->thread() ); // moves also children
339  }
340  QgsDebugMsg( "finished path = " + item->path() );
341  return children;
342 }
343 
345 {
346  QgsDebugMsg( QString( "path = %1 children.size() = %2" ).arg( path() ).arg( mFutureWatcher->result().size() ) );
347 
348  if ( deferredDelete() )
349  {
350  QgsDebugMsg( "Item was scheduled to be deleted later" );
352  return;
353  }
354 
355  if ( mChildren.size() == 0 ) // usually populating but may also be refresh if originaly there were no children
356  {
357  populate( mFutureWatcher->result() );
358  }
359  else // refreshing
360  {
361  refresh( mFutureWatcher->result() );
362  }
363  disconnect( mFutureWatcher, SIGNAL( finished() ), this, SLOT( childrenCreated() ) );
364  emit dataChanged( this ); // to replace loading icon by normal icon
365 }
366 
367 void QgsDataItem::populate( QVector<QgsDataItem*> children )
368 {
369  QgsDebugMsg( "mPath = " + mPath );
370 
371  foreach ( QgsDataItem *child, children )
372  {
373  if ( !child ) // should not happen
374  continue;
375  // update after thread finished -> refresh
376  addChildItem( child, true );
377  }
378  setState( Populated );
379 }
380 
382 {
383  QgsDebugMsg( "mPath = " + mPath );
384 
385  foreach ( QgsDataItem *child, mChildren )
386  {
387  QgsDebugMsg( "remove " + child->path() );
388  child->depopulate(); // recursive
389  deleteChildItem( child );
390  }
392 }
393 
395 {
396  if ( state() == Populating )
397  return;
398 
399  QgsDebugMsg( "mPath = " + mPath );
400 
402  {
403  refresh( createChildren() );
404  }
405  else
406  {
407  setState( Populating );
408  if ( !mFutureWatcher )
409  {
410  mFutureWatcher = new QFutureWatcher< QVector <QgsDataItem*> >( this );
411  }
412  connect( mFutureWatcher, SIGNAL( finished() ), SLOT( childrenCreated() ) );
413  mFutureWatcher->setFuture( QtConcurrent::run( runCreateChildren, this ) );
414  }
415 }
416 
417 void QgsDataItem::refresh( QVector<QgsDataItem*> children )
418 {
419  QgsDebugMsgLevel( "mPath = " + mPath, 2 );
420 
421  // Remove no more present children
422  QVector<QgsDataItem*> remove;
423  foreach ( QgsDataItem *child, mChildren )
424  {
425  if ( !child ) // should not happen
426  continue;
427  if ( findItem( children, child ) >= 0 )
428  continue;
429  remove.append( child );
430  }
431  foreach ( QgsDataItem *child, remove )
432  {
433  QgsDebugMsg( "remove " + child->path() );
434  deleteChildItem( child );
435  }
436 
437  // Add new children
438  foreach ( QgsDataItem *child, children )
439  {
440  if ( !child ) // should not happen
441  continue;
442 
443  int index = findItem( mChildren, child );
444  if ( index >= 0 )
445  {
446  // Refresh recursively (some providers may create more generations of descendants)
447  if ( !( child->capabilities2() & QgsDataItem::Fertile ) )
448  {
449  // The child cannot createChildren() itself
450  mChildren.value( index )->refresh( child->children() );
451  }
452 
453  child->deleteLater();
454  continue;
455  }
456  addChildItem( child, true );
457  }
458  setState( Populated );
459 }
460 
462 {
463  return mChildren.size();
464 }
466 {
467  return ( state() == Populated ? mChildren.count() > 0 : true );
468 }
469 
471 {
472  if ( mParent )
473  {
474  disconnect( this, 0, mParent, 0 );
475  }
476  if ( parent )
477  {
478  connect( this, SIGNAL( beginInsertItems( QgsDataItem*, int, int ) ),
479  parent, SLOT( emitBeginInsertItems( QgsDataItem*, int, int ) ) );
480  connect( this, SIGNAL( endInsertItems() ),
481  parent, SLOT( emitEndInsertItems() ) );
482  connect( this, SIGNAL( beginRemoveItems( QgsDataItem*, int, int ) ),
483  parent, SLOT( emitBeginRemoveItems( QgsDataItem*, int, int ) ) );
484  connect( this, SIGNAL( endRemoveItems() ),
485  parent, SLOT( emitEndRemoveItems() ) );
486  connect( this, SIGNAL( dataChanged( QgsDataItem* ) ),
487  parent, SLOT( emitDataChanged( QgsDataItem* ) ) );
488  connect( this, SIGNAL( stateChanged( QgsDataItem*, QgsDataItem::State ) ),
489  parent, SLOT( emitStateChanged( QgsDataItem*, QgsDataItem::State ) ) );
490  }
491  mParent = parent;
492 }
493 
494 void QgsDataItem::addChildItem( QgsDataItem * child, bool refresh )
495 {
496  Q_ASSERT( child );
497  QgsDebugMsg( QString( "path = %1 add child #%2 - %3 - %4" ).arg( mPath ).arg( mChildren.size() ).arg( child->mName ).arg( child->mType ) );
498 
499  int i;
500  if ( type() == Directory )
501  {
502  for ( i = 0; i < mChildren.size(); i++ )
503  {
504  // sort items by type, so directories are before data items
505  if ( mChildren[i]->mType == child->mType &&
506  mChildren[i]->mName.localeAwareCompare( child->mName ) > 0 )
507  break;
508  }
509  }
510  else
511  {
512  for ( i = 0; i < mChildren.size(); i++ )
513  {
514  if ( mChildren[i]->mName.localeAwareCompare( child->mName ) >= 0 )
515  break;
516  }
517  }
518 
519  if ( refresh )
520  emit beginInsertItems( this, i, i );
521 
522  mChildren.insert( i, child );
523  child->setParent( this );
524 
525  if ( refresh )
526  emit endInsertItems();
527 }
529 {
530  QgsDebugMsgLevel( "mName = " + child->mName, 2 );
531  int i = mChildren.indexOf( child );
532  Q_ASSERT( i >= 0 );
533  emit beginRemoveItems( this, i, i );
534  mChildren.remove( i );
535  child->deleteLater();
536  emit endRemoveItems();
537 }
538 
540 {
541  QgsDebugMsgLevel( "mName = " + child->mName, 2 );
542  int i = mChildren.indexOf( child );
543  Q_ASSERT( i >= 0 );
544  emit beginRemoveItems( this, i, i );
545  mChildren.remove( i );
546  emit endRemoveItems();
547  child->setParent( 0 );
548  return child;
549 }
550 
551 int QgsDataItem::findItem( QVector<QgsDataItem*> items, QgsDataItem * item )
552 {
553  for ( int i = 0; i < items.size(); i++ )
554  {
555  Q_ASSERT_X( items[i], "findItem", QString( "item %1 is NULL" ).arg( i ).toAscii() );
556  QgsDebugMsgLevel( QString::number( i ) + " : " + items[i]->mPath + " x " + item->mPath, 2 );
557  if ( items[i]->equal( item ) )
558  return i;
559  }
560  return -1;
561 }
562 
563 bool QgsDataItem::equal( const QgsDataItem *other )
564 {
565  if ( metaObject()->className() == other->metaObject()->className() &&
566  mPath == other->path() )
567  {
568  return true;
569  }
570  return false;
571 }
572 
574 {
575  mPopulatingIcon = QIcon( mPopulatingMovie->currentPixmap() );
576 }
577 
579 {
580  // for backward compatibility (if subclass set mPopulated directly)
581  // TODO: remove in 3.0
582  if ( mPopulated )
583  return Populated;
584  return mState;
585 }
586 
588 {
589  QgsDebugMsg( QString( "item %1 set state %2 -> %3" ).arg( path() ).arg( this->state() ).arg( state ) );
590  if ( state == mState )
591  return;
592 
593  State oldState = mState;
594 
595  if ( state == Populating ) // start loading
596  {
597  if ( !mPopulatingMovie )
598  {
599  // QApplication as parent to ensure that it is deleted before QApplication
600  mPopulatingMovie = new QMovie( QApplication::instance() );
601  mPopulatingMovie->setFileName( QgsApplication::iconPath( "/mIconLoading.gif" ) );
602  mPopulatingMovie->setCacheMode( QMovie::CacheAll );
603  connect( mPopulatingMovie, SIGNAL( frameChanged( int ) ), SLOT( setPopulatingIcon() ) );
604  }
605  connect( mPopulatingMovie, SIGNAL( frameChanged( int ) ), SLOT( emitDataChanged() ) );
606  mPopulatingCount++;
607  mPopulatingMovie->setPaused( false );
608  }
609  else if ( mState == Populating && mPopulatingMovie ) // stop loading
610  {
611  disconnect( mPopulatingMovie, SIGNAL( frameChanged( int ) ), this, SLOT( emitDataChanged() ) );
612  mPopulatingCount--;
613  if ( mPopulatingCount == 0 )
614  {
615  mPopulatingMovie->setPaused( true );
616  }
617  }
618 
619  mState = state;
620  // for backward compatibility (if subclass access mPopulated directly)
621  // TODO: remove in 3.0
622  mPopulated = state == Populated;
623 
624  emit stateChanged( this, oldState );
625 }
626 
627 // ---------------------------------------------------------------------
628 
629 QgsLayerItem::QgsLayerItem( QgsDataItem* parent, QString name, QString path, QString uri, LayerType layerType, QString providerKey )
630  : QgsDataItem( Layer, parent, name, path )
631  , mProviderKey( providerKey )
632  , mUri( uri )
633  , mLayerType( layerType )
634 {
635  switch ( layerType )
636  {
637  case Point: mIconName = "/mIconPointLayer.svg"; break;
638  case Line: mIconName = "/mIconLineLayer.svg"; break;
639  case Polygon: mIconName = "/mIconPolygonLayer.svg"; break;
640  // TODO add a new icon for generic Vector layers
641  case Vector : mIconName = "/mIconPolygonLayer.svg"; break;
642  case TableLayer: mIconName = "/mIconTableLayer.png"; break;
643  case Raster: mIconName = "/mIconRaster.svg"; break;
644  default: mIconName = "/mIconLayer.png"; break;
645  }
646 }
647 
649 {
653 }
654 
655 bool QgsLayerItem::equal( const QgsDataItem *other )
656 {
657  //QgsDebugMsg ( mPath + " x " + other->mPath );
658  if ( type() != other->type() )
659  {
660  return false;
661  }
662  //const QgsLayerItem *o = qobject_cast<const QgsLayerItem *> ( other );
663  const QgsLayerItem *o = dynamic_cast<const QgsLayerItem *>( other );
664  if ( !o )
665  return false;
666 
667  return ( mPath == o->mPath && mName == o->mName && mUri == o->mUri && mProviderKey == o->mProviderKey );
668 }
669 
670 // ---------------------------------------------------------------------
671 QgsDataCollectionItem::QgsDataCollectionItem( QgsDataItem* parent, QString name, QString path )
672  : QgsDataItem( Collection, parent, name, path )
673 {
675  mIconName = "/mIconDbSchema.png";
676 }
677 
679 {
680  QgsDebugMsgLevel( "mName = " + mName + " mPath = " + mPath, 2 );
681 
682 // Do not delete children, children are deleted by QObject parent
683 #if 0
684  foreach ( QgsDataItem* i, mChildren )
685  {
686  QgsDebugMsgLevel( QString( "delete child = 0x%0" ).arg(( qlonglong )i, 8, 16, QLatin1Char( '0' ) ), 2 );
687  delete i;
688  }
689 #endif
690 }
691 
692 //-----------------------------------------------------------------------
693 // QVector<QgsDataProvider*> QgsDirectoryItem::mProviders = QVector<QgsDataProvider*>();
694 QVector<QLibrary*> QgsDirectoryItem::mLibraries = QVector<QLibrary*>();
695 
696 QgsDirectoryItem::QgsDirectoryItem( QgsDataItem* parent, QString name, QString path )
697  : QgsDataCollectionItem( parent, name, path )
698  , mDirPath( path )
699  , mFileSystemWatcher( 0 )
700  , mRefreshLater( false )
701 {
702  mType = Directory;
703  init();
704 }
705 
706 QgsDirectoryItem::QgsDirectoryItem( QgsDataItem* parent, QString name, QString dirPath, QString path )
707  : QgsDataCollectionItem( parent, name, path )
708  , mDirPath( dirPath )
709  , mFileSystemWatcher( 0 )
710  , mRefreshLater( false )
711 {
712  mType = Directory;
713  init();
714 }
715 
717 {
718  if ( mLibraries.size() > 0 )
719  return;
720 
721  QStringList keys = QgsProviderRegistry::instance()->providerList();
722  QStringList::const_iterator i;
723  for ( i = keys.begin(); i != keys.end(); ++i )
724  {
725  QString k( *i );
726  // some providers hangs with empty uri (Postgis) etc...
727  // -> using libraries directly
728  QLibrary *library = QgsProviderRegistry::instance()->providerLibrary( k );
729  if ( library )
730  {
731  dataCapabilities_t * dataCapabilities = ( dataCapabilities_t * ) cast_to_fptr( library->resolve( "dataCapabilities" ) );
732  if ( !dataCapabilities )
733  {
734  QgsDebugMsg( library->fileName() + " does not have dataCapabilities" );
735  continue;
736  }
737  if ( dataCapabilities() == QgsDataProvider::NoDataCapabilities )
738  {
739  QgsDebugMsg( library->fileName() + " has NoDataCapabilities" );
740  continue;
741  }
742 
743  QgsDebugMsg( QString( "%1 dataCapabilities : %2" ).arg( library->fileName() ).arg( dataCapabilities() ) );
744  mLibraries.append( library );
745  }
746  else
747  {
748  //QgsDebugMsg ( "Cannot get provider " + k );
749  }
750  }
751 }
752 
754 {
755 }
756 
758 {
759  if ( state() == Populating )
760  return populatingIcon();
761  return iconDir();
762 }
763 
764 QVector<QgsDataItem*> QgsDirectoryItem::createChildren()
765 {
766  QVector<QgsDataItem*> children;
767  QDir dir( mDirPath );
768  QSettings settings;
769 
770  QStringList entries = dir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name | QDir::IgnoreCase );
771  foreach ( QString subdir, entries )
772  {
773  if ( mRefreshLater )
774  {
775  deleteLater( children );
776  return children;
777  }
778  QString subdirPath = dir.absoluteFilePath( subdir );
779  QgsDebugMsgLevel( QString( "creating subdir: %1" ).arg( subdirPath ), 2 );
780 
781  QString path = mPath + "/" + subdir; // may differ from subdirPath
782  QgsDirectoryItem *item = new QgsDirectoryItem( this, subdir, subdirPath, path );
783  // propagate signals up to top
784 
785  children.append( item );
786  }
787 
788  QStringList fileEntries = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot | QDir::Files, QDir::Name );
789  foreach ( QString name, fileEntries )
790  {
791  if ( mRefreshLater )
792  {
793  deleteLater( children );
794  return children;
795  }
796 
797  QString path = dir.absoluteFilePath( name );
798  QFileInfo fileInfo( path );
799 
800  // vsizip support was added to GDAL/OGR 1.6 but GDAL_VERSION_NUM not available here
801  // so we assume it's available anyway
802  {
803  QgsDataItem * item = QgsZipItem::itemFromPath( this, path, name, mPath + "/" + name );
804  if ( item )
805  {
806  children.append( item );
807  continue;
808  }
809  }
810 
811  foreach ( QLibrary *library, mLibraries )
812  {
813  // we could/should create separate list of providers for each purpose
814 
815  // TODO: use existing fileVectorFilters(),directoryDrivers() ?
816  dataCapabilities_t * dataCapabilities = ( dataCapabilities_t * ) cast_to_fptr( library->resolve( "dataCapabilities" ) );
817  if ( !dataCapabilities )
818  {
819  continue;
820  }
821 
822  int capabilities = dataCapabilities();
823 
824  if ( !(( fileInfo.isFile() && ( capabilities & QgsDataProvider::File ) ) ||
825  ( fileInfo.isDir() && ( capabilities & QgsDataProvider::Dir ) ) ) )
826  {
827  continue;
828  }
829 
830  dataItem_t * dataItem = ( dataItem_t * ) cast_to_fptr( library->resolve( "dataItem" ) );
831  if ( ! dataItem )
832  {
833  QgsDebugMsg( library->fileName() + " does not have dataItem" );
834  continue;
835  }
836 
837  QgsDataItem * item = dataItem( path, this );
838  if ( item )
839  {
840  children.append( item );
841  }
842  }
843  }
844 
845  return children;
846 }
847 
849 {
850  QgsDebugMsg( "Entered" );
852 
853  if ( state == Populated )
854  {
855  if ( !mFileSystemWatcher )
856  {
857  mFileSystemWatcher = new QFileSystemWatcher( this );
858  mFileSystemWatcher->addPath( mDirPath );
859  connect( mFileSystemWatcher, SIGNAL( directoryChanged( const QString & ) ), SLOT( directoryChanged() ) );
860  }
861  }
862  else if ( state == NotPopulated )
863  {
864  if ( mFileSystemWatcher )
865  {
866  delete mFileSystemWatcher;
867  mFileSystemWatcher = 0;
868  }
869  }
870 }
871 
873 {
874  QgsDebugMsg( "Entered" );
875  if ( state() == Populating )
876  {
877  // schedule to refresh later, because refres() simply returns if Populating
878  mRefreshLater = true;
879  }
880  else
881  {
882  refresh();
883  }
884 }
885 
887 {
888  QgsDebugMsg( QString( "mRefreshLater = %1" ).arg( mRefreshLater ) );
889 
890  if ( mRefreshLater )
891  {
892  QgsDebugMsg( "directory changed during createChidren() -> refresh() again" );
893  mRefreshLater = false;
894  setState( Populated );
895  refresh();
896  }
897  else
898  {
900  }
901 }
902 
904 {
905  //QgsDebugMsg ( mPath + " x " + other->mPath );
906  if ( type() != other->type() )
907  {
908  return false;
909  }
910  return ( path() == other->path() );
911 }
912 
914 {
915  return new QgsDirectoryParamWidget( mPath );
916 }
917 
918 QgsDirectoryParamWidget::QgsDirectoryParamWidget( QString path, QWidget* parent )
919  : QTreeWidget( parent )
920 {
921  setRootIsDecorated( false );
922 
923  // name, size, date, permissions, owner, group, type
924  setColumnCount( 7 );
925  QStringList labels;
926  labels << tr( "Name" ) << tr( "Size" ) << tr( "Date" ) << tr( "Permissions" ) << tr( "Owner" ) << tr( "Group" ) << tr( "Type" );
927  setHeaderLabels( labels );
928 
929  QStyle* style = QApplication::style();
930  QIcon iconDirectory = QIcon( style->standardPixmap( QStyle::SP_DirClosedIcon ) );
931  QIcon iconFile = QIcon( style->standardPixmap( QStyle::SP_FileIcon ) );
932  QIcon iconLink = QIcon( style->standardPixmap( QStyle::SP_FileLinkIcon ) ); // TODO: symlink to directory?
933 
934  QList<QTreeWidgetItem *> items;
935 
936  QDir dir( path );
937  QStringList entries = dir.entryList( QDir::AllEntries | QDir::NoDotAndDotDot, QDir::Name | QDir::IgnoreCase );
938  foreach ( QString name, entries )
939  {
940  QFileInfo fi( dir.absoluteFilePath( name ) );
941  QStringList texts;
942  texts << name;
943  QString size;
944  if ( fi.size() > 1024 )
945  {
946  size = size.sprintf( "%.1f KiB", fi.size() / 1024.0 );
947  }
948  else if ( fi.size() > 1.048576e6 )
949  {
950  size = size.sprintf( "%.1f MiB", fi.size() / 1.048576e6 );
951  }
952  else
953  {
954  size = QString( "%1 B" ).arg( fi.size() );
955  }
956  texts << size;
957  texts << fi.lastModified().toString( Qt::SystemLocaleShortDate );
958  QString perm;
959  perm += fi.permission( QFile::ReadOwner ) ? 'r' : '-';
960  perm += fi.permission( QFile::WriteOwner ) ? 'w' : '-';
961  perm += fi.permission( QFile::ExeOwner ) ? 'x' : '-';
962  // QFile::ReadUser, QFile::WriteUser, QFile::ExeUser
963  perm += fi.permission( QFile::ReadGroup ) ? 'r' : '-';
964  perm += fi.permission( QFile::WriteGroup ) ? 'w' : '-';
965  perm += fi.permission( QFile::ExeGroup ) ? 'x' : '-';
966  perm += fi.permission( QFile::ReadOther ) ? 'r' : '-';
967  perm += fi.permission( QFile::WriteOther ) ? 'w' : '-';
968  perm += fi.permission( QFile::ExeOther ) ? 'x' : '-';
969  texts << perm;
970 
971  texts << fi.owner();
972  texts << fi.group();
973 
974  QString type;
975  QIcon icon;
976  if ( fi.isDir() )
977  {
978  type = tr( "folder" );
979  icon = iconDirectory;
980  }
981  else if ( fi.isFile() )
982  {
983  type = tr( "file" );
984  icon = iconFile;
985  }
986  else if ( fi.isSymLink() )
987  {
988  type = tr( "link" );
989  icon = iconLink;
990  }
991 
992  texts << type;
993 
994  QTreeWidgetItem *item = new QTreeWidgetItem( texts );
995  item->setIcon( 0, icon );
996  items << item;
997  }
998 
999  addTopLevelItems( items );
1000 
1001  // hide columns that are not requested
1002  QSettings settings;
1003  QList<QVariant> lst = settings.value( "/dataitem/directoryHiddenColumns" ).toList();
1004  foreach ( QVariant colVariant, lst )
1005  {
1006  setColumnHidden( colVariant.toInt(), true );
1007  }
1008 }
1009 
1011 {
1012  if ( event->button() == Qt::RightButton )
1013  {
1014  // show the popup menu
1015  QMenu popupMenu;
1016 
1017  QStringList labels;
1018  labels << tr( "Name" ) << tr( "Size" ) << tr( "Date" ) << tr( "Permissions" ) << tr( "Owner" ) << tr( "Group" ) << tr( "Type" );
1019  for ( int i = 0; i < labels.count(); i++ )
1020  {
1021  QAction* action = popupMenu.addAction( labels[i], this, SLOT( showHideColumn() ) );
1022  action->setObjectName( QString::number( i ) );
1023  action->setCheckable( true );
1024  action->setChecked( !isColumnHidden( i ) );
1025  }
1026 
1027  popupMenu.exec( event->globalPos() );
1028  }
1029 }
1030 
1032 {
1033  QAction* action = qobject_cast<QAction*>( sender() );
1034  if ( !action )
1035  return; // something is wrong
1036 
1037  int columnIndex = action->objectName().toInt();
1038  setColumnHidden( columnIndex, !isColumnHidden( columnIndex ) );
1039 
1040  // save in settings
1041  QSettings settings;
1042  QList<QVariant> lst;
1043  for ( int i = 0; i < columnCount(); i++ )
1044  {
1045  if ( isColumnHidden( i ) )
1046  lst.append( QVariant( i ) );
1047  }
1048  settings.setValue( "/dataitem/directoryHiddenColumns", lst );
1049 }
1050 
1051 
1052 QgsErrorItem::QgsErrorItem( QgsDataItem* parent, QString error, QString path )
1053  : QgsDataItem( QgsDataItem::Error, parent, error, path )
1054 {
1055  mIconName = "/mIconDelete.png";
1056 
1057  setState( Populated ); // no more children
1058 }
1059 
1061 {
1062 }
1063 
1064 QgsFavouritesItem::QgsFavouritesItem( QgsDataItem* parent, QString name, QString path )
1065  : QgsDataCollectionItem( parent, name, "favourites:" )
1066 {
1067  Q_UNUSED( path );
1068  mCapabilities |= Fast;
1069  mType = Favourites;
1070  mIconName = "/mIconFavourites.png";
1071  populate();
1072 }
1073 
1075 {
1076 }
1077 
1078 QVector<QgsDataItem*> QgsFavouritesItem::createChildren()
1079 {
1080  QVector<QgsDataItem*> children;
1081 
1082  QSettings settings;
1083  QStringList favDirs = settings.value( "/browser/favourites", QVariant() ).toStringList();
1084 
1085  foreach ( QString favDir, favDirs )
1086  {
1087  QString pathName = pathComponent( favDir );
1088  QgsDataItem *item = new QgsDirectoryItem( this, favDir, favDir, mPath + "/" + pathName );
1089  if ( item )
1090  {
1091  children.append( item );
1092  }
1093  }
1094 
1095  return children;
1096 }
1097 
1098 void QgsFavouritesItem::addDirectory( QString favDir )
1099 {
1100  QSettings settings;
1101  QStringList favDirs = settings.value( "/browser/favourites" ).toStringList();
1102  favDirs.append( favDir );
1103  settings.setValue( "/browser/favourites", favDirs );
1104 
1105  if ( state() == Populated )
1106  {
1107  QString pathName = pathComponent( favDir );
1108  addChildItem( new QgsDirectoryItem( this, favDir, favDir, mPath + "/" + pathName ), true );
1109  }
1110 }
1111 
1113 {
1114  if ( !item )
1115  return;
1116 
1117  QSettings settings;
1118  QStringList favDirs = settings.value( "/browser/favourites" ).toStringList();
1119  favDirs.removeAll( item->dirPath() );
1120  settings.setValue( "/browser/favourites", favDirs );
1121 
1122  int idx = findItem( mChildren, item );
1123  if ( idx < 0 )
1124  {
1125  QgsDebugMsg( QString( "favourites item %1 not found" ).arg( item->path() ) );
1126  return;
1127  }
1128 
1129  if ( state() == Populated )
1130  deleteChildItem( mChildren[idx] );
1131 }
1132 
1133 //-----------------------------------------------------------------------
1134 QStringList QgsZipItem::mProviderNames = QStringList();
1135 QVector<dataItem_t *> QgsZipItem::mDataItemPtr = QVector<dataItem_t*>();
1136 
1137 
1138 QgsZipItem::QgsZipItem( QgsDataItem* parent, QString name, QString path )
1139  : QgsDataCollectionItem( parent, name, path )
1140 {
1141  mFilePath = path;
1142  init();
1143 }
1144 
1145 QgsZipItem::QgsZipItem( QgsDataItem* parent, QString name, QString filePath, QString path )
1146  : QgsDataCollectionItem( parent, name, path )
1147  , mFilePath( filePath )
1148 {
1149  init();
1150 }
1151 
1152 void QgsZipItem::init()
1153 {
1154  mType = Collection; //Zip??
1155  mIconName = "/mIconZip.png";
1157 
1158  if ( mProviderNames.size() == 0 )
1159  {
1160  // QStringList keys = QgsProviderRegistry::instance()->providerList();
1161  // only use GDAL and OGR providers as we use the VSIFILE mechanism
1162  QStringList keys;
1163  // keys << "ogr" << "gdal";
1164  keys << "gdal" << "ogr";
1165 
1166  QStringList::const_iterator i;
1167  for ( i = keys.begin(); i != keys.end(); ++i )
1168  {
1169  QString k( *i );
1170  QgsDebugMsg( "provider " + k );
1171  // some providers hangs with empty uri (Postgis) etc...
1172  // -> using libraries directly
1173  QLibrary *library = QgsProviderRegistry::instance()->providerLibrary( k );
1174  if ( library )
1175  {
1176  dataCapabilities_t * dataCapabilities = ( dataCapabilities_t * ) cast_to_fptr( library->resolve( "dataCapabilities" ) );
1177  if ( !dataCapabilities )
1178  {
1179  QgsDebugMsg( library->fileName() + " does not have dataCapabilities" );
1180  continue;
1181  }
1182  if ( dataCapabilities() == QgsDataProvider::NoDataCapabilities )
1183  {
1184  QgsDebugMsg( library->fileName() + " has NoDataCapabilities" );
1185  continue;
1186  }
1187  QgsDebugMsg( QString( "%1 dataCapabilities : %2" ).arg( library->fileName() ).arg( dataCapabilities() ) );
1188 
1189  dataItem_t * dataItem = ( dataItem_t * ) cast_to_fptr( library->resolve( "dataItem" ) );
1190  if ( ! dataItem )
1191  {
1192  QgsDebugMsg( library->fileName() + " does not have dataItem" );
1193  continue;
1194  }
1195 
1196  // mLibraries.append( library );
1197  mDataItemPtr.append( dataItem );
1198  mProviderNames.append( k );
1199  }
1200  else
1201  {
1202  //QgsDebugMsg ( "Cannot get provider " + k );
1203  }
1204  }
1205  }
1206 
1207 }
1208 
1210 {
1211 }
1212 
1213 // internal function to scan a vsidir (zip or tar file) recursively
1214 // GDAL trunk has this since r24423 (05/16/12) - VSIReadDirRecursive()
1215 // use a copy of the function internally for now,
1216 // but use char ** and CSLAddString, because CPLStringList was added in gdal-1.9
1217 char **VSIReadDirRecursive1( const char *pszPath )
1218 {
1219  // CPLStringList oFiles = NULL;
1220  char **papszOFiles = NULL;
1221  char **papszFiles1 = NULL;
1222  char **papszFiles2 = NULL;
1223  VSIStatBufL psStatBuf;
1224  CPLString osTemp1, osTemp2;
1225  int i, j;
1226  int nCount1, nCount2;
1227 
1228  // get listing
1229  papszFiles1 = VSIReadDir( pszPath );
1230  if ( ! papszFiles1 )
1231  return NULL;
1232 
1233  // get files and directories inside listing
1234  nCount1 = CSLCount( papszFiles1 );
1235  for ( i = 0; i < nCount1; i++ )
1236  {
1237  // build complete file name for stat
1238  osTemp1.clear();
1239  osTemp1.append( pszPath );
1240  osTemp1.append( "/" );
1241  osTemp1.append( papszFiles1[i] );
1242 
1243  // if is file, add it
1244  if ( VSIStatL( osTemp1.c_str(), &psStatBuf ) == 0 &&
1245  VSI_ISREG( psStatBuf.st_mode ) )
1246  {
1247  // oFiles.AddString( papszFiles1[i] );
1248  papszOFiles = CSLAddString( papszOFiles, papszFiles1[i] );
1249  }
1250  else if ( VSIStatL( osTemp1.c_str(), &psStatBuf ) == 0 &&
1251  VSI_ISDIR( psStatBuf.st_mode ) )
1252  {
1253  // add directory entry
1254  osTemp2.clear();
1255  osTemp2.append( papszFiles1[i] );
1256  osTemp2.append( "/" );
1257  // oFiles.AddString( osTemp2.c_str() );
1258  papszOFiles = CSLAddString( papszOFiles, osTemp2.c_str() );
1259 
1260  // recursively add files inside directory
1261  papszFiles2 = VSIReadDirRecursive1( osTemp1.c_str() );
1262  if ( papszFiles2 )
1263  {
1264  nCount2 = CSLCount( papszFiles2 );
1265  for ( j = 0; j < nCount2; j++ )
1266  {
1267  osTemp2.clear();
1268  osTemp2.append( papszFiles1[i] );
1269  osTemp2.append( "/" );
1270  osTemp2.append( papszFiles2[j] );
1271  // oFiles.AddString( osTemp2.c_str() );
1272  papszOFiles = CSLAddString( papszOFiles, osTemp2.c_str() );
1273  }
1274  CSLDestroy( papszFiles2 );
1275  }
1276  }
1277  }
1278  CSLDestroy( papszFiles1 );
1279 
1280  // return oFiles.StealList();
1281  return papszOFiles;
1282 }
1283 
1284 QVector<QgsDataItem*> QgsZipItem::createChildren()
1285 {
1286  QVector<QgsDataItem*> children;
1287  QString tmpPath;
1288  QSettings settings;
1289  QString scanZipSetting = settings.value( "/qgis/scanZipInBrowser2", "basic" ).toString();
1290 
1291  mZipFileList.clear();
1292 
1293  QgsDebugMsgLevel( QString( "mFilePath = %1 path = %2 name= %3 scanZipSetting= %4 vsiPrefix= %5" ).arg( mFilePath ).arg( path() ).arg( name() ).arg( scanZipSetting ).arg( mVsiPrefix ), 2 );
1294 
1295  // if scanZipBrowser == no: skip to the next file
1296  if ( scanZipSetting == "no" )
1297  {
1298  return children;
1299  }
1300 
1301  // first get list of files
1302  getZipFileList();
1303 
1304  // loop over files inside zip
1305  foreach ( QString fileName, mZipFileList )
1306  {
1307  QFileInfo info( fileName );
1308  tmpPath = mVsiPrefix + mFilePath + "/" + fileName;
1309  QgsDebugMsgLevel( "tmpPath = " + tmpPath, 3 );
1310 
1311  // foreach( dataItem_t *dataItem, mDataItemPtr )
1312  for ( int i = 0; i < mProviderNames.size(); i++ )
1313  {
1314  // ugly hack to remove .dbf file if there is a .shp file
1315  if ( mProviderNames[i] == "ogr" )
1316  {
1317  if ( info.suffix().toLower() == "dbf" )
1318  {
1319  if ( mZipFileList.indexOf( fileName.left( fileName.count() - 4 ) + ".shp" ) != -1 )
1320  continue;
1321  }
1322  if ( info.completeSuffix().toLower() == "shp.xml" )
1323  {
1324  continue;
1325  }
1326  }
1327 
1328  // try to get data item from provider
1329  dataItem_t *dataItem = mDataItemPtr[i];
1330  if ( dataItem )
1331  {
1332  QgsDebugMsgLevel( QString( "trying to load item %1 with %2" ).arg( tmpPath ).arg( mProviderNames[i] ), 3 );
1333  QgsDataItem * item = dataItem( tmpPath, this );
1334  if ( item )
1335  {
1336  QgsDebugMsgLevel( "loaded item", 3 );
1337  // the item comes with zipped file name, set the name to relative path within zip file
1338  item->setName( fileName );
1339  children.append( item );
1340  break;
1341  }
1342  else
1343  {
1344  QgsDebugMsgLevel( "not loaded item", 3 );
1345  }
1346  }
1347  }
1348 
1349  }
1350 
1351  return children;
1352 }
1353 
1354 QgsDataItem* QgsZipItem::itemFromPath( QgsDataItem* parent, QString path, QString name )
1355 {
1356  return itemFromPath( parent, path, name, path );
1357 }
1358 
1359 QgsDataItem* QgsZipItem::itemFromPath( QgsDataItem* parent, QString filePath, QString name, QString path )
1360 {
1361  QSettings settings;
1362  QString scanZipSetting = settings.value( "/qgis/scanZipInBrowser2", "basic" ).toString();
1363  int zipFileCount = 0;
1364  QStringList zipFileList;
1365  QFileInfo fileInfo( filePath );
1366  QString vsiPrefix = QgsZipItem::vsiPrefix( filePath );
1367  QgsZipItem * zipItem = 0;
1368  bool populated = false;
1369 
1370  QgsDebugMsgLevel( QString( "path = %1 name= %2 scanZipSetting= %3 vsiPrefix= %4" ).arg( path ).arg( name ).arg( scanZipSetting ).arg( vsiPrefix ), 3 );
1371 
1372  // don't scan if scanZipBrowser == no
1373  if ( scanZipSetting == "no" )
1374  return 0;
1375 
1376  // don't scan if this file is not a /vsizip/ or /vsitar/ item
1377  if (( vsiPrefix != "/vsizip/" && vsiPrefix != "/vsitar/" ) )
1378  return 0;
1379 
1380  zipItem = new QgsZipItem( parent, name, filePath, path );
1381 
1382  if ( zipItem )
1383  {
1384  // force populate zipItem if it has less than 10 items and is not a .tgz or .tar.gz file (slow loading)
1385  // for other items populating will be delayed until item is opened
1386  // this might be polluting the tree with empty items but is necessary for performance reasons
1387  // could also accept all files smaller than a certain size and add options for file count and/or size
1388 
1389  // first get list of files inside .zip or .tar files
1390  if ( path.endsWith( ".zip", Qt::CaseInsensitive ) ||
1391  path.endsWith( ".tar", Qt::CaseInsensitive ) )
1392  {
1393  zipFileList = zipItem->getZipFileList();
1394  }
1395  // force populate if less than 10 items
1396  if ( zipFileList.count() > 0 && zipFileList.count() <= 10 )
1397  {
1398  zipItem->populate( zipItem->createChildren() );
1399  populated = true; // there is no QgsDataItem::isPopulated() function
1400  QgsDebugMsgLevel( QString( "Got zipItem with %1 children, path=%2, name=%3" ).arg( zipItem->rowCount() ).arg( zipItem->path() ).arg( zipItem->name() ), 3 );
1401  }
1402  else
1403  {
1404  QgsDebugMsgLevel( QString( "Delaying populating zipItem with path=%1, name=%2" ).arg( zipItem->path() ).arg( zipItem->name() ), 3 );
1405  }
1406  }
1407 
1408  // only display if has children or if is not populated
1409  if ( zipItem && ( !populated || zipItem->rowCount() > 1 ) )
1410  {
1411  QgsDebugMsgLevel( "returning zipItem", 3 );
1412  return zipItem;
1413  }
1414  // if 1 or 0 child found, create a single data item using the normal path or the full path given by QgsZipItem
1415  else
1416  {
1417  QString vsiPath = vsiPrefix + filePath;
1418  if ( zipItem )
1419  {
1420  if ( zipItem->children().size() == 1 )
1421  {
1422  // take the name of the only child so we can get a normal data item from it
1423  QgsLayerItem *layerItem = qobject_cast<QgsLayerItem*>( zipItem->children().first() );
1424  if ( layerItem )
1425  vsiPath = layerItem->uri();
1426  }
1427  zipFileCount = zipFileList.count();
1428  delete zipItem;
1429  }
1430 
1431  QgsDebugMsgLevel( QString( "will try to create a normal dataItem from filePath= %2 or vsiPath = %3" ).arg( filePath ).arg( vsiPath ), 3 );
1432 
1433  // try to open using registered providers (gdal and ogr)
1434  for ( int i = 0; i < mProviderNames.size(); i++ )
1435  {
1436  dataItem_t *dataItem = mDataItemPtr[i];
1437  if ( dataItem )
1438  {
1439  QgsDataItem *item = 0;
1440  // try first with normal path (Passthru)
1441  // this is to simplify .qml handling, and without this some tests will fail
1442  // (e.g. testZipItemVectorTransparency(), second test)
1443  if (( mProviderNames[i] == "ogr" ) ||
1444  ( mProviderNames[i] == "gdal" && zipFileCount == 1 ) )
1445  item = dataItem( filePath, parent );
1446  // try with /vsizip/
1447  if ( ! item )
1448  item = dataItem( vsiPath, parent );
1449  if ( item )
1450  return item;
1451  }
1452  }
1453  }
1454 
1455  return 0;
1456 }
1457 
1458 const QStringList &QgsZipItem::getZipFileList()
1459 {
1460  if ( ! mZipFileList.isEmpty() )
1461  return mZipFileList;
1462 
1463  QString tmpPath;
1464  QSettings settings;
1465  QString scanZipSetting = settings.value( "/qgis/scanZipInBrowser2", "basic" ).toString();
1466 
1467  QgsDebugMsgLevel( QString( "mFilePath = %1 name= %2 scanZipSetting= %3 vsiPrefix= %4" ).arg( mFilePath ).arg( name() ).arg( scanZipSetting ).arg( mVsiPrefix ), 3 );
1468 
1469  // if scanZipBrowser == no: skip to the next file
1470  if ( scanZipSetting == "no" )
1471  {
1472  return mZipFileList;
1473  }
1474 
1475  // get list of files inside zip file
1476  QgsDebugMsgLevel( QString( "Open file %1 with gdal vsi" ).arg( mVsiPrefix + mFilePath ), 3 );
1477  char **papszSiblingFiles = VSIReadDirRecursive1( QString( mVsiPrefix + mFilePath ).toLocal8Bit().constData() );
1478  if ( papszSiblingFiles )
1479  {
1480  for ( int i = 0; i < CSLCount( papszSiblingFiles ); i++ )
1481  {
1482  tmpPath = papszSiblingFiles[i];
1483  QgsDebugMsgLevel( QString( "Read file %1" ).arg( tmpPath ), 3 );
1484  // skip directories (files ending with /)
1485  if ( tmpPath.right( 1 ) != "/" )
1486  mZipFileList << tmpPath;
1487  }
1488  CSLDestroy( papszSiblingFiles );
1489  }
1490  else
1491  {
1492  QgsDebugMsg( QString( "Error reading %1" ).arg( mFilePath ) );
1493  }
1494 
1495  return mZipFileList;
1496 }