/*************************************************************************** qgsmaplayer.cpp - description ------------------- begin : Fri Jun 28 2002 copyright : (C) 2002 by Gary E.Sherman email : sherman at mrcc.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 #include #include #include #include #include #include #include #include #include #include "qgssqliteutils.h" #include "qgssqliteutils.h" #include "qgs3drendererregistry.h" #include "qgsabstract3drenderer.h" #include "qgsapplication.h" #include "qgscoordinatereferencesystem.h" #include "qgsdatasourceuri.h" #include "qgslogger.h" #include "qgsauthmanager.h" #include "qgsmaplayer.h" #include "qgsmaplayerlegend.h" #include "qgsmaplayerstylemanager.h" #include "qgsmeshlayer.h" #include "qgspathresolver.h" #include "qgsprojectfiletransform.h" #include "qgsproject.h" #include "qgsproviderregistry.h" #include "qgsrasterlayer.h" #include "qgsreadwritecontext.h" #include "qgsrectangle.h" #include "qgsvectorlayer.h" #include "qgsvectordataprovider.h" #include "qgsxmlutils.h" #include "qgsstringutils.h" QString QgsMapLayer::extensionPropertyType( QgsMapLayer::PropertyType type ) { switch ( type ) { case Metadata: return QStringLiteral( ".qmd" ); case Style: return QStringLiteral( ".qml" ); } return QString(); } QgsMapLayer::QgsMapLayer( QgsMapLayer::LayerType type, const QString &lyrname, const QString &source ) : mDataSource( source ) , mLayerName( lyrname ) , mLayerType( type ) , mUndoStack( new QUndoStack( this ) ) , mUndoStackStyles( new QUndoStack( this ) ) , mStyleManager( new QgsMapLayerStyleManager( this ) ) , mRefreshTimer( new QTimer( this ) ) { //mShortName.replace( QRegExp( "[\\W]" ), "_" ); // Generate the unique ID of this layer QString uuid = QUuid::createUuid().toString(); // trim { } from uuid mID = lyrname + '_' + uuid.mid( 1, uuid.length() - 2 ); // Tidy the ID up to avoid characters that may cause problems // elsewhere (e.g in some parts of XML). Replaces every non-word // character (word characters are the alphabet, numbers and // underscore) with an underscore. // Note that the first backslashe in the regular expression is // there for the compiler, so the pattern is actually \W mID.replace( QRegExp( "[\\W]" ), QStringLiteral( "_" ) ); connect( this, &QgsMapLayer::crsChanged, this, &QgsMapLayer::configChanged ); connect( this, &QgsMapLayer::nameChanged, this, &QgsMapLayer::configChanged ); connect( mStyleManager, &QgsMapLayerStyleManager::currentStyleChanged, this, &QgsMapLayer::styleChanged ); connect( mRefreshTimer, &QTimer::timeout, this, [ = ] { triggerRepaint( true ); } ); } QgsMapLayer::~QgsMapLayer() { delete m3DRenderer; delete mLegend; delete mStyleManager; } void QgsMapLayer::clone( QgsMapLayer *layer ) const { layer->setBlendMode( blendMode() ); Q_FOREACH ( const QString &s, styleManager()->styles() ) { layer->styleManager()->addStyle( s, styleManager()->style( s ) ); } layer->setName( name() ); layer->setShortName( shortName() ); layer->setExtent( extent() ); layer->setMaximumScale( maximumScale() ); layer->setMinimumScale( minimumScale() ); layer->setScaleBasedVisibility( hasScaleBasedVisibility() ); layer->setTitle( title() ); layer->setAbstract( abstract() ); layer->setKeywordList( keywordList() ); layer->setDataUrl( dataUrl() ); layer->setDataUrlFormat( dataUrlFormat() ); layer->setAttribution( attribution() ); layer->setAttributionUrl( attributionUrl() ); layer->setMetadataUrl( metadataUrl() ); layer->setMetadataUrlType( metadataUrlType() ); layer->setMetadataUrlFormat( metadataUrlFormat() ); layer->setLegendUrl( legendUrl() ); layer->setLegendUrlFormat( legendUrlFormat() ); layer->setDependencies( dependencies() ); layer->setCrs( crs() ); layer->setCustomProperties( mCustomProperties ); } QgsMapLayer::LayerType QgsMapLayer::type() const { return mLayerType; } QString QgsMapLayer::id() const { return mID; } void QgsMapLayer::setName( const QString &name ) { if ( name == mLayerName ) return; mLayerName = name; emit nameChanged(); } QString QgsMapLayer::name() const { QgsDebugMsgLevel( "returning name '" + mLayerName + '\'', 4 ); return mLayerName; } QgsDataProvider *QgsMapLayer::dataProvider() { return nullptr; } const QgsDataProvider *QgsMapLayer::dataProvider() const { return nullptr; } QString QgsMapLayer::publicSource() const { // Redo this every time we're asked for it, as we don't know if // dataSource has changed. QString safeName = QgsDataSourceUri::removePassword( mDataSource ); return safeName; } QString QgsMapLayer::source() const { return mDataSource; } QgsRectangle QgsMapLayer::extent() const { return mExtent; } void QgsMapLayer::setBlendMode( QPainter::CompositionMode blendMode ) { mBlendMode = blendMode; emit blendModeChanged( blendMode ); emit styleChanged(); } QPainter::CompositionMode QgsMapLayer::blendMode() const { return mBlendMode; } bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, QgsReadWriteContext &context ) { bool layerError; QDomNode mnl; QDomElement mne; // read provider QString provider; mnl = layerElement.namedItem( QStringLiteral( "provider" ) ); mne = mnl.toElement(); provider = mne.text(); // set data source mnl = layerElement.namedItem( QStringLiteral( "datasource" ) ); mne = mnl.toElement(); mDataSource = mne.text(); // if the layer needs authentication, ensure the master password is set QRegExp rx( "authcfg=([a-z]|[A-Z]|[0-9]){7}" ); if ( ( rx.indexIn( mDataSource ) != -1 ) && !QgsApplication::authManager()->setMasterPassword( true ) ) { return false; } mDataSource = decodedSource( mDataSource, provider, context ); // Set the CRS from project file, asking the user if necessary. // Make it the saved CRS to have WMS layer projected correctly. // We will still overwrite whatever GDAL etc picks up anyway // further down this function. mnl = layerElement.namedItem( QStringLiteral( "layername" ) ); mne = mnl.toElement(); QgsCoordinateReferenceSystem savedCRS; CUSTOM_CRS_VALIDATION savedValidation; QDomNode srsNode = layerElement.namedItem( QStringLiteral( "srs" ) ); mCRS.readXml( srsNode ); mCRS.setValidationHint( tr( "Specify CRS for layer %1" ).arg( mne.text() ) ); mCRS.validate(); savedCRS = mCRS; // Do not validate any projections in children, they will be overwritten anyway. // No need to ask the user for a projections when it is overwritten, is there? savedValidation = QgsCoordinateReferenceSystem::customCrsValidation(); QgsCoordinateReferenceSystem::setCustomCrsValidation( nullptr ); // read custom properties before passing reading further to a subclass, so that // the subclass can also read custom properties readCustomProperties( layerElement ); QgsReadWriteContextCategoryPopper p = context.enterCategory( tr( "Layer" ), mne.text() ); // now let the children grab what they need from the Dom node. layerError = !readXml( layerElement, context ); // overwrite CRS with what we read from project file before the raster/vector // file reading functions changed it. They will if projections is specified in the file. // FIXME: is this necessary? QgsCoordinateReferenceSystem::setCustomCrsValidation( savedValidation ); mCRS = savedCRS; // Abort if any error in layer, such as not found. if ( layerError ) { return false; } // the internal name is just the data source basename //QFileInfo dataSourceFileInfo( mDataSource ); //internalName = dataSourceFileInfo.baseName(); // set ID mnl = layerElement.namedItem( QStringLiteral( "id" ) ); if ( ! mnl.isNull() ) { mne = mnl.toElement(); if ( ! mne.isNull() && mne.text().length() > 10 ) // should be at least 17 (yyyyMMddhhmmsszzz) { mID = mne.text(); } } // use scale dependent visibility flag setScaleBasedVisibility( layerElement.attribute( QStringLiteral( "hasScaleBasedVisibilityFlag" ) ).toInt() == 1 ); if ( layerElement.hasAttribute( QStringLiteral( "minimumScale" ) ) ) { // older element, when scales were reversed setMaximumScale( layerElement.attribute( QStringLiteral( "minimumScale" ) ).toDouble() ); setMinimumScale( layerElement.attribute( QStringLiteral( "maximumScale" ) ).toDouble() ); } else { setMaximumScale( layerElement.attribute( QStringLiteral( "maxScale" ) ).toDouble() ); setMinimumScale( layerElement.attribute( QStringLiteral( "minScale" ) ).toDouble() ); } setAutoRefreshInterval( layerElement.attribute( QStringLiteral( "autoRefreshTime" ), QStringLiteral( "0" ) ).toInt() ); setAutoRefreshEnabled( layerElement.attribute( QStringLiteral( "autoRefreshEnabled" ), QStringLiteral( "0" ) ).toInt() ); setRefreshOnNofifyMessage( layerElement.attribute( QStringLiteral( "refreshOnNotifyMessage" ), QString() ) ); setRefreshOnNotifyEnabled( layerElement.attribute( QStringLiteral( "refreshOnNotifyEnabled" ), QStringLiteral( "0" ) ).toInt() ); // set name mnl = layerElement.namedItem( QStringLiteral( "layername" ) ); mne = mnl.toElement(); //name can be translated setName( QgsProject::instance()->translate( QStringLiteral( "project:layers:%1" ).arg( layerElement.namedItem( QStringLiteral( "id" ) ).toElement().text() ), mne.text() ) ); //short name QDomElement shortNameElem = layerElement.firstChildElement( QStringLiteral( "shortname" ) ); if ( !shortNameElem.isNull() ) { mShortName = shortNameElem.text(); } //title QDomElement titleElem = layerElement.firstChildElement( QStringLiteral( "title" ) ); if ( !titleElem.isNull() ) { mTitle = titleElem.text(); } //abstract QDomElement abstractElem = layerElement.firstChildElement( QStringLiteral( "abstract" ) ); if ( !abstractElem.isNull() ) { mAbstract = abstractElem.text(); } //keywordList QDomElement keywordListElem = layerElement.firstChildElement( QStringLiteral( "keywordList" ) ); if ( !keywordListElem.isNull() ) { QStringList kwdList; for ( QDomNode n = keywordListElem.firstChild(); !n.isNull(); n = n.nextSibling() ) { kwdList << n.toElement().text(); } mKeywordList = kwdList.join( QStringLiteral( ", " ) ); } //metadataUrl QDomElement dataUrlElem = layerElement.firstChildElement( QStringLiteral( "dataUrl" ) ); if ( !dataUrlElem.isNull() ) { mDataUrl = dataUrlElem.text(); mDataUrlFormat = dataUrlElem.attribute( QStringLiteral( "format" ), QLatin1String( "" ) ); } //legendUrl QDomElement legendUrlElem = layerElement.firstChildElement( QStringLiteral( "legendUrl" ) ); if ( !legendUrlElem.isNull() ) { mLegendUrl = legendUrlElem.text(); mLegendUrlFormat = legendUrlElem.attribute( QStringLiteral( "format" ), QLatin1String( "" ) ); } //attribution QDomElement attribElem = layerElement.firstChildElement( QStringLiteral( "attribution" ) ); if ( !attribElem.isNull() ) { mAttribution = attribElem.text(); mAttributionUrl = attribElem.attribute( QStringLiteral( "href" ), QLatin1String( "" ) ); } //metadataUrl QDomElement metaUrlElem = layerElement.firstChildElement( QStringLiteral( "metadataUrl" ) ); if ( !metaUrlElem.isNull() ) { mMetadataUrl = metaUrlElem.text(); mMetadataUrlType = metaUrlElem.attribute( QStringLiteral( "type" ), QLatin1String( "" ) ); mMetadataUrlFormat = metaUrlElem.attribute( QStringLiteral( "format" ), QLatin1String( "" ) ); } // mMetadata.readFromLayer( this ); QDomElement metadataElem = layerElement.firstChildElement( QStringLiteral( "resourceMetadata" ) ); mMetadata.readMetadataXml( metadataElem ); return true; } // bool QgsMapLayer::readLayerXML bool QgsMapLayer::readXml( const QDomNode &layer_node, QgsReadWriteContext &context ) { Q_UNUSED( layer_node ); Q_UNUSED( context ); // NOP by default; children will over-ride with behavior specific to them return true; } // void QgsMapLayer::readXml bool QgsMapLayer::writeLayerXml( QDomElement &layerElement, QDomDocument &document, const QgsReadWriteContext &context ) const { // use scale dependent visibility flag layerElement.setAttribute( QStringLiteral( "hasScaleBasedVisibilityFlag" ), hasScaleBasedVisibility() ? 1 : 0 ); layerElement.setAttribute( QStringLiteral( "maxScale" ), QString::number( maximumScale() ) ); layerElement.setAttribute( QStringLiteral( "minScale" ), QString::number( minimumScale() ) ); if ( !extent().isNull() ) { layerElement.appendChild( QgsXmlUtils::writeRectangle( mExtent, document ) ); } layerElement.setAttribute( QStringLiteral( "autoRefreshTime" ), QString::number( mRefreshTimer->interval() ) ); layerElement.setAttribute( QStringLiteral( "autoRefreshEnabled" ), mRefreshTimer->isActive() ? 1 : 0 ); layerElement.setAttribute( QStringLiteral( "refreshOnNotifyEnabled" ), mIsRefreshOnNofifyEnabled ? 1 : 0 ); layerElement.setAttribute( QStringLiteral( "refreshOnNotifyMessage" ), mRefreshOnNofifyMessage ); // ID QDomElement layerId = document.createElement( QStringLiteral( "id" ) ); QDomText layerIdText = document.createTextNode( id() ); layerId.appendChild( layerIdText ); layerElement.appendChild( layerId ); // data source QDomElement dataSource = document.createElement( QStringLiteral( "datasource" ) ); QString src = encodedSource( source(), context ); QDomText dataSourceText = document.createTextNode( src ); dataSource.appendChild( dataSourceText ); layerElement.appendChild( dataSource ); // layer name QDomElement layerName = document.createElement( QStringLiteral( "layername" ) ); QDomText layerNameText = document.createTextNode( name() ); layerName.appendChild( layerNameText ); layerElement.appendChild( layerName ); // layer short name if ( !mShortName.isEmpty() ) { QDomElement layerShortName = document.createElement( QStringLiteral( "shortname" ) ); QDomText layerShortNameText = document.createTextNode( mShortName ); layerShortName.appendChild( layerShortNameText ); layerElement.appendChild( layerShortName ); } // layer title if ( !mTitle.isEmpty() ) { QDomElement layerTitle = document.createElement( QStringLiteral( "title" ) ); QDomText layerTitleText = document.createTextNode( mTitle ); layerTitle.appendChild( layerTitleText ); layerElement.appendChild( layerTitle ); } // layer abstract if ( !mAbstract.isEmpty() ) { QDomElement layerAbstract = document.createElement( QStringLiteral( "abstract" ) ); QDomText layerAbstractText = document.createTextNode( mAbstract ); layerAbstract.appendChild( layerAbstractText ); layerElement.appendChild( layerAbstract ); } // layer keyword list QStringList keywordStringList = keywordList().split( ',' ); if ( !keywordStringList.isEmpty() ) { QDomElement layerKeywordList = document.createElement( QStringLiteral( "keywordList" ) ); for ( int i = 0; i < keywordStringList.size(); ++i ) { QDomElement layerKeywordValue = document.createElement( QStringLiteral( "value" ) ); QDomText layerKeywordText = document.createTextNode( keywordStringList.at( i ).trimmed() ); layerKeywordValue.appendChild( layerKeywordText ); layerKeywordList.appendChild( layerKeywordValue ); } layerElement.appendChild( layerKeywordList ); } // layer metadataUrl QString aDataUrl = dataUrl(); if ( !aDataUrl.isEmpty() ) { QDomElement layerDataUrl = document.createElement( QStringLiteral( "dataUrl" ) ); QDomText layerDataUrlText = document.createTextNode( aDataUrl ); layerDataUrl.appendChild( layerDataUrlText ); layerDataUrl.setAttribute( QStringLiteral( "format" ), dataUrlFormat() ); layerElement.appendChild( layerDataUrl ); } // layer legendUrl QString aLegendUrl = legendUrl(); if ( !aLegendUrl.isEmpty() ) { QDomElement layerLegendUrl = document.createElement( QStringLiteral( "legendUrl" ) ); QDomText layerLegendUrlText = document.createTextNode( aLegendUrl ); layerLegendUrl.appendChild( layerLegendUrlText ); layerLegendUrl.setAttribute( QStringLiteral( "format" ), legendUrlFormat() ); layerElement.appendChild( layerLegendUrl ); } // layer attribution QString aAttribution = attribution(); if ( !aAttribution.isEmpty() ) { QDomElement layerAttribution = document.createElement( QStringLiteral( "attribution" ) ); QDomText layerAttributionText = document.createTextNode( aAttribution ); layerAttribution.appendChild( layerAttributionText ); layerAttribution.setAttribute( QStringLiteral( "href" ), attributionUrl() ); layerElement.appendChild( layerAttribution ); } // layer metadataUrl QString aMetadataUrl = metadataUrl(); if ( !aMetadataUrl.isEmpty() ) { QDomElement layerMetadataUrl = document.createElement( QStringLiteral( "metadataUrl" ) ); QDomText layerMetadataUrlText = document.createTextNode( aMetadataUrl ); layerMetadataUrl.appendChild( layerMetadataUrlText ); layerMetadataUrl.setAttribute( QStringLiteral( "type" ), metadataUrlType() ); layerMetadataUrl.setAttribute( QStringLiteral( "format" ), metadataUrlFormat() ); layerElement.appendChild( layerMetadataUrl ); } // timestamp if supported if ( timestamp() > QDateTime() ) { QDomElement stamp = document.createElement( QStringLiteral( "timestamp" ) ); QDomText stampText = document.createTextNode( timestamp().toString( Qt::ISODate ) ); stamp.appendChild( stampText ); layerElement.appendChild( stamp ); } layerElement.appendChild( layerName ); // zorder // This is no longer stored in the project file. It is superfluous since the layers // are written and read in the proper order. // spatial reference system id QDomElement mySrsElement = document.createElement( QStringLiteral( "srs" ) ); mCRS.writeXml( mySrsElement, document ); layerElement.appendChild( mySrsElement ); // layer metadata QDomElement myMetadataElem = document.createElement( QStringLiteral( "resourceMetadata" ) ); mMetadata.writeMetadataXml( myMetadataElem, document ); layerElement.appendChild( myMetadataElem ); // now append layer node to map layer node writeCustomProperties( layerElement, document ); return writeXml( layerElement, document, context ); } bool QgsMapLayer::writeXml( QDomNode &layer_node, QDomDocument &document, const QgsReadWriteContext &context ) const { Q_UNUSED( layer_node ); Q_UNUSED( document ); Q_UNUSED( context ); // NOP by default; children will over-ride with behavior specific to them return true; } QString QgsMapLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const { Q_UNUSED( context ); return source; } QString QgsMapLayer::decodedSource( const QString &source, const QString &dataProvider, const QgsReadWriteContext &context ) const { Q_UNUSED( context ); Q_UNUSED( dataProvider ); return source; } void QgsMapLayer::resolveReferences( QgsProject *project ) { if ( m3DRenderer ) m3DRenderer->resolveReferences( *project ); } void QgsMapLayer::readCustomProperties( const QDomNode &layerNode, const QString &keyStartsWith ) { mCustomProperties.readXml( layerNode, keyStartsWith ); } void QgsMapLayer::writeCustomProperties( QDomNode &layerNode, QDomDocument &doc ) const { mCustomProperties.writeXml( layerNode, doc ); } void QgsMapLayer::readStyleManager( const QDomNode &layerNode ) { QDomElement styleMgrElem = layerNode.firstChildElement( QStringLiteral( "map-layer-style-manager" ) ); if ( !styleMgrElem.isNull() ) mStyleManager->readXml( styleMgrElem ); else mStyleManager->reset(); } void QgsMapLayer::writeStyleManager( QDomNode &layerNode, QDomDocument &doc ) const { if ( mStyleManager ) { QDomElement styleMgrElem = doc.createElement( QStringLiteral( "map-layer-style-manager" ) ); mStyleManager->writeXml( styleMgrElem ); layerNode.appendChild( styleMgrElem ); } } bool QgsMapLayer::isValid() const { return mValid; } #if 0 void QgsMapLayer::connectNotify( const char *signal ) { Q_UNUSED( signal ); QgsDebugMsgLevel( "QgsMapLayer connected to " + QString( signal ), 3 ); } // QgsMapLayer::connectNotify #endif bool QgsMapLayer::isInScaleRange( double scale ) const { return !mScaleBasedVisibility || ( ( mMinScale == 0 || mMinScale * Qgis::SCALE_PRECISION < scale ) && ( mMaxScale == 0 || scale < mMaxScale ) ); } bool QgsMapLayer::hasScaleBasedVisibility() const { return mScaleBasedVisibility; } bool QgsMapLayer::hasAutoRefreshEnabled() const { return mRefreshTimer->isActive(); } int QgsMapLayer::autoRefreshInterval() const { return mRefreshTimer->interval(); } void QgsMapLayer::setAutoRefreshInterval( int interval ) { if ( interval <= 0 ) { mRefreshTimer->stop(); mRefreshTimer->setInterval( 0 ); } else { mRefreshTimer->setInterval( interval ); } emit autoRefreshIntervalChanged( mRefreshTimer->isActive() ? mRefreshTimer->interval() : 0 ); } void QgsMapLayer::setAutoRefreshEnabled( bool enabled ) { if ( !enabled ) mRefreshTimer->stop(); else if ( mRefreshTimer->interval() > 0 ) mRefreshTimer->start(); emit autoRefreshIntervalChanged( mRefreshTimer->isActive() ? mRefreshTimer->interval() : 0 ); } const QgsLayerMetadata &QgsMapLayer::metadata() const { return mMetadata; } void QgsMapLayer::setMaximumScale( double scale ) { mMinScale = scale; } double QgsMapLayer::maximumScale() const { return mMinScale; } void QgsMapLayer::setMinimumScale( double scale ) { mMaxScale = scale; } void QgsMapLayer::setScaleBasedVisibility( const bool enabled ) { mScaleBasedVisibility = enabled; } double QgsMapLayer::minimumScale() const { return mMaxScale; } QStringList QgsMapLayer::subLayers() const { return QStringList(); // Empty } void QgsMapLayer::setLayerOrder( const QStringList &layers ) { Q_UNUSED( layers ); // NOOP } void QgsMapLayer::setSubLayerVisibility( const QString &name, bool vis ) { Q_UNUSED( name ); Q_UNUSED( vis ); // NOOP } QgsCoordinateReferenceSystem QgsMapLayer::crs() const { return mCRS; } void QgsMapLayer::setCrs( const QgsCoordinateReferenceSystem &srs, bool emitSignal ) { mCRS = srs; if ( !mCRS.isValid() ) { mCRS.setValidationHint( tr( "Specify CRS for layer %1" ).arg( name() ) ); mCRS.validate(); } if ( emitSignal ) emit crsChanged(); } QString QgsMapLayer::formatLayerName( const QString &name ) { QString layerName( name ); layerName.replace( '_', ' ' ); layerName = QgsStringUtils::capitalize( layerName, QgsStringUtils::ForceFirstLetterToCapital ); return layerName; } QString QgsMapLayer::baseURI( PropertyType type ) const { QString myURI = publicSource(); // if file is using the VSIFILE mechanism, remove the prefix if ( myURI.startsWith( QLatin1String( "/vsigzip/" ), Qt::CaseInsensitive ) ) { myURI.remove( 0, 9 ); } else if ( myURI.startsWith( QLatin1String( "/vsizip/" ), Qt::CaseInsensitive ) && myURI.endsWith( QLatin1String( ".zip" ), Qt::CaseInsensitive ) ) { // ideally we should look for .qml file inside zip file myURI.remove( 0, 8 ); } else if ( myURI.startsWith( QLatin1String( "/vsitar/" ), Qt::CaseInsensitive ) && ( myURI.endsWith( QLatin1String( ".tar" ), Qt::CaseInsensitive ) || myURI.endsWith( QLatin1String( ".tar.gz" ), Qt::CaseInsensitive ) || myURI.endsWith( QLatin1String( ".tgz" ), Qt::CaseInsensitive ) ) ) { // ideally we should look for .qml file inside tar file myURI.remove( 0, 8 ); } QFileInfo myFileInfo( myURI ); QString key; if ( myFileInfo.exists() ) { // if file is using the /vsizip/ or /vsigzip/ mechanism, cleanup the name if ( myURI.endsWith( QLatin1String( ".gz" ), Qt::CaseInsensitive ) ) myURI.chop( 3 ); else if ( myURI.endsWith( QLatin1String( ".zip" ), Qt::CaseInsensitive ) ) myURI.chop( 4 ); else if ( myURI.endsWith( QLatin1String( ".tar" ), Qt::CaseInsensitive ) ) myURI.chop( 4 ); else if ( myURI.endsWith( QLatin1String( ".tar.gz" ), Qt::CaseInsensitive ) ) myURI.chop( 7 ); else if ( myURI.endsWith( QLatin1String( ".tgz" ), Qt::CaseInsensitive ) ) myURI.chop( 4 ); myFileInfo.setFile( myURI ); // get the file name for our .qml style file key = myFileInfo.path() + QDir::separator() + myFileInfo.completeBaseName() + QgsMapLayer::extensionPropertyType( type ); } else { key = publicSource(); } return key; } QString QgsMapLayer::metadataUri() const { return baseURI( PropertyType::Metadata ); } QString QgsMapLayer::saveDefaultMetadata( bool &resultFlag ) { return saveNamedMetadata( metadataUri(), resultFlag ); } QString QgsMapLayer::loadDefaultMetadata( bool &resultFlag ) { return loadNamedMetadata( metadataUri(), resultFlag ); } QString QgsMapLayer::styleURI() const { return baseURI( PropertyType::Style ); } QString QgsMapLayer::loadDefaultStyle( bool &resultFlag ) { return loadNamedStyle( styleURI(), resultFlag ); } bool QgsMapLayer::loadNamedMetadataFromDatabase( const QString &db, const QString &uri, QString &qmd ) { return loadNamedPropertyFromDatabase( db, uri, qmd, PropertyType::Metadata ); } bool QgsMapLayer::loadNamedStyleFromDatabase( const QString &db, const QString &uri, QString &qml ) { return loadNamedPropertyFromDatabase( db, uri, qml, PropertyType::Style ); } bool QgsMapLayer::loadNamedPropertyFromDatabase( const QString &db, const QString &uri, QString &xml, QgsMapLayer::PropertyType type ) { QgsDebugMsgLevel( QString( "db = %1 uri = %2" ).arg( db, uri ), 4 ); bool resultFlag = false; // read from database sqlite3_database_unique_ptr database; sqlite3_statement_unique_ptr statement; int myResult; QgsDebugMsgLevel( QString( "Trying to load style or metadata for \"%1\" from \"%2\"" ).arg( uri, db ), 4 ); if ( db.isEmpty() || !QFile( db ).exists() ) return false; myResult = database.open_v2( db, SQLITE_OPEN_READONLY, nullptr ); if ( myResult != SQLITE_OK ) { return false; } QString mySql; switch ( type ) { case Metadata: mySql = QStringLiteral( "select qmd from tbl_metadata where metadata=?" ); break; case Style: mySql = QStringLiteral( "select qml from tbl_styles where style=?" ); break; } statement = database.prepare( mySql, myResult ); if ( myResult == SQLITE_OK ) { QByteArray param = uri.toUtf8(); if ( sqlite3_bind_text( statement.get(), 1, param.data(), param.length(), SQLITE_STATIC ) == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW ) { xml = QString::fromUtf8( reinterpret_cast< const char * >( sqlite3_column_text( statement.get(), 0 ) ) ); resultFlag = true; } } return resultFlag; } QString QgsMapLayer::loadNamedStyle( const QString &uri, bool &resultFlag ) { return loadNamedProperty( uri, PropertyType::Style, resultFlag ); } QString QgsMapLayer::loadNamedProperty( const QString &uri, QgsMapLayer::PropertyType type, bool &resultFlag ) { QgsDebugMsgLevel( QString( "uri = %1 myURI = %2" ).arg( uri, publicSource() ), 4 ); resultFlag = false; QDomDocument myDocument( QStringLiteral( "qgis" ) ); // location of problem associated with errorMsg int line, column; QString myErrorMessage; QFile myFile( uri ); if ( myFile.open( QFile::ReadOnly ) ) { QgsDebugMsgLevel( QString( "file found %1" ).arg( uri ), 2 ); // read file resultFlag = myDocument.setContent( &myFile, &myErrorMessage, &line, &column ); if ( !resultFlag ) myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column ); myFile.close(); } else { QFileInfo project( QgsProject::instance()->fileName() ); QgsDebugMsgLevel( QString( "project fileName: %1" ).arg( project.absoluteFilePath() ), 4 ); QString xml; switch ( type ) { case QgsMapLayer::Style: { if ( loadNamedStyleFromDatabase( QDir( QgsApplication::qgisSettingsDirPath() ).absoluteFilePath( QStringLiteral( "qgis.qmldb" ) ), uri, xml ) || ( project.exists() && loadNamedStyleFromDatabase( project.absoluteDir().absoluteFilePath( project.baseName() + ".qmldb" ), uri, xml ) ) || loadNamedStyleFromDatabase( QDir( QgsApplication::pkgDataPath() ).absoluteFilePath( QStringLiteral( "resources/qgis.qmldb" ) ), uri, xml ) ) { resultFlag = myDocument.setContent( xml, &myErrorMessage, &line, &column ); if ( !resultFlag ) { myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column ); } } else { myErrorMessage = tr( "Style not found in database" ); resultFlag = false; } break; } case QgsMapLayer::Metadata: { if ( loadNamedMetadataFromDatabase( QDir( QgsApplication::qgisSettingsDirPath() ).absoluteFilePath( QStringLiteral( "qgis.qmldb" ) ), uri, xml ) || ( project.exists() && loadNamedMetadataFromDatabase( project.absoluteDir().absoluteFilePath( project.baseName() + ".qmldb" ), uri, xml ) ) || loadNamedMetadataFromDatabase( QDir( QgsApplication::pkgDataPath() ).absoluteFilePath( QStringLiteral( "resources/qgis.qmldb" ) ), uri, xml ) ) { resultFlag = myDocument.setContent( xml, &myErrorMessage, &line, &column ); if ( !resultFlag ) { myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column ); } } else { myErrorMessage = tr( "Metadata not found in database" ); resultFlag = false; } break; } } } if ( !resultFlag ) { return myErrorMessage; } switch ( type ) { case QgsMapLayer::Style: resultFlag = importNamedStyle( myDocument, myErrorMessage ); if ( !resultFlag ) myErrorMessage = tr( "Loading style file %1 failed because:\n%2" ).arg( uri, myErrorMessage ); break; case QgsMapLayer::Metadata: resultFlag = importNamedMetadata( myDocument, myErrorMessage ); if ( !resultFlag ) myErrorMessage = tr( "Loading metadata file %1 failed because:\n%2" ).arg( uri, myErrorMessage ); break; } return myErrorMessage; } bool QgsMapLayer::importNamedMetadata( QDomDocument &document, QString &errorMessage ) { QDomElement myRoot = document.firstChildElement( QStringLiteral( "qgis" ) ); if ( myRoot.isNull() ) { errorMessage = tr( "Root element could not be found" ); return false; } return mMetadata.readMetadataXml( myRoot ); } bool QgsMapLayer::importNamedStyle( QDomDocument &myDocument, QString &myErrorMessage ) { QDomElement myRoot = myDocument.firstChildElement( QStringLiteral( "qgis" ) ); if ( myRoot.isNull() ) { myErrorMessage = tr( "Root element could not be found" ); return false; } // get style file version string, if any QgsProjectVersion fileVersion( myRoot.attribute( QStringLiteral( "version" ) ) ); QgsProjectVersion thisVersion( Qgis::QGIS_VERSION ); if ( thisVersion > fileVersion ) { QgsProjectFileTransform styleFile( myDocument, fileVersion ); styleFile.updateRevision( thisVersion ); } //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() ) { QgsVectorLayer *vl = qobject_cast( this ); QgsWkbTypes::GeometryType importLayerGeometryType = static_cast( myRoot.firstChildElement( QStringLiteral( "layerGeometryType" ) ).text().toInt() ); if ( vl->geometryType() != importLayerGeometryType ) { myErrorMessage = tr( "Cannot apply style to layer with a different geometry type" ); return false; } } // use scale dependent visibility flag setScaleBasedVisibility( myRoot.attribute( QStringLiteral( "hasScaleBasedVisibilityFlag" ) ).toInt() == 1 ); if ( myRoot.hasAttribute( QStringLiteral( "minimumScale" ) ) ) { //older scale element, when min/max were reversed setMaximumScale( myRoot.attribute( QStringLiteral( "minimumScale" ) ).toDouble() ); setMinimumScale( myRoot.attribute( QStringLiteral( "maximumScale" ) ).toDouble() ); } else { setMaximumScale( myRoot.attribute( QStringLiteral( "maxScale" ) ).toDouble() ); setMinimumScale( myRoot.attribute( QStringLiteral( "minScale" ) ).toDouble() ); } QgsReadWriteContext context = QgsReadWriteContext(); return readSymbology( myRoot, myErrorMessage, context ); // TODO: support relative paths in QML? } void QgsMapLayer::exportNamedMetadata( QDomDocument &doc, QString &errorMsg ) const { QDomImplementation DomImplementation; QDomDocumentType documentType = DomImplementation.createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) ); QDomDocument myDocument( documentType ); QDomElement myRootNode = myDocument.createElement( QStringLiteral( "qgis" ) ); myRootNode.setAttribute( QStringLiteral( "version" ), Qgis::QGIS_VERSION ); myDocument.appendChild( myRootNode ); if ( !mMetadata.writeMetadataXml( myRootNode, myDocument ) ) { errorMsg = QObject::tr( "Could not save metadata" ); return; } doc = myDocument; } void QgsMapLayer::exportNamedStyle( QDomDocument &doc, QString &errorMsg ) const { QDomImplementation DomImplementation; QDomDocumentType documentType = DomImplementation.createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) ); QDomDocument myDocument( documentType ); QDomElement myRootNode = myDocument.createElement( QStringLiteral( "qgis" ) ); myRootNode.setAttribute( QStringLiteral( "version" ), Qgis::QGIS_VERSION ); myDocument.appendChild( myRootNode ); myRootNode.setAttribute( QStringLiteral( "hasScaleBasedVisibilityFlag" ), hasScaleBasedVisibility() ? 1 : 0 ); myRootNode.setAttribute( QStringLiteral( "maxScale" ), QString::number( maximumScale() ) ); myRootNode.setAttribute( QStringLiteral( "minScale" ), QString::number( minimumScale() ) ); if ( !writeSymbology( myRootNode, myDocument, errorMsg, QgsReadWriteContext() ) ) // TODO: support relative paths in QML? { errorMsg = QObject::tr( "Could not save symbology because:\n%1" ).arg( errorMsg ); return; } /* * Check to see if the layer is vector - in which case we should also export its geometryType * to avoid eventually pasting to a layer with a different geometry */ if ( type() == QgsMapLayer::VectorLayer ) { //Getting the selectionLayer geometry const QgsVectorLayer *vl = qobject_cast( this ); QString geoType = QString::number( vl->geometryType() ); //Adding geometryinformation QDomElement layerGeometryType = myDocument.createElement( QStringLiteral( "layerGeometryType" ) ); QDomText type = myDocument.createTextNode( geoType ); layerGeometryType.appendChild( type ); myRootNode.appendChild( layerGeometryType ); } doc = myDocument; } QString QgsMapLayer::saveDefaultStyle( bool &resultFlag ) { return saveNamedStyle( styleURI(), resultFlag ); } QString QgsMapLayer::saveNamedMetadata( const QString &uri, bool &resultFlag ) { return saveNamedProperty( uri, QgsMapLayer::Metadata, resultFlag ); } QString QgsMapLayer::loadNamedMetadata( const QString &uri, bool &resultFlag ) { return loadNamedProperty( uri, QgsMapLayer::Metadata, resultFlag ); } QString QgsMapLayer::saveNamedProperty( const QString &uri, QgsMapLayer::PropertyType type, bool &resultFlag ) { // check if the uri is a file or ends with .qml/.qmd, // which indicates that it should become one // everything else goes to the database QString filename; QgsVectorLayer *vlayer = qobject_cast( this ); if ( vlayer && vlayer->providerType() == QLatin1String( "ogr" ) ) { QStringList theURIParts = uri.split( '|' ); filename = theURIParts[0]; } else if ( vlayer && vlayer->providerType() == QLatin1String( "gpx" ) ) { QStringList theURIParts = uri.split( '?' ); filename = theURIParts[0]; } else if ( vlayer && vlayer->providerType() == QLatin1String( "delimitedtext" ) ) { filename = QUrl::fromEncoded( uri.toLatin1() ).toLocalFile(); // toLocalFile() returns an empty string if theURI is a plain Windows-path, e.g. "C:/style.qml" if ( filename.isEmpty() ) filename = uri; } else { filename = uri; } QString myErrorMessage; QDomDocument myDocument; switch ( type ) { case Metadata: exportNamedMetadata( myDocument, myErrorMessage ); break; case Style: exportNamedStyle( myDocument, myErrorMessage ); break; } QFileInfo myFileInfo( filename ); if ( myFileInfo.exists() || filename.endsWith( QgsMapLayer::extensionPropertyType( type ), Qt::CaseInsensitive ) ) { QFileInfo myDirInfo( myFileInfo.path() ); //excludes file name if ( !myDirInfo.isWritable() ) { return tr( "The directory containing your dataset needs to be writable!" ); } // now construct the file name for our .qml or .qmd file QString myFileName = myFileInfo.path() + QDir::separator() + myFileInfo.completeBaseName() + QgsMapLayer::extensionPropertyType( type ); QFile myFile( myFileName ); if ( myFile.open( QFile::WriteOnly | QFile::Truncate ) ) { QTextStream myFileStream( &myFile ); // save as utf-8 with 2 spaces for indents myDocument.save( myFileStream, 2 ); myFile.close(); resultFlag = true; switch ( type ) { case Metadata: return tr( "Created default metadata file as %1" ).arg( myFileName ); case Style: return tr( "Created default style file as %1" ).arg( myFileName ); } } else { resultFlag = false; switch ( type ) { case Metadata: return tr( "ERROR: Failed to created default metadata file as %1. Check file permissions and retry." ).arg( myFileName ); case Style: return tr( "ERROR: Failed to created default style file as %1. Check file permissions and retry." ).arg( myFileName ); } } } else { QString qml = myDocument.toString(); // read from database sqlite3_database_unique_ptr database; sqlite3_statement_unique_ptr statement; int myResult = database.open( QDir( QgsApplication::qgisSettingsDirPath() ).absoluteFilePath( QStringLiteral( "qgis.qmldb" ) ) ); if ( myResult != SQLITE_OK ) { return tr( "User database could not be opened." ); } QByteArray param0 = uri.toUtf8(); QByteArray param1 = qml.toUtf8(); QString mySql; switch ( type ) { case Metadata: mySql = QStringLiteral( "create table if not exists tbl_metadata(metadata varchar primary key,qmd varchar)" ); break; case Style: mySql = QStringLiteral( "create table if not exists tbl_styles(style varchar primary key,qml varchar)" ); break; } statement = database.prepare( mySql, myResult ); if ( myResult == SQLITE_OK ) { if ( sqlite3_step( statement.get() ) != SQLITE_DONE ) { resultFlag = false; switch ( type ) { case Metadata: return tr( "The metadata table could not be created." ); case Style: return tr( "The style table could not be created." ); } } } switch ( type ) { case Metadata: mySql = QStringLiteral( "insert into tbl_metadata(metadata,qmd) values (?,?)" ); break; case Style: mySql = QStringLiteral( "insert into tbl_styles(style,qml) values (?,?)" ); break; } statement = database.prepare( mySql, myResult ); if ( myResult == SQLITE_OK ) { if ( sqlite3_bind_text( statement.get(), 1, param0.data(), param0.length(), SQLITE_STATIC ) == SQLITE_OK && sqlite3_bind_text( statement.get(), 2, param1.data(), param1.length(), SQLITE_STATIC ) == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_DONE ) { resultFlag = true; switch ( type ) { case Metadata: myErrorMessage = tr( "The metadata %1 was saved to database" ).arg( uri ); break; case Style: myErrorMessage = tr( "The style %1 was saved to database" ).arg( uri ); break; } } } if ( !resultFlag ) { QString mySql; switch ( type ) { case Metadata: mySql = QStringLiteral( "update tbl_metadata set qmd=? where metadata=?" ); break; case Style: mySql = QStringLiteral( "update tbl_styles set qml=? where style=?" ); break; } statement = database.prepare( mySql, myResult ); if ( myResult == SQLITE_OK ) { if ( sqlite3_bind_text( statement.get(), 2, param0.data(), param0.length(), SQLITE_STATIC ) == SQLITE_OK && sqlite3_bind_text( statement.get(), 1, param1.data(), param1.length(), SQLITE_STATIC ) == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_DONE ) { resultFlag = true; switch ( type ) { case Metadata: myErrorMessage = tr( "The metadata %1 was updated in the database." ).arg( uri ); break; case Style: myErrorMessage = tr( "The style %1 was updated in the database." ).arg( uri ); break; } } else { resultFlag = false; switch ( type ) { case Metadata: myErrorMessage = tr( "The metadata %1 could not be updated in the database." ).arg( uri ); break; case Style: myErrorMessage = tr( "The style %1 could not be updated in the database." ).arg( uri ); break; } } } else { resultFlag = false; switch ( type ) { case Metadata: myErrorMessage = tr( "The metadata %1 could not be inserted into database." ).arg( uri ); break; case Style: myErrorMessage = tr( "The style %1 could not be inserted into database." ).arg( uri ); break; } } } } return myErrorMessage; } QString QgsMapLayer::saveNamedStyle( const QString &uri, bool &resultFlag ) { return saveNamedProperty( uri, QgsMapLayer::Style, resultFlag ); } void QgsMapLayer::exportSldStyle( QDomDocument &doc, QString &errorMsg ) const { QDomDocument myDocument = QDomDocument(); QDomNode header = myDocument.createProcessingInstruction( QStringLiteral( "xml" ), QStringLiteral( "version=\"1.0\" encoding=\"UTF-8\"" ) ); myDocument.appendChild( header ); // Create the root element QDomElement root = myDocument.createElementNS( QStringLiteral( "http://www.opengis.net/sld" ), QStringLiteral( "StyledLayerDescriptor" ) ); root.setAttribute( QStringLiteral( "version" ), QStringLiteral( "1.1.0" ) ); root.setAttribute( QStringLiteral( "xsi:schemaLocation" ), QStringLiteral( "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd" ) ); root.setAttribute( QStringLiteral( "xmlns:ogc" ), QStringLiteral( "http://www.opengis.net/ogc" ) ); root.setAttribute( QStringLiteral( "xmlns:se" ), QStringLiteral( "http://www.opengis.net/se" ) ); root.setAttribute( QStringLiteral( "xmlns:xlink" ), QStringLiteral( "http://www.w3.org/1999/xlink" ) ); root.setAttribute( QStringLiteral( "xmlns:xsi" ), QStringLiteral( "http://www.w3.org/2001/XMLSchema-instance" ) ); myDocument.appendChild( root ); // Create the NamedLayer element QDomElement namedLayerNode = myDocument.createElement( QStringLiteral( "NamedLayer" ) ); root.appendChild( namedLayerNode ); const QgsVectorLayer *vlayer = qobject_cast( this ); if ( !vlayer ) { errorMsg = tr( "Could not save symbology because:\n%1" ) .arg( QStringLiteral( "Non-vector layers not supported yet" ) ); return; } QgsStringMap props; if ( hasScaleBasedVisibility() ) { props[ QStringLiteral( "scaleMinDenom" )] = QString::number( mMinScale ); props[ QStringLiteral( "scaleMaxDenom" )] = QString::number( mMaxScale ); } if ( !vlayer->writeSld( namedLayerNode, myDocument, errorMsg, props ) ) { errorMsg = tr( "Could not save symbology because:\n%1" ).arg( errorMsg ); return; } doc = myDocument; } QString QgsMapLayer::saveSldStyle( const QString &uri, bool &resultFlag ) const { QString errorMsg; QDomDocument myDocument; exportSldStyle( myDocument, errorMsg ); if ( !errorMsg.isNull() ) { resultFlag = false; return errorMsg; } const QgsVectorLayer *vlayer = qobject_cast( this ); // check if the uri is a file or ends with .sld, // which indicates that it should become one QString filename; if ( vlayer->providerType() == QLatin1String( "ogr" ) ) { QStringList theURIParts = uri.split( '|' ); filename = theURIParts[0]; } else if ( vlayer->providerType() == QLatin1String( "gpx" ) ) { QStringList theURIParts = uri.split( '?' ); filename = theURIParts[0]; } else if ( vlayer->providerType() == QLatin1String( "delimitedtext" ) ) { filename = QUrl::fromEncoded( uri.toLatin1() ).toLocalFile(); // toLocalFile() returns an empty string if theURI is a plain Windows-path, e.g. "C:/style.qml" if ( filename.isEmpty() ) filename = uri; } else { filename = uri; } QFileInfo myFileInfo( filename ); if ( myFileInfo.exists() || filename.endsWith( QLatin1String( ".sld" ), Qt::CaseInsensitive ) ) { QFileInfo myDirInfo( myFileInfo.path() ); //excludes file name if ( !myDirInfo.isWritable() ) { return tr( "The directory containing your dataset needs to be writable!" ); } // now construct the file name for our .sld style file QString myFileName = myFileInfo.path() + QDir::separator() + myFileInfo.completeBaseName() + ".sld"; QFile myFile( myFileName ); if ( myFile.open( QFile::WriteOnly | QFile::Truncate ) ) { QTextStream myFileStream( &myFile ); // save as utf-8 with 2 spaces for indents myDocument.save( myFileStream, 2 ); myFile.close(); resultFlag = true; return tr( "Created default style file as %1" ).arg( myFileName ); } } resultFlag = false; return tr( "ERROR: Failed to created SLD style file as %1. Check file permissions and retry." ).arg( filename ); } QString QgsMapLayer::loadSldStyle( const QString &uri, bool &resultFlag ) { resultFlag = false; QDomDocument myDocument; // location of problem associated with errorMsg int line, column; QString myErrorMessage; QFile myFile( uri ); if ( myFile.open( QFile::ReadOnly ) ) { // read file resultFlag = myDocument.setContent( &myFile, true, &myErrorMessage, &line, &column ); if ( !resultFlag ) myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column ); myFile.close(); } else { myErrorMessage = tr( "Unable to open file %1" ).arg( uri ); } if ( !resultFlag ) { return myErrorMessage; } // check for root SLD element QDomElement myRoot = myDocument.firstChildElement( QStringLiteral( "StyledLayerDescriptor" ) ); if ( myRoot.isNull() ) { myErrorMessage = QStringLiteral( "Error: StyledLayerDescriptor element not found in %1" ).arg( uri ); resultFlag = false; return myErrorMessage; } // now get the style node out and pass it over to the layer // to deserialise... QDomElement namedLayerElem = myRoot.firstChildElement( QStringLiteral( "NamedLayer" ) ); if ( namedLayerElem.isNull() ) { myErrorMessage = QStringLiteral( "Info: NamedLayer element not found." ); resultFlag = false; return myErrorMessage; } QString errorMsg; resultFlag = readSld( namedLayerElem, errorMsg ); if ( !resultFlag ) { myErrorMessage = tr( "Loading style file %1 failed because:\n%2" ).arg( uri, errorMsg ); return myErrorMessage; } return QLatin1String( "" ); } bool QgsMapLayer::readStyle( const QDomNode &node, QString &errorMessage, QgsReadWriteContext &context ) { Q_UNUSED( node ); Q_UNUSED( errorMessage ); Q_UNUSED( context ); return false; } bool QgsMapLayer::writeStyle( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const { Q_UNUSED( node ); Q_UNUSED( doc ); Q_UNUSED( errorMessage ); Q_UNUSED( context ); return false; } void QgsMapLayer::writeCommonStyle( QDomElement &layerElement, QDomDocument &document, const QgsReadWriteContext &context ) const { if ( m3DRenderer ) { QDomElement renderer3DElem = document.createElement( QStringLiteral( "renderer-3d" ) ); renderer3DElem.setAttribute( QStringLiteral( "type" ), m3DRenderer->type() ); m3DRenderer->writeXml( renderer3DElem, context ); layerElement.appendChild( renderer3DElem ); } } void QgsMapLayer::readCommonStyle( const QDomElement &layerElement, const QgsReadWriteContext &context ) { QgsAbstract3DRenderer *r3D = nullptr; QDomElement renderer3DElem = layerElement.firstChildElement( QStringLiteral( "renderer-3d" ) ); if ( !renderer3DElem.isNull() ) { QString type3D = renderer3DElem.attribute( QStringLiteral( "type" ) ); Qgs3DRendererAbstractMetadata *meta3D = QgsApplication::renderer3DRegistry()->rendererMetadata( type3D ); if ( meta3D ) { r3D = meta3D->createRenderer( renderer3DElem, context ); } } setRenderer3D( r3D ); } QUndoStack *QgsMapLayer::undoStack() { return mUndoStack; } QUndoStack *QgsMapLayer::undoStackStyles() { return mUndoStackStyles; } QStringList QgsMapLayer::customPropertyKeys() const { return mCustomProperties.keys(); } void QgsMapLayer::setCustomProperty( const QString &key, const QVariant &value ) { mCustomProperties.setValue( key, value ); } void QgsMapLayer::setCustomProperties( const QgsObjectCustomProperties &properties ) { mCustomProperties = properties; } QVariant QgsMapLayer::customProperty( const QString &value, const QVariant &defaultValue ) const { return mCustomProperties.value( value, defaultValue ); } void QgsMapLayer::removeCustomProperty( const QString &key ) { mCustomProperties.remove( key ); } QgsError QgsMapLayer::error() const { return mError; } bool QgsMapLayer::isEditable() const { return false; } bool QgsMapLayer::isSpatial() const { return true; } void QgsMapLayer::setValid( bool valid ) { mValid = valid; } void QgsMapLayer::setLegend( QgsMapLayerLegend *legend ) { if ( legend == mLegend ) return; delete mLegend; mLegend = legend; if ( mLegend ) { mLegend->setParent( this ); connect( mLegend, &QgsMapLayerLegend::itemsChanged, this, &QgsMapLayer::legendChanged ); } emit legendChanged(); } QgsMapLayerLegend *QgsMapLayer::legend() const { return mLegend; } QgsMapLayerStyleManager *QgsMapLayer::styleManager() const { return mStyleManager; } void QgsMapLayer::setRenderer3D( QgsAbstract3DRenderer *renderer ) { if ( renderer == m3DRenderer ) return; delete m3DRenderer; m3DRenderer = renderer; emit renderer3DChanged(); } QgsAbstract3DRenderer *QgsMapLayer::renderer3D() const { return m3DRenderer; } void QgsMapLayer::triggerRepaint( bool deferredUpdate ) { emit repaintRequested( deferredUpdate ); } void QgsMapLayer::setMetadata( const QgsLayerMetadata &metadata ) { mMetadata = metadata; // mMetadata.saveToLayer( this ); emit metadataChanged(); } QString QgsMapLayer::htmlMetadata() const { return QString(); } QDateTime QgsMapLayer::timestamp() const { return QDateTime(); } void QgsMapLayer::emitStyleChanged() { emit styleChanged(); } void QgsMapLayer::setExtent( const QgsRectangle &r ) { mExtent = r; } static QList _depOutEdges( const QgsMapLayer *vl, const QgsMapLayer *that, const QSet &layers ) { QList lst; if ( vl == that ) { Q_FOREACH ( const QgsMapLayerDependency &dep, layers ) { if ( const QgsMapLayer *l = QgsProject::instance()->mapLayer( dep.layerId() ) ) lst << l; } } else { Q_FOREACH ( const QgsMapLayerDependency &dep, vl->dependencies() ) { if ( const QgsMapLayer *l = QgsProject::instance()->mapLayer( dep.layerId() ) ) lst << l; } } return lst; } static bool _depHasCycleDFS( const QgsMapLayer *n, QHash &mark, const QgsMapLayer *that, const QSet &layers ) { if ( mark.value( n ) == 1 ) // temporary return true; if ( mark.value( n ) == 0 ) // not visited { mark[n] = 1; // temporary Q_FOREACH ( const QgsMapLayer *m, _depOutEdges( n, that, layers ) ) { if ( _depHasCycleDFS( m, mark, that, layers ) ) return true; } mark[n] = 2; // permanent } return false; } bool QgsMapLayer::hasDependencyCycle( const QSet &layers ) const { QHash marks; return _depHasCycleDFS( this, marks, this, layers ); } bool QgsMapLayer::isReadOnly() const { return true; } QSet QgsMapLayer::dependencies() const { return mDependencies; } bool QgsMapLayer::setDependencies( const QSet &oDeps ) { QSet deps; Q_FOREACH ( const QgsMapLayerDependency &dep, oDeps ) { if ( dep.origin() == QgsMapLayerDependency::FromUser ) deps << dep; } if ( hasDependencyCycle( deps ) ) return false; mDependencies = deps; emit dependenciesChanged(); return true; } void QgsMapLayer::setRefreshOnNotifyEnabled( bool enabled ) { if ( !dataProvider() ) return; if ( enabled && !isRefreshOnNotifyEnabled() ) { dataProvider()->setListening( enabled ); connect( dataProvider(), &QgsVectorDataProvider::notify, this, &QgsMapLayer::onNotifiedTriggerRepaint ); } else if ( !enabled && isRefreshOnNotifyEnabled() ) { // we don't want to disable provider listening because someone else could need it (e.g. actions) disconnect( dataProvider(), &QgsVectorDataProvider::notify, this, &QgsMapLayer::onNotifiedTriggerRepaint ); } mIsRefreshOnNofifyEnabled = enabled; } void QgsMapLayer::onNotifiedTriggerRepaint( const QString &message ) { if ( refreshOnNotifyMessage().isEmpty() || refreshOnNotifyMessage() == message ) triggerRepaint(); }