Skip to content

Commit

Permalink
Merge pull request #8115 from 3nids/fix_cannot_paste_geom_style
Browse files Browse the repository at this point in the history
[#19980] Fix cannot paste style when geometry doesn't match
  • Loading branch information
3nids committed Oct 4, 2018
2 parents 226fb47 + 7858b4c commit 2473973
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 24 deletions.
4 changes: 4 additions & 0 deletions python/core/auto_generated/qgsxmlutils.sip.in
Expand Up @@ -9,6 +9,9 @@






class QgsXmlUtils
{
%Docstring
Expand Down Expand Up @@ -67,6 +70,7 @@ Supported types are
%Docstring
Read a QVariant from a QDomElement.
%End

};


Expand Down
1 change: 1 addition & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -8884,6 +8884,7 @@ void QgisApp::copyStyle( QgsMapLayer *sourceLayer, QgsMapLayer::StyleCategories
}
// Copies data in text form as well, so the XML can be pasted into a text editor
clipboard()->setData( QGSCLIPBOARD_STYLE_MIME, doc.toByteArray(), doc.toString() );

// Enables the paste menu element
mActionPasteStyle->setEnabled( true );
}
Expand Down
50 changes: 33 additions & 17 deletions src/app/qgsapplayertreeviewmenuprovider.cpp
Expand Up @@ -39,6 +39,8 @@
#include "qgssymbolselectordialog.h"
#include "qgssinglesymbolrenderer.h"
#include "qgsmaplayerstylecategoriesmodel.h"
#include "qgsxmlutils.h"



QgsAppLayerTreeViewMenuProvider::QgsAppLayerTreeViewMenuProvider( QgsLayerTreeView *view, QgsMapCanvas *canvas )
Expand Down Expand Up @@ -349,24 +351,38 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
{
if ( layer->type() == QgsMapLayer::VectorLayer )
{
QMenu *pasteStyleMenu = menuStyleManager->addMenu( tr( "Paste Style" ) );
pasteStyleMenu->setToolTipsVisible( true );

QgsMapLayerStyleCategoriesModel *model = new QgsMapLayerStyleCategoriesModel( pasteStyleMenu );
model->setShowAllCategories( true );
for ( int row = 0; row < model->rowCount(); ++row )
QDomDocument doc( QStringLiteral( "qgis" ) );
QString errorMsg;
int errorLine, errorColumn;
if ( doc.setContent( app->clipboard()->data( QGSCLIPBOARD_STYLE_MIME ), false, &errorMsg, &errorLine, &errorColumn ) )
{
QModelIndex index = model->index( row, 0 );
QgsMapLayer::StyleCategory category = model->data( index, Qt::UserRole ).value<QgsMapLayer::StyleCategory>();
QString name = model->data( index, Qt::DisplayRole ).toString();
QString tooltip = model->data( index, Qt::ToolTipRole ).toString();
QIcon icon = model->data( index, Qt::DecorationRole ).value<QIcon>();
QAction *copyAction = new QAction( icon, name, pasteStyleMenu );
copyAction->setToolTip( tooltip );
connect( copyAction, &QAction::triggered, this, [ = ]() {app->pasteStyle( layer, category );} );
pasteStyleMenu->addAction( copyAction );
if ( category == QgsMapLayer::AllStyleCategories )
pasteStyleMenu->addSeparator();
QDomElement myRoot = doc.firstChildElement( QStringLiteral( "qgis" ) );
if ( !myRoot.isNull() )
{
QMenu *pasteStyleMenu = menuStyleManager->addMenu( tr( "Paste Style" ) );
pasteStyleMenu->setToolTipsVisible( true );

QgsMapLayer::StyleCategories sourceCategories = QgsXmlUtils::readFlagAttribute( myRoot, QStringLiteral( "styleCategories" ), QgsMapLayer::AllStyleCategories );

QgsMapLayerStyleCategoriesModel *model = new QgsMapLayerStyleCategoriesModel( pasteStyleMenu );
model->setShowAllCategories( true );
for ( int row = 0; row < model->rowCount(); ++row )
{
QModelIndex index = model->index( row, 0 );
QgsMapLayer::StyleCategory category = model->data( index, Qt::UserRole ).value<QgsMapLayer::StyleCategory>();
QString name = model->data( index, Qt::DisplayRole ).toString();
QString tooltip = model->data( index, Qt::ToolTipRole ).toString();
QIcon icon = model->data( index, Qt::DecorationRole ).value<QIcon>();
QAction *pasteAction = new QAction( icon, name, pasteStyleMenu );
pasteAction->setToolTip( tooltip );
connect( pasteAction, &QAction::triggered, this, [ = ]() {app->pasteStyle( layer, category );} );
pasteStyleMenu->addAction( pasteAction );
if ( category == QgsMapLayer::AllStyleCategories )
pasteStyleMenu->addSeparator();
else
pasteAction->setEnabled( sourceCategories.testFlag( category ) );
}
}
}
}
else
Expand Down
24 changes: 18 additions & 6 deletions src/core/qgsmaplayer.cpp
Expand Up @@ -551,6 +551,11 @@ bool QgsMapLayer::writeLayerXml( QDomElement &layerElement, QDomDocument &docume
void QgsMapLayer::writeCommonStyle( QDomElement &layerElement, QDomDocument &document,
const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) const
{
// save categories
QMetaEnum metaEnum = QMetaEnum::fromType<QgsMapLayer::StyleCategories>();
QString categoriesKeys( metaEnum.valueToKeys( static_cast<int>( categories ) ) );
layerElement.setAttribute( QStringLiteral( "styleCategories" ), categoriesKeys );

if ( categories.testFlag( Rendering ) )
{
// use scale dependent visibility flag
Expand Down Expand Up @@ -1054,15 +1059,22 @@ bool QgsMapLayer::importNamedStyle( QDomDocument &myDocument, QString &myErrorMe
styleFile.updateRevision( thisVersion );
}

// Get source categories
QgsMapLayer::StyleCategories sourceCategories = QgsXmlUtils::readFlagAttribute( myRoot, QStringLiteral( "styleCategories" ), QgsMapLayer::AllStyleCategories );

//Test for matching geometry type on vector layers when applying, if geometry type is given in the style
if ( type() == QgsMapLayer::VectorLayer && !myRoot.firstChildElement( QStringLiteral( "layerGeometryType" ) ).isNull() )
if ( ( sourceCategories.testFlag( QgsMapLayer::Symbology ) || sourceCategories.testFlag( QgsMapLayer::Symbology3D ) ) &&
( categories.testFlag( QgsMapLayer::Symbology ) || categories.testFlag( QgsMapLayer::Symbology3D ) ) )
{
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( this );
QgsWkbTypes::GeometryType importLayerGeometryType = static_cast<QgsWkbTypes::GeometryType>( myRoot.firstChildElement( QStringLiteral( "layerGeometryType" ) ).text().toInt() );
if ( vl->geometryType() != importLayerGeometryType )
if ( type() == QgsMapLayer::VectorLayer && !myRoot.firstChildElement( QStringLiteral( "layerGeometryType" ) ).isNull() )
{
myErrorMessage = tr( "Cannot apply style to layer with a different geometry type" );
return false;
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( this );
QgsWkbTypes::GeometryType importLayerGeometryType = static_cast<QgsWkbTypes::GeometryType>( myRoot.firstChildElement( QStringLiteral( "layerGeometryType" ) ).text().toInt() );
if ( vl->geometryType() != importLayerGeometryType )
{
myErrorMessage = tr( "Cannot apply style with symbology to layer with a different geometry type" );
return false;
}
}
}

Expand Down
31 changes: 30 additions & 1 deletion src/core/qgsxmlutils.h
Expand Up @@ -16,14 +16,17 @@
#define QGSXMLUTILS_H

class QDomDocument;
class QDomElement;

class QgsRectangle;

#include <QDomElement>

#include "qgis_core.h"
#include "qgis.h"
#include "qgsunittypes.h"



/**
* \ingroup core
* Assorted helper methods for reading and writing chunks of XML
Expand Down Expand Up @@ -75,6 +78,32 @@ class CORE_EXPORT QgsXmlUtils
* Read a QVariant from a QDomElement.
*/
static QVariant readVariant( const QDomElement &element );

/**
* Read a flag value from an attribute of the element.
* \param element the element to read the attribute from
* \param attributeName the attribute name
* \param defaultValue the default value as a flag
* \note The flag value is a text as returned by \see QMetaEnum::valueToKeys.
* The flag must have been declared with Q_ENUM macro.
* \since QGIS 3.4
*/
template<class T> static T readFlagAttribute( const QDomElement &element, const QString &attributeName, T defaultValue ) SIP_SKIP
{
T value = defaultValue;
// Get source categories
QMetaEnum metaEnum = QMetaEnum::fromType<T>();
QString sourceCategoriesStr( element.attribute( attributeName, metaEnum.valueToKeys( static_cast<int>( defaultValue ) ) ) );
if ( metaEnum.isValid() )
{
bool ok = false;
const char *vs = sourceCategoriesStr.toUtf8().data();
int newValue = metaEnum.keysToValue( vs, &ok );
if ( ok )
value = static_cast<T>( newValue );
}
return value;
}
};


Expand Down

0 comments on commit 2473973

Please sign in to comment.