Skip to content

Commit

Permalink
Cleanup handling of QgsMimeDataUtils::Uri corresponding to project la…
Browse files Browse the repository at this point in the history
…yers

And add tests
  • Loading branch information
nyalldawson committed Jun 12, 2019
1 parent 54434a6 commit 760af67
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 69 deletions.
30 changes: 30 additions & 0 deletions python/core/auto_generated/qgsmimedatautils.sip.in
Expand Up @@ -26,6 +26,13 @@ Constructs invalid URI
explicit Uri( QString &encData );
%Docstring
Constructs URI from encoded data
%End

explicit Uri( QgsMapLayer *layer );
%Docstring
Constructs a URI corresponding to the specified ``layer``.

.. versionadded:: 3.8
%End

bool isValid() const;
Expand Down Expand Up @@ -62,6 +69,17 @@ Gets mesh layer from uri if possible, otherwise returns ``None`` and error is se

:param owner: set to ``True`` if caller becomes owner
:param error: set to error message if cannot get raster
%End

QgsMapLayer *mapLayer() const;
%Docstring
Returns the layer from the active project corresponding to this uri (if possible),
otherwise returns ``None``.

Unlike vectorLayer(), rasterLayer(), or meshLayer(), this method will not attempt
to create a new layer corresponding to the URI.

.. versionadded:: 3.8
%End

QString layerType;
Expand All @@ -73,6 +91,10 @@ Gets mesh layer from uri if possible, otherwise returns ``None`` and error is se
QStringList supportedCrs;
QStringList supportedFormats;

QString layerId;

QString pId;

SIP_PYOBJECT __repr__();
%MethodCode
QString str = QStringLiteral( "<QgsMimeDataUtils::Uri (%1): %2>" ).arg( sipCpp->providerKey, sipCpp->uri );
Expand All @@ -95,6 +117,14 @@ Encodes a URI list to a new QMimeData object.
Returns encoded URI list from a list of layer tree nodes.

.. versionadded:: 3.0
%End

static bool hasOriginatedFromCurrentAppInstance( const QgsMimeDataUtils::Uri &uri );
%Docstring
Returns ``True`` if ``uri`` originated from the current QGIS application
instance.

.. versionadded:: 3.8
%End

};
Expand Down
162 changes: 101 additions & 61 deletions src/core/qgsmimedatautils.cpp
Expand Up @@ -31,7 +31,7 @@ static const char *QGIS_URILIST_MIMETYPE = "application/x-vnd.qgis.qgis.uri";
QgsMimeDataUtils::Uri::Uri( QString &encData )
{
QgsDebugMsg( "encData: " + encData );
QStringList decoded = decode( encData );
const QStringList decoded = decode( encData );
if ( decoded.size() < 4 )
return;

Expand All @@ -40,7 +40,7 @@ QgsMimeDataUtils::Uri::Uri( QString &encData )
name = decoded[2];
uri = decoded[3];

if ( layerType == QLatin1String( "raster" ) && decoded.size() == 6 )
if ( layerType == QLatin1String( "raster" ) && decoded.size() >= 6 )
{
supportedCrs = decode( decoded[4] );
supportedFormats = decode( decoded[5] );
Expand All @@ -51,48 +51,79 @@ QgsMimeDataUtils::Uri::Uri( QString &encData )
supportedFormats.clear();
}

QgsDebugMsg( QStringLiteral( "type:%1 key:%2 name:%3 uri:%4 supportedCRS:%5 supportedFormats:%6" )
.arg( layerType, providerKey, name, uri,
supportedCrs.join( ", " ),
supportedFormats.join( ", " ) ) );
if ( decoded.size() > 6 )
layerId = decoded.at( 6 );
if ( decoded.size() > 7 )
pId = decoded.at( 7 );

QgsDebugMsgLevel( QStringLiteral( "type:%1 key:%2 name:%3 uri:%4 supportedCRS:%5 supportedFormats:%6" )
.arg( layerType, providerKey, name, uri,
supportedCrs.join( ',' ),
supportedFormats.join( ',' ) ), 2 );
}

QgsMimeDataUtils::Uri::Uri( QgsMapLayer *layer )
: providerKey( layer->dataProvider()->name() )
, name( layer->name() )
, uri( layer->dataProvider()->dataSourceUri() )
, layerId( layer->id() )
, pId( QString::number( QCoreApplication::applicationPid() ) )
{
switch ( layer->type() )
{
case QgsMapLayerType::VectorLayer:
{
layerType = QStringLiteral( "vector" );
break;
}
case QgsMapLayerType::RasterLayer:
{
layerType = QStringLiteral( "raster" );
break;
}

case QgsMapLayerType::MeshLayer:
{
layerType = QStringLiteral( "mesh" );
break;
}

case QgsMapLayerType::PluginLayer:
{
// plugin layers do not have a standard way of storing their URI...
return;
}
}
}

QString QgsMimeDataUtils::Uri::data() const
{
return encode( QStringList() << layerType << providerKey << name << uri << encode( supportedCrs ) << encode( supportedFormats ) );
return encode( QStringList() << layerType << providerKey << name << uri << encode( supportedCrs ) << encode( supportedFormats ) << layerId << pId );
}

QgsVectorLayer *QgsMimeDataUtils::Uri::vectorLayer( bool &owner, QString &error ) const
{
owner = false;
error.clear();
if ( layerType != QLatin1String( "vector" ) )
{
error = QObject::tr( "%1: Not a vector layer." ).arg( name );
return nullptr;
}
if ( providerKey == QLatin1String( "memory" ) )

if ( !layerId.isEmpty() && QgsMimeDataUtils::hasOriginatedFromCurrentAppInstance( *this ) )
{
QUrl url = QUrl::fromEncoded( uri.toUtf8() );
if ( !url.hasQueryItem( QStringLiteral( "pid" ) ) || !url.hasQueryItem( QStringLiteral( "layerid" ) ) )
if ( QgsVectorLayer *vectorLayer = QgsProject::instance()->mapLayer<QgsVectorLayer *>( layerId ) )
{
error = QObject::tr( "Memory layer uri does not contain process or layer id." );
return nullptr;
return vectorLayer;
}
qint64 pid = url.queryItemValue( QStringLiteral( "pid" ) ).toLongLong();
if ( pid != QCoreApplication::applicationPid() )
{
error = QObject::tr( "Memory layer from another QGIS instance." );
return nullptr;
}
QString layerId = url.queryItemValue( QStringLiteral( "layerid" ) );
QgsVectorLayer *vectorLayer = QgsProject::instance()->mapLayer<QgsVectorLayer *>( layerId );
if ( !vectorLayer )
{
error = QObject::tr( "Cannot get memory layer." );
return nullptr;
}
return vectorLayer;
}
if ( providerKey == QLatin1String( "memory" ) )
{
error = QObject::tr( "Cannot get memory layer." );
return nullptr;
}

owner = true;
const QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };
return new QgsVectorLayer( uri, name, providerKey, options );
Expand All @@ -101,27 +132,56 @@ QgsVectorLayer *QgsMimeDataUtils::Uri::vectorLayer( bool &owner, QString &error
QgsRasterLayer *QgsMimeDataUtils::Uri::rasterLayer( bool &owner, QString &error ) const
{
owner = false;
error.clear();
if ( layerType != QLatin1String( "raster" ) )
{
error = QObject::tr( "%1: Not a raster layer." ).arg( name );
return nullptr;
}

if ( !layerId.isEmpty() && QgsMimeDataUtils::hasOriginatedFromCurrentAppInstance( *this ) )
{
if ( QgsRasterLayer *rasterLayer = QgsProject::instance()->mapLayer<QgsRasterLayer *>( layerId ) )
{
return rasterLayer;
}
}

owner = true;
return new QgsRasterLayer( uri, name, providerKey );
}

QgsMeshLayer *QgsMimeDataUtils::Uri::meshLayer( bool &owner, QString &error ) const
{
owner = false;
error.clear();
if ( layerType != QLatin1String( "mesh" ) )
{
error = QObject::tr( "%1: Not a mesh layer." ).arg( name );
return nullptr;
}

if ( !layerId.isEmpty() && QgsMimeDataUtils::hasOriginatedFromCurrentAppInstance( *this ) )
{
if ( QgsMeshLayer *meshLayer = QgsProject::instance()->mapLayer<QgsMeshLayer *>( layerId ) )
{
return meshLayer;
}
}

owner = true;
return new QgsMeshLayer( uri, name, providerKey );
}

QgsMapLayer *QgsMimeDataUtils::Uri::mapLayer() const
{
if ( !layerId.isEmpty() && QgsMimeDataUtils::hasOriginatedFromCurrentAppInstance( *this ) )
{
return QgsProject::instance()->mapLayer( layerId );
}
return nullptr;
}

// -----

bool QgsMimeDataUtils::isUriList( const QMimeData *data )
Expand Down Expand Up @@ -169,39 +229,10 @@ static void _addLayerTreeNodeToUriList( QgsLayerTreeNode *node, QgsMimeDataUtils
if ( !layer )
return;

QgsMimeDataUtils::Uri uri;
uri.name = layer->name();
uri.uri = layer->dataProvider()->dataSourceUri();
uri.providerKey = layer->dataProvider()->name();
if ( layer->type() == QgsMapLayerType::PluginLayer )
return; // plugin layers do not have a standard way of storing their URI...

switch ( layer->type() )
{
case QgsMapLayerType::VectorLayer:
{
uri.layerType = QStringLiteral( "vector" );
if ( uri.providerKey == QStringLiteral( "memory" ) )
{
QUrl url = QUrl::fromEncoded( uri.uri.toUtf8() );
url.addQueryItem( QStringLiteral( "pid" ), QString::number( QCoreApplication::applicationPid() ) );
url.addQueryItem( QStringLiteral( "layerid" ), layer->id() );
uri.uri = QString( url.toEncoded() );
}
break;
}
case QgsMapLayerType::RasterLayer:
{
uri.layerType = QStringLiteral( "raster" );
break;
}

case QgsMapLayerType::MeshLayer:
case QgsMapLayerType::PluginLayer:
{
// plugin layers do not have a standard way of storing their URI...
return;
}
}
uris << uri;
uris << QgsMimeDataUtils::Uri( layer );
}
}

Expand All @@ -214,17 +245,26 @@ QByteArray QgsMimeDataUtils::layerTreeNodesToUriList( const QList<QgsLayerTreeNo
return uriListToByteArray( uris );
}

bool QgsMimeDataUtils::hasOriginatedFromCurrentAppInstance( const QgsMimeDataUtils::Uri &uri )
{
if ( uri.pId.isEmpty() )
return false;

const qint64 pid = uri.pId.toLongLong();
return pid == QCoreApplication::applicationPid();
}

QString QgsMimeDataUtils::encode( const QStringList &items )
{
QString encoded;
// Do not escape colon twice
QRegularExpression re( "(?<!\\\\):" );
QRegularExpression re( QStringLiteral( "(?<!\\\\):" ) );
const auto constItems = items;
for ( const QString &item : constItems )
{
QString str = item;
str.replace( '\\', QLatin1String( "\\\\" ) );
str.replace( re, QLatin1String( "\\:" ) );
str.replace( '\\', QStringLiteral( "\\\\" ) );
str.replace( re, QStringLiteral( "\\:" ) );
encoded += str + ':';
}
return encoded.left( encoded.length() - 1 );
Expand Down
40 changes: 40 additions & 0 deletions src/core/qgsmimedatautils.h
Expand Up @@ -26,6 +26,7 @@ class QgsLayerTreeNode;
class QgsVectorLayer;
class QgsRasterLayer;
class QgsMeshLayer;
class QgsMapLayer;

/**
* \ingroup core
Expand All @@ -42,6 +43,13 @@ class CORE_EXPORT QgsMimeDataUtils
//! Constructs URI from encoded data
explicit Uri( QString &encData );

/**
* Constructs a URI corresponding to the specified \a layer.
*
* \since QGIS 3.8
*/
explicit Uri( QgsMapLayer *layer );

/**
* Returns whether the object contains valid data
* \since QGIS 3.0
Expand Down Expand Up @@ -72,6 +80,17 @@ class CORE_EXPORT QgsMimeDataUtils
*/
QgsMeshLayer *meshLayer( bool &owner, QString &error ) const;

/**
* Returns the layer from the active project corresponding to this uri (if possible),
* otherwise returns NULLPTR.
*
* Unlike vectorLayer(), rasterLayer(), or meshLayer(), this method will not attempt
* to create a new layer corresponding to the URI.
*
* \since QGIS 3.8
*/
QgsMapLayer *mapLayer() const;

/**
* Type of URI.
*
Expand Down Expand Up @@ -103,6 +122,19 @@ class CORE_EXPORT QgsMimeDataUtils
QStringList supportedCrs;
QStringList supportedFormats;

/**
* Layer ID, if uri is associated with a layer from a QgsProject.
* \since QGIS 3.8
*/
QString layerId;

/**
* Unique ID associated with application instance. Can be used to identify
* if mime data was created inside the current application instance or not.
* \since QGIS 3.8
*/
QString pId;

#ifdef SIP_RUN
SIP_PYOBJECT __repr__();
% MethodCode
Expand All @@ -128,6 +160,14 @@ class CORE_EXPORT QgsMimeDataUtils
*/
static QByteArray layerTreeNodesToUriList( const QList<QgsLayerTreeNode *> &nodes );

/**
* Returns TRUE if \a uri originated from the current QGIS application
* instance.
*
* \since QGIS 3.8
*/
static bool hasOriginatedFromCurrentAppInstance( const QgsMimeDataUtils::Uri &uri );

private:
static QString encode( const QStringList &items );
static QStringList decode( const QString &encoded );
Expand Down

0 comments on commit 760af67

Please sign in to comment.