QGIS/src/core/qgsdataitem.cpp
2017-12-14 15:44:51 +10:00

1637 lines
46 KiB
C++

/***************************************************************************
qgsdataitem.cpp - Data items
-------------------
begin : 2011-04-01
copyright : (C) 2011 Radim Blazek
email : radim dot blazek at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <QApplication>
#include <QtConcurrentMap>
#include <QtConcurrentRun>
#include <QDateTime>
#include <QDir>
#include <QFileInfo>
#include <QMenu>
#include <QMouseEvent>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QVector>
#include <QStyle>
#include <QDesktopServices>
#include "qgis.h"
#include "qgsdataitem.h"
#include "qgsapplication.h"
#include "qgsdataitemprovider.h"
#include "qgsdataitemproviderregistry.h"
#include "qgsdataprovider.h"
#include "qgslogger.h"
#include "qgsproviderregistry.h"
#include "qgsconfig.h"
#include "qgssettings.h"
#include "qgsanimatedicon.h"
// use GDAL VSI mechanism
#include "cpl_vsi.h"
#include "cpl_string.h"
// shared icons
QIcon QgsLayerItem::iconPoint()
{
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconPointLayer.svg" ) );
}
QIcon QgsLayerItem::iconLine()
{
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconLineLayer.svg" ) );
}
QIcon QgsLayerItem::iconPolygon()
{
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconPolygonLayer.svg" ) );
}
QIcon QgsLayerItem::iconTable()
{
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconTableLayer.svg" ) );
}
QIcon QgsLayerItem::iconRaster()
{
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconRaster.svg" ) );
}
QIcon QgsLayerItem::iconDefault()
{
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconLayer.png" ) );
}
QIcon QgsDataCollectionItem::iconDataCollection()
{
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconDbSchema.png" ) );
}
QIcon QgsDataCollectionItem::iconDir()
{
static QIcon sIcon;
if ( sIcon.isNull() )
{
// initialize shared icons
QStyle *style = QApplication::style();
sIcon = QIcon( style->standardPixmap( QStyle::SP_DirClosedIcon ) );
sIcon.addPixmap( style->standardPixmap( QStyle::SP_DirOpenIcon ),
QIcon::Normal, QIcon::On );
}
return sIcon;
}
QIcon QgsFavoritesItem::iconFavorites()
{
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconFavourites.png" ) );
}
QVariant QgsFavoritesItem::sortKey() const
{
return QStringLiteral( " 0" );
}
QIcon QgsZipItem::iconZip()
{
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconZip.png" ) );
// icon from http://www.softicons.com/free-icons/application-icons/mega-pack-icons-1-by-nikolay-verin/winzip-folder-icon
}
QgsAnimatedIcon *QgsDataItem::sPopulatingIcon = nullptr;
QgsDataItem::QgsDataItem( QgsDataItem::Type type, QgsDataItem *parent, const QString &name, const QString &path )
// Do not pass parent to QObject, Qt would delete this when parent is deleted
: mType( type )
, mCapabilities( NoCapabilities )
, mParent( parent )
, mState( NotPopulated )
, mName( name )
, mPath( path )
, mDeferredDelete( false )
, mFutureWatcher( nullptr )
{
}
QgsDataItem::~QgsDataItem()
{
QgsDebugMsgLevel( QString( "mName = %1 mPath = %2 mChildren.size() = %3" ).arg( mName, mPath ).arg( mChildren.size() ), 2 );
Q_FOREACH ( QgsDataItem *child, mChildren )
{
if ( !child ) // should not happen
continue;
child->deleteLater();
}
mChildren.clear();
if ( mFutureWatcher && !mFutureWatcher->isFinished() )
{
// this should not usually happen (until the item was deleted directly when createChildren was running)
QgsDebugMsg( "mFutureWatcher not finished (should not happen) -> waitForFinished()" );
mDeferredDelete = true;
mFutureWatcher->waitForFinished();
}
delete mFutureWatcher;
}
QString QgsDataItem::pathComponent( const QString &string )
{
return QString( string ).replace( QRegExp( "[\\\\/]" ), QStringLiteral( "|" ) );
}
QVariant QgsDataItem::sortKey() const
{
return mSortKey.isValid() ? mSortKey : name();
}
void QgsDataItem::setSortKey( const QVariant &key )
{
mSortKey = key;
}
void QgsDataItem::deleteLater()
{
QgsDebugMsgLevel( "path = " + path(), 3 );
setParent( nullptr ); // also disconnects parent
Q_FOREACH ( QgsDataItem *child, mChildren )
{
if ( !child ) // should not happen
continue;
child->deleteLater();
}
mChildren.clear();
if ( mFutureWatcher && !mFutureWatcher->isFinished() )
{
QgsDebugMsg( "mFutureWatcher not finished -> schedule to delete later" );
mDeferredDelete = true;
}
else
{
QObject::deleteLater();
}
}
void QgsDataItem::deleteLater( QVector<QgsDataItem *> &items )
{
Q_FOREACH ( QgsDataItem *item, items )
{
if ( !item ) // should not happen
continue;
item->deleteLater();
}
items.clear();
}
void QgsDataItem::moveToThread( QThread *targetThread )
{
// QObject::moveToThread() cannot move objects with parent, but QgsDataItem is not using paren/children from QObject
Q_FOREACH ( QgsDataItem *child, mChildren )
{
if ( !child ) // should not happen
continue;
QgsDebugMsgLevel( "moveToThread child " + child->path(), 3 );
child->QObject::setParent( nullptr ); // to be sure
child->moveToThread( targetThread );
}
QObject::moveToThread( targetThread );
}
QIcon QgsDataItem::icon()
{
if ( state() == Populating && sPopulatingIcon )
return sPopulatingIcon->icon();
if ( !mIcon.isNull() )
return mIcon;
if ( !mIconMap.contains( mIconName ) )
{
mIconMap.insert( mIconName, mIconName.startsWith( ':' ) ? QIcon( mIconName ) : QgsApplication::getThemeIcon( mIconName ) );
}
return mIconMap.value( mIconName );
}
void QgsDataItem::setName( const QString &name )
{
mName = name;
emit dataChanged( this );
}
QVector<QgsDataItem *> QgsDataItem::createChildren()
{
return QVector<QgsDataItem *>();
}
void QgsDataItem::populate( bool foreground )
{
if ( state() == Populated || state() == Populating )
return;
QgsDebugMsgLevel( "mPath = " + mPath, 2 );
if ( capabilities2() & QgsDataItem::Fast || foreground )
{
populate( createChildren() );
}
else
{
setState( Populating );
// 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.
if ( !mFutureWatcher )
{
mFutureWatcher = new QFutureWatcher< QVector <QgsDataItem *> >( this );
}
connect( mFutureWatcher, &QFutureWatcherBase::finished, this, &QgsDataItem::childrenCreated );
mFutureWatcher->setFuture( QtConcurrent::run( runCreateChildren, this ) );
}
}
// This is expected to be run in a separate thread
QVector<QgsDataItem *> QgsDataItem::runCreateChildren( QgsDataItem *item )
{
QgsDebugMsgLevel( "path = " + item->path(), 2 );
QTime time;
time.start();
QVector <QgsDataItem *> children = item->createChildren();
QgsDebugMsgLevel( QString( "%1 children created in %2 ms" ).arg( children.size() ).arg( time.elapsed() ), 3 );
// Children objects must be pushed to main thread.
Q_FOREACH ( QgsDataItem *child, children )
{
if ( !child ) // should not happen
continue;
QgsDebugMsgLevel( "moveToThread child " + child->path(), 2 );
if ( qApp )
child->moveToThread( qApp->thread() ); // moves also children
}
QgsDebugMsgLevel( QString( "finished path %1: %2 children" ).arg( item->path() ).arg( children.size() ), 3 );
return children;
}
void QgsDataItem::childrenCreated()
{
QgsDebugMsgLevel( QString( "path = %1 children.size() = %2" ).arg( path() ).arg( mFutureWatcher->result().size() ), 3 );
if ( deferredDelete() )
{
QgsDebugMsg( "Item was scheduled to be deleted later" );
QObject::deleteLater();
return;
}
if ( mChildren.isEmpty() ) // usually populating but may also be refresh if originally there were no children
{
populate( mFutureWatcher->result() );
}
else // refreshing
{
refresh( mFutureWatcher->result() );
}
disconnect( mFutureWatcher, &QFutureWatcherBase::finished, this, &QgsDataItem::childrenCreated );
emit dataChanged( this ); // to replace loading icon by normal icon
}
void QgsDataItem::updateIcon()
{
emit dataChanged( this );
}
void QgsDataItem::populate( const QVector<QgsDataItem *> &children )
{
QgsDebugMsgLevel( "mPath = " + mPath, 3 );
Q_FOREACH ( QgsDataItem *child, children )
{
if ( !child ) // should not happen
continue;
// update after thread finished -> refresh
addChildItem( child, true );
}
setState( Populated );
}
void QgsDataItem::depopulate()
{
QgsDebugMsgLevel( "mPath = " + mPath, 3 );
Q_FOREACH ( QgsDataItem *child, mChildren )
{
QgsDebugMsgLevel( "remove " + child->path(), 3 );
child->depopulate(); // recursive
deleteChildItem( child );
}
setState( NotPopulated );
}
void QgsDataItem::refresh()
{
if ( state() == Populating )
return;
QgsDebugMsgLevel( "mPath = " + mPath, 3 );
if ( capabilities2() & QgsDataItem::Fast )
{
refresh( createChildren() );
}
else
{
setState( Populating );
if ( !mFutureWatcher )
{
mFutureWatcher = new QFutureWatcher< QVector <QgsDataItem *> >( this );
}
connect( mFutureWatcher, &QFutureWatcherBase::finished, this, &QgsDataItem::childrenCreated );
mFutureWatcher->setFuture( QtConcurrent::run( runCreateChildren, this ) );
}
}
void QgsDataItem::refreshConnections()
{
// Walk up until the root node is reached
if ( mParent )
{
mParent->refreshConnections();
}
else
{
refresh();
emit connectionsChanged();
}
}
void QgsDataItem::refresh( const QVector<QgsDataItem *> &children )
{
QgsDebugMsgLevel( "mPath = " + mPath, 2 );
// Remove no more present children
QVector<QgsDataItem *> remove;
Q_FOREACH ( QgsDataItem *child, mChildren )
{
if ( !child ) // should not happen
continue;
if ( findItem( children, child ) >= 0 )
continue;
remove.append( child );
}
Q_FOREACH ( QgsDataItem *child, remove )
{
QgsDebugMsgLevel( "remove " + child->path(), 3 );
deleteChildItem( child );
}
// Add new children
Q_FOREACH ( QgsDataItem *child, children )
{
if ( !child ) // should not happen
continue;
int index = findItem( mChildren, child );
if ( index >= 0 )
{
// Refresh recursively (some providers may create more generations of descendants)
if ( !( child->capabilities2() & QgsDataItem::Fertile ) )
{
// The child cannot createChildren() itself
mChildren.value( index )->refresh( child->children() );
}
child->deleteLater();
continue;
}
addChildItem( child, true );
}
setState( Populated );
}
int QgsDataItem::rowCount()
{
return mChildren.size();
}
bool QgsDataItem::hasChildren()
{
return ( state() == Populated ? !mChildren.isEmpty() : true );
}
void QgsDataItem::setParent( QgsDataItem *parent )
{
if ( mParent )
{
disconnect( this, nullptr, mParent, nullptr );
}
if ( parent )
{
connect( this, &QgsDataItem::beginInsertItems, parent, &QgsDataItem::beginInsertItems );
connect( this, &QgsDataItem::endInsertItems, parent, &QgsDataItem::endInsertItems );
connect( this, &QgsDataItem::beginRemoveItems, parent, &QgsDataItem::beginRemoveItems );
connect( this, &QgsDataItem::endRemoveItems, parent, &QgsDataItem::endRemoveItems );
connect( this, &QgsDataItem::dataChanged, parent, &QgsDataItem::dataChanged );
connect( this, &QgsDataItem::stateChanged, parent, &QgsDataItem::stateChanged );
}
mParent = parent;
}
void QgsDataItem::addChildItem( QgsDataItem *child, bool refresh )
{
Q_ASSERT( child );
QgsDebugMsgLevel( QString( "path = %1 add child #%2 - %3 - %4" ).arg( mPath ).arg( mChildren.size() ).arg( child->mName ).arg( child->mType ), 3 );
//calculate position to insert child
int i;
if ( type() == Directory )
{
for ( i = 0; i < mChildren.size(); i++ )
{
// sort items by type, so directories are before data items
if ( mChildren.at( i )->mType == child->mType &&
mChildren.at( i )->mName.localeAwareCompare( child->mName ) > 0 )
break;
}
}
else
{
for ( i = 0; i < mChildren.size(); i++ )
{
if ( mChildren.at( i )->mName.localeAwareCompare( child->mName ) >= 0 )
break;
}
}
if ( refresh )
emit beginInsertItems( this, i, i );
mChildren.insert( i, child );
child->setParent( this );
if ( refresh )
emit endInsertItems();
}
void QgsDataItem::deleteChildItem( QgsDataItem *child )
{
QgsDebugMsgLevel( "mName = " + child->mName, 2 );
int i = mChildren.indexOf( child );
Q_ASSERT( i >= 0 );
emit beginRemoveItems( this, i, i );
mChildren.remove( i );
child->deleteLater();
emit endRemoveItems();
}
QgsDataItem *QgsDataItem::removeChildItem( QgsDataItem *child )
{
QgsDebugMsgLevel( "mName = " + child->mName, 2 );
int i = mChildren.indexOf( child );
Q_ASSERT( i >= 0 );
if ( i < 0 )
{
child->setParent( nullptr );
return nullptr;
}
emit beginRemoveItems( this, i, i );
mChildren.remove( i );
emit endRemoveItems();
return child;
}
int QgsDataItem::findItem( QVector<QgsDataItem *> items, QgsDataItem *item )
{
for ( int i = 0; i < items.size(); i++ )
{
Q_ASSERT_X( items[i], "findItem", QString( "item %1 is nullptr" ).arg( i ).toLatin1() );
QgsDebugMsgLevel( QString::number( i ) + " : " + items[i]->mPath + " x " + item->mPath, 2 );
if ( items[i]->equal( item ) )
return i;
}
return -1;
}
bool QgsDataItem::equal( const QgsDataItem *other )
{
return ( metaObject()->className() == other->metaObject()->className() &&
mPath == other->path() );
}
QList<QAction *> QgsDataItem::actions( QWidget *parent )
{
Q_UNUSED( parent );
return QList<QAction *>();
}
bool QgsDataItem::handleDoubleClick()
{
return false;
}
QgsDataItem::State QgsDataItem::state() const
{
return mState;
}
void QgsDataItem::setState( State state )
{
QgsDebugMsgLevel( QString( "item %1 set state %2 -> %3" ).arg( path() ).arg( this->state() ).arg( state ), 3 );
if ( state == mState )
return;
State oldState = mState;
if ( state == Populating ) // start loading
{
if ( !sPopulatingIcon )
{
// TODO: ensure that QgsAnimatedIcon is created on UI thread only
sPopulatingIcon = new QgsAnimatedIcon( QgsApplication::iconPath( QStringLiteral( "/mIconLoading.gif" ) ), QgsApplication::instance() );
}
sPopulatingIcon->connectFrameChanged( this, &QgsDataItem::updateIcon );
}
else if ( mState == Populating && sPopulatingIcon ) // stop loading
{
sPopulatingIcon->disconnectFrameChanged( this, &QgsDataItem::updateIcon );
}
mState = state;
emit stateChanged( this, oldState );
if ( state == Populated )
updateIcon();
}
QList<QMenu *> QgsDataItem::menus( QWidget *parent )
{
Q_UNUSED( parent );
return QList<QMenu *>();
}
// ---------------------------------------------------------------------
QgsLayerItem::QgsLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri, LayerType layerType, const QString &providerKey )
: QgsDataItem( Layer, parent, name, path )
, mProviderKey( providerKey )
, mUri( uri )
, mLayerType( layerType )
{
mIconName = iconName( layerType );
}
QgsMapLayer::LayerType QgsLayerItem::mapLayerType() const
{
if ( mLayerType == QgsLayerItem::Raster )
return QgsMapLayer::RasterLayer;
if ( mLayerType == QgsLayerItem::Plugin )
return QgsMapLayer::PluginLayer;
return QgsMapLayer::VectorLayer;
}
QString QgsLayerItem::layerTypeAsString( const QgsLayerItem::LayerType &layerType )
{
static int enumIdx = staticMetaObject.indexOfEnumerator( "LayerType" );
return staticMetaObject.enumerator( enumIdx ).valueToKey( layerType );
}
QString QgsLayerItem::iconName( const QgsLayerItem::LayerType &layerType )
{
switch ( layerType )
{
case Point:
return QStringLiteral( "/mIconPointLayer.svg" );
break;
case Line:
return QStringLiteral( "/mIconLineLayer.svg" );
break;
case Polygon:
return QStringLiteral( "/mIconPolygonLayer.svg" );
break;
// TODO add a new icon for generic Vector layers
case Vector :
return QStringLiteral( "/mIconPolygonLayer.svg" );
break;
case TableLayer:
case Table:
return QStringLiteral( "/mIconTableLayer.svg" );
break;
case Raster:
return QStringLiteral( "/mIconRaster.svg" );
break;
default:
return QStringLiteral( "/mIconLayer.png" );
break;
}
}
bool QgsLayerItem::equal( const QgsDataItem *other )
{
//QgsDebugMsg ( mPath + " x " + other->mPath );
if ( type() != other->type() )
{
return false;
}
//const QgsLayerItem *o = qobject_cast<const QgsLayerItem *> ( other );
const QgsLayerItem *o = dynamic_cast<const QgsLayerItem *>( other );
if ( !o )
return false;
return ( mPath == o->mPath && mName == o->mName && mUri == o->mUri && mProviderKey == o->mProviderKey );
}
QgsMimeDataUtils::Uri QgsLayerItem::mimeUri() const
{
QgsMimeDataUtils::Uri u;
switch ( mapLayerType() )
{
case QgsMapLayer::VectorLayer:
u.layerType = QStringLiteral( "vector" );
break;
case QgsMapLayer::RasterLayer:
u.layerType = QStringLiteral( "raster" );
break;
case QgsMapLayer::PluginLayer:
u.layerType = QStringLiteral( "plugin" );
break;
default:
return u; // invalid URI
}
u.providerKey = providerKey();
u.name = layerName();
u.uri = uri();
u.supportedCrs = supportedCrs();
u.supportedFormats = supportedFormats();
return u;
}
// ---------------------------------------------------------------------
QgsDataCollectionItem::QgsDataCollectionItem( QgsDataItem *parent, const QString &name, const QString &path )
: QgsDataItem( Collection, parent, name, path )
{
mCapabilities = Fertile;
mIconName = QStringLiteral( "/mIconDbSchema.png" );
}
QgsDataCollectionItem::~QgsDataCollectionItem()
{
QgsDebugMsgLevel( "mName = " + mName + " mPath = " + mPath, 2 );
// Do not delete children, children are deleted by QObject parent
#if 0
Q_FOREACH ( QgsDataItem *i, mChildren )
{
QgsDebugMsgLevel( QString( "delete child = 0x%0" ).arg( ( qlonglong )i, 8, 16, QLatin1Char( '0' ) ), 2 );
delete i;
}
#endif
}
//-----------------------------------------------------------------------
QgsDirectoryItem::QgsDirectoryItem( QgsDataItem *parent, const QString &name, const QString &path )
: QgsDataCollectionItem( parent, QDir::toNativeSeparators( name ), path )
, mDirPath( path )
, mRefreshLater( false )
{
mType = Directory;
init();
}
QgsDirectoryItem::QgsDirectoryItem( QgsDataItem *parent, const QString &name, const QString &dirPath, const QString &path )
: QgsDataCollectionItem( parent, QDir::toNativeSeparators( name ), path )
, mDirPath( dirPath )
, mRefreshLater( false )
{
mType = Directory;
init();
}
void QgsDirectoryItem::init()
{
setToolTip( QDir::toNativeSeparators( mDirPath ) );
}
QIcon QgsDirectoryItem::icon()
{
if ( state() == Populating )
return QgsDataItem::icon();
return iconDir();
}
QVector<QgsDataItem *> QgsDirectoryItem::createChildren()
{
QVector<QgsDataItem *> children;
QDir dir( mDirPath );
const QList<QgsDataItemProvider *> providers = QgsApplication::dataItemProviderRegistry()->providers();
QStringList entries = dir.entryList( QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name | QDir::IgnoreCase );
Q_FOREACH ( const QString &subdir, entries )
{
if ( mRefreshLater )
{
deleteLater( children );
return children;
}
QString subdirPath = dir.absoluteFilePath( subdir );
QgsDebugMsgLevel( QString( "creating subdir: %1" ).arg( subdirPath ), 2 );
QString path = mPath + '/' + subdir; // may differ from subdirPath
if ( QgsDirectoryItem::hiddenPath( path ) )
continue;
bool handledByProvider = false;
for ( QgsDataItemProvider *provider : providers )
{
if ( provider->handlesDirectoryPath( path ) )
{
handledByProvider = true;
break;
}
}
if ( handledByProvider )
continue;
QgsDirectoryItem *item = new QgsDirectoryItem( this, subdir, subdirPath, path );
// we want directories shown before files
item->setSortKey( QStringLiteral( " %1" ).arg( subdir ) );
// propagate signals up to top
children.append( item );
}
QStringList fileEntries = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot | QDir::Files, QDir::Name );
Q_FOREACH ( const QString &name, fileEntries )
{
if ( mRefreshLater )
{
deleteLater( children );
return children;
}
QString path = dir.absoluteFilePath( name );
QFileInfo fileInfo( path );
if ( fileInfo.suffix() == QLatin1String( "qgs" ) )
{
QgsDataItem *item = new QgsProjectItem( this, name, path );
children.append( item );
continue;
}
// vsizip support was added to GDAL/OGR 1.6 but GDAL_VERSION_NUM not available here
// so we assume it's available anyway
{
QgsDataItem *item = QgsZipItem::itemFromPath( this, path, name, mPath + '/' + name );
if ( item )
{
children.append( item );
continue;
}
}
for ( QgsDataItemProvider *provider : providers )
{
int capabilities = provider->capabilities();
if ( !( ( fileInfo.isFile() && ( capabilities & QgsDataProvider::File ) ) ||
( fileInfo.isDir() && ( capabilities & QgsDataProvider::Dir ) ) ) )
{
continue;
}
QgsDataItem *item = provider->createDataItem( path, this );
if ( item )
{
children.append( item );
}
}
}
return children;
}
void QgsDirectoryItem::setState( State state )
{
QgsDataCollectionItem::setState( state );
if ( state == Populated )
{
if ( !mFileSystemWatcher )
{
mFileSystemWatcher = new QFileSystemWatcher( this );
mFileSystemWatcher->addPath( mDirPath );
connect( mFileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &QgsDirectoryItem::directoryChanged );
}
mLastScan = QDateTime::currentDateTime();
}
else if ( state == NotPopulated )
{
if ( mFileSystemWatcher )
{
delete mFileSystemWatcher;
mFileSystemWatcher = nullptr;
}
}
}
void QgsDirectoryItem::directoryChanged()
{
// If the last scan was less than 10 seconds ago, skip this
if ( mLastScan.msecsTo( QDateTime::currentDateTime() ) < QgsSettings().value( QStringLiteral( "browser/minscaninterval" ), 10000 ).toInt() )
{
return;
}
if ( state() == Populating )
{
// schedule to refresh later, because refresh() simply returns if Populating
mRefreshLater = true;
}
else
{
// We definintely don't want the temporary files created by sqlite
// to re-trigger a refresh in an infinite loop.
disconnect( mFileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &QgsDirectoryItem::directoryChanged );
// QFileSystemWhatcher::directoryChanged is emitted when a
// file is created and not when it is closed/flushed.
//
// Delay to give to OS the time to complete writing the file
// this happens when a new file appears in the directory and
// the item's children thread will try to open the file with
// GDAL or OGR even if it is still being written.
QTimer::singleShot( 100, this, SLOT( refresh() ) );
}
}
bool QgsDirectoryItem::hiddenPath( const QString &path )
{
QgsSettings settings;
QStringList hiddenItems = settings.value( QStringLiteral( "browser/hiddenPaths" ),
QStringList() ).toStringList();
int idx = hiddenItems.indexOf( path );
return ( idx > -1 );
}
QList<QAction *> QgsDirectoryItem::actions( QWidget *parent )
{
QList<QAction *> result;
QAction *openFolder = new QAction( tr( "Open Directory…" ), parent );
connect( openFolder, &QAction::triggered, this, [ = ]
{
QDesktopServices::openUrl( QUrl::fromLocalFile( mDirPath ) );
} );
result << openFolder;
return result;
}
void QgsDirectoryItem::childrenCreated()
{
QgsDebugMsgLevel( QString( "mRefreshLater = %1" ).arg( mRefreshLater ), 3 );
if ( mRefreshLater )
{
QgsDebugMsgLevel( "directory changed during createChidren() -> refresh() again", 3 );
mRefreshLater = false;
setState( Populated );
refresh();
}
else
{
QgsDataCollectionItem::childrenCreated();
}
// Re-connect the file watcher after all children have been created
connect( mFileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &QgsDirectoryItem::directoryChanged );
}
bool QgsDirectoryItem::equal( const QgsDataItem *other )
{
//QgsDebugMsg ( mPath + " x " + other->mPath );
if ( type() != other->type() )
{
return false;
}
return ( path() == other->path() );
}
QWidget *QgsDirectoryItem::paramWidget()
{
return new QgsDirectoryParamWidget( mPath );
}
QgsDirectoryParamWidget::QgsDirectoryParamWidget( const QString &path, QWidget *parent )
: QTreeWidget( parent )
{
setRootIsDecorated( false );
// name, size, date, permissions, owner, group, type
setColumnCount( 7 );
QStringList labels;
labels << tr( "Name" ) << tr( "Size" ) << tr( "Date" ) << tr( "Permissions" ) << tr( "Owner" ) << tr( "Group" ) << tr( "Type" );
setHeaderLabels( labels );
QStyle *style = QApplication::style();
QIcon iconDirectory = QIcon( style->standardPixmap( QStyle::SP_DirClosedIcon ) );
QIcon iconFile = QIcon( style->standardPixmap( QStyle::SP_FileIcon ) );
QIcon iconDirLink = QIcon( style->standardPixmap( QStyle::SP_DirLinkIcon ) );
QIcon iconFileLink = QIcon( style->standardPixmap( QStyle::SP_FileLinkIcon ) );
QList<QTreeWidgetItem *> items;
QDir dir( path );
QStringList entries = dir.entryList( QDir::AllEntries | QDir::NoDotAndDotDot, QDir::Name | QDir::IgnoreCase );
Q_FOREACH ( const QString &name, entries )
{
QFileInfo fi( dir.absoluteFilePath( name ) );
QStringList texts;
texts << name;
QString size;
if ( fi.size() > 1024 )
{
size = size.sprintf( "%.1f KiB", fi.size() / 1024.0 );
}
else if ( fi.size() > 1.048576e6 )
{
size = size.sprintf( "%.1f MiB", fi.size() / 1.048576e6 );
}
else
{
size = QStringLiteral( "%1 B" ).arg( fi.size() );
}
texts << size;
texts << fi.lastModified().toString( Qt::SystemLocaleShortDate );
QString perm;
perm += fi.permission( QFile::ReadOwner ) ? 'r' : '-';
perm += fi.permission( QFile::WriteOwner ) ? 'w' : '-';
perm += fi.permission( QFile::ExeOwner ) ? 'x' : '-';
// QFile::ReadUser, QFile::WriteUser, QFile::ExeUser
perm += fi.permission( QFile::ReadGroup ) ? 'r' : '-';
perm += fi.permission( QFile::WriteGroup ) ? 'w' : '-';
perm += fi.permission( QFile::ExeGroup ) ? 'x' : '-';
perm += fi.permission( QFile::ReadOther ) ? 'r' : '-';
perm += fi.permission( QFile::WriteOther ) ? 'w' : '-';
perm += fi.permission( QFile::ExeOther ) ? 'x' : '-';
texts << perm;
texts << fi.owner();
texts << fi.group();
QString type;
QIcon icon;
if ( fi.isDir() && fi.isSymLink() )
{
type = tr( "folder" );
icon = iconDirLink;
}
else if ( fi.isDir() )
{
type = tr( "folder" );
icon = iconDirectory;
}
else if ( fi.isFile() && fi.isSymLink() )
{
type = tr( "file" );
icon = iconFileLink;
}
else if ( fi.isFile() )
{
type = tr( "file" );
icon = iconFile;
}
texts << type;
QTreeWidgetItem *item = new QTreeWidgetItem( texts );
item->setIcon( 0, icon );
items << item;
}
addTopLevelItems( items );
// hide columns that are not requested
QgsSettings settings;
QList<QVariant> lst = settings.value( QStringLiteral( "dataitem/directoryHiddenColumns" ) ).toList();
Q_FOREACH ( const QVariant &colVariant, lst )
{
setColumnHidden( colVariant.toInt(), true );
}
}
void QgsDirectoryParamWidget::mousePressEvent( QMouseEvent *event )
{
if ( event->button() == Qt::RightButton )
{
// show the popup menu
QMenu popupMenu;
QStringList labels;
labels << tr( "Name" ) << tr( "Size" ) << tr( "Date" ) << tr( "Permissions" ) << tr( "Owner" ) << tr( "Group" ) << tr( "Type" );
for ( int i = 0; i < labels.count(); i++ )
{
QAction *action = popupMenu.addAction( labels[i], this, SLOT( showHideColumn() ) );
action->setObjectName( QString::number( i ) );
action->setCheckable( true );
action->setChecked( !isColumnHidden( i ) );
}
popupMenu.exec( event->globalPos() );
}
}
void QgsDirectoryParamWidget::showHideColumn()
{
QAction *action = qobject_cast<QAction *>( sender() );
if ( !action )
return; // something is wrong
int columnIndex = action->objectName().toInt();
setColumnHidden( columnIndex, !isColumnHidden( columnIndex ) );
// save in settings
QgsSettings settings;
QList<QVariant> lst;
for ( int i = 0; i < columnCount(); i++ )
{
if ( isColumnHidden( i ) )
lst.append( QVariant( i ) );
}
settings.setValue( QStringLiteral( "dataitem/directoryHiddenColumns" ), lst );
}
QgsProjectItem::QgsProjectItem( QgsDataItem *parent, const QString &name, const QString &path )
: QgsDataItem( QgsDataItem::Project, parent, name, path )
{
mIconName = QStringLiteral( ":/images/icons/qgis-icon-16x16.png" );
setState( Populated ); // no more children
}
QgsErrorItem::QgsErrorItem( QgsDataItem *parent, const QString &error, const QString &path )
: QgsDataItem( QgsDataItem::Error, parent, error, path )
{
mIconName = QStringLiteral( "/mIconDelete.png" );
setState( Populated ); // no more children
}
QgsFavoritesItem::QgsFavoritesItem( QgsDataItem *parent, const QString &name, const QString &path )
: QgsDataCollectionItem( parent, name, QStringLiteral( "favorites:" ) )
{
Q_UNUSED( path );
mCapabilities |= Fast;
mType = Favorites;
mIconName = QStringLiteral( "/mIconFavourites.png" );
populate();
}
QVector<QgsDataItem *> QgsFavoritesItem::createChildren()
{
QVector<QgsDataItem *> children;
QgsSettings settings;
const QStringList favDirs = settings.value( QStringLiteral( "browser/favourites" ), QVariant() ).toStringList();
for ( const QString &favDir : favDirs )
{
QStringList parts = favDir.split( QStringLiteral( "|||" ) );
if ( parts.empty() )
continue;
QString dir = parts.at( 0 );
QString name = dir;
if ( parts.count() > 1 )
name = parts.at( 1 );
children << createChildren( dir, name );
}
return children;
}
void QgsFavoritesItem::addDirectory( const QString &favDir, const QString &n )
{
QString name = n.isEmpty() ? favDir : n;
QgsSettings settings;
QStringList favDirs = settings.value( QStringLiteral( "browser/favourites" ) ).toStringList();
favDirs.append( QStringLiteral( "%1|||%2" ).arg( favDir, name ) );
settings.setValue( QStringLiteral( "browser/favourites" ), favDirs );
if ( state() == Populated )
{
QVector<QgsDataItem *> items = createChildren( favDir, name );
Q_FOREACH ( QgsDataItem *item, items )
{
addChildItem( item, true );
}
}
}
void QgsFavoritesItem::removeDirectory( QgsDirectoryItem *item )
{
if ( !item )
return;
QgsSettings settings;
QStringList favDirs = settings.value( QStringLiteral( "browser/favourites" ) ).toStringList();
for ( int i = favDirs.count() - 1; i >= 0; --i )
{
QStringList parts = favDirs.at( i ).split( QStringLiteral( "|||" ) );
if ( parts.empty() )
continue;
QString dir = parts.at( 0 );
if ( dir == item->dirPath() )
favDirs.removeAt( i );
}
settings.setValue( QStringLiteral( "browser/favourites" ), favDirs );
int idx = findItem( mChildren, item );
if ( idx < 0 )
{
QgsDebugMsg( QString( "favorites item %1 not found" ).arg( item->path() ) );
return;
}
if ( state() == Populated )
deleteChildItem( mChildren.at( idx ) );
}
void QgsFavoritesItem::renameFavorite( const QString &path, const QString &name )
{
// update stored name
QgsSettings settings;
QStringList favDirs = settings.value( QStringLiteral( "browser/favourites" ) ).toStringList();
for ( int i = 0; i < favDirs.count(); ++i )
{
QStringList parts = favDirs.at( i ).split( QStringLiteral( "|||" ) );
if ( parts.empty() )
continue;
QString dir = parts.at( 0 );
if ( dir == path )
{
favDirs[i] = QStringLiteral( "%1|||%2" ).arg( path, name );
break;
}
}
settings.setValue( QStringLiteral( "browser/favourites" ), favDirs );
// also update existing data item
const QVector<QgsDataItem *> ch = children();
for ( QgsDataItem *child : ch )
{
if ( QgsFavoriteItem *favorite = qobject_cast< QgsFavoriteItem * >( child ) )
{
if ( favorite->dirPath() == path )
{
favorite->setName( name );
break;
}
}
}
}
QVector<QgsDataItem *> QgsFavoritesItem::createChildren( const QString &favDir, const QString &name )
{
QVector<QgsDataItem *> children;
QString pathName = pathComponent( favDir );
Q_FOREACH ( QgsDataItemProvider *provider, QgsApplication::dataItemProviderRegistry()->providers() )
{
int capabilities = provider->capabilities();
if ( capabilities & QgsDataProvider::Dir )
{
QgsDataItem *item = provider->createDataItem( favDir, this );
if ( item )
{
item->setName( name );
children.append( item );
}
}
}
if ( children.isEmpty() )
{
QgsFavoriteItem *item = new QgsFavoriteItem( this, name, favDir, mPath + '/' + pathName );
if ( item )
{
children.append( item );
}
}
return children;
}
//-----------------------------------------------------------------------
QStringList QgsZipItem::sProviderNames = QStringList();
QVector<dataItem_t *> QgsZipItem::sDataItemPtr = QVector<dataItem_t *>();
QgsZipItem::QgsZipItem( QgsDataItem *parent, const QString &name, const QString &path )
: QgsDataCollectionItem( parent, name, path )
{
mFilePath = path;
init();
}
QgsZipItem::QgsZipItem( QgsDataItem *parent, const QString &name, const QString &filePath, const QString &path )
: QgsDataCollectionItem( parent, name, path )
, mFilePath( filePath )
{
init();
}
void QgsZipItem::init()
{
mType = Collection; //Zip??
mIconName = QStringLiteral( "/mIconZip.png" );
mVsiPrefix = vsiPrefix( mFilePath );
if ( sProviderNames.isEmpty() )
{
// QStringList keys = QgsProviderRegistry::instance()->providerList();
// only use GDAL and OGR providers as we use the VSIFILE mechanism
QStringList keys;
// keys << "ogr" << "gdal";
keys << QStringLiteral( "gdal" ) << QStringLiteral( "ogr" );
for ( const auto &k : qgis::as_const( keys ) )
{
QgsDebugMsgLevel( "provider " + k, 3 );
// some providers hangs with empty uri (PostGIS) etc...
// -> using libraries directly
std::unique_ptr< QLibrary > library( QgsProviderRegistry::instance()->createProviderLibrary( k ) );
if ( library )
{
dataCapabilities_t *dataCapabilities = reinterpret_cast< dataCapabilities_t * >( cast_to_fptr( library->resolve( "dataCapabilities" ) ) );
if ( !dataCapabilities )
{
QgsDebugMsg( library->fileName() + " does not have dataCapabilities" );
continue;
}
if ( dataCapabilities() == QgsDataProvider::NoDataCapabilities )
{
QgsDebugMsg( library->fileName() + " has NoDataCapabilities" );
continue;
}
QgsDebugMsg( QString( "%1 dataCapabilities : %2" ).arg( library->fileName() ).arg( dataCapabilities() ) );
dataItem_t *dataItem = reinterpret_cast< dataItem_t * >( cast_to_fptr( library->resolve( "dataItem" ) ) );
if ( ! dataItem )
{
QgsDebugMsg( library->fileName() + " does not have dataItem" );
continue;
}
// mLibraries.append( library );
sDataItemPtr.append( dataItem );
sProviderNames.append( k );
}
else
{
//QgsDebugMsg ( "Cannot get provider " + k );
}
}
}
}
// internal function to scan a vsidir (zip or tar file) recursively
// GDAL trunk has this since r24423 (05/16/12) - VSIReadDirRecursive()
// use a copy of the function internally for now,
// but use char ** and CSLAddString, because CPLStringList was added in gdal-1.9
char **VSIReadDirRecursive1( const char *pszPath )
{
// CPLStringList oFiles = nullptr;
char **papszOFiles = nullptr;
char **papszFiles1 = nullptr;
char **papszFiles2 = nullptr;
VSIStatBufL psStatBuf;
CPLString osTemp1, osTemp2;
int i, j;
int nCount1, nCount2;
// get listing
papszFiles1 = VSIReadDir( pszPath );
if ( ! papszFiles1 )
return nullptr;
// get files and directories inside listing
nCount1 = CSLCount( papszFiles1 );
for ( i = 0; i < nCount1; i++ )
{
// build complete file name for stat
osTemp1.clear();
osTemp1.append( pszPath );
osTemp1.append( "/" );
osTemp1.append( papszFiles1[i] );
// if is file, add it
if ( VSIStatL( osTemp1.c_str(), &psStatBuf ) == 0 &&
VSI_ISREG( psStatBuf.st_mode ) )
{
// oFiles.AddString( papszFiles1[i] );
papszOFiles = CSLAddString( papszOFiles, papszFiles1[i] );
}
else if ( VSIStatL( osTemp1.c_str(), &psStatBuf ) == 0 &&
VSI_ISDIR( psStatBuf.st_mode ) )
{
// add directory entry
osTemp2.clear();
osTemp2.append( papszFiles1[i] );
osTemp2.append( "/" );
// oFiles.AddString( osTemp2.c_str() );
papszOFiles = CSLAddString( papszOFiles, osTemp2.c_str() );
// recursively add files inside directory
papszFiles2 = VSIReadDirRecursive1( osTemp1.c_str() );
if ( papszFiles2 )
{
nCount2 = CSLCount( papszFiles2 );
for ( j = 0; j < nCount2; j++ )
{
osTemp2.clear();
osTemp2.append( papszFiles1[i] );
osTemp2.append( "/" );
osTemp2.append( papszFiles2[j] );
// oFiles.AddString( osTemp2.c_str() );
papszOFiles = CSLAddString( papszOFiles, osTemp2.c_str() );
}
CSLDestroy( papszFiles2 );
}
}
}
CSLDestroy( papszFiles1 );
// return oFiles.StealList();
return papszOFiles;
}
QVector<QgsDataItem *> QgsZipItem::createChildren()
{
QVector<QgsDataItem *> children;
QString tmpPath;
QgsSettings settings;
QString scanZipSetting = settings.value( QStringLiteral( "qgis/scanZipInBrowser2" ), "basic" ).toString();
mZipFileList.clear();
QgsDebugMsgLevel( QString( "mFilePath = %1 path = %2 name= %3 scanZipSetting= %4 vsiPrefix= %5" ).arg( mFilePath, path(), name(), scanZipSetting, mVsiPrefix ), 2 );
// if scanZipBrowser == no: skip to the next file
if ( scanZipSetting == QLatin1String( "no" ) )
{
return children;
}
// first get list of files
getZipFileList();
// loop over files inside zip
Q_FOREACH ( const QString &fileName, mZipFileList )
{
QFileInfo info( fileName );
tmpPath = mVsiPrefix + mFilePath + '/' + fileName;
QgsDebugMsgLevel( "tmpPath = " + tmpPath, 3 );
// Q_FOREACH( dataItem_t *dataItem, mDataItemPtr )
for ( int i = 0; i < sProviderNames.size(); i++ )
{
// ugly hack to remove .dbf file if there is a .shp file
if ( sProviderNames[i] == QLatin1String( "ogr" ) )
{
if ( info.suffix().toLower() == QLatin1String( "dbf" ) )
{
if ( mZipFileList.indexOf( fileName.left( fileName.count() - 4 ) + ".shp" ) != -1 )
continue;
}
if ( info.completeSuffix().toLower() == QLatin1String( "shp.xml" ) )
{
continue;
}
}
// try to get data item from provider
dataItem_t *dataItem = sDataItemPtr.at( i );
if ( dataItem )
{
QgsDebugMsgLevel( QString( "trying to load item %1 with %2" ).arg( tmpPath, sProviderNames.at( i ) ), 3 );
QgsDataItem *item = dataItem( tmpPath, this );
if ( item )
{
QgsDebugMsgLevel( "loaded item", 3 );
// the item comes with zipped file name, set the name to relative path within zip file
item->setName( fileName );
children.append( item );
}
else
{
QgsDebugMsgLevel( "not loaded item", 3 );
}
}
}
}
return children;
}
QgsDataItem *QgsZipItem::itemFromPath( QgsDataItem *parent, const QString &path, const QString &name )
{
return itemFromPath( parent, path, name, path );
}
QgsDataItem *QgsZipItem::itemFromPath( QgsDataItem *parent, const QString &filePath, const QString &name, const QString &path )
{
QgsSettings settings;
QString scanZipSetting = settings.value( QStringLiteral( "qgis/scanZipInBrowser2" ), "basic" ).toString();
int zipFileCount = 0;
QStringList zipFileList;
QString vsiPrefix = QgsZipItem::vsiPrefix( filePath );
QgsZipItem *zipItem = nullptr;
bool populated = false;
QgsDebugMsgLevel( QString( "path = %1 name= %2 scanZipSetting= %3 vsiPrefix= %4" ).arg( path, name, scanZipSetting, vsiPrefix ), 3 );
// don't scan if scanZipBrowser == no
if ( scanZipSetting == QLatin1String( "no" ) )
return nullptr;
// don't scan if this file is not a /vsizip/ or /vsitar/ item
if ( ( vsiPrefix != QLatin1String( "/vsizip/" ) && vsiPrefix != QLatin1String( "/vsitar/" ) ) )
return nullptr;
zipItem = new QgsZipItem( parent, name, filePath, path );
if ( zipItem )
{
// force populate zipItem if it has less than 10 items and is not a .tgz or .tar.gz file (slow loading)
// for other items populating will be delayed until item is opened
// this might be polluting the tree with empty items but is necessary for performance reasons
// could also accept all files smaller than a certain size and add options for file count and/or size
// first get list of files inside .zip or .tar files
if ( path.endsWith( QLatin1String( ".zip" ), Qt::CaseInsensitive ) ||
path.endsWith( QLatin1String( ".tar" ), Qt::CaseInsensitive ) )
{
zipFileList = zipItem->getZipFileList();
}
// force populate if less than 10 items
if ( !zipFileList.isEmpty() && zipFileList.count() <= 10 )
{
zipItem->populate( zipItem->createChildren() );
populated = true; // there is no QgsDataItem::isPopulated() function
QgsDebugMsgLevel( QString( "Got zipItem with %1 children, path=%2, name=%3" ).arg( zipItem->rowCount() ).arg( zipItem->path(), zipItem->name() ), 3 );
}
else
{
QgsDebugMsgLevel( QString( "Delaying populating zipItem with path=%1, name=%2" ).arg( zipItem->path(), zipItem->name() ), 3 );
}
}
// only display if has children or if is not populated
if ( zipItem && ( !populated || zipItem->rowCount() > 1 ) )
{
QgsDebugMsgLevel( "returning zipItem", 3 );
return zipItem;
}
// if 1 or 0 child found, create a single data item using the normal path or the full path given by QgsZipItem
else
{
QString vsiPath = vsiPrefix + filePath;
if ( zipItem )
{
QVector<QgsDataItem *> children = zipItem->children();
if ( children.size() == 1 )
{
// take the name of the only child so we can get a normal data item from it
QgsLayerItem *layerItem = qobject_cast<QgsLayerItem *>( children.first() );
if ( layerItem )
vsiPath = layerItem->uri();
}
zipFileCount = zipFileList.count();
delete zipItem;
}
QgsDebugMsgLevel( QString( "will try to create a normal dataItem from filePath= %2 or vsiPath = %3" ).arg( filePath, vsiPath ), 3 );
// try to open using registered providers (gdal and ogr)
for ( int i = 0; i < sProviderNames.size(); i++ )
{
dataItem_t *dataItem = sDataItemPtr.at( i );
if ( dataItem )
{
QgsDataItem *item = nullptr;
// try first with normal path (Passthru)
// this is to simplify .qml handling, and without this some tests will fail
// (e.g. testZipItemVectorTransparency(), second test)
if ( ( sProviderNames.at( i ) == QLatin1String( "ogr" ) ) ||
( sProviderNames.at( i ) == QLatin1String( "gdal" ) && zipFileCount == 1 ) )
item = dataItem( filePath, parent );
// try with /vsizip/
if ( ! item )
item = dataItem( vsiPath, parent );
if ( item )
return item;
}
}
}
return nullptr;
}
QStringList QgsZipItem::getZipFileList()
{
if ( ! mZipFileList.isEmpty() )
return mZipFileList;
QString tmpPath;
QgsSettings settings;
QString scanZipSetting = settings.value( QStringLiteral( "qgis/scanZipInBrowser2" ), "basic" ).toString();
QgsDebugMsgLevel( QString( "mFilePath = %1 name= %2 scanZipSetting= %3 vsiPrefix= %4" ).arg( mFilePath, name(), scanZipSetting, mVsiPrefix ), 3 );
// if scanZipBrowser == no: skip to the next file
if ( scanZipSetting == QLatin1String( "no" ) )
{
return mZipFileList;
}
// get list of files inside zip file
QgsDebugMsgLevel( QString( "Open file %1 with gdal vsi" ).arg( mVsiPrefix + mFilePath ), 3 );
char **papszSiblingFiles = VSIReadDirRecursive1( QString( mVsiPrefix + mFilePath ).toLocal8Bit().constData() );
if ( papszSiblingFiles )
{
for ( int i = 0; i < CSLCount( papszSiblingFiles ); i++ )
{
tmpPath = papszSiblingFiles[i];
QgsDebugMsgLevel( QString( "Read file %1" ).arg( tmpPath ), 3 );
// skip directories (files ending with /)
if ( tmpPath.right( 1 ) != QLatin1String( "/" ) )
mZipFileList << tmpPath;
}
CSLDestroy( papszSiblingFiles );
}
else
{
QgsDebugMsg( QString( "Error reading %1" ).arg( mFilePath ) );
}
return mZipFileList;
}
///@cond PRIVATE
QgsProjectHomeItem::QgsProjectHomeItem( QgsDataItem *parent, const QString &name, const QString &dirPath, const QString &path )
: QgsDirectoryItem( parent, name, dirPath, path )
{
}
QIcon QgsProjectHomeItem::icon()
{
return QgsApplication::getThemeIcon( QStringLiteral( "mIconQgsProjectFile.svg" ) );
}
QVariant QgsProjectHomeItem::sortKey() const
{
return QStringLiteral( " 1" );
}
QgsFavoriteItem::QgsFavoriteItem( QgsFavoritesItem *parent, const QString &name, const QString &dirPath, const QString &path )
: QgsDirectoryItem( parent, name, dirPath, path )
, mFavorites( parent )
{
}
void QgsFavoriteItem::rename( const QString &name )
{
mFavorites->renameFavorite( dirPath(), name );
}
///@endcond