QGIS/src/core/qgsproject.cpp
Sebastian Dietrich d26bd46d99 remove 'layercount' attribute from project file
The 'layercount' attribute was not used anyway and the calculated number could already be outdated when loading the project file again due to changes to embedded projects.
2016-01-08 16:10:58 +01:00

2089 lines
58 KiB
C++

/***************************************************************************
qgsproject.cpp - description
-------------------
begin : July 23, 2004
copyright : (C) 2004 by Mark Coletti
email : mcoletti at gmail.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 "qgsproject.h"
#include "qgsdatasourceuri.h"
#include "qgsexception.h"
#include "qgslayertree.h"
#include "qgslayertreeutils.h"
#include "qgslayertreeregistrybridge.h"
#include "qgslogger.h"
#include "qgsmaplayerregistry.h"
#include "qgsmessagelog.h"
#include "qgspluginlayer.h"
#include "qgspluginlayerregistry.h"
#include "qgsprojectfiletransform.h"
#include "qgsprojectproperty.h"
#include "qgsprojectversion.h"
#include "qgsrasterlayer.h"
#include "qgsrectangle.h"
#include "qgsrelationmanager.h"
#include "qgsvectorlayer.h"
#include "qgsvisibilitypresetcollection.h"
#include "qgslayerdefinition.h"
#include <QApplication>
#include <QFileInfo>
#include <QDomNode>
#include <QObject>
#include <QTextStream>
#include <QTemporaryFile>
#include <QDir>
#include <QUrl>
#ifdef Q_OS_UNIX
#include <utime.h>
#elif _MSC_VER
#include <sys/utime.h>
#endif
// canonical project instance
QgsProject *QgsProject::theProject_ = nullptr;
/**
Take the given scope and key and convert them to a string list of key
tokens that will be used to navigate through a Property hierarchy
E.g., scope "someplugin" and key "/foo/bar/baz" will become a string list
of { "properties", "someplugin", "foo", "bar", "baz" }. "properties" is
always first because that's the permanent ``root'' Property node.
*/
static
QStringList makeKeyTokens_( QString const &scope, QString const &key )
{
QStringList keyTokens = QStringList( scope );
keyTokens += key.split( '/', QString::SkipEmptyParts );
// be sure to include the canonical root node
keyTokens.push_front( "properties" );
//check validy of keys since an unvalid xml name will will be dropped upon saving the xml file. If not valid, we print a message to the console.
for ( int i = 0; i < keyTokens.size(); ++i )
{
QString keyToken = keyTokens.at( i );
//invalid chars in XML are found at http://www.w3.org/TR/REC-xml/#NT-NameChar
//note : it seems \x10000-\xEFFFF is valid, but it when added to the regexp, a lot of unwanted characters remain
QString nameCharRegexp = QString( "[^:A-Z_a-z\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\x2FF\\x370-\\x37D\\x37F-\\x1FFF\\x200C-\\x200D\\x2070-\\x218F\\x2C00-\\x2FEF\\x3001-\\xD7FF\\xF900-\\xFDCF\\xFDF0-\\xFFFD\\-\\.0-9\\xB7\\x0300-\\x036F\\x203F-\\x2040]" );
QString nameStartCharRegexp = QString( "^[^:A-Z_a-z\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\x2FF\\x370-\\x37D\\x37F-\\x1FFF\\x200C-\\x200D\\x2070-\\x218F\\x2C00-\\x2FEF\\x3001-\\xD7FF\\xF900-\\xFDCF\\xFDF0-\\xFFFD]" );
if ( keyToken.contains( QRegExp( nameCharRegexp ) ) || keyToken.contains( QRegExp( nameStartCharRegexp ) ) )
{
QString errorString = QObject::tr( "Entry token invalid : '%1'. The token will not be saved to file." ).arg( keyToken );
QgsMessageLog::logMessage( errorString, QString::null, QgsMessageLog::CRITICAL );
}
}
return keyTokens;
} // makeKeyTokens_
/**
return the property that matches the given key sequence, if any
@param scope scope of key
@param key keyname
@param rootProperty is likely to be the top level QgsPropertyKey in QgsProject:e:Imp.
@return null if not found, otherwise located Property
*/
static
QgsProperty *findKey_( QString const &scope,
QString const &key,
QgsPropertyKey &rootProperty )
{
QgsPropertyKey *currentProperty = &rootProperty;
QgsProperty *nextProperty; // link to next property down hiearchy
QStringList keySequence = makeKeyTokens_( scope, key );
while ( !keySequence.isEmpty() )
{
// if the current head of the sequence list matches the property name,
// then traverse down the property hierarchy
if ( keySequence.first() == currentProperty->name() )
{
// remove front key since we're traversing down a level
keySequence.pop_front();
if ( 1 == keySequence.count() )
{
// if we have only one key name left, then return the key found
return currentProperty->find( keySequence.front() );
}
else if ( keySequence.isEmpty() )
{
// if we're out of keys then the current property is the one we
// want; i.e., we're in the rate case of being at the top-most
// property node
return currentProperty;
}
else if (( nextProperty = currentProperty->find( keySequence.first() ) ) )
{
if ( nextProperty->isKey() )
{
currentProperty = static_cast<QgsPropertyKey*>( nextProperty );
}
else if ( nextProperty->isValue() && 1 == keySequence.count() )
{
// it may be that this may be one of several property value
// nodes keyed by QDict string; if this is the last remaining
// key token and the next property is a value node, then
// that's the situation, so return the currentProperty
return currentProperty;
}
else
{
// QgsPropertyValue not Key, so return null
return nullptr;
}
}
else
{
// if the next key down isn't found
// then the overall key sequence doesn't exist
return nullptr;
}
}
else
{
return nullptr;
}
}
return nullptr;
} // findKey_
/** Add the given key and value
@param scope scope of key
@param key key name
@param rootProperty is the property from which to start adding
@param value the value associated with the key
*/
static
QgsProperty *addKey_( QString const &scope,
QString const &key,
QgsPropertyKey *rootProperty,
const QVariant& value )
{
QStringList keySequence = makeKeyTokens_( scope, key );
// cursor through property key/value hierarchy
QgsPropertyKey *currentProperty = rootProperty;
QgsProperty *nextProperty; // link to next property down hiearchy
QgsPropertyKey* newPropertyKey;
while ( ! keySequence.isEmpty() )
{
// if the current head of the sequence list matches the property name,
// then traverse down the property hierarchy
if ( keySequence.first() == currentProperty->name() )
{
// remove front key since we're traversing down a level
keySequence.pop_front();
// if key sequence has one last element, then we use that as the
// name to store the value
if ( 1 == keySequence.count() )
{
currentProperty->setValue( keySequence.front(), value );
return currentProperty;
}
// we're at the top element if popping the keySequence element
// will leave it empty; in that case, just add the key
else if ( keySequence.isEmpty() )
{
currentProperty->setValue( value );
return currentProperty;
}
else if (( nextProperty = currentProperty->find( keySequence.first() ) ) )
{
currentProperty = dynamic_cast<QgsPropertyKey*>( nextProperty );
if ( currentProperty )
{
continue;
}
else // QgsPropertyValue not Key, so return null
{
return nullptr;
}
}
else // the next subkey doesn't exist, so add it
{
if (( newPropertyKey = currentProperty->addKey( keySequence.first() ) ) )
{
currentProperty = newPropertyKey;
}
continue;
}
}
else
{
return nullptr;
}
}
return nullptr;
} // addKey_
static
void removeKey_( QString const &scope,
QString const &key,
QgsPropertyKey &rootProperty )
{
QgsPropertyKey *currentProperty = &rootProperty;
QgsProperty *nextProperty = nullptr; // link to next property down hiearchy
QgsPropertyKey *previousQgsPropertyKey = nullptr; // link to previous property up hiearchy
QStringList keySequence = makeKeyTokens_( scope, key );
while ( ! keySequence.isEmpty() )
{
// if the current head of the sequence list matches the property name,
// then traverse down the property hierarchy
if ( keySequence.first() == currentProperty->name() )
{
// remove front key since we're traversing down a level
keySequence.pop_front();
// if we have only one key name left, then try to remove the key
// with that name
if ( 1 == keySequence.count() )
{
currentProperty->removeKey( keySequence.front() );
}
// if we're out of keys then the current property is the one we
// want to remove, but we can't delete it directly; we need to
// delete it from the parent property key container
else if ( keySequence.isEmpty() )
{
previousQgsPropertyKey->removeKey( currentProperty->name() );
}
else if (( nextProperty = currentProperty->find( keySequence.first() ) ) )
{
previousQgsPropertyKey = currentProperty;
currentProperty = dynamic_cast<QgsPropertyKey*>( nextProperty );
if ( currentProperty )
{
continue;
}
else // QgsPropertyValue not Key, so return null
{
return;
}
}
else // if the next key down isn't found
{ // then the overall key sequence doesn't exist
return;
}
}
else
{
return;
}
}
} // void removeKey_
struct QgsProject::Imp
{
QFile file; // current physical project file
QgsPropertyKey properties_; // property hierarchy
QString title; // project title
bool dirty; // project has been modified since it has been read or saved
Imp()
: title()
, dirty( false )
{ // top property node is the root
// "properties" that contains all plug-in
// and extra property keys and values
properties_.name() = "properties"; // root property node always this value
}
/** Clear project properties when a new project is started
*/
void clear()
{
// QgsDebugMsg( "Clearing project properties Impl->clear();" );
file.setFileName( QString() );
properties_.clearKeys();
title.clear();
dirty = false;
}
}; // struct QgsProject::Imp
QgsProject::QgsProject()
: imp_( new QgsProject::Imp )
, mBadLayerHandler( new QgsProjectBadLayerDefaultHandler() )
, mRelationManager( new QgsRelationManager( this ) )
, mRootGroup( new QgsLayerTreeGroup )
{
clear();
// bind the layer tree to the map layer registry.
// whenever layers are added to or removed from the registry,
// layer tree will be updated
mLayerTreeRegistryBridge = new QgsLayerTreeRegistryBridge( mRootGroup, this );
} // QgsProject ctor
QgsProject::~QgsProject()
{
delete mBadLayerHandler;
delete mRelationManager;
delete mRootGroup;
// note that QScopedPointer automatically deletes imp_ when it's destroyed
} // QgsProject dtor
QgsProject *QgsProject::instance()
{
if ( !theProject_ )
{
theProject_ = new QgsProject;
}
return theProject_;
} // QgsProject *instance()
void QgsProject::setTitle( const QString &title )
{
imp_->title = title;
dirty( true );
}
QString QgsProject::title() const
{
return imp_->title;
} // QgsProject::title() const
bool QgsProject::isDirty() const
{
return imp_->dirty;
} // bool QgsProject::isDirty()
void QgsProject::dirty( bool b )
{
imp_->dirty = b;
} // bool QgsProject::isDirty()
void QgsProject::setDirty( bool b )
{
dirty( b );
}
void QgsProject::setFileName( QString const &name )
{
imp_->file.setFileName( name );
dirty( true );
} // void QgsProject::setFileName( QString const &name )
QString QgsProject::fileName() const
{
return imp_->file.fileName();
}
QFileInfo QgsProject::fileInfo() const
{
return QFileInfo( imp_->file );
}
void QgsProject::clear()
{
imp_->clear();
mEmbeddedLayers.clear();
mRelationManager->clear();
mVisibilityPresetCollection.reset( new QgsVisibilityPresetCollection() );
mRootGroup->removeAllChildren();
// reset some default project properties
// XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
writeEntry( "PositionPrecision", "/Automatic", true );
writeEntry( "PositionPrecision", "/DecimalPlaces", 2 );
writeEntry( "Paths", "/Absolute", false );
setDirty( false );
}
// basically a debugging tool to dump property list values
static void dump_( QgsPropertyKey const &topQgsPropertyKey )
{
QgsDebugMsg( "current properties:" );
topQgsPropertyKey.dump();
} // dump_
/**
Restore any optional properties found in "doc" to "properties".
properties tags for all optional properties. Within that there will be scope
tags. In the following example there exist one property in the "fsplugin"
scope. "layers" is a list containing three string values.
\code{.xml}
<properties>
<fsplugin>
<foo type="int" >42</foo>
<baz type="int" >1</baz>
<layers type="QStringList" >
<value>railroad</value>
<value>airport</value>
</layers>
<xyqzzy type="int" >1</xyqzzy>
<bar type="double" >123.456</bar>
<feature_types type="QStringList" >
<value>type</value>
</feature_types>
</fsplugin>
</properties>
\endcode
@param doc xml document
@param project_properties should be the top QgsPropertyKey node.
*/
static
void
_getProperties( QDomDocument const &doc, QgsPropertyKey &project_properties )
{
QDomNodeList properties = doc.elementsByTagName( "properties" );
if ( properties.count() > 1 )
{
QgsDebugMsg( "there appears to be more than one ``properties'' XML tag ... bailing" );
return;
}
else if ( properties.count() < 1 ) // no properties found, so we're done
{
return;
}
// item(0) because there should only be ONE "properties" node
QDomNodeList scopes = properties.item( 0 ).childNodes();
if ( scopes.count() < 1 )
{
QgsDebugMsg( "empty ``properties'' XML tag ... bailing" );
return;
}
QDomNode propertyNode = properties.item( 0 );
if ( ! project_properties.readXML( propertyNode ) )
{
QgsDebugMsg( "Project_properties.readXML() failed" );
}
} // _getProperties
/**
Get the project title
XML in file has this form:
\code{.xml}
<qgis projectname="default project">
<title>a project title</title>
\endcode
@todo XXX we should go with the attribute xor title, not both.
*/
static void _getTitle( QDomDocument const &doc, QString &title )
{
QDomNodeList nl = doc.elementsByTagName( "title" );
title = ""; // by default the title will be empty
if ( !nl.count() )
{
QgsDebugMsg( "unable to find title element" );
return;
}
QDomNode titleNode = nl.item( 0 ); // there should only be one, so zeroth element ok
if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text
{
QgsDebugMsg( "unable to find title element" );
return;
}
QDomNode titleTextNode = titleNode.firstChild(); // should only have one child
if ( !titleTextNode.isText() )
{
QgsDebugMsg( "unable to find title element" );
return;
}
QDomText titleText = titleTextNode.toText();
title = titleText.data();
} // _getTitle
/** Return the version string found in the given Dom document
@returns the version string or an empty string if none found
*/
static QgsProjectVersion _getVersion( QDomDocument const &doc )
{
QDomNodeList nl = doc.elementsByTagName( "qgis" );
if ( !nl.count() )
{
QgsDebugMsg( " unable to find qgis element in project file" );
return QgsProjectVersion( 0, 0, 0, QString() );
}
QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element ok
QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
QgsProjectVersion projectVersion( qgisElement.attribute( "version" ) );
return projectVersion;
} // _getVersion
/**
Read map layers from project file
@returns \code QPair< bool, QList<QDomNode> > \endcode
bool is true if function worked; else is false.
list contains nodes corresponding to layers that couldn't be loaded
@note XML of form:
\code{.xml}
<maplayer type="vector">
<layername>Hydrop</layername>
<datasource>/data/usgs/city_shp/hydrop.shp</datasource>
<zorder>0</zorder>
<provider>ogr</provider>
<singlesymbol>
<renderitem>
<value>blabla</value>
<symbol>
<outlinecolor red="85" green="0" blue="255" />
<outlinestyle>SolidLine</outlinestyle>
<outlinewidth>1</outlinewidth>
<fillcolor red="0" green="170" blue="255" />
<fillpattern>SolidPattern</fillpattern>
</symbol>
<label>blabla</label>
</renderitem>
</singlesymbol>
<label>0</label>
<labelattributes>
<label text="Label" field="" />
<family name="Sans Serif" field="" />
<size value="12" units="pt" field="" />
<bold on="0" field="" />
<italic on="0" field="" />
<underline on="0" field="" />
<color red="0" green="0" blue="0" field="" />
<x field="" />
<y field="" />
<offset units="pt" x="0" xfield="" y="0" yfield="" />
<angle value="0" field="" />
<alignment value="center" field="" />
</labelattributes>
</maplayer>
\endcode
*/
QPair< bool, QList<QDomNode> > QgsProject::_getMapLayers( QDomDocument const &doc )
{
// Layer order is set by the restoring the legend settings from project file.
// This is done on the 'readProject( ... )' signal
QDomNodeList nl = doc.elementsByTagName( "maplayer" );
QList<QDomNode> brokenNodes; // a list of Dom nodes corresponding to layers
// that we were unable to load; this could be
// because the layers were removed or
// re-located after the project was last saved
// process the map layer nodes
if ( 0 == nl.count() ) // if we have no layers to process, bail
{
return qMakePair( true, brokenNodes ); // Decided to return "true" since it's
// possible for there to be a project with no
// layers; but also, more imporantly, this
// would cause the tests/qgsproject to fail
// since the test suite doesn't currently
// support test layers
}
bool returnStatus = true;
emit layerLoaded( 0, nl.count() );
// order layers based on their dependencies
QgsLayerDefinition::DependencySorter depSorter( doc );
if ( depSorter.hasCycle() || depSorter.hasMissingDependency() )
return qMakePair( false, QList<QDomNode>() );
QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
// Collect vector layers with joins.
// They need to refresh join caches and symbology infos after all layers are loaded
QList< QPair< QgsVectorLayer*, QDomElement > > vLayerList;
int i = 0;
foreach ( QDomNode node, sortedLayerNodes )
{
QDomElement element = node.toElement();
QString name = node.namedItem( "layername" ).toElement().text();
if ( !name.isNull() )
emit loadingLayer( tr( "Loading layer %1" ).arg( name ) );
if ( element.attribute( "embedded" ) == "1" )
{
createEmbeddedLayer( element.attribute( "id" ), readPath( element.attribute( "project" ) ), brokenNodes, vLayerList );
continue;
}
else
{
if ( !addLayer( element, brokenNodes, vLayerList ) )
{
returnStatus = false;
}
}
emit layerLoaded( i + 1, nl.count() );
i++;
}
// Update field map of layers with joins and create join caches if necessary
// Needs to be done here once all dependent layers are loaded
QList< QPair< QgsVectorLayer*, QDomElement > >::iterator vIt = vLayerList.begin();
for ( ; vIt != vLayerList.end(); ++vIt )
{
vIt->first->createJoinCaches();
vIt->first->updateFields();
}
QSet<QgsVectorLayer *> notified;
for ( vIt = vLayerList.begin(); vIt != vLayerList.end(); ++vIt )
{
if ( notified.contains( vIt->first ) )
continue;
notified << vIt->first;
emit readMapLayer( vIt->first, vIt->second );
}
return qMakePair( returnStatus, brokenNodes );
} // _getMapLayers
bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &brokenNodes, QList< QPair< QgsVectorLayer*, QDomElement > > &vectorLayerList )
{
QString type = layerElem.attribute( "type" );
QgsDebugMsg( "Layer type is " + type );
QgsMapLayer *mapLayer = nullptr;
if ( type == "vector" )
{
mapLayer = new QgsVectorLayer;
}
else if ( type == "raster" )
{
mapLayer = new QgsRasterLayer;
}
else if ( type == "plugin" )
{
QString typeName = layerElem.attribute( "name" );
mapLayer = QgsPluginLayerRegistry::instance()->createLayer( typeName );
}
if ( !mapLayer )
{
QgsDebugMsg( "Unable to create layer" );
return false;
}
Q_CHECK_PTR( mapLayer );
// have the layer restore state that is stored in Dom node
if ( mapLayer->readLayerXML( layerElem ) && mapLayer->isValid() )
{
// postpone readMapLayer signal for vector layers with joins
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer*>( mapLayer );
if ( !vLayer || vLayer->vectorJoins().isEmpty() )
emit readMapLayer( mapLayer, layerElem );
else
vectorLayerList.push_back( qMakePair( vLayer, layerElem ) );
QList<QgsMapLayer *> myLayers;
myLayers << mapLayer;
QgsMapLayerRegistry::instance()->addMapLayers( myLayers );
return true;
}
else
{
delete mapLayer;
QgsDebugMsg( "Unable to load " + type + " layer" );
brokenNodes.push_back( layerElem );
return false;
}
}
/**
Please note that most of the contents were copied from qgsproject
*/
bool QgsProject::read( QFileInfo const &file )
{
imp_->file.setFileName( file.filePath() );
return read();
} // QgsProject::read
/**
presuming that the caller has already reset the map canvas, map registry, and legend
*/
bool QgsProject::read()
{
clearError();
QScopedPointer<QDomDocument> doc( new QDomDocument( "qgis" ) );
if ( !imp_->file.open( QIODevice::ReadOnly | QIODevice::Text ) )
{
imp_->file.close();
setError( tr( "Unable to open %1" ).arg( imp_->file.fileName() ) );
return false;
}
// location of problem associated with errorMsg
int line, column;
QString errorMsg;
if ( !doc->setContent( &imp_->file, &errorMsg, &line, &column ) )
{
// want to make this class as GUI independent as possible; so commented out
#if 0
QMessageBox::critical( 0, tr( "Project File Read Error" ),
tr( "%1 at line %2 column %3" ).arg( errorMsg ).arg( line ).arg( column ) );
#endif
QString errorString = tr( "Project file read error: %1 at line %2 column %3" )
.arg( errorMsg ).arg( line ).arg( column );
QgsDebugMsg( errorString );
imp_->file.close();
setError( tr( "%1 for file %2" ).arg( errorString, imp_->file.fileName() ) );
return false;
}
imp_->file.close();
QgsDebugMsg( "Opened document " + imp_->file.fileName() );
QgsDebugMsg( "Project title: " + imp_->title );
// get project version string, if any
QgsProjectVersion fileVersion = _getVersion( *doc );
QgsProjectVersion thisVersion( QGis::QGIS_VERSION );
if ( thisVersion > fileVersion )
{
QgsLogger::warning( "Loading a file that was saved with an older "
"version of qgis (saved in " + fileVersion.text() +
", loaded in " + QGis::QGIS_VERSION +
"). Problems may occur." );
QgsProjectFileTransform projectFile( *doc, fileVersion );
//! Shows a warning when an old project file is read.
emit oldProjectVersionWarning( fileVersion.text() );
QgsDebugMsg( "Emitting oldProjectVersionWarning(oldVersion)." );
projectFile.updateRevision( thisVersion );
}
// start new project, just keep the file name
QString fileName = imp_->file.fileName();
clear();
imp_->file.setFileName( fileName );
// now get any properties
_getProperties( *doc, imp_->properties_ );
QgsDebugMsg( QString::number( imp_->properties_.count() ) + " properties read" );
dump_( imp_->properties_ );
// now get project title
_getTitle( *doc, imp_->title );
// read the layer tree from project file
mRootGroup->setCustomProperty( "loading", 1 );
QDomElement layerTreeElem = doc->documentElement().firstChildElement( "layer-tree-group" );
if ( !layerTreeElem.isNull() )
{
mRootGroup->readChildrenFromXML( layerTreeElem );
}
else
{
QgsLayerTreeUtils::readOldLegend( mRootGroup, doc->documentElement().firstChildElement( "legend" ) );
}
QgsDebugMsg( "Loaded layer tree:\n " + mRootGroup->dump() );
mLayerTreeRegistryBridge->setEnabled( false );
// get the map layers
QPair< bool, QList<QDomNode> > getMapLayersResults = _getMapLayers( *doc );
// review the integrity of the retrieved map layers
bool clean = getMapLayersResults.first;
if ( !clean )
{
QgsDebugMsg( "Unable to get map layers from project file." );
if ( ! getMapLayersResults.second.isEmpty() )
{
QgsDebugMsg( "there are " + QString::number( getMapLayersResults.second.size() ) + " broken layers" );
}
// we let a custom handler to decide what to do with missing layers
// (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
mBadLayerHandler->handleBadLayers( getMapLayersResults.second, *doc );
}
mLayerTreeRegistryBridge->setEnabled( true );
// load embedded groups and layers
loadEmbeddedNodes( mRootGroup );
// make sure the are just valid layers
QgsLayerTreeUtils::removeInvalidLayers( mRootGroup );
mRootGroup->removeCustomProperty( "loading" );
mVisibilityPresetCollection.reset( new QgsVisibilityPresetCollection() );
mVisibilityPresetCollection->readXML( *doc );
// read the project: used by map canvas and legend
emit readProject( *doc );
// if all went well, we're allegedly in pristine state
if ( clean )
dirty( false );
return true;
} // QgsProject::read
void QgsProject::loadEmbeddedNodes( QgsLayerTreeGroup *group )
{
Q_FOREACH ( QgsLayerTreeNode *child, group->children() )
{
if ( QgsLayerTree::isGroup( child ) )
{
QgsLayerTreeGroup *childGroup = QgsLayerTree::toGroup( child );
if ( childGroup->customProperty( "embedded" ).toInt() )
{
// make sure to convert the path from relative to absolute
QString projectPath = readPath( childGroup->customProperty( "embedded_project" ).toString() );
childGroup->setCustomProperty( "embedded_project", projectPath );
QgsLayerTreeGroup *newGroup = createEmbeddedGroup( childGroup->name(), projectPath, childGroup->customProperty( "embedded-invisible-layers" ).toStringList() );
if ( newGroup )
{
QList<QgsLayerTreeNode*> clonedChildren;
Q_FOREACH ( QgsLayerTreeNode *newGroupChild, newGroup->children() )
clonedChildren << newGroupChild->clone();
delete newGroup;
childGroup->insertChildNodes( 0, clonedChildren );
}
}
else
{
loadEmbeddedNodes( childGroup );
}
}
else if ( QgsLayerTree::isLayer( child ) )
{
if ( child->customProperty( "embedded" ).toInt() )
{
QList<QDomNode> brokenNodes;
QList< QPair< QgsVectorLayer*, QDomElement > > vectorLayerList;
createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), child->customProperty( "embedded_project" ).toString(), brokenNodes, vectorLayerList );
}
}
}
}
bool QgsProject::read( QDomNode &layerNode )
{
QList<QDomNode> brokenNodes;
QList< QPair< QgsVectorLayer*, QDomElement > > vectorLayerList;
return addLayer( layerNode.toElement(), brokenNodes, vectorLayerList );
} // QgsProject::read( QDomNode &layerNode )
bool QgsProject::write( QFileInfo const &file )
{
imp_->file.setFileName( file.filePath() );
return write();
} // QgsProject::write( QFileInfo const &file )
bool QgsProject::write()
{
clearError();
// if we have problems creating or otherwise writing to the project file,
// let's find out up front before we go through all the hand-waving
// necessary to create all the Dom objects
QFileInfo myFileInfo( imp_->file );
if ( myFileInfo.exists() && !myFileInfo.isWritable() )
{
setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." )
.arg( imp_->file.fileName() ) );
return false;
}
QDomImplementation DomImplementation;
DomImplementation.setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
QDomDocumentType documentType =
DomImplementation.createDocumentType( "qgis", "http://mrcc.com/qgis.dtd",
"SYSTEM" );
QScopedPointer<QDomDocument> doc( new QDomDocument( documentType ) );
QDomElement qgisNode = doc->createElement( "qgis" );
qgisNode.setAttribute( "projectname", title() );
qgisNode.setAttribute( "version", QString( "%1" ).arg( QGis::QGIS_VERSION ) );
doc->appendChild( qgisNode );
// title
QDomElement titleNode = doc->createElement( "title" );
qgisNode.appendChild( titleNode );
QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
titleNode.appendChild( titleText );
// write layer tree - make sure it is without embedded subgroups
QgsLayerTreeNode *clonedRoot = mRootGroup->clone();
QgsLayerTreeUtils::replaceChildrenOfEmbeddedGroups( QgsLayerTree::toGroup( clonedRoot ) );
QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ) ); // convert absolute paths to relative paths if required
clonedRoot->writeXML( qgisNode );
delete clonedRoot;
// let map canvas and legend write their information
emit writeProject( *doc );
// within top level node save list of layers
const QMap<QString, QgsMapLayer*> &layers = QgsMapLayerRegistry::instance()->mapLayers();
// Iterate over layers in zOrder
// Call writeXML() on each
QDomElement projectLayersNode = doc->createElement( "projectlayers" );
QMap<QString, QgsMapLayer*>::ConstIterator li = layers.constBegin();
while ( li != layers.end() )
{
QgsMapLayer *ml = li.value();
if ( ml )
{
QString externalProjectFile = layerIsEmbedded( ml->id() );
QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.constFind( ml->id() );
if ( emIt == mEmbeddedLayers.constEnd() )
{
// general layer metadata
QDomElement maplayerElem = doc->createElement( "maplayer" );
ml->writeLayerXML( maplayerElem, *doc );
emit writeMapLayer( ml, maplayerElem, *doc );
projectLayersNode.appendChild( maplayerElem );
}
else
{
// layer defined in an external project file
// only save embedded layer if not managed by a legend group
if ( emIt.value().second )
{
QDomElement mapLayerElem = doc->createElement( "maplayer" );
mapLayerElem.setAttribute( "embedded", 1 );
mapLayerElem.setAttribute( "project", writePath( emIt.value().first ) );
mapLayerElem.setAttribute( "id", ml->id() );
projectLayersNode.appendChild( mapLayerElem );
}
}
}
li++;
}
qgisNode.appendChild( projectLayersNode );
// now add the optional extra properties
dump_( imp_->properties_ );
QgsDebugMsg( QString( "there are %1 property scopes" ).arg( static_cast<int>( imp_->properties_.count() ) ) );
if ( !imp_->properties_.isEmpty() ) // only worry about properties if we
// actually have any properties
{
imp_->properties_.writeXML( "properties", qgisNode, *doc );
}
mVisibilityPresetCollection->writeXML( *doc );
// now wrap it up and ship it to the project file
doc->normalize(); // XXX I'm not entirely sure what this does
// Create backup file
if ( QFile::exists( fileName() ) )
{
QFile backupFile( fileName() + '~' );
bool ok = true;
ok &= backupFile.open( QIODevice::WriteOnly | QIODevice::Truncate );
ok &= imp_->file.open( QIODevice::ReadOnly );
QByteArray ba;
while ( ok && !imp_->file.atEnd() )
{
ba = imp_->file.read( 10240 );
ok &= backupFile.write( ba ) == ba.size();
}
imp_->file.close();
backupFile.close();
if ( !ok )
{
setError( tr( "Unable to create backup file %1" ).arg( backupFile.fileName() ) );
return false;
}
QFileInfo fi( fileName() );
struct utimbuf tb = { fi.lastRead().toTime_t(), fi.lastModified().toTime_t() };
utime( backupFile.fileName().toUtf8().constData(), &tb );
}
if ( !imp_->file.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
{
imp_->file.close(); // even though we got an error, let's make
// sure it's closed anyway
setError( tr( "Unable to save to file %1" ).arg( imp_->file.fileName() ) );
return false;
}
QTemporaryFile tempFile;
bool ok = tempFile.open();
if ( ok )
{
QTextStream projectFileStream( &tempFile );
doc->save( projectFileStream, 2 ); // save as utf-8
ok &= projectFileStream.pos() > -1;
ok &= tempFile.seek( 0 );
QByteArray ba;
while ( ok && !tempFile.atEnd() )
{
ba = tempFile.read( 10240 );
ok &= imp_->file.write( ba ) == ba.size();
}
ok &= imp_->file.error() == QFile::NoError;
imp_->file.close();
}
tempFile.close();
if ( !ok )
{
setError( tr( "Unable to save to file %1. Your project "
"may be corrupted on disk. Try clearing some space on the volume and "
"check file permissions before pressing save again." )
.arg( imp_->file.fileName() ) );
return false;
}
dirty( false ); // reset to pristine state
emit projectSaved();
return true;
} // QgsProject::write
void QgsProject::clearProperties()
{
clear();
dirty( true );
} // QgsProject::clearProperties()
bool
QgsProject::writeEntry( QString const &scope, const QString &key, bool value )
{
dirty( true );
return addKey_( scope, key, &imp_->properties_, value );
} // QgsProject::writeEntry ( ..., bool value )
bool
QgsProject::writeEntry( QString const &scope, const QString &key,
double value )
{
dirty( true );
return addKey_( scope, key, &imp_->properties_, value );
} // QgsProject::writeEntry ( ..., double value )
bool
QgsProject::writeEntry( QString const &scope, const QString &key, int value )
{
dirty( true );
return addKey_( scope, key, &imp_->properties_, value );
} // QgsProject::writeEntry ( ..., int value )
bool
QgsProject::writeEntry( QString const &scope, const QString &key,
const QString &value )
{
dirty( true );
return addKey_( scope, key, &imp_->properties_, value );
} // QgsProject::writeEntry ( ..., const QString &value )
bool
QgsProject::writeEntry( QString const &scope, const QString &key,
const QStringList &value )
{
dirty( true );
return addKey_( scope, key, &imp_->properties_, value );
} // QgsProject::writeEntry ( ..., const QStringList &value )
QStringList
QgsProject::readListEntry( QString const &scope,
const QString &key,
const QStringList& def,
bool *ok ) const
{
QgsProperty *property = findKey_( scope, key, imp_->properties_ );
QVariant value;
if ( property )
{
value = property->value();
bool valid = QVariant::StringList == value.type();
if ( ok )
*ok = valid;
if ( valid )
{
return value.toStringList();
}
}
return def;
} // QgsProject::readListEntry
QString
QgsProject::readEntry( QString const &scope,
const QString &key,
const QString &def,
bool *ok ) const
{
QgsProperty *property = findKey_( scope, key, imp_->properties_ );
QVariant value;
if ( property )
{
value = property->value();
bool valid = value.canConvert( QVariant::String );
if ( ok )
*ok = valid;
if ( valid )
return value.toString();
}
return def;
} // QgsProject::readEntry
int
QgsProject::readNumEntry( QString const &scope, const QString &key, int def,
bool *ok ) const
{
QgsProperty *property = findKey_( scope, key, imp_->properties_ );
QVariant value;
if ( property )
{
value = property->value();
}
bool valid = value.canConvert( QVariant::String );
if ( ok )
{
*ok = valid;
}
if ( valid )
{
return value.toInt();
}
return def;
} // QgsProject::readNumEntry
double
QgsProject::readDoubleEntry( QString const &scope, const QString &key,
double def,
bool *ok ) const
{
QgsProperty *property = findKey_( scope, key, imp_->properties_ );
if ( property )
{
QVariant value = property->value();
bool valid = value.canConvert( QVariant::Double );
if ( ok )
*ok = valid;
if ( valid )
return value.toDouble();
}
return def;
} // QgsProject::readDoubleEntry
bool
QgsProject::readBoolEntry( QString const &scope, const QString &key, bool def,
bool *ok ) const
{
QgsProperty *property = findKey_( scope, key, imp_->properties_ );
if ( property )
{
QVariant value = property->value();
bool valid = value.canConvert( QVariant::Bool );
if ( ok )
*ok = valid;
if ( valid )
return value.toBool();
}
return def;
} // QgsProject::readBoolEntry
bool QgsProject::removeEntry( QString const &scope, const QString &key )
{
removeKey_( scope, key, imp_->properties_ );
dirty( true );
return !findKey_( scope, key, imp_->properties_ );
} // QgsProject::removeEntry
QStringList QgsProject::entryList( QString const &scope, QString const &key ) const
{
QgsProperty *foundProperty = findKey_( scope, key, imp_->properties_ );
QStringList entries;
if ( foundProperty )
{
QgsPropertyKey *propertyKey = dynamic_cast<QgsPropertyKey*>( foundProperty );
if ( propertyKey )
{ propertyKey->entryList( entries ); }
}
return entries;
} // QgsProject::entryList
QStringList QgsProject::subkeyList( QString const &scope, QString const &key ) const
{
QgsProperty *foundProperty = findKey_( scope, key, imp_->properties_ );
QStringList entries;
if ( foundProperty )
{
QgsPropertyKey *propertyKey = dynamic_cast<QgsPropertyKey*>( foundProperty );
if ( propertyKey )
{ propertyKey->subkeyList( entries ); }
}
return entries;
} // QgsProject::subkeyList
void QgsProject::dumpProperties() const
{
dump_( imp_->properties_ );
} // QgsProject::dumpProperties
// return the absolute path from a filename read from project file
QString QgsProject::readPath( QString src ) const
{
if ( readBoolEntry( "Paths", "/Absolute", false ) )
{
return src;
}
// if this is a VSIFILE, remove the VSI prefix and append to final result
QString vsiPrefix = qgsVsiPrefix( src );
if ( ! vsiPrefix.isEmpty() )
{
// unfortunately qgsVsiPrefix returns prefix also for files like "/x/y/z.gz"
// so we need to check if we really have the prefix
if ( src.startsWith( "/vsi", Qt::CaseInsensitive ) )
src.remove( 0, vsiPrefix.size() );
else
vsiPrefix.clear();
}
// relative path should always start with ./ or ../
if ( !src.startsWith( "./" ) && !src.startsWith( "../" ) )
{
#if defined(Q_OS_WIN)
if ( src.startsWith( "\\\\" ) ||
src.startsWith( "//" ) ||
( src[0].isLetter() && src[1] == ':' ) )
{
// UNC or absolute path
return vsiPrefix + src;
}
#else
if ( src[0] == '/' )
{
// absolute path
return vsiPrefix + src;
}
#endif
// so this one isn't absolute, but also doesn't start // with ./ or ../.
// That means that it was saved with an earlier version of "relative path support",
// where the source file had to exist and only the project directory was stripped
// from the filename.
QString home = homePath();
if ( home.isNull() )
return vsiPrefix + src;
QFileInfo fi( home + '/' + src );
if ( !fi.exists() )
{
return vsiPrefix + src;
}
else
{
return vsiPrefix + fi.canonicalFilePath();
}
}
QString srcPath = src;
QString projPath = fileName();
if ( projPath.isEmpty() )
{
return vsiPrefix + src;
}
#if defined(Q_OS_WIN)
srcPath.replace( '\\', '/' );
projPath.replace( '\\', '/' );
bool uncPath = projPath.startsWith( "//" );
#endif
QStringList srcElems = srcPath.split( '/', QString::SkipEmptyParts );
QStringList projElems = projPath.split( '/', QString::SkipEmptyParts );
#if defined(Q_OS_WIN)
if ( uncPath )
{
projElems.insert( 0, "" );
projElems.insert( 0, "" );
}
#endif
// remove project file element
projElems.removeLast();
// append source path elements
projElems << srcElems;
projElems.removeAll( "." );
// resolve ..
int pos;
while (( pos = projElems.indexOf( ".." ) ) > 0 )
{
// remove preceding element and ..
projElems.removeAt( pos - 1 );
projElems.removeAt( pos - 1 );
}
#if !defined(Q_OS_WIN)
// make path absolute
projElems.prepend( "" );
#endif
return vsiPrefix + projElems.join( "/" );
}
// return the absolute or relative path to write it to the project file
QString QgsProject::writePath( const QString& src, const QString& relativeBasePath ) const
{
if ( readBoolEntry( "Paths", "/Absolute", false ) || src.isEmpty() )
{
return src;
}
QFileInfo srcFileInfo( src );
QFileInfo projFileInfo( fileName() );
QString srcPath = srcFileInfo.exists() ? srcFileInfo.canonicalFilePath() : src;
QString projPath = projFileInfo.canonicalFilePath();
if ( !relativeBasePath.isNull() )
{
projPath = relativeBasePath;
}
if ( projPath.isEmpty() )
{
return src;
}
// if this is a VSIFILE, remove the VSI prefix and append to final result
QString vsiPrefix = qgsVsiPrefix( src );
if ( ! vsiPrefix.isEmpty() )
{
srcPath.remove( 0, vsiPrefix.size() );
}
#if defined( Q_OS_WIN )
const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
srcPath.replace( '\\', '/' );
if ( srcPath.startsWith( "//" ) )
{
// keep UNC prefix
srcPath = "\\\\" + srcPath.mid( 2 );
}
projPath.replace( '\\', '/' );
if ( projPath.startsWith( "//" ) )
{
// keep UNC prefix
projPath = "\\\\" + projPath.mid( 2 );
}
#else
const Qt::CaseSensitivity cs = Qt::CaseSensitive;
#endif
QStringList projElems = projPath.split( '/', QString::SkipEmptyParts );
QStringList srcElems = srcPath.split( '/', QString::SkipEmptyParts );
// remove project file element
projElems.removeLast();
projElems.removeAll( "." );
srcElems.removeAll( "." );
// remove common part
int n = 0;
while ( !srcElems.isEmpty() &&
!projElems.isEmpty() &&
srcElems[0].compare( projElems[0], cs ) == 0 )
{
srcElems.removeFirst();
projElems.removeFirst();
n++;
}
if ( n == 0 )
{
// no common parts; might not even by a file
return src;
}
if ( !projElems.isEmpty() )
{
// go up to the common directory
for ( int i = 0; i < projElems.size(); i++ )
{
srcElems.insert( 0, ".." );
}
}
else
{
// let it start with . nevertheless,
// so relative path always start with either ./ or ../
srcElems.insert( 0, "." );
}
return vsiPrefix + srcElems.join( "/" );
}
void QgsProject::setError( const QString& errorMessage )
{
mErrorMessage = errorMessage;
}
QString QgsProject::error() const
{
return mErrorMessage;
}
void QgsProject::clearError()
{
setError( QString() );
}
void QgsProject::setBadLayerHandler( QgsProjectBadLayerHandler *handler )
{
delete mBadLayerHandler;
mBadLayerHandler = handler;
}
QString QgsProject::layerIsEmbedded( const QString &id ) const
{
QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
if ( it == mEmbeddedLayers.constEnd() )
{
return QString();
}
return it.value().first;
}
bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &projectFilePath, QList<QDomNode> &brokenNodes,
QList< QPair< QgsVectorLayer*, QDomElement > > &vectorLayerList, bool saveFlag )
{
QgsDebugCall;
static QString prevProjectFilePath;
static QDateTime prevProjectFileTimestamp;
static QDomDocument projectDocument;
QDateTime projectFileTimestamp = QFileInfo( projectFilePath ).lastModified();
if ( projectFilePath != prevProjectFilePath || projectFileTimestamp != prevProjectFileTimestamp )
{
prevProjectFilePath.clear();
QFile projectFile( projectFilePath );
if ( !projectFile.open( QIODevice::ReadOnly ) )
{
return false;
}
if ( !projectDocument.setContent( &projectFile ) )
{
return false;
}
prevProjectFilePath = projectFilePath;
prevProjectFileTimestamp = projectFileTimestamp;
}
// does project store pathes absolute or relative?
bool useAbsolutePathes = true;
QDomElement propertiesElem = projectDocument.documentElement().firstChildElement( "properties" );
if ( !propertiesElem.isNull() )
{
QDomElement absElem = propertiesElem.firstChildElement( "Paths" ).firstChildElement( "Absolute" );
if ( !absElem.isNull() )
{
useAbsolutePathes = absElem.text().compare( "true", Qt::CaseInsensitive ) == 0;
}
}
QDomElement projectLayersElem = projectDocument.documentElement().firstChildElement( "projectlayers" );
if ( projectLayersElem.isNull() )
{
return false;
}
QDomNodeList mapLayerNodes = projectLayersElem.elementsByTagName( "maplayer" );
for ( int i = 0; i < mapLayerNodes.size(); ++i )
{
// get layer id
QDomElement mapLayerElem = mapLayerNodes.at( i ).toElement();
QString id = mapLayerElem.firstChildElement( "id" ).text();
if ( id == layerId )
{
// layer can be embedded only once
if ( mapLayerElem.attribute( "embedded" ) == "1" )
{
return false;
}
mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
// change datasource path from relative to absolute if necessary
// see also QgsMapLayer::readLayerXML
if ( !useAbsolutePathes )
{
QString provider( mapLayerElem.firstChildElement( "provider" ).text() );
QDomElement dsElem( mapLayerElem.firstChildElement( "datasource" ) );
QString datasource( dsElem.text() );
if ( provider == "spatialite" )
{
QgsDataSourceURI uri( datasource );
QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + '/' + uri.database() );
if ( absoluteDs.exists() )
{
uri.setDatabase( absoluteDs.absoluteFilePath() );
datasource = uri.uri();
}
}
else if ( provider == "ogr" )
{
QStringList theURIParts( datasource.split( '|' ) );
QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + '/' + theURIParts[0] );
if ( absoluteDs.exists() )
{
theURIParts[0] = absoluteDs.absoluteFilePath();
datasource = theURIParts.join( "|" );
}
}
else if ( provider == "gpx" )
{
QStringList theURIParts( datasource.split( '?' ) );
QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + '/' + theURIParts[0] );
if ( absoluteDs.exists() )
{
theURIParts[0] = absoluteDs.absoluteFilePath();
datasource = theURIParts.join( "?" );
}
}
else if ( provider == "delimitedtext" )
{
QUrl urlSource( QUrl::fromEncoded( datasource.toAscii() ) );
if ( !datasource.startsWith( "file:" ) )
{
QUrl file( QUrl::fromLocalFile( datasource.left( datasource.indexOf( '?' ) ) ) );
urlSource.setScheme( "file" );
urlSource.setPath( file.path() );
}
QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + '/' + urlSource.toLocalFile() );
if ( absoluteDs.exists() )
{
QUrl urlDest = QUrl::fromLocalFile( absoluteDs.absoluteFilePath() );
urlDest.setQueryItems( urlSource.queryItems() );
datasource = QString::fromAscii( urlDest.toEncoded() );
}
}
else
{
QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + '/' + datasource );
if ( absoluteDs.exists() )
{
datasource = absoluteDs.absoluteFilePath();
}
}
dsElem.removeChild( dsElem.childNodes().at( 0 ) );
dsElem.appendChild( projectDocument.createTextNode( datasource ) );
}
if ( addLayer( mapLayerElem, brokenNodes, vectorLayerList ) )
{
return true;
}
else
{
mEmbeddedLayers.remove( layerId );
return false;
}
}
}
return false;
}
QgsLayerTreeGroup *QgsProject::createEmbeddedGroup( const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers )
{
// open project file, get layer ids in group, add the layers
QFile projectFile( projectFilePath );
if ( !projectFile.open( QIODevice::ReadOnly ) )
{
return nullptr;
}
QDomDocument projectDocument;
if ( !projectDocument.setContent( &projectFile ) )
{
return nullptr;
}
// store identify disabled layers of the embedded project
QSet<QString> embeddedIdentifyDisabledLayers;
QDomElement disabledLayersElem = projectDocument.documentElement().firstChildElement( "properties" ).firstChildElement( "Identify" ).firstChildElement( "disabledLayers" );
if ( !disabledLayersElem.isNull() )
{
QDomNodeList valueList = disabledLayersElem.elementsByTagName( "value" );
for ( int i = 0; i < valueList.size(); ++i )
{
embeddedIdentifyDisabledLayers.insert( valueList.at( i ).toElement().text() );
}
}
QgsLayerTreeGroup *root = new QgsLayerTreeGroup;
QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( "layer-tree-group" );
if ( !layerTreeElem.isNull() )
{
root->readChildrenFromXML( layerTreeElem );
}
else
{
QgsLayerTreeUtils::readOldLegend( root, projectDocument.documentElement().firstChildElement( "legend" ) );
}
QgsLayerTreeGroup *group = root->findGroup( groupName );
if ( !group || group->customProperty( "embedded" ).toBool() )
{
// embedded groups cannot be embedded again
delete root;
return nullptr;
}
// clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
QgsLayerTreeGroup *newGroup = QgsLayerTree::toGroup( group->clone() );
delete root;
root = nullptr;
newGroup->setCustomProperty( "embedded", 1 );
newGroup->setCustomProperty( "embedded_project", projectFilePath );
// set "embedded" to all children + load embedded layers
mLayerTreeRegistryBridge->setEnabled( false );
initializeEmbeddedSubtree( projectFilePath, newGroup );
mLayerTreeRegistryBridge->setEnabled( true );
// consider the layers might be identify disabled in its project
Q_FOREACH ( const QString& layerId, newGroup->findLayerIds() )
{
if ( embeddedIdentifyDisabledLayers.contains( layerId ) )
{
QStringList thisProjectIdentifyDisabledLayers = QgsProject::instance()->readListEntry( "Identify", "/disabledLayers" );
thisProjectIdentifyDisabledLayers.append( layerId );
QgsProject::instance()->writeEntry( "Identify", "/disabledLayers", thisProjectIdentifyDisabledLayers );
}
QgsLayerTreeLayer *layer = newGroup->findLayer( layerId );
if ( layer )
{
layer->setVisible( invisibleLayers.contains( layerId ) ? Qt::Unchecked : Qt::Checked );
}
}
return newGroup;
}
void QgsProject::initializeEmbeddedSubtree( const QString &projectFilePath, QgsLayerTreeGroup *group )
{
Q_FOREACH ( QgsLayerTreeNode *child, group->children() )
{
// all nodes in the subtree will have "embedded" custom property set
child->setCustomProperty( "embedded", 1 );
if ( QgsLayerTree::isGroup( child ) )
{
initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ) );
}
else if ( QgsLayerTree::isLayer( child ) )
{
// load the layer into our project
QList<QDomNode> brokenNodes;
QList< QPair< QgsVectorLayer*, QDomElement > > vectorLayerList;
createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, vectorLayerList, false );
}
}
}
void QgsProject::setSnapSettingsForLayer( const QString &layerId, bool enabled, QgsSnapper::SnappingType type, QgsTolerance::UnitType unit, double tolerance, bool avoidIntersection )
{
QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList;
snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList );
int idx = layerIdList.indexOf( layerId );
if ( idx != -1 )
{
layerIdList.removeAt( idx );
enabledList.removeAt( idx );
snapTypeList.removeAt( idx );
toleranceUnitList.removeAt( idx );
toleranceList.removeAt( idx );
avoidIntersectionList.removeOne( layerId );
}
layerIdList.append( layerId );
// enabled
enabledList.append( enabled ? "enabled" : "disabled" );
// snap type
QString typeString;
if ( type == QgsSnapper::SnapToSegment )
{
typeString = "to_segment";
}
else if ( type == QgsSnapper::SnapToVertexAndSegment )
{
typeString = "to_vertex_and_segment";
}
else
{
typeString = "to_vertex";
}
snapTypeList.append( typeString );
// units
toleranceUnitList.append( QString::number( unit ) );
// tolerance
toleranceList.append( QString::number( tolerance ) );
// avoid intersection
if ( avoidIntersection )
{
avoidIntersectionList.append( layerId );
}
writeEntry( "Digitizing", "/LayerSnappingList", layerIdList );
writeEntry( "Digitizing", "/LayerSnappingEnabledList", enabledList );
writeEntry( "Digitizing", "/LayerSnappingToleranceList", toleranceList );
writeEntry( "Digitizing", "/LayerSnappingToleranceUnitList", toleranceUnitList );
writeEntry( "Digitizing", "/LayerSnapToList", snapTypeList );
writeEntry( "Digitizing", "/AvoidIntersectionsList", avoidIntersectionList );
emit snapSettingsChanged();
}
bool QgsProject::snapSettingsForLayer( const QString &layerId, bool &enabled, QgsSnapper::SnappingType &type, QgsTolerance::UnitType &units, double &tolerance,
bool &avoidIntersection ) const
{
QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList;
snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList );
int idx = layerIdList.indexOf( layerId );
if ( idx == -1 )
{
return false;
}
// make sure all lists are long enough
int minListEntries = idx + 1;
if ( layerIdList.size() < minListEntries || enabledList.size() < minListEntries || snapTypeList.size() < minListEntries ||
toleranceUnitList.size() < minListEntries || toleranceList.size() < minListEntries )
{
return false;
}
// enabled
enabled = enabledList.at( idx ) == "enabled";
// snap type
QString snapType = snapTypeList.at( idx );
if ( snapType == "to_segment" )
{
type = QgsSnapper::SnapToSegment;
}
else if ( snapType == "to_vertex_and_segment" )
{
type = QgsSnapper::SnapToVertexAndSegment;
}
else // to vertex
{
type = QgsSnapper::SnapToVertex;
}
// units
if ( toleranceUnitList.at( idx ) == "1" )
{
units = QgsTolerance::Pixels;
}
else if ( toleranceUnitList.at( idx ) == "2" )
{
units = QgsTolerance::ProjectUnits;
}
else
{
units = QgsTolerance::LayerUnits;
}
// tolerance
tolerance = toleranceList.at( idx ).toDouble();
// avoid intersection
avoidIntersection = ( avoidIntersectionList.indexOf( layerId ) != -1 );
return true;
}
void QgsProject::snapSettings( QStringList &layerIdList, QStringList &enabledList, QStringList &snapTypeList, QStringList &toleranceUnitList, QStringList &toleranceList,
QStringList &avoidIntersectionList ) const
{
layerIdList = readListEntry( "Digitizing", "/LayerSnappingList" );
enabledList = readListEntry( "Digitizing", "/LayerSnappingEnabledList" );
toleranceList = readListEntry( "Digitizing", "/LayerSnappingToleranceList" );
toleranceUnitList = readListEntry( "Digitizing", "/LayerSnappingToleranceUnitList" );
snapTypeList = readListEntry( "Digitizing", "/LayerSnapToList" );
avoidIntersectionList = readListEntry( "Digitizing", "/AvoidIntersectionsList" );
}
void QgsProject::setTopologicalEditing( bool enabled )
{
QgsProject::instance()->writeEntry( "Digitizing", "/TopologicalEditing", ( enabled ? 1 : 0 ) );
emit snapSettingsChanged();
}
bool QgsProject::topologicalEditing() const
{
return ( QgsProject::instance()->readNumEntry( "Digitizing", "/TopologicalEditing", 0 ) > 0 );
}
void QgsProjectBadLayerDefaultHandler::handleBadLayers( const QList<QDomNode>& /*layers*/, const QDomDocument& /*projectDom*/ )
{
// just ignore any bad layers
}
QString QgsProject::homePath() const
{
QFileInfo pfi( fileName() );
if ( !pfi.exists() )
return QString::null;
return pfi.canonicalPath();
}
QgsRelationManager *QgsProject::relationManager() const
{
return mRelationManager;
}
QgsLayerTreeGroup *QgsProject::layerTreeRoot() const
{
return mRootGroup;
}
QgsVisibilityPresetCollection* QgsProject::visibilityPresetCollection()
{
return mVisibilityPresetCollection.data();
}