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