1 /***************************************************************************
2                           qgsproject.cpp -  description
3                              -------------------
4   begin                : July 23, 2004
5   copyright            : (C) 2004 by Mark Coletti
6   email                : mcoletti at gmail.com
7 ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgsproject.h"
19 
20 #include "qgsdatasourceuri.h"
21 #include "qgslabelingenginesettings.h"
22 #include "qgslayertree.h"
23 #include "qgslayertreeutils.h"
24 #include "qgslayertreeregistrybridge.h"
25 #include "qgslogger.h"
26 #include "qgsmessagelog.h"
27 #include "qgspluginlayer.h"
28 #include "qgspluginlayerregistry.h"
29 #include "qgsprojectfiletransform.h"
30 #include "qgssnappingconfig.h"
31 #include "qgspathresolver.h"
32 #include "qgsprojectstorage.h"
33 #include "qgsprojectstorageregistry.h"
34 #include "qgsprojectversion.h"
35 #include "qgsrasterlayer.h"
36 #include "qgsreadwritecontext.h"
37 #include "qgsrectangle.h"
38 #include "qgsrelationmanager.h"
39 #include "qgsannotationmanager.h"
40 #include "qgsvectorlayerjoininfo.h"
41 #include "qgsmapthemecollection.h"
42 #include "qgslayerdefinition.h"
43 #include "qgsunittypes.h"
44 #include "qgstransaction.h"
45 #include "qgstransactiongroup.h"
46 #include "qgsvectordataprovider.h"
47 #include "qgsprojectbadlayerhandler.h"
48 #include "qgssettings.h"
49 #include "qgsmaplayerlistutils.h"
50 #include "qgsmeshlayer.h"
51 #include "qgslayoutmanager.h"
52 #include "qgsbookmarkmanager.h"
53 #include "qgsmaplayerstore.h"
54 #include "qgsziputils.h"
55 #include "qgsauxiliarystorage.h"
56 #include "qgssymbollayerutils.h"
57 #include "qgsapplication.h"
58 #include "qgsexpressioncontextutils.h"
59 #include "qgsstyleentityvisitor.h"
60 #include "qgsprojectviewsettings.h"
61 #include "qgsprojectdisplaysettings.h"
62 #include "qgsprojecttimesettings.h"
63 #include "qgsvectortilelayer.h"
64 #include "qgsruntimeprofiler.h"
65 #include "qgsannotationlayer.h"
66 
67 #include <algorithm>
68 #include <QApplication>
69 #include <QFileInfo>
70 #include <QDomNode>
71 #include <QObject>
72 #include <QTextStream>
73 #include <QTemporaryFile>
74 #include <QDir>
75 #include <QUrl>
76 
77 
78 #ifdef _MSC_VER
79 #include <sys/utime.h>
80 #else
81 #include <utime.h>
82 #endif
83 
84 // canonical project instance
85 QgsProject *QgsProject::sProject = nullptr;
86 
87 ///@cond PRIVATE
88 class ScopedIntIncrementor
89 {
90   public:
91 
ScopedIntIncrementor(int * variable)92     ScopedIntIncrementor( int *variable )
93       : mVariable( variable )
94     {
95       ( *mVariable )++;
96     }
97 
98     ScopedIntIncrementor( const ScopedIntIncrementor &other ) = delete;
99     ScopedIntIncrementor &operator=( const ScopedIntIncrementor &other ) = delete;
100 
release()101     void release()
102     {
103       if ( mVariable )
104         ( *mVariable )--;
105 
106       mVariable = nullptr;
107     }
108 
~ScopedIntIncrementor()109     ~ScopedIntIncrementor()
110     {
111       release();
112     }
113 
114   private:
115     int *mVariable = nullptr;
116 };
117 ///@endcond
118 
119 
120 /**
121     Take the given scope and key and convert them to a string list of key
122     tokens that will be used to navigate through a Property hierarchy
123 
124     E.g., scope "someplugin" and key "/foo/bar/baz" will become a string list
125     of { "properties", "someplugin", "foo", "bar", "baz" }.  "properties" is
126     always first because that's the permanent ``root'' Property node.
127  */
makeKeyTokens_(const QString & scope,const QString & key)128 QStringList makeKeyTokens_( const QString &scope, const QString &key )
129 {
130   QStringList keyTokens = QStringList( scope );
131 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
132   keyTokens += key.split( '/', QString::SkipEmptyParts );
133 #else
134   keyTokens += key.split( '/', Qt::SkipEmptyParts );
135 #endif
136 
137   // be sure to include the canonical root node
138   keyTokens.push_front( QStringLiteral( "properties" ) );
139 
140   //check validy of keys since an invalid xml name will will be dropped upon saving the xml file. If not valid, we print a message to the console.
141   for ( int i = 0; i < keyTokens.size(); ++i )
142   {
143     QString keyToken = keyTokens.at( i );
144 
145     //invalid chars in XML are found at http://www.w3.org/TR/REC-xml/#NT-NameChar
146     //note : it seems \x10000-\xEFFFF is valid, but it when added to the regexp, a lot of unwanted characters remain
147     QString nameCharRegexp = QStringLiteral( "[^: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]" );
148     QString nameStartCharRegexp = QStringLiteral( "^[^: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]" );
149 
150     if ( keyToken.contains( QRegExp( nameCharRegexp ) ) || keyToken.contains( QRegExp( nameStartCharRegexp ) ) )
151     {
152 
153       QString errorString = QObject::tr( "Entry token invalid : '%1'. The token will not be saved to file." ).arg( keyToken );
154       QgsMessageLog::logMessage( errorString, QString(), Qgis::Critical );
155 
156     }
157 
158   }
159 
160   return keyTokens;
161 }
162 
163 
164 
165 /**
166    return the property that matches the given key sequence, if any
167 
168    \param scope scope of key
169    \param key keyname
170    \param rootProperty is likely to be the top level QgsProjectPropertyKey in QgsProject:e:Imp.
171 
172    \return null if not found, otherwise located Property
173 */
findKey_(const QString & scope,const QString & key,QgsProjectPropertyKey & rootProperty)174 QgsProjectProperty *findKey_( const QString &scope,
175                               const QString &key,
176                               QgsProjectPropertyKey &rootProperty )
177 {
178   QgsProjectPropertyKey *currentProperty = &rootProperty;
179   QgsProjectProperty *nextProperty;           // link to next property down hierarchy
180 
181   QStringList keySequence = makeKeyTokens_( scope, key );
182 
183   while ( !keySequence.isEmpty() )
184   {
185     // if the current head of the sequence list matches the property name,
186     // then traverse down the property hierarchy
187     if ( keySequence.first() == currentProperty->name() )
188     {
189       // remove front key since we're traversing down a level
190       keySequence.pop_front();
191 
192       if ( 1 == keySequence.count() )
193       {
194         // if we have only one key name left, then return the key found
195         return currentProperty->find( keySequence.front() );
196       }
197       else if ( keySequence.isEmpty() )
198       {
199         // if we're out of keys then the current property is the one we
200         // want; i.e., we're in the rate case of being at the top-most
201         // property node
202         return currentProperty;
203       }
204       else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
205       {
206         if ( nextProperty->isKey() )
207         {
208           currentProperty = static_cast<QgsProjectPropertyKey *>( nextProperty );
209         }
210         else if ( nextProperty->isValue() && 1 == keySequence.count() )
211         {
212           // it may be that this may be one of several property value
213           // nodes keyed by QDict string; if this is the last remaining
214           // key token and the next property is a value node, then
215           // that's the situation, so return the currentProperty
216           return currentProperty;
217         }
218         else
219         {
220           // QgsProjectPropertyValue not Key, so return null
221           return nullptr;
222         }
223       }
224       else
225       {
226         // if the next key down isn't found
227         // then the overall key sequence doesn't exist
228         return nullptr;
229       }
230     }
231     else
232     {
233       return nullptr;
234     }
235   }
236 
237   return nullptr;
238 }
239 
240 
241 
242 /**
243  * Add the given key and value
244 
245 \param scope scope of key
246 \param key key name
247 \param rootProperty is the property from which to start adding
248 \param value the value associated with the key
249 \param propertiesModified the parameter will be set to true if the written entry modifies pre-existing properties
250 */
addKey_(const QString & scope,const QString & key,QgsProjectPropertyKey * rootProperty,const QVariant & value,bool & propertiesModified)251 QgsProjectProperty *addKey_( const QString &scope,
252                              const QString &key,
253                              QgsProjectPropertyKey *rootProperty,
254                              const QVariant &value,
255                              bool &propertiesModified )
256 {
257   QStringList keySequence = makeKeyTokens_( scope, key );
258 
259   // cursor through property key/value hierarchy
260   QgsProjectPropertyKey *currentProperty = rootProperty;
261   QgsProjectProperty *nextProperty; // link to next property down hierarchy
262   QgsProjectPropertyKey *newPropertyKey = nullptr;
263 
264   propertiesModified = false;
265   while ( ! keySequence.isEmpty() )
266   {
267     // if the current head of the sequence list matches the property name,
268     // then traverse down the property hierarchy
269     if ( keySequence.first() == currentProperty->name() )
270     {
271       // remove front key since we're traversing down a level
272       keySequence.pop_front();
273 
274       // if key sequence has one last element, then we use that as the
275       // name to store the value
276       if ( 1 == keySequence.count() )
277       {
278         QgsProjectProperty *property = currentProperty->find( keySequence.front() );
279         if ( !property || property->value() != value )
280         {
281           currentProperty->setValue( keySequence.front(), value );
282           propertiesModified = true;
283         }
284 
285         return currentProperty;
286       }
287       // we're at the top element if popping the keySequence element
288       // will leave it empty; in that case, just add the key
289       else if ( keySequence.isEmpty() )
290       {
291         if ( currentProperty->value() != value )
292         {
293           currentProperty->setValue( value );
294           propertiesModified = true;
295         }
296 
297         return currentProperty;
298       }
299       else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
300       {
301         currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
302 
303         if ( currentProperty )
304         {
305           continue;
306         }
307         else            // QgsProjectPropertyValue not Key, so return null
308         {
309           return nullptr;
310         }
311       }
312       else                // the next subkey doesn't exist, so add it
313       {
314         if ( ( newPropertyKey = currentProperty->addKey( keySequence.first() ) ) )
315         {
316           currentProperty = newPropertyKey;
317         }
318         continue;
319       }
320     }
321     else
322     {
323       return nullptr;
324     }
325   }
326 
327   return nullptr;
328 }
329 
330 /**
331  * Remove a given key
332 
333 \param scope scope of key
334 \param key key name
335 \param rootProperty is the property from which to start adding
336 */
337 
removeKey_(const QString & scope,const QString & key,QgsProjectPropertyKey & rootProperty)338 void removeKey_( const QString &scope,
339                  const QString &key,
340                  QgsProjectPropertyKey &rootProperty )
341 {
342   QgsProjectPropertyKey *currentProperty = &rootProperty;
343 
344   QgsProjectProperty *nextProperty = nullptr;   // link to next property down hierarchy
345   QgsProjectPropertyKey *previousQgsPropertyKey = nullptr; // link to previous property up hierarchy
346 
347   QStringList keySequence = makeKeyTokens_( scope, key );
348 
349   while ( ! keySequence.isEmpty() )
350   {
351     // if the current head of the sequence list matches the property name,
352     // then traverse down the property hierarchy
353     if ( keySequence.first() == currentProperty->name() )
354     {
355       // remove front key since we're traversing down a level
356       keySequence.pop_front();
357 
358       // if we have only one key name left, then try to remove the key
359       // with that name
360       if ( 1 == keySequence.count() )
361       {
362         currentProperty->removeKey( keySequence.front() );
363       }
364       // if we're out of keys then the current property is the one we
365       // want to remove, but we can't delete it directly; we need to
366       // delete it from the parent property key container
367       else if ( keySequence.isEmpty() )
368       {
369         previousQgsPropertyKey->removeKey( currentProperty->name() );
370       }
371       else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
372       {
373         previousQgsPropertyKey = currentProperty;
374         currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
375 
376         if ( currentProperty )
377         {
378           continue;
379         }
380         else            // QgsProjectPropertyValue not Key, so return null
381         {
382           return;
383         }
384       }
385       else                // if the next key down isn't found
386       {
387         // then the overall key sequence doesn't exist
388         return;
389       }
390     }
391     else
392     {
393       return;
394     }
395   }
396 }
397 
QgsProject(QObject * parent)398 QgsProject::QgsProject( QObject *parent )
399   : QObject( parent )
400   , mLayerStore( new QgsMapLayerStore( this ) )
401   , mBadLayerHandler( new QgsProjectBadLayerHandler() )
402   , mSnappingConfig( this )
403   , mRelationManager( new QgsRelationManager( this ) )
404   , mAnnotationManager( new QgsAnnotationManager( this ) )
405   , mLayoutManager( new QgsLayoutManager( this ) )
406   , mBookmarkManager( QgsBookmarkManager::createProjectBasedManager( this ) )
407   , mViewSettings( new QgsProjectViewSettings( this ) )
408   , mTimeSettings( new QgsProjectTimeSettings( this ) )
409   , mDisplaySettings( new QgsProjectDisplaySettings( this ) )
410   , mRootGroup( new QgsLayerTree )
411   , mLabelingEngineSettings( new QgsLabelingEngineSettings )
412   , mArchive( new QgsProjectArchive() )
413   , mAuxiliaryStorage( new QgsAuxiliaryStorage() )
414 {
415   mProperties.setName( QStringLiteral( "properties" ) );
416 
417   mMainAnnotationLayer = new QgsAnnotationLayer( QObject::tr( "Annotations" ), QgsAnnotationLayer::LayerOptions( mTransformContext ) );
418   mMainAnnotationLayer->setParent( this );
419 
420   clear();
421 
422   // bind the layer tree to the map layer registry.
423   // whenever layers are added to or removed from the registry,
424   // layer tree will be updated
425   mLayerTreeRegistryBridge = new QgsLayerTreeRegistryBridge( mRootGroup, this, this );
426   connect( this, &QgsProject::layersAdded, this, &QgsProject::onMapLayersAdded );
427   connect( this, &QgsProject::layersRemoved, this, [ = ] { cleanTransactionGroups(); } );
428   connect( this, qgis::overload< const QList<QgsMapLayer *> & >::of( &QgsProject::layersWillBeRemoved ), this, &QgsProject::onMapLayersRemoved );
429 
430   // proxy map layer store signals to this
431   connect( mLayerStore.get(), qgis::overload<const QStringList &>::of( &QgsMapLayerStore::layersWillBeRemoved ),
432   this, [ = ]( const QStringList & layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
433   connect( mLayerStore.get(), qgis::overload< const QList<QgsMapLayer *> & >::of( &QgsMapLayerStore::layersWillBeRemoved ),
434   this, [ = ]( const QList<QgsMapLayer *> &layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
435   connect( mLayerStore.get(), qgis::overload< const QString & >::of( &QgsMapLayerStore::layerWillBeRemoved ),
436   this, [ = ]( const QString & layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
437   connect( mLayerStore.get(), qgis::overload< QgsMapLayer * >::of( &QgsMapLayerStore::layerWillBeRemoved ),
438   this, [ = ]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
439   connect( mLayerStore.get(), qgis::overload<const QStringList & >::of( &QgsMapLayerStore::layersRemoved ), this,
440   [ = ]( const QStringList & layers ) { mProjectScope.reset(); emit layersRemoved( layers ); } );
441   connect( mLayerStore.get(), &QgsMapLayerStore::layerRemoved, this,
442   [ = ]( const QString & layer ) { mProjectScope.reset(); emit layerRemoved( layer ); } );
443   connect( mLayerStore.get(), &QgsMapLayerStore::allLayersRemoved, this,
444   [ = ]() { mProjectScope.reset(); emit removeAll(); } );
445   connect( mLayerStore.get(), &QgsMapLayerStore::layersAdded, this,
446   [ = ]( const QList< QgsMapLayer * > &layers ) { mProjectScope.reset(); emit layersAdded( layers ); } );
447   connect( mLayerStore.get(), &QgsMapLayerStore::layerWasAdded, this,
448   [ = ]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWasAdded( layer ); } );
449 
450   if ( QgsApplication::instance() )
451   {
452     connect( QgsApplication::instance(), &QgsApplication::requestForTranslatableObjects, this, &QgsProject::registerTranslatableObjects );
453   }
454 
455   connect( mLayerStore.get(), qgis::overload< const QList<QgsMapLayer *> & >::of( &QgsMapLayerStore::layersWillBeRemoved ), this,
456            [ = ]( const QList<QgsMapLayer *> &layers )
457   {
458     for ( const auto &layer : layers )
459     {
460       disconnect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager, &QgsRelationManager::updateRelationsStatus );
461     }
462   }
463          );
464   connect( mLayerStore.get(),  qgis::overload< const QList<QgsMapLayer *> & >::of( &QgsMapLayerStore::layersAdded ), this,
465            [ = ]( const QList<QgsMapLayer *> &layers )
466   {
467     for ( const auto &layer : layers )
468     {
469       connect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager, &QgsRelationManager::updateRelationsStatus );
470     }
471   }
472          );
473 
474   Q_NOWARN_DEPRECATED_PUSH
475   connect( mViewSettings, &QgsProjectViewSettings::mapScalesChanged, this, &QgsProject::mapScalesChanged );
476   Q_NOWARN_DEPRECATED_POP
477 }
478 
479 
~QgsProject()480 QgsProject::~QgsProject()
481 {
482   mIsBeingDeleted = true;
483 
484   clear();
485   delete mBadLayerHandler;
486   delete mRelationManager;
487   delete mLayerTreeRegistryBridge;
488   delete mRootGroup;
489   if ( this == sProject )
490   {
491     sProject = nullptr;
492   }
493 }
494 
setInstance(QgsProject * project)495 void QgsProject::setInstance( QgsProject *project )
496 {
497   sProject = project;
498 }
499 
500 
instance()501 QgsProject *QgsProject::instance()
502 {
503   if ( !sProject )
504   {
505     sProject = new QgsProject;
506   }
507   return sProject;
508 }
509 
setTitle(const QString & title)510 void QgsProject::setTitle( const QString &title )
511 {
512   if ( title == mMetadata.title() )
513     return;
514 
515   mMetadata.setTitle( title );
516   mProjectScope.reset();
517   emit metadataChanged();
518 
519   setDirty( true );
520 }
521 
title() const522 QString QgsProject::title() const
523 {
524   return mMetadata.title();
525 }
526 
saveUser() const527 QString QgsProject::saveUser() const
528 {
529   return mSaveUser;
530 }
531 
saveUserFullName() const532 QString QgsProject::saveUserFullName() const
533 {
534   return mSaveUserFull;
535 }
536 
lastSaveDateTime() const537 QDateTime QgsProject::lastSaveDateTime() const
538 {
539   return mSaveDateTime;
540 }
541 
lastSaveVersion() const542 QgsProjectVersion QgsProject::lastSaveVersion() const
543 {
544   return mSaveVersion;
545 }
546 
isDirty() const547 bool QgsProject::isDirty() const
548 {
549   return mDirty;
550 }
551 
setDirty(const bool dirty)552 void QgsProject::setDirty( const bool dirty )
553 {
554   if ( dirty && mDirtyBlockCount > 0 )
555     return;
556 
557   if ( mDirty == dirty )
558     return;
559 
560   mDirty = dirty;
561   emit isDirtyChanged( mDirty );
562 }
563 
setPresetHomePath(const QString & path)564 void QgsProject::setPresetHomePath( const QString &path )
565 {
566   if ( path == mHomePath )
567     return;
568 
569   mHomePath = path;
570   mCachedHomePath.clear();
571   mProjectScope.reset();
572 
573   emit homePathChanged();
574 
575   setDirty( true );
576 }
577 
registerTranslatableContainers(QgsTranslationContext * translationContext,QgsAttributeEditorContainer * parent,const QString & layerId)578 void QgsProject::registerTranslatableContainers( QgsTranslationContext *translationContext, QgsAttributeEditorContainer *parent, const QString &layerId )
579 {
580   const QList<QgsAttributeEditorElement *> elements = parent->children();
581 
582   for ( QgsAttributeEditorElement *element : elements )
583   {
584     if ( element->type() == QgsAttributeEditorElement::AeTypeContainer )
585     {
586       QgsAttributeEditorContainer *container = dynamic_cast<QgsAttributeEditorContainer *>( element );
587 
588       translationContext->registerTranslation( QStringLiteral( "project:layers:%1:formcontainers" ).arg( layerId ), container->name() );
589 
590       if ( !container->children().empty() )
591         registerTranslatableContainers( translationContext, container, layerId );
592     }
593   }
594 }
595 
registerTranslatableObjects(QgsTranslationContext * translationContext)596 void QgsProject::registerTranslatableObjects( QgsTranslationContext *translationContext )
597 {
598   //register layers
599   const QList<QgsLayerTreeLayer *> layers = mRootGroup->findLayers();
600 
601   for ( const QgsLayerTreeLayer *layer : layers )
602   {
603     translationContext->registerTranslation( QStringLiteral( "project:layers:%1" ).arg( layer->layerId() ), layer->name() );
604 
605     QgsMapLayer *mapLayer = layer->layer();
606     if ( mapLayer && mapLayer->type() == QgsMapLayerType::VectorLayer )
607     {
608       QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer );
609 
610       //register aliases and fields
611       const QgsFields fields = vlayer->fields();
612       for ( const QgsField &field : fields )
613       {
614         QString fieldName;
615         if ( field.alias().isEmpty() )
616           fieldName = field.name();
617         else
618           fieldName = field.alias();
619 
620         translationContext->registerTranslation( QStringLiteral( "project:layers:%1:fieldaliases" ).arg( vlayer->id() ), fieldName );
621 
622         if ( field.editorWidgetSetup().type() == QLatin1String( "ValueRelation" ) )
623         {
624           translationContext->registerTranslation( QStringLiteral( "project:layers:%1:fields:%2:valuerelationvalue" ).arg( vlayer->id(), field.name() ), field.editorWidgetSetup().config().value( QStringLiteral( "Value" ) ).toString() );
625         }
626       }
627 
628       //register formcontainers
629       registerTranslatableContainers( translationContext, vlayer->editFormConfig().invisibleRootContainer(), vlayer->id() );
630 
631     }
632   }
633 
634   //register layergroups
635   const QList<QgsLayerTreeGroup *> groupLayers = mRootGroup->findGroups();
636   for ( const QgsLayerTreeGroup *groupLayer : groupLayers )
637   {
638     translationContext->registerTranslation( QStringLiteral( "project:layergroups" ), groupLayer->name() );
639   }
640 
641   //register relations
642   const QList<QgsRelation> &relations = mRelationManager->relations().values();
643   for ( const QgsRelation &relation : relations )
644   {
645     translationContext->registerTranslation( QStringLiteral( "project:relations" ), relation.name() );
646   }
647 }
648 
setDataDefinedServerProperties(const QgsPropertyCollection & properties)649 void QgsProject::setDataDefinedServerProperties( const QgsPropertyCollection &properties )
650 {
651   mDataDefinedServerProperties = properties;
652 }
653 
dataDefinedServerProperties() const654 QgsPropertyCollection QgsProject::dataDefinedServerProperties() const
655 {
656   return mDataDefinedServerProperties;
657 }
658 
setFileName(const QString & name)659 void QgsProject::setFileName( const QString &name )
660 {
661   if ( name == mFile.fileName() )
662     return;
663 
664   QString oldHomePath = homePath();
665 
666   mFile.setFileName( name );
667   mCachedHomePath.clear();
668   mProjectScope.reset();
669 
670   emit fileNameChanged();
671 
672   QString newHomePath = homePath();
673   if ( newHomePath != oldHomePath )
674     emit homePathChanged();
675 
676   setDirty( true );
677 }
678 
fileName() const679 QString QgsProject::fileName() const
680 {
681   return mFile.fileName();
682 }
683 
setOriginalPath(const QString & path)684 void QgsProject::setOriginalPath( const QString &path )
685 {
686   mOriginalPath = path;
687 }
688 
originalPath() const689 QString QgsProject::originalPath() const
690 {
691   return mOriginalPath;
692 }
693 
fileInfo() const694 QFileInfo QgsProject::fileInfo() const
695 {
696   return QFileInfo( mFile );
697 }
698 
projectStorage() const699 QgsProjectStorage *QgsProject::projectStorage() const
700 {
701   return QgsApplication::projectStorageRegistry()->projectStorageFromUri( mFile.fileName() );
702 }
703 
lastModified() const704 QDateTime QgsProject::lastModified() const
705 {
706   if ( QgsProjectStorage *storage = projectStorage() )
707   {
708     QgsProjectStorage::Metadata metadata;
709     storage->readProjectStorageMetadata( mFile.fileName(), metadata );
710     return metadata.lastModified;
711   }
712   else
713   {
714     return QFileInfo( mFile.fileName() ).lastModified();
715   }
716 }
717 
absolutePath() const718 QString QgsProject::absolutePath() const
719 {
720   if ( projectStorage() )
721     return QString();
722 
723   if ( mFile.fileName().isEmpty() )
724     return QString();  // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
725 
726   return QFileInfo( mFile.fileName() ).absolutePath();
727 }
728 
absoluteFilePath() const729 QString QgsProject::absoluteFilePath() const
730 {
731   if ( projectStorage() )
732     return QString();
733 
734   if ( mFile.fileName().isEmpty() )
735     return QString();  // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
736 
737   return QFileInfo( mFile.fileName() ).absoluteFilePath();
738 }
739 
baseName() const740 QString QgsProject::baseName() const
741 {
742   if ( QgsProjectStorage *storage = projectStorage() )
743   {
744     QgsProjectStorage::Metadata metadata;
745     storage->readProjectStorageMetadata( mFile.fileName(), metadata );
746     return metadata.name;
747   }
748   else
749   {
750     return QFileInfo( mFile.fileName() ).completeBaseName();
751   }
752 }
753 
crs() const754 QgsCoordinateReferenceSystem QgsProject::crs() const
755 {
756   return mCrs;
757 }
758 
setCrs(const QgsCoordinateReferenceSystem & crs,bool adjustEllipsoid)759 void QgsProject::setCrs( const QgsCoordinateReferenceSystem &crs, bool adjustEllipsoid )
760 {
761   if ( crs != mCrs )
762   {
763     mCrs = crs;
764     writeEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), crs.isValid() ? 1 : 0 );
765     mProjectScope.reset();
766     setDirty( true );
767     emit crsChanged();
768   }
769 
770   if ( adjustEllipsoid )
771     setEllipsoid( crs.ellipsoidAcronym() );
772 }
773 
ellipsoid() const774 QString QgsProject::ellipsoid() const
775 {
776   if ( !crs().isValid() )
777     return geoNone();
778 
779   return readEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ), geoNone() );
780 }
781 
setEllipsoid(const QString & ellipsoid)782 void QgsProject::setEllipsoid( const QString &ellipsoid )
783 {
784   if ( ellipsoid == readEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ) ) )
785     return;
786 
787   mProjectScope.reset();
788   writeEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ), ellipsoid );
789   emit ellipsoidChanged( ellipsoid );
790 }
791 
transformContext() const792 QgsCoordinateTransformContext QgsProject::transformContext() const
793 {
794   return mTransformContext;
795 }
796 
setTransformContext(const QgsCoordinateTransformContext & context)797 void QgsProject::setTransformContext( const QgsCoordinateTransformContext &context )
798 {
799   if ( context == mTransformContext )
800     return;
801 
802   mTransformContext = context;
803   mProjectScope.reset();
804 
805   mMainAnnotationLayer->setTransformContext( context );
806   for ( auto &layer : mLayerStore.get()->mapLayers() )
807   {
808     layer->setTransformContext( context );
809   }
810   emit transformContextChanged();
811 }
812 
clear()813 void QgsProject::clear()
814 {
815   ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
816 
817   QgsSettings settings;
818 
819   mProjectScope.reset();
820   mFile.setFileName( QString() );
821   mProperties.clearKeys();
822   mSaveUser.clear();
823   mSaveUserFull.clear();
824   mSaveDateTime = QDateTime();
825   mSaveVersion = QgsProjectVersion();
826   mHomePath.clear();
827   mCachedHomePath.clear();
828   mAutoTransaction = false;
829   mEvaluateDefaultValues = false;
830   mDirty = false;
831   mTrustLayerMetadata = false;
832   mCustomVariables.clear();
833   mCrs = QgsCoordinateReferenceSystem();
834   mMetadata = QgsProjectMetadata();
835   if ( !mSettings.value( QStringLiteral( "projects/anonymize_new_projects" ), false, QgsSettings::Core ).toBool() )
836   {
837     mMetadata.setCreationDateTime( QDateTime::currentDateTime() );
838     mMetadata.setAuthor( QgsApplication::userFullName() );
839   }
840   emit metadataChanged();
841 
842   QgsCoordinateTransformContext context;
843   context.readSettings();
844   setTransformContext( context );
845 
846   mEmbeddedLayers.clear();
847   mRelationManager->clear();
848   mAnnotationManager->clear();
849   mLayoutManager->clear();
850   mBookmarkManager->clear();
851   mViewSettings->reset();
852   mTimeSettings->reset();
853   mDisplaySettings->reset();
854   mSnappingConfig.reset();
855   emit avoidIntersectionsModeChanged();
856   emit topologicalEditingChanged();
857 
858   mMapThemeCollection.reset( new QgsMapThemeCollection( this ) );
859   emit mapThemeCollectionChanged();
860 
861   mLabelingEngineSettings->clear();
862 
863   mAuxiliaryStorage.reset( new QgsAuxiliaryStorage() );
864   mArchive->clear();
865 
866   emit labelingEngineSettingsChanged();
867 
868   if ( !mIsBeingDeleted )
869   {
870     // possibly other signals should also not be thrown on destruction -- e.g. labelEngineSettingsChanged, etc.
871     emit projectColorsChanged();
872   }
873 
874   // reset some default project properties
875   // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
876   writeEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/Automatic" ), true );
877   writeEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DecimalPlaces" ), 2 );
878   writeEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), false );
879 
880   //copy default units to project
881   writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), mSettings.value( QStringLiteral( "/qgis/measure/displayunits" ) ).toString() );
882   writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), mSettings.value( QStringLiteral( "/qgis/measure/areaunits" ) ).toString() );
883 
884   int red = mSettings.value( QStringLiteral( "qgis/default_canvas_color_red" ), 255 ).toInt();
885   int green = mSettings.value( QStringLiteral( "qgis/default_canvas_color_green" ), 255 ).toInt();
886   int blue = mSettings.value( QStringLiteral( "qgis/default_canvas_color_blue" ), 255 ).toInt();
887   setBackgroundColor( QColor( red, green, blue ) );
888 
889   red = mSettings.value( QStringLiteral( "qgis/default_selection_color_red" ), 255 ).toInt();
890   green = mSettings.value( QStringLiteral( "qgis/default_selection_color_green" ), 255 ).toInt();
891   blue = mSettings.value( QStringLiteral( "qgis/default_selection_color_blue" ), 0 ).toInt();
892   int alpha = mSettings.value( QStringLiteral( "qgis/default_selection_color_alpha" ), 255 ).toInt();
893   setSelectionColor( QColor( red, green, blue, alpha ) );
894 
895   mSnappingConfig.clearIndividualLayerSettings();
896 
897   removeAllMapLayers();
898   mRootGroup->clear();
899   if ( mMainAnnotationLayer )
900     mMainAnnotationLayer->reset();
901 
902   snapSingleBlocker.release();
903 
904   if ( !mBlockSnappingUpdates )
905     emit snappingConfigChanged( mSnappingConfig );
906 
907   setDirty( false );
908   emit homePathChanged();
909   emit cleared();
910 }
911 
912 // basically a debugging tool to dump property list values
dump_(const QgsProjectPropertyKey & topQgsPropertyKey)913 void dump_( const QgsProjectPropertyKey &topQgsPropertyKey )
914 {
915   QgsDebugMsgLevel( QStringLiteral( "current properties:" ), 3 );
916   topQgsPropertyKey.dump();
917 }
918 
919 
920 /**
921 
922 Restore any optional properties found in "doc" to "properties".
923 
924 properties tags for all optional properties.  Within that there will be scope
925 tags.  In the following example there exist one property in the "fsplugin"
926 scope.  "layers" is a list containing three string values.
927 
928 \code{.xml}
929 <properties>
930   <fsplugin>
931     <foo type="int" >42</foo>
932     <baz type="int" >1</baz>
933     <layers type="QStringList" >
934       <value>railroad</value>
935       <value>airport</value>
936     </layers>
937     <xyqzzy type="int" >1</xyqzzy>
938     <bar type="double" >123.456</bar>
939     <feature_types type="QStringList" >
940        <value>type</value>
941     </feature_types>
942   </fsplugin>
943 </properties>
944 \endcode
945 
946 \param doc xml document
947 \param project_properties should be the top QgsProjectPropertyKey node.
948 
949 */
_getProperties(const QDomDocument & doc,QgsProjectPropertyKey & project_properties)950 void _getProperties( const QDomDocument &doc, QgsProjectPropertyKey &project_properties )
951 {
952   QDomElement propertiesElem = doc.documentElement().firstChildElement( QStringLiteral( "properties" ) );
953 
954   if ( propertiesElem.isNull() )  // no properties found, so we're done
955   {
956     return;
957   }
958 
959   QDomNodeList scopes = propertiesElem.childNodes();
960 
961   if ( scopes.count() < 1 )
962   {
963     QgsDebugMsg( QStringLiteral( "empty ``properties'' XML tag ... bailing" ) );
964     return;
965   }
966 
967   if ( ! project_properties.readXml( propertiesElem ) )
968   {
969     QgsDebugMsg( QStringLiteral( "Project_properties.readXml() failed" ) );
970   }
971 }
972 
973 /**
974  * Returns the data defined server properties collection found in "doc" to "dataDefinedServerProperties".
975  * \param doc xml document
976  * \param dataDefinedServerPropertyDefinitions property collection of the server overrides
977  * \since QGIS 3.14
978 **/
getDataDefinedServerProperties(const QDomDocument & doc,const QgsPropertiesDefinition & dataDefinedServerPropertyDefinitions)979 QgsPropertyCollection getDataDefinedServerProperties( const QDomDocument &doc, const QgsPropertiesDefinition &dataDefinedServerPropertyDefinitions )
980 {
981   QgsPropertyCollection ddServerProperties;
982   // Read data defined server properties
983   QDomElement ddElem = doc.documentElement().firstChildElement( QStringLiteral( "dataDefinedServerProperties" ) );
984   if ( !ddElem.isNull() )
985   {
986     if ( !ddServerProperties.readXml( ddElem, dataDefinedServerPropertyDefinitions ) )
987     {
988       QgsDebugMsg( QStringLiteral( "dataDefinedServerProperties.readXml() failed" ) );
989     }
990   }
991   return ddServerProperties;
992 }
993 
994 /**
995    Get the project title
996    \todo XXX we should go with the attribute xor title, not both.
997 */
_getTitle(const QDomDocument & doc,QString & title)998 static void _getTitle( const QDomDocument &doc, QString &title )
999 {
1000   QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "title" ) );
1001 
1002   title.clear();               // by default the title will be empty
1003 
1004   if ( !nl.count() )
1005   {
1006     QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1007     return;
1008   }
1009 
1010   QDomNode titleNode = nl.item( 0 );  // there should only be one, so zeroth element OK
1011 
1012   if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text
1013   {
1014     QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1015     return;
1016   }
1017 
1018   QDomNode titleTextNode = titleNode.firstChild();  // should only have one child
1019 
1020   if ( !titleTextNode.isText() )
1021   {
1022     QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1023     return;
1024   }
1025 
1026   QDomText titleText = titleTextNode.toText();
1027 
1028   title = titleText.data();
1029 
1030 }
1031 
readProjectFileMetadata(const QDomDocument & doc,QString & lastUser,QString & lastUserFull,QDateTime & lastSaveDateTime)1032 static void readProjectFileMetadata( const QDomDocument &doc, QString &lastUser, QString &lastUserFull, QDateTime &lastSaveDateTime )
1033 {
1034   QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
1035 
1036   if ( !nl.count() )
1037   {
1038     QgsDebugMsg( "unable to find qgis element" );
1039     return;
1040   }
1041 
1042   QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1043 
1044   QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1045   lastUser = qgisElement.attribute( QStringLiteral( "saveUser" ), QString() );
1046   lastUserFull = qgisElement.attribute( QStringLiteral( "saveUserFull" ), QString() );
1047   lastSaveDateTime = QDateTime::fromString( qgisElement.attribute( QStringLiteral( "saveDateTime" ), QString() ), Qt::ISODate );
1048 }
1049 
1050 
getVersion(const QDomDocument & doc)1051 QgsProjectVersion getVersion( const QDomDocument &doc )
1052 {
1053   QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
1054 
1055   if ( !nl.count() )
1056   {
1057     QgsDebugMsg( QStringLiteral( " unable to find qgis element in project file" ) );
1058     return QgsProjectVersion( 0, 0, 0, QString() );
1059   }
1060 
1061   QDomNode qgisNode = nl.item( 0 );  // there should only be one, so zeroth element OK
1062 
1063   QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1064   QgsProjectVersion projectVersion( qgisElement.attribute( QStringLiteral( "version" ) ) );
1065   return projectVersion;
1066 }
1067 
1068 
snappingConfig() const1069 QgsSnappingConfig QgsProject::snappingConfig() const
1070 {
1071   return mSnappingConfig;
1072 }
1073 
setSnappingConfig(const QgsSnappingConfig & snappingConfig)1074 void QgsProject::setSnappingConfig( const QgsSnappingConfig &snappingConfig )
1075 {
1076   if ( mSnappingConfig == snappingConfig )
1077     return;
1078 
1079   mSnappingConfig = snappingConfig;
1080   setDirty( true );
1081   emit snappingConfigChanged( mSnappingConfig );
1082 }
1083 
setAvoidIntersectionsMode(const AvoidIntersectionsMode mode)1084 void QgsProject::setAvoidIntersectionsMode( const AvoidIntersectionsMode mode )
1085 {
1086   if ( mAvoidIntersectionsMode == mode )
1087     return;
1088 
1089   mAvoidIntersectionsMode = mode;
1090   emit avoidIntersectionsModeChanged();
1091 }
1092 
_getMapLayers(const QDomDocument & doc,QList<QDomNode> & brokenNodes,QgsProject::ReadFlags flags)1093 bool QgsProject::_getMapLayers( const QDomDocument &doc, QList<QDomNode> &brokenNodes, QgsProject::ReadFlags flags )
1094 {
1095   // Layer order is set by the restoring the legend settings from project file.
1096   // This is done on the 'readProject( ... )' signal
1097 
1098   QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "maplayer" ) );
1099 
1100   // process the map layer nodes
1101 
1102   if ( 0 == nl.count() )      // if we have no layers to process, bail
1103   {
1104     return true; // Decided to return "true" since it's
1105     // possible for there to be a project with no
1106     // layers; but also, more imporantly, this
1107     // would cause the tests/qgsproject to fail
1108     // since the test suite doesn't currently
1109     // support test layers
1110   }
1111 
1112   bool returnStatus = true;
1113 
1114   // order layers based on their dependencies
1115   QgsScopedRuntimeProfile profile( tr( "Sorting layers" ), QStringLiteral( "projectload" ) );
1116   QgsLayerDefinition::DependencySorter depSorter( doc );
1117   if ( depSorter.hasCycle() )
1118     return false;
1119 
1120   // Missing a dependency? We still load all the layers, otherwise the project is completely broken!
1121   if ( depSorter.hasMissingDependency() )
1122     returnStatus = false;
1123 
1124   emit layerLoaded( 0, nl.count() );
1125 
1126   const QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
1127   const int totalLayerCount = sortedLayerNodes.count();
1128 
1129   int i = 0;
1130   for ( const QDomNode &node : sortedLayerNodes )
1131   {
1132     const QDomElement element = node.toElement();
1133 
1134     const QString name = translate( QStringLiteral( "project:layers:%1" ).arg( node.namedItem( QStringLiteral( "id" ) ).toElement().text() ), node.namedItem( QStringLiteral( "layername" ) ).toElement().text() );
1135     if ( !name.isNull() )
1136       emit loadingLayer( tr( "Loading layer %1" ).arg( name ) );
1137 
1138     profile.switchTask( name );
1139 
1140     if ( element.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
1141     {
1142       createEmbeddedLayer( element.attribute( QStringLiteral( "id" ) ), readPath( element.attribute( QStringLiteral( "project" ) ) ), brokenNodes, true, flags );
1143     }
1144     else
1145     {
1146       QgsReadWriteContext context;
1147       context.setPathResolver( pathResolver() );
1148       context.setProjectTranslator( this );
1149       context.setTransformContext( transformContext() );
1150 
1151       if ( !addLayer( element, brokenNodes, context, flags ) )
1152       {
1153         returnStatus = false;
1154       }
1155       const auto messages = context.takeMessages();
1156       if ( !messages.isEmpty() )
1157       {
1158         emit loadingLayerMessageReceived( tr( "Loading layer %1" ).arg( name ), messages );
1159       }
1160     }
1161     emit layerLoaded( i + 1, totalLayerCount );
1162     i++;
1163   }
1164 
1165   return returnStatus;
1166 }
1167 
addLayer(const QDomElement & layerElem,QList<QDomNode> & brokenNodes,QgsReadWriteContext & context,QgsProject::ReadFlags flags)1168 bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &brokenNodes, QgsReadWriteContext &context, QgsProject::ReadFlags flags )
1169 {
1170   QString type = layerElem.attribute( QStringLiteral( "type" ) );
1171   QgsDebugMsgLevel( "Layer type is " + type, 4 );
1172   std::unique_ptr<QgsMapLayer> mapLayer;
1173 
1174   QgsScopedRuntimeProfile profile( tr( "Create layer" ), QStringLiteral( "projectload" ) );
1175   if ( type == QLatin1String( "vector" ) )
1176   {
1177     mapLayer = qgis::make_unique<QgsVectorLayer>();
1178     // apply specific settings to vector layer
1179     if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1180     {
1181       vl->setReadExtentFromXml( mTrustLayerMetadata || ( flags & QgsProject::ReadFlag::FlagTrustLayerMetadata ) );
1182     }
1183   }
1184   else if ( type == QLatin1String( "raster" ) )
1185   {
1186     mapLayer =  qgis::make_unique<QgsRasterLayer>();
1187   }
1188   else if ( type == QLatin1String( "mesh" ) )
1189   {
1190     mapLayer = qgis::make_unique<QgsMeshLayer>();
1191   }
1192   else if ( type == QLatin1String( "vector-tile" ) )
1193   {
1194     mapLayer = qgis::make_unique<QgsVectorTileLayer>();
1195   }
1196   else if ( type == QLatin1String( "plugin" ) )
1197   {
1198     QString typeName = layerElem.attribute( QStringLiteral( "name" ) );
1199     mapLayer.reset( QgsApplication::pluginLayerRegistry()->createLayer( typeName ) );
1200   }
1201   else if ( type == QLatin1String( "annotation" ) )
1202   {
1203     QgsAnnotationLayer::LayerOptions options( mTransformContext );
1204     mapLayer = qgis::make_unique<QgsAnnotationLayer>( QString(), options );
1205   }
1206   if ( !mapLayer )
1207   {
1208     QgsDebugMsg( QStringLiteral( "Unable to create layer" ) );
1209     return false;
1210   }
1211 
1212   Q_CHECK_PTR( mapLayer ); // NOLINT
1213 
1214   // This is tricky: to avoid a leak we need to check if the layer was already in the store
1215   // because if it was, the newly created layer will not be added to the store and it would leak.
1216   const QString layerId { layerElem.namedItem( QStringLiteral( "id" ) ).toElement().text() };
1217   Q_ASSERT( ! layerId.isEmpty() );
1218   const bool layerWasStored { layerStore()->mapLayer( layerId ) != nullptr };
1219 
1220   // have the layer restore state that is stored in Dom node
1221   QgsMapLayer::ReadFlags layerFlags = QgsMapLayer::ReadFlags();
1222   if ( flags & QgsProject::ReadFlag::FlagDontResolveLayers )
1223     layerFlags |= QgsMapLayer::FlagDontResolveLayers;
1224   // Propagate trust layer metadata flag
1225   if ( mTrustLayerMetadata || ( flags & QgsProject::ReadFlag::FlagTrustLayerMetadata ) )
1226     layerFlags |= QgsMapLayer::FlagTrustLayerMetadata;
1227 
1228   profile.switchTask( tr( "Load layer source" ) );
1229   bool layerIsValid = mapLayer->readLayerXml( layerElem, context, layerFlags ) && mapLayer->isValid();
1230 
1231   profile.switchTask( tr( "Add layer to project" ) );
1232   QList<QgsMapLayer *> newLayers;
1233   newLayers << mapLayer.get();
1234   if ( layerIsValid || flags & QgsProject::ReadFlag::FlagDontResolveLayers )
1235   {
1236     emit readMapLayer( mapLayer.get(), layerElem );
1237     addMapLayers( newLayers );
1238   }
1239   else
1240   {
1241     // It's a bad layer: do not add to legend (the user will decide if she wants to do so)
1242     addMapLayers( newLayers, false );
1243     newLayers.first();
1244     QgsDebugMsg( "Unable to load " + type + " layer" );
1245     brokenNodes.push_back( layerElem );
1246   }
1247 
1248   // It should be safe to delete the layer now if layer was stored, because all the store
1249   // had to to was to reset the data source in case the validity changed.
1250   if ( ! layerWasStored )
1251   {
1252     mapLayer.release();
1253   }
1254 
1255   return layerIsValid;
1256 }
1257 
read(const QString & filename,QgsProject::ReadFlags flags)1258 bool QgsProject::read( const QString &filename, QgsProject::ReadFlags flags )
1259 {
1260   mFile.setFileName( filename );
1261   mCachedHomePath.clear();
1262   mProjectScope.reset();
1263 
1264   return read( flags );
1265 }
1266 
read(QgsProject::ReadFlags flags)1267 bool QgsProject::read( QgsProject::ReadFlags flags )
1268 {
1269   QString filename = mFile.fileName();
1270   bool returnValue;
1271 
1272   if ( QgsProjectStorage *storage = projectStorage() )
1273   {
1274     QTemporaryFile inDevice;
1275     if ( !inDevice.open() )
1276     {
1277       setError( tr( "Unable to open %1" ).arg( inDevice.fileName() ) );
1278       return false;
1279     }
1280 
1281     QgsReadWriteContext context;
1282     context.setProjectTranslator( this );
1283     if ( !storage->readProject( filename, &inDevice, context ) )
1284     {
1285       QString err = tr( "Unable to open %1" ).arg( filename );
1286       QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
1287       if ( !messages.isEmpty() )
1288         err += QStringLiteral( "\n\n" ) + messages.last().message();
1289       setError( err );
1290       return false;
1291     }
1292     returnValue = unzip( inDevice.fileName(), flags );  // calls setError() if returning false
1293   }
1294   else
1295   {
1296     if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
1297     {
1298       returnValue = unzip( mFile.fileName(), flags );
1299     }
1300     else
1301     {
1302       mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
1303       returnValue = readProjectFile( mFile.fileName(), flags );
1304     }
1305 
1306     //on translation we should not change the filename back
1307     if ( !mTranslator )
1308     {
1309       mFile.setFileName( filename );
1310       mCachedHomePath.clear();
1311       mProjectScope.reset();
1312     }
1313     else
1314     {
1315       //but delete the translator
1316       mTranslator.reset( nullptr );
1317     }
1318   }
1319   emit homePathChanged();
1320   return returnValue;
1321 }
1322 
readProjectFile(const QString & filename,QgsProject::ReadFlags flags)1323 bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags flags )
1324 {
1325   // avoid multiple emission of snapping updated signals
1326   ScopedIntIncrementor snapSignalBlock( &mBlockSnappingUpdates );
1327 
1328   QFile projectFile( filename );
1329   clearError();
1330 
1331   QgsApplication::profiler()->clear( QStringLiteral( "projectload" ) );
1332   QgsScopedRuntimeProfile profile( tr( "Setting up translations" ), QStringLiteral( "projectload" ) );
1333 
1334   QString localeFileName = QStringLiteral( "%1_%2" ).arg( QFileInfo( projectFile.fileName() ).baseName(), mSettings.value( QStringLiteral( "locale/userLocale" ), QString() ).toString() );
1335 
1336   if ( QFile( QStringLiteral( "%1/%2.qm" ).arg( QFileInfo( projectFile.fileName() ).absolutePath(), localeFileName ) ).exists() )
1337   {
1338     mTranslator.reset( new QTranslator() );
1339     mTranslator->load( localeFileName, QFileInfo( projectFile.fileName() ).absolutePath() );
1340   }
1341 
1342   profile.switchTask( tr( "Reading project file" ) );
1343   std::unique_ptr<QDomDocument> doc( new QDomDocument( QStringLiteral( "qgis" ) ) );
1344 
1345   if ( !projectFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
1346   {
1347     projectFile.close();
1348 
1349     setError( tr( "Unable to open %1" ).arg( projectFile.fileName() ) );
1350 
1351     return false;
1352   }
1353 
1354   // location of problem associated with errorMsg
1355   int line, column;
1356   QString errorMsg;
1357 
1358   if ( !doc->setContent( &projectFile, &errorMsg, &line, &column ) )
1359   {
1360     // want to make this class as GUI independent as possible; so commented out
1361 #if 0
1362     QMessageBox::critical( 0, tr( "Read Project File" ),
1363                            tr( "%1 at line %2 column %3" ).arg( errorMsg ).arg( line ).arg( column ) );
1364 #endif
1365 
1366     QString errorString = tr( "Project file read error in file %1: %2 at line %3 column %4" )
1367                           .arg( projectFile.fileName(), errorMsg ).arg( line ).arg( column );
1368 
1369     QgsDebugMsg( errorString );
1370 
1371     projectFile.close();
1372 
1373     setError( tr( "%1 for file %2" ).arg( errorString, projectFile.fileName() ) );
1374 
1375     return false;
1376   }
1377 
1378   projectFile.close();
1379 
1380   QgsDebugMsgLevel( "Opened document " + projectFile.fileName(), 2 );
1381 
1382   // get project version string, if any
1383   QgsProjectVersion fileVersion = getVersion( *doc );
1384   const QgsProjectVersion thisVersion( Qgis::version() );
1385 
1386   profile.switchTask( tr( "Updating project file" ) );
1387   if ( thisVersion > fileVersion )
1388   {
1389     QgsLogger::warning( "Loading a file that was saved with an older "
1390                         "version of qgis (saved in " + fileVersion.text() +
1391                         ", loaded in " + Qgis::version() +
1392                         "). Problems may occur." );
1393 
1394     QgsProjectFileTransform projectFile( *doc, fileVersion );
1395 
1396     // Shows a warning when an old project file is read.
1397     emit oldProjectVersionWarning( fileVersion.text() );
1398 
1399     projectFile.updateRevision( thisVersion );
1400   }
1401 
1402   // start new project, just keep the file name and auxiliary storage
1403   profile.switchTask( tr( "Creating auxiliary storage" ) );
1404   QString fileName = mFile.fileName();
1405   std::unique_ptr<QgsAuxiliaryStorage> aStorage = std::move( mAuxiliaryStorage );
1406   clear();
1407   mAuxiliaryStorage = std::move( aStorage );
1408   mFile.setFileName( fileName );
1409   mCachedHomePath.clear();
1410   mProjectScope.reset();
1411   mSaveVersion = fileVersion;
1412 
1413   // now get any properties
1414   profile.switchTask( tr( "Reading properties" ) );
1415   _getProperties( *doc, mProperties );
1416 
1417   // now get the data defined server properties
1418   mDataDefinedServerProperties = getDataDefinedServerProperties( *doc, dataDefinedServerPropertyDefinitions() );
1419 
1420   QgsDebugMsgLevel( QString::number( mProperties.count() ) + " properties read", 2 );
1421 
1422 #if 0
1423   dump_( mProperties );
1424 #endif
1425 
1426   // get older style project title
1427   QString oldTitle;
1428   _getTitle( *doc, oldTitle );
1429 
1430   readProjectFileMetadata( *doc, mSaveUser, mSaveUserFull, mSaveDateTime );
1431 
1432   QDomNodeList homePathNl = doc->elementsByTagName( QStringLiteral( "homePath" ) );
1433   if ( homePathNl.count() > 0 )
1434   {
1435     QDomElement homePathElement = homePathNl.at( 0 ).toElement();
1436     QString homePath = homePathElement.attribute( QStringLiteral( "path" ) );
1437     if ( !homePath.isEmpty() )
1438       setPresetHomePath( homePath );
1439   }
1440   else
1441   {
1442     emit homePathChanged();
1443   }
1444 
1445   const QColor backgroundColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), 255 ),
1446                                 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), 255 ),
1447                                 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), 255 ) );
1448   setBackgroundColor( backgroundColor );
1449   const QColor selectionColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), 255 ),
1450                                readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), 255 ),
1451                                readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), 255 ),
1452                                readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), 255 ) );
1453   setSelectionColor( selectionColor );
1454 
1455   QgsReadWriteContext context;
1456   context.setPathResolver( pathResolver() );
1457   context.setProjectTranslator( this );
1458 
1459   //crs
1460   QgsCoordinateReferenceSystem projectCrs;
1461   if ( readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), 0 ) )
1462   {
1463     // first preference - dedicated projectCrs node
1464     QDomNode srsNode = doc->documentElement().namedItem( QStringLiteral( "projectCrs" ) );
1465     if ( !srsNode.isNull() )
1466     {
1467       projectCrs.readXml( srsNode );
1468     }
1469 
1470     if ( !projectCrs.isValid() )
1471     {
1472       QString projCrsString = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSProj4String" ) );
1473       long currentCRS = readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSID" ), -1 );
1474       const QString authid = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCrs" ) );
1475 
1476       // authid should be prioritized over all
1477       bool isUserAuthId = authid.startsWith( QLatin1String( "USER:" ), Qt::CaseInsensitive );
1478       if ( !authid.isEmpty() && !isUserAuthId )
1479         projectCrs = QgsCoordinateReferenceSystem( authid );
1480 
1481       // try the CRS
1482       if ( !projectCrs.isValid() && currentCRS >= 0 )
1483       {
1484         projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
1485       }
1486 
1487       // if that didn't produce a match, try the proj.4 string
1488       if ( !projCrsString.isEmpty() && ( authid.isEmpty() || isUserAuthId ) && ( !projectCrs.isValid() || projectCrs.toProj() != projCrsString ) )
1489       {
1490         projectCrs = QgsCoordinateReferenceSystem::fromProj( projCrsString );
1491       }
1492 
1493       // last just take the given id
1494       if ( !projectCrs.isValid() )
1495       {
1496         projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
1497       }
1498     }
1499   }
1500   mCrs = projectCrs;
1501 
1502   QStringList datumErrors;
1503   if ( !mTransformContext.readXml( doc->documentElement(), context, datumErrors ) && !datumErrors.empty() )
1504   {
1505     emit missingDatumTransforms( datumErrors );
1506   }
1507   emit transformContextChanged();
1508 
1509   //add variables defined in project file - do this early in the reading cycle, as other components
1510   //(e.g. layouts) may depend on these variables
1511   QStringList variableNames = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ) );
1512   QStringList variableValues = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ) );
1513 
1514   mCustomVariables.clear();
1515   if ( variableNames.length() == variableValues.length() )
1516   {
1517     for ( int i = 0; i < variableNames.length(); ++i )
1518     {
1519       mCustomVariables.insert( variableNames.at( i ), variableValues.at( i ) );
1520     }
1521   }
1522   else
1523   {
1524     QgsMessageLog::logMessage( tr( "Project Variables Invalid" ), tr( "The project contains invalid variable settings." ) );
1525   }
1526 
1527   QDomNodeList nl = doc->elementsByTagName( QStringLiteral( "projectMetadata" ) );
1528   if ( !nl.isEmpty() )
1529   {
1530     QDomElement metadataElement = nl.at( 0 ).toElement();
1531     mMetadata.readMetadataXml( metadataElement );
1532   }
1533   else
1534   {
1535     // older project, no metadata => remove auto generated metadata which is populated on QgsProject::clear()
1536     mMetadata = QgsProjectMetadata();
1537   }
1538   if ( mMetadata.title().isEmpty() && !oldTitle.isEmpty() )
1539   {
1540     // upgrade older title storage to storing within project metadata.
1541     mMetadata.setTitle( oldTitle );
1542   }
1543   emit metadataChanged();
1544 
1545   nl = doc->elementsByTagName( QStringLiteral( "autotransaction" ) );
1546   if ( nl.count() )
1547   {
1548     QDomElement transactionElement = nl.at( 0 ).toElement();
1549     if ( transactionElement.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
1550       mAutoTransaction = true;
1551   }
1552 
1553   nl = doc->elementsByTagName( QStringLiteral( "evaluateDefaultValues" ) );
1554   if ( nl.count() )
1555   {
1556     QDomElement evaluateDefaultValuesElement = nl.at( 0 ).toElement();
1557     if ( evaluateDefaultValuesElement.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
1558       mEvaluateDefaultValues = true;
1559   }
1560 
1561   // Read trust layer metadata config in the project
1562   nl = doc->elementsByTagName( QStringLiteral( "trust" ) );
1563   if ( nl.count() )
1564   {
1565     QDomElement trustElement = nl.at( 0 ).toElement();
1566     if ( trustElement.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
1567       mTrustLayerMetadata = true;
1568   }
1569 
1570   // read the layer tree from project file
1571   profile.switchTask( tr( "Loading layer tree" ) );
1572   mRootGroup->setCustomProperty( QStringLiteral( "loading" ), 1 );
1573 
1574   QDomElement layerTreeElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
1575   if ( !layerTreeElem.isNull() )
1576   {
1577     mRootGroup->readChildrenFromXml( layerTreeElem, context );
1578   }
1579   else
1580   {
1581     QgsLayerTreeUtils::readOldLegend( mRootGroup, doc->documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
1582   }
1583 
1584   mLayerTreeRegistryBridge->setEnabled( false );
1585 
1586   // get the map layers
1587   profile.switchTask( tr( "Reading map layers" ) );
1588 
1589   QList<QDomNode> brokenNodes;
1590   bool clean = _getMapLayers( *doc, brokenNodes, flags );
1591 
1592   // review the integrity of the retrieved map layers
1593   if ( !clean )
1594   {
1595     QgsDebugMsg( QStringLiteral( "Unable to get map layers from project file." ) );
1596 
1597     if ( !brokenNodes.isEmpty() )
1598     {
1599       QgsDebugMsg( "there are " + QString::number( brokenNodes.size() ) + " broken layers" );
1600     }
1601 
1602     // we let a custom handler decide what to do with missing layers
1603     // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
1604     mBadLayerHandler->handleBadLayers( brokenNodes );
1605   }
1606 
1607   mMainAnnotationLayer->readLayerXml( doc->documentElement().firstChildElement( QStringLiteral( "main-annotation-layer" ) ), context );
1608   mMainAnnotationLayer->setTransformContext( mTransformContext );
1609 
1610   // Resolve references to other layers
1611   // Needs to be done here once all dependent layers are loaded
1612   profile.switchTask( tr( "Resolving layer references" ) );
1613   QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
1614   for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
1615   {
1616     it.value()->resolveReferences( this );
1617   }
1618 
1619   mLayerTreeRegistryBridge->setEnabled( true );
1620 
1621   // load embedded groups and layers
1622   profile.switchTask( tr( "Loading embedded layers" ) );
1623   loadEmbeddedNodes( mRootGroup, flags );
1624 
1625   // now that layers are loaded, we can resolve layer tree's references to the layers
1626   profile.switchTask( tr( "Resolving references" ) );
1627   mRootGroup->resolveReferences( this );
1628 
1629   if ( !layerTreeElem.isNull() )
1630   {
1631     mRootGroup->readLayerOrderFromXml( layerTreeElem );
1632   }
1633 
1634   // Load pre 3.0 configuration
1635   QDomElement layerTreeCanvasElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-canvas" ) );
1636   if ( !layerTreeCanvasElem.isNull( ) )
1637   {
1638     mRootGroup->readLayerOrderFromXml( layerTreeCanvasElem );
1639   }
1640 
1641   // Convert pre 3.4 to create layers flags
1642   if ( QgsProjectVersion( 3, 4, 0 ) > mSaveVersion )
1643   {
1644     const QStringList requiredLayerIds = readListEntry( QStringLiteral( "RequiredLayers" ), QStringLiteral( "Layers" ) );
1645     for ( const QString &layerId : requiredLayerIds )
1646     {
1647       if ( QgsMapLayer *layer = mapLayer( layerId ) )
1648       {
1649         layer->setFlags( layer->flags() & ~QgsMapLayer::Removable );
1650       }
1651     }
1652     const QStringList disabledLayerIds = readListEntry( QStringLiteral( "Identify" ), QStringLiteral( "/disabledLayers" ) );
1653     for ( const QString &layerId : disabledLayerIds )
1654     {
1655       if ( QgsMapLayer *layer = mapLayer( layerId ) )
1656       {
1657         layer->setFlags( layer->flags() & ~QgsMapLayer::Identifiable );
1658       }
1659     }
1660   }
1661 
1662   // After bad layer handling we might still have invalid layers,
1663   // store them in case the user wanted to handle them later
1664   // or wanted to pass them through when saving
1665   if ( !( flags & QgsProject::ReadFlag::FlagDontStoreOriginalStyles ) )
1666   {
1667     profile.switchTask( tr( "Storing original layer properties" ) );
1668     QgsLayerTreeUtils::storeOriginalLayersProperties( mRootGroup, doc.get() );
1669   }
1670 
1671   mRootGroup->removeCustomProperty( QStringLiteral( "loading" ) );
1672 
1673   profile.switchTask( tr( "Loading map themes" ) );
1674   mMapThemeCollection.reset( new QgsMapThemeCollection( this ) );
1675   emit mapThemeCollectionChanged();
1676   mMapThemeCollection->readXml( *doc );
1677 
1678   profile.switchTask( tr( "Loading label settings" ) );
1679   mLabelingEngineSettings->readSettingsFromProject( this );
1680   emit labelingEngineSettingsChanged();
1681 
1682   profile.switchTask( tr( "Loading annotations" ) );
1683   mAnnotationManager->readXml( doc->documentElement(), context );
1684   if ( !( flags & QgsProject::ReadFlag::FlagDontLoadLayouts ) )
1685   {
1686     profile.switchTask( tr( "Loading layouts" ) );
1687     mLayoutManager->readXml( doc->documentElement(), *doc );
1688   }
1689   profile.switchTask( tr( "Loading bookmarks" ) );
1690   mBookmarkManager->readXml( doc->documentElement(), *doc );
1691 
1692   // reassign change dependencies now that all layers are loaded
1693   QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
1694   for ( QMap<QString, QgsMapLayer *>::iterator it = existingMaps.begin(); it != existingMaps.end(); ++it )
1695   {
1696     it.value()->setDependencies( it.value()->dependencies() );
1697   }
1698 
1699   profile.switchTask( tr( "Loading snapping settings" ) );
1700   mSnappingConfig.readProject( *doc );
1701   mAvoidIntersectionsMode = static_cast<AvoidIntersectionsMode>( readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( AvoidIntersectionsMode::AvoidIntersectionsLayers ) ) );
1702 
1703   profile.switchTask( tr( "Loading view settings" ) );
1704   // restore older project scales settings
1705   mViewSettings->setUseProjectScales( readBoolEntry( QStringLiteral( "Scales" ), QStringLiteral( "/useProjectScales" ) ) );
1706   const QStringList scales = readListEntry( QStringLiteral( "Scales" ), QStringLiteral( "/ScalesList" ) );
1707   QVector<double> res;
1708   for ( const QString &scale : scales )
1709   {
1710     const QStringList parts = scale.split( ':' );
1711     if ( parts.size() != 2 )
1712       continue;
1713 
1714     bool ok = false;
1715     const double denominator = QLocale().toDouble( parts[1], &ok );
1716     if ( ok )
1717     {
1718       res << denominator;
1719     }
1720   }
1721   mViewSettings->setMapScales( res );
1722   QDomElement viewSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectViewSettings" ) );
1723   if ( !viewSettingsElement.isNull() )
1724     mViewSettings->readXml( viewSettingsElement, context );
1725 
1726   // restore time settings
1727   profile.switchTask( tr( "Loading temporal settings" ) );
1728   QDomElement timeSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectTimeSettings" ) );
1729   if ( !timeSettingsElement.isNull() )
1730     mTimeSettings->readXml( timeSettingsElement, context );
1731 
1732   profile.switchTask( tr( "Loading display settings" ) );
1733   QDomElement displaySettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectDisplaySettings" ) );
1734   if ( !displaySettingsElement.isNull() )
1735     mDisplaySettings->readXml( displaySettingsElement, context );
1736 
1737   profile.switchTask( tr( "Updating variables" ) );
1738   emit customVariablesChanged();
1739   profile.switchTask( tr( "Updating CRS" ) );
1740   emit crsChanged();
1741   emit ellipsoidChanged( ellipsoid() );
1742 
1743   // read the project: used by map canvas and legend
1744   profile.switchTask( tr( "Reading external settings" ) );
1745   emit readProject( *doc );
1746   emit readProjectWithContext( *doc, context );
1747 
1748   profile.switchTask( tr( "Updating interface" ) );
1749 
1750   snapSignalBlock.release();
1751   if ( !mBlockSnappingUpdates )
1752     emit snappingConfigChanged( mSnappingConfig );
1753 
1754   emit avoidIntersectionsModeChanged();
1755   emit topologicalEditingChanged();
1756   emit projectColorsChanged();
1757 
1758   // if all went well, we're allegedly in pristine state
1759   if ( clean )
1760     setDirty( false );
1761 
1762   QgsDebugMsgLevel( QString( "Project save user: %1" ).arg( mSaveUser ), 2 );
1763   QgsDebugMsgLevel( QString( "Project save user: %1" ).arg( mSaveUserFull ), 2 );
1764 
1765   Q_NOWARN_DEPRECATED_PUSH
1766   emit nonIdentifiableLayersChanged( nonIdentifiableLayers() );
1767   Q_NOWARN_DEPRECATED_POP
1768 
1769   if ( mTranslator )
1770   {
1771     //project possibly translated -> rename it with locale postfix
1772     QString newFileName( QStringLiteral( "%1/%2.qgs" ).arg( QFileInfo( projectFile.fileName() ).absolutePath(), localeFileName ) );
1773     setFileName( newFileName );
1774 
1775     if ( write() )
1776     {
1777       setTitle( localeFileName );
1778       QgsMessageLog::logMessage( tr( "Translated project saved with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::Success );
1779     }
1780     else
1781     {
1782       QgsMessageLog::logMessage( tr( "Error saving translated project with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::Critical );
1783     }
1784   }
1785   return true;
1786 }
1787 
1788 
loadEmbeddedNodes(QgsLayerTreeGroup * group,QgsProject::ReadFlags flags)1789 bool QgsProject::loadEmbeddedNodes( QgsLayerTreeGroup *group, QgsProject::ReadFlags flags )
1790 {
1791   bool valid = true;
1792   const auto constChildren = group->children();
1793   for ( QgsLayerTreeNode *child : constChildren )
1794   {
1795     if ( QgsLayerTree::isGroup( child ) )
1796     {
1797       QgsLayerTreeGroup *childGroup = QgsLayerTree::toGroup( child );
1798       if ( childGroup->customProperty( QStringLiteral( "embedded" ) ).toInt() )
1799       {
1800         // make sure to convert the path from relative to absolute
1801         QString projectPath = readPath( childGroup->customProperty( QStringLiteral( "embedded_project" ) ).toString() );
1802         childGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectPath );
1803         QgsLayerTreeGroup *newGroup = createEmbeddedGroup( childGroup->name(), projectPath, childGroup->customProperty( QStringLiteral( "embedded-invisible-layers" ) ).toStringList(), flags );
1804         if ( newGroup )
1805         {
1806           QList<QgsLayerTreeNode *> clonedChildren;
1807           const auto constChildren = newGroup->children();
1808           for ( QgsLayerTreeNode *newGroupChild : constChildren )
1809             clonedChildren << newGroupChild->clone();
1810           delete newGroup;
1811 
1812           childGroup->insertChildNodes( 0, clonedChildren );
1813         }
1814       }
1815       else
1816       {
1817         loadEmbeddedNodes( childGroup, flags );
1818       }
1819     }
1820     else if ( QgsLayerTree::isLayer( child ) )
1821     {
1822       if ( child->customProperty( QStringLiteral( "embedded" ) ).toInt() )
1823       {
1824         QList<QDomNode> brokenNodes;
1825         if ( ! createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), readPath( child->customProperty( QStringLiteral( "embedded_project" ) ).toString() ), brokenNodes, true, flags ) )
1826         {
1827           valid = valid && false;
1828         }
1829       }
1830     }
1831 
1832   }
1833 
1834   return valid;
1835 }
1836 
customVariables() const1837 QVariantMap QgsProject::customVariables() const
1838 {
1839   return mCustomVariables;
1840 }
1841 
setCustomVariables(const QVariantMap & variables)1842 void QgsProject::setCustomVariables( const QVariantMap &variables )
1843 {
1844   if ( variables == mCustomVariables )
1845     return;
1846 
1847   //write variable to project
1848   QStringList variableNames;
1849   QStringList variableValues;
1850 
1851   QVariantMap::const_iterator it = variables.constBegin();
1852   for ( ; it != variables.constEnd(); ++it )
1853   {
1854     variableNames << it.key();
1855     variableValues << it.value().toString();
1856   }
1857 
1858   writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ), variableNames );
1859   writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ), variableValues );
1860 
1861   mCustomVariables = variables;
1862   mProjectScope.reset();
1863 
1864   emit customVariablesChanged();
1865 }
1866 
setLabelingEngineSettings(const QgsLabelingEngineSettings & settings)1867 void QgsProject::setLabelingEngineSettings( const QgsLabelingEngineSettings &settings )
1868 {
1869   *mLabelingEngineSettings = settings;
1870   emit labelingEngineSettingsChanged();
1871 }
1872 
labelingEngineSettings() const1873 const QgsLabelingEngineSettings &QgsProject::labelingEngineSettings() const
1874 {
1875   return *mLabelingEngineSettings;
1876 }
1877 
layerStore()1878 QgsMapLayerStore *QgsProject::layerStore()
1879 {
1880   mProjectScope.reset();
1881   return mLayerStore.get();
1882 }
1883 
layerStore() const1884 const QgsMapLayerStore *QgsProject::layerStore() const
1885 {
1886   return mLayerStore.get();
1887 }
1888 
avoidIntersectionsLayers() const1889 QList<QgsVectorLayer *> QgsProject::avoidIntersectionsLayers() const
1890 {
1891   QList<QgsVectorLayer *> layers;
1892   QStringList layerIds = readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), QStringList() );
1893   const auto constLayerIds = layerIds;
1894   for ( const QString &layerId : constLayerIds )
1895   {
1896     if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer( layerId ) ) )
1897       layers << vlayer;
1898   }
1899   return layers;
1900 }
1901 
setAvoidIntersectionsLayers(const QList<QgsVectorLayer * > & layers)1902 void QgsProject::setAvoidIntersectionsLayers( const QList<QgsVectorLayer *> &layers )
1903 {
1904   QStringList list;
1905   const auto constLayers = layers;
1906   for ( QgsVectorLayer *layer : constLayers )
1907     list << layer->id();
1908   writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), list );
1909   emit avoidIntersectionsLayersChanged();
1910 }
1911 
createExpressionContext() const1912 QgsExpressionContext QgsProject::createExpressionContext() const
1913 {
1914   QgsExpressionContext context;
1915 
1916   context << QgsExpressionContextUtils::globalScope()
1917           << QgsExpressionContextUtils::projectScope( this );
1918 
1919   return context;
1920 }
1921 
createExpressionContextScope() const1922 QgsExpressionContextScope *QgsProject::createExpressionContextScope() const
1923 {
1924   // MUCH cheaper to clone than build
1925   if ( mProjectScope )
1926   {
1927     std::unique_ptr< QgsExpressionContextScope > projectScope = qgis::make_unique< QgsExpressionContextScope >( *mProjectScope );
1928     // we can't cache these
1929     projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_distance_units" ), QgsUnitTypes::toString( distanceUnits() ), true, true ) );
1930     projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_area_units" ), QgsUnitTypes::toString( areaUnits() ), true, true ) );
1931     return projectScope.release();
1932   }
1933 
1934   mProjectScope = qgis::make_unique< QgsExpressionContextScope >( QObject::tr( "Project" ) );
1935 
1936   const QVariantMap vars = customVariables();
1937 
1938   QVariantMap::const_iterator it = vars.constBegin();
1939 
1940   for ( ; it != vars.constEnd(); ++it )
1941   {
1942     mProjectScope->setVariable( it.key(), it.value(), true );
1943   }
1944 
1945   QString projectPath = projectStorage() ? fileName() : absoluteFilePath();
1946   if ( projectPath.isEmpty() )
1947     projectPath = mOriginalPath;
1948   QString projectFolder = QFileInfo( projectPath ).path();
1949   QString projectFilename = QFileInfo( projectPath ).fileName();
1950   QString projectBasename = baseName();
1951 
1952   //add other known project variables
1953   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_title" ), title(), true, true ) );
1954   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_path" ), QDir::toNativeSeparators( projectPath ), true, true ) );
1955   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_folder" ), QDir::toNativeSeparators( projectFolder ), true, true ) );
1956   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_filename" ), projectFilename, true, true ) );
1957   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_basename" ), projectBasename, true, true ) );
1958   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_home" ), QDir::toNativeSeparators( homePath() ), true, true ) );
1959   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_last_saved" ), mSaveDateTime.isNull() ? QVariant() : QVariant( mSaveDateTime ), true, true ) );
1960   QgsCoordinateReferenceSystem projectCrs = crs();
1961   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs" ), projectCrs.authid(), true, true ) );
1962   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_definition" ), projectCrs.toProj(), true, true ) );
1963   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_description" ), projectCrs.description(), true, true ) );
1964   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_ellipsoid" ), ellipsoid(), true, true ) );
1965   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "_project_transform_context" ), QVariant::fromValue<QgsCoordinateTransformContext>( transformContext() ), true, true ) );
1966   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_units" ), QgsUnitTypes::toString( projectCrs.mapUnits() ), true ) );
1967   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_acronym" ), projectCrs.projectionAcronym(), true ) );
1968   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_ellipsoid" ), projectCrs.ellipsoidAcronym(), true ) );
1969   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_proj4" ), projectCrs.toProj(), true ) );
1970   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_wkt" ), projectCrs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ), true ) );
1971 
1972   // metadata
1973   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_author" ), metadata().author(), true, true ) );
1974   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_abstract" ), metadata().abstract(), true, true ) );
1975   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_creation_date" ), metadata().creationDateTime(), true, true ) );
1976   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_identifier" ), metadata().identifier(), true, true ) );
1977 
1978   // keywords
1979   QVariantMap keywords;
1980   QgsAbstractMetadataBase::KeywordMap metadataKeywords = metadata().keywords();
1981   for ( auto it = metadataKeywords.constBegin(); it != metadataKeywords.constEnd(); ++it )
1982   {
1983     keywords.insert( it.key(), it.value() );
1984   }
1985   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_keywords" ), keywords, true, true ) );
1986 
1987   // layers
1988   QVariantList layersIds;
1989   QVariantList layers;
1990   const QMap<QString, QgsMapLayer *> layersInProject = mLayerStore->mapLayers();
1991   layersIds.reserve( layersInProject.count() );
1992   layers.reserve( layersInProject.count() );
1993   for ( auto it = layersInProject.constBegin(); it != layersInProject.constEnd(); ++it )
1994   {
1995     layersIds << it.value()->id();
1996     layers << QVariant::fromValue<QgsWeakMapLayerPointer>( QgsWeakMapLayerPointer( it.value() ) );
1997   }
1998   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer_ids" ), layersIds, true ) );
1999   mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layers" ), layers, true ) );
2000 
2001   mProjectScope->addFunction( QStringLiteral( "project_color" ), new GetNamedProjectColor( this ) );
2002 
2003   return createExpressionContextScope();
2004 }
2005 
onMapLayersAdded(const QList<QgsMapLayer * > & layers)2006 void QgsProject::onMapLayersAdded( const QList<QgsMapLayer *> &layers )
2007 {
2008   QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2009 
2010   bool tgChanged = false;
2011 
2012   const auto constLayers = layers;
2013   for ( QgsMapLayer *layer : constLayers )
2014   {
2015     if ( layer->isValid() )
2016     {
2017       QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
2018       if ( vlayer )
2019       {
2020         if ( autoTransaction() )
2021         {
2022           if ( QgsTransaction::supportsTransaction( vlayer ) )
2023           {
2024             const QString connString = QgsTransaction::connectionString( vlayer->source() );
2025             const QString key = vlayer->providerType();
2026 
2027             QgsTransactionGroup *tg = mTransactionGroups.value( qMakePair( key, connString ) );
2028 
2029             if ( !tg )
2030             {
2031               tg = new QgsTransactionGroup();
2032               mTransactionGroups.insert( qMakePair( key, connString ), tg );
2033               tgChanged = true;
2034             }
2035             tg->addLayer( vlayer );
2036           }
2037         }
2038         vlayer->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, evaluateDefaultValues() );
2039       }
2040 
2041       if ( tgChanged )
2042         emit transactionGroupsChanged();
2043 
2044       connect( layer, &QgsMapLayer::configChanged, this, [ = ] { setDirty(); } );
2045 
2046       // check if we have to update connections for layers with dependencies
2047       for ( QMap<QString, QgsMapLayer *>::const_iterator it = existingMaps.cbegin(); it != existingMaps.cend(); ++it )
2048       {
2049         QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
2050         if ( deps.contains( layer->id() ) )
2051         {
2052           // reconnect to change signals
2053           it.value()->setDependencies( deps );
2054         }
2055       }
2056     }
2057   }
2058 
2059   if ( !mBlockSnappingUpdates && mSnappingConfig.addLayers( layers ) )
2060     emit snappingConfigChanged( mSnappingConfig );
2061 }
2062 
onMapLayersRemoved(const QList<QgsMapLayer * > & layers)2063 void QgsProject::onMapLayersRemoved( const QList<QgsMapLayer *> &layers )
2064 {
2065   if ( !mBlockSnappingUpdates && mSnappingConfig.removeLayers( layers ) )
2066     emit snappingConfigChanged( mSnappingConfig );
2067 }
2068 
cleanTransactionGroups(bool force)2069 void QgsProject::cleanTransactionGroups( bool force )
2070 {
2071   bool changed = false;
2072   for ( QMap< QPair< QString, QString>, QgsTransactionGroup *>::Iterator tg = mTransactionGroups.begin(); tg != mTransactionGroups.end(); )
2073   {
2074     if ( tg.value()->isEmpty() || force )
2075     {
2076       delete tg.value();
2077       tg = mTransactionGroups.erase( tg );
2078       changed = true;
2079     }
2080     else
2081     {
2082       ++tg;
2083     }
2084   }
2085   if ( changed )
2086     emit transactionGroupsChanged();
2087 }
2088 
readLayer(const QDomNode & layerNode)2089 bool QgsProject::readLayer( const QDomNode &layerNode )
2090 {
2091   QgsReadWriteContext context;
2092   context.setPathResolver( pathResolver() );
2093   context.setProjectTranslator( this );
2094   context.setTransformContext( transformContext() );
2095   QList<QDomNode> brokenNodes;
2096   if ( addLayer( layerNode.toElement(), brokenNodes, context ) )
2097   {
2098     // have to try to update joins for all layers now - a previously added layer may be dependent on this newly
2099     // added layer for joins
2100     QVector<QgsVectorLayer *> vectorLayers = layers<QgsVectorLayer *>();
2101     const auto constVectorLayers = vectorLayers;
2102     for ( QgsVectorLayer *layer : constVectorLayers )
2103     {
2104       // TODO: should be only done later - and with all layers (other layers may have referenced this layer)
2105       layer->resolveReferences( this );
2106     }
2107 
2108     return true;
2109   }
2110   return false;
2111 }
2112 
write(const QString & filename)2113 bool QgsProject::write( const QString &filename )
2114 {
2115   mFile.setFileName( filename );
2116   mCachedHomePath.clear();
2117   return write();
2118 }
2119 
write()2120 bool QgsProject::write()
2121 {
2122   mProjectScope.reset();
2123   if ( QgsProjectStorage *storage = projectStorage() )
2124   {
2125     QgsReadWriteContext context;
2126     // for projects stored in a custom storage, we have to check for the support
2127     // of relative paths since the storage most likely will not be in a file system
2128     QString storageFilePath { storage->filePath( mFile.fileName() ) };
2129     if ( storageFilePath.isEmpty() )
2130     {
2131       writeEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), true );
2132     }
2133     context.setPathResolver( pathResolver() );
2134 
2135     QString tempPath = QStandardPaths::standardLocations( QStandardPaths::TempLocation ).at( 0 );
2136     QString tmpZipFilename( tempPath + QDir::separator() + QUuid::createUuid().toString() );
2137 
2138     if ( !zip( tmpZipFilename ) )
2139       return false;  // zip() already calls setError() when returning false
2140 
2141     QFile tmpZipFile( tmpZipFilename );
2142     if ( !tmpZipFile.open( QIODevice::ReadOnly ) )
2143     {
2144       setError( tr( "Unable to read file %1" ).arg( tmpZipFilename ) );
2145       return false;
2146     }
2147 
2148     context.setTransformContext( transformContext() );
2149     if ( !storage->writeProject( mFile.fileName(), &tmpZipFile, context ) )
2150     {
2151       QString err = tr( "Unable to save project to storage %1" ).arg( mFile.fileName() );
2152       QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
2153       if ( !messages.isEmpty() )
2154         err += QStringLiteral( "\n\n" ) + messages.last().message();
2155       setError( err );
2156       return false;
2157     }
2158 
2159     tmpZipFile.close();
2160     QFile::remove( tmpZipFilename );
2161 
2162     return true;
2163   }
2164 
2165   if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
2166   {
2167     return zip( mFile.fileName() );
2168   }
2169   else
2170   {
2171     // write project file even if the auxiliary storage is not correctly
2172     // saved
2173     const bool asOk = saveAuxiliaryStorage();
2174     const bool writeOk = writeProjectFile( mFile.fileName() );
2175 
2176     // errors raised during writing project file are more important
2177     if ( !asOk && writeOk )
2178     {
2179       const QString err = mAuxiliaryStorage->errorString();
2180       setError( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
2181     }
2182 
2183     return asOk && writeOk;
2184   }
2185 }
2186 
writeProjectFile(const QString & filename)2187 bool QgsProject::writeProjectFile( const QString &filename )
2188 {
2189   QFile projectFile( filename );
2190   clearError();
2191 
2192   // if we have problems creating or otherwise writing to the project file,
2193   // let's find out up front before we go through all the hand-waving
2194   // necessary to create all the Dom objects
2195   QFileInfo myFileInfo( projectFile );
2196   if ( myFileInfo.exists() && !myFileInfo.isWritable() )
2197   {
2198     setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." )
2199               .arg( projectFile.fileName() ) );
2200     return false;
2201   }
2202 
2203   QgsReadWriteContext context;
2204   context.setPathResolver( pathResolver() );
2205   context.setTransformContext( transformContext() );
2206 
2207   QDomImplementation DomImplementation;
2208   DomImplementation.setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
2209 
2210   QDomDocumentType documentType =
2211     DomImplementation.createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ),
2212                                           QStringLiteral( "SYSTEM" ) );
2213   std::unique_ptr<QDomDocument> doc( new QDomDocument( documentType ) );
2214 
2215   QDomElement qgisNode = doc->createElement( QStringLiteral( "qgis" ) );
2216   qgisNode.setAttribute( QStringLiteral( "projectname" ), title() );
2217   qgisNode.setAttribute( QStringLiteral( "version" ), Qgis::version() );
2218 
2219   if ( !mSettings.value( QStringLiteral( "projects/anonymize_saved_projects" ), false, QgsSettings::Core ).toBool() )
2220   {
2221     QString newSaveUser = QgsApplication::userLoginName();
2222     QString newSaveUserFull = QgsApplication::userFullName();
2223     qgisNode.setAttribute( QStringLiteral( "saveUser" ), newSaveUser );
2224     qgisNode.setAttribute( QStringLiteral( "saveUserFull" ), newSaveUserFull );
2225     mSaveUser = newSaveUser;
2226     mSaveUserFull = newSaveUserFull;
2227     mSaveDateTime = QDateTime::currentDateTime();
2228     qgisNode.setAttribute( QStringLiteral( "saveDateTime" ), mSaveDateTime.toString( Qt::ISODate ) );
2229   }
2230   else
2231   {
2232     mSaveUser.clear();
2233     mSaveUserFull.clear();
2234     mSaveDateTime = QDateTime();
2235   }
2236   doc->appendChild( qgisNode );
2237   mSaveVersion = QgsProjectVersion( Qgis::version() );
2238 
2239   QDomElement homePathNode = doc->createElement( QStringLiteral( "homePath" ) );
2240   homePathNode.setAttribute( QStringLiteral( "path" ), mHomePath );
2241   qgisNode.appendChild( homePathNode );
2242 
2243   // title
2244   QDomElement titleNode = doc->createElement( QStringLiteral( "title" ) );
2245   qgisNode.appendChild( titleNode );
2246 
2247   QDomElement transactionNode = doc->createElement( QStringLiteral( "autotransaction" ) );
2248   transactionNode.setAttribute( QStringLiteral( "active" ), mAutoTransaction ? 1 : 0 );
2249   qgisNode.appendChild( transactionNode );
2250 
2251   QDomElement evaluateDefaultValuesNode = doc->createElement( QStringLiteral( "evaluateDefaultValues" ) );
2252   evaluateDefaultValuesNode.setAttribute( QStringLiteral( "active" ), mEvaluateDefaultValues ? 1 : 0 );
2253   qgisNode.appendChild( evaluateDefaultValuesNode );
2254 
2255   QDomElement trustNode = doc->createElement( QStringLiteral( "trust" ) );
2256   trustNode.setAttribute( QStringLiteral( "active" ), mTrustLayerMetadata ? 1 : 0 );
2257   qgisNode.appendChild( trustNode );
2258 
2259   QDomText titleText = doc->createTextNode( title() );  // XXX why have title TWICE?
2260   titleNode.appendChild( titleText );
2261 
2262   // write project CRS
2263   QDomElement srsNode = doc->createElement( QStringLiteral( "projectCrs" ) );
2264   mCrs.writeXml( srsNode, *doc );
2265   qgisNode.appendChild( srsNode );
2266 
2267   // write layer tree - make sure it is without embedded subgroups
2268   QgsLayerTreeNode *clonedRoot = mRootGroup->clone();
2269   QgsLayerTreeUtils::replaceChildrenOfEmbeddedGroups( QgsLayerTree::toGroup( clonedRoot ) );
2270   QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ), this ); // convert absolute paths to relative paths if required
2271 
2272   clonedRoot->writeXml( qgisNode, context );
2273   delete clonedRoot;
2274 
2275   mSnappingConfig.writeProject( *doc );
2276   writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( mAvoidIntersectionsMode ) );
2277 
2278   // let map canvas and legend write their information
2279   emit writeProject( *doc );
2280 
2281   // within top level node save list of layers
2282   const QMap<QString, QgsMapLayer *> &layers = mapLayers();
2283 
2284   QDomElement annotationLayerNode = doc->createElement( QStringLiteral( "main-annotation-layer" ) );
2285   mMainAnnotationLayer->writeLayerXml( annotationLayerNode, *doc, context );
2286   qgisNode.appendChild( annotationLayerNode );
2287 
2288   // Iterate over layers in zOrder
2289   // Call writeXml() on each
2290   QDomElement projectLayersNode = doc->createElement( QStringLiteral( "projectlayers" ) );
2291 
2292   QMap<QString, QgsMapLayer *>::ConstIterator li = layers.constBegin();
2293   while ( li != layers.end() )
2294   {
2295     QgsMapLayer *ml = li.value();
2296 
2297     if ( ml )
2298     {
2299       QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.constFind( ml->id() );
2300       if ( emIt == mEmbeddedLayers.constEnd() )
2301       {
2302         QDomElement maplayerElem;
2303         // If layer is not valid, prefer to restore saved properties from invalidLayerProperties. But if that's
2304         // not available, just write what we DO have
2305         if ( ml->isValid() || ml->originalXmlProperties().isEmpty() )
2306         {
2307           // general layer metadata
2308           maplayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
2309           ml->writeLayerXml( maplayerElem, *doc, context );
2310         }
2311         else if ( ! ml->originalXmlProperties().isEmpty() )
2312         {
2313           QDomDocument document;
2314           if ( document.setContent( ml->originalXmlProperties() ) )
2315           {
2316             maplayerElem = document.firstChildElement();
2317           }
2318           else
2319           {
2320             QgsDebugMsg( QStringLiteral( "Could not restore layer properties for layer %1" ).arg( ml->id() ) );
2321           }
2322         }
2323 
2324         emit writeMapLayer( ml, maplayerElem, *doc );
2325 
2326         projectLayersNode.appendChild( maplayerElem );
2327       }
2328       else
2329       {
2330         // layer defined in an external project file
2331         // only save embedded layer if not managed by a legend group
2332         if ( emIt.value().second )
2333         {
2334           QDomElement mapLayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
2335           mapLayerElem.setAttribute( QStringLiteral( "embedded" ), 1 );
2336           mapLayerElem.setAttribute( QStringLiteral( "project" ), writePath( emIt.value().first ) );
2337           mapLayerElem.setAttribute( QStringLiteral( "id" ), ml->id() );
2338           projectLayersNode.appendChild( mapLayerElem );
2339         }
2340       }
2341     }
2342     li++;
2343   }
2344 
2345   qgisNode.appendChild( projectLayersNode );
2346 
2347   QDomElement layerOrderNode = doc->createElement( QStringLiteral( "layerorder" ) );
2348   const auto constCustomLayerOrder = mRootGroup->customLayerOrder();
2349   for ( QgsMapLayer *layer : constCustomLayerOrder )
2350   {
2351     QDomElement mapLayerElem = doc->createElement( QStringLiteral( "layer" ) );
2352     mapLayerElem.setAttribute( QStringLiteral( "id" ), layer->id() );
2353     layerOrderNode.appendChild( mapLayerElem );
2354   }
2355   qgisNode.appendChild( layerOrderNode );
2356 
2357   mLabelingEngineSettings->writeSettingsToProject( this );
2358 
2359   writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), mBackgroundColor.red() );
2360   writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), mBackgroundColor.green() );
2361   writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), mBackgroundColor.blue() );
2362 
2363   writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), mSelectionColor.red() );
2364   writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), mSelectionColor.green() );
2365   writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), mSelectionColor.blue() );
2366   writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), mSelectionColor.alpha() );
2367 
2368   // now add the optional extra properties
2369 #if 0
2370   dump_( mProperties );
2371 #endif
2372 
2373   QgsDebugMsgLevel( QStringLiteral( "there are %1 property scopes" ).arg( static_cast<int>( mProperties.count() ) ), 2 );
2374 
2375   if ( !mProperties.isEmpty() ) // only worry about properties if we
2376     // actually have any properties
2377   {
2378     mProperties.writeXml( QStringLiteral( "properties" ), qgisNode, *doc );
2379   }
2380 
2381   QDomElement ddElem = doc->createElement( QStringLiteral( "dataDefinedServerProperties" ) );
2382   mDataDefinedServerProperties.writeXml( ddElem, dataDefinedServerPropertyDefinitions() );
2383   qgisNode.appendChild( ddElem );
2384 
2385   mMapThemeCollection->writeXml( *doc );
2386 
2387   mTransformContext.writeXml( qgisNode, context );
2388 
2389   QDomElement metadataElem = doc->createElement( QStringLiteral( "projectMetadata" ) );
2390   mMetadata.writeMetadataXml( metadataElem, *doc );
2391   qgisNode.appendChild( metadataElem );
2392 
2393   QDomElement annotationsElem = mAnnotationManager->writeXml( *doc, context );
2394   qgisNode.appendChild( annotationsElem );
2395 
2396   QDomElement layoutElem = mLayoutManager->writeXml( *doc );
2397   qgisNode.appendChild( layoutElem );
2398 
2399   QDomElement bookmarkElem = mBookmarkManager->writeXml( *doc );
2400   qgisNode.appendChild( bookmarkElem );
2401 
2402   QDomElement viewSettingsElem = mViewSettings->writeXml( *doc, context );
2403   qgisNode.appendChild( viewSettingsElem );
2404 
2405   QDomElement timeSettingsElement = mTimeSettings->writeXml( *doc, context );
2406   qgisNode.appendChild( timeSettingsElement );
2407 
2408   QDomElement displaySettingsElem = mDisplaySettings->writeXml( *doc, context );
2409   qgisNode.appendChild( displaySettingsElem );
2410 
2411   // now wrap it up and ship it to the project file
2412   doc->normalize();             // XXX I'm not entirely sure what this does
2413 
2414   // Create backup file
2415   if ( QFile::exists( fileName() ) )
2416   {
2417     QFile backupFile( QStringLiteral( "%1~" ).arg( filename ) );
2418     bool ok = true;
2419     ok &= backupFile.open( QIODevice::WriteOnly | QIODevice::Truncate );
2420     ok &= projectFile.open( QIODevice::ReadOnly );
2421 
2422     QByteArray ba;
2423     while ( ok && !projectFile.atEnd() )
2424     {
2425       ba = projectFile.read( 10240 );
2426       ok &= backupFile.write( ba ) == ba.size();
2427     }
2428 
2429     projectFile.close();
2430     backupFile.close();
2431 
2432     if ( !ok )
2433     {
2434       setError( tr( "Unable to create backup file %1" ).arg( backupFile.fileName() ) );
2435       return false;
2436     }
2437 
2438     QFileInfo fi( fileName() );
2439     struct utimbuf tb = { static_cast<time_t>( fi.lastRead().toSecsSinceEpoch() ), static_cast<time_t>( fi.lastModified().toSecsSinceEpoch() ) };
2440     utime( backupFile.fileName().toUtf8().constData(), &tb );
2441   }
2442 
2443   if ( !projectFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
2444   {
2445     projectFile.close();         // even though we got an error, let's make
2446     // sure it's closed anyway
2447 
2448     setError( tr( "Unable to save to file %1" ).arg( projectFile.fileName() ) );
2449     return false;
2450   }
2451 
2452   QTemporaryFile tempFile;
2453   bool ok = tempFile.open();
2454   if ( ok )
2455   {
2456     QTextStream projectFileStream( &tempFile );
2457     doc->save( projectFileStream, 2 );  // save as utf-8
2458     ok &= projectFileStream.pos() > -1;
2459 
2460     ok &= tempFile.seek( 0 );
2461 
2462     QByteArray ba;
2463     while ( ok && !tempFile.atEnd() )
2464     {
2465       ba = tempFile.read( 10240 );
2466       ok &= projectFile.write( ba ) == ba.size();
2467     }
2468 
2469     ok &= projectFile.error() == QFile::NoError;
2470 
2471     projectFile.close();
2472   }
2473 
2474   tempFile.close();
2475 
2476   if ( !ok )
2477   {
2478     setError( tr( "Unable to save to file %1. Your project "
2479                   "may be corrupted on disk. Try clearing some space on the volume and "
2480                   "check file permissions before pressing save again." )
2481               .arg( projectFile.fileName() ) );
2482     return false;
2483   }
2484 
2485   setDirty( false );               // reset to pristine state
2486 
2487   emit projectSaved();
2488   return true;
2489 }
2490 
writeEntry(const QString & scope,QString const & key,bool value)2491 bool QgsProject::writeEntry( const QString &scope, QString const &key, bool value )
2492 {
2493   bool propertiesModified;
2494   bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
2495 
2496   if ( propertiesModified )
2497     setDirty( true );
2498 
2499   return success;
2500 }
2501 
writeEntry(const QString & scope,const QString & key,double value)2502 bool QgsProject::writeEntry( const QString &scope, const QString &key, double value )
2503 {
2504   bool propertiesModified;
2505   bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
2506 
2507   if ( propertiesModified )
2508     setDirty( true );
2509 
2510   return success;
2511 }
2512 
writeEntry(const QString & scope,QString const & key,int value)2513 bool QgsProject::writeEntry( const QString &scope, QString const &key, int value )
2514 {
2515   bool propertiesModified;
2516   bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
2517 
2518   if ( propertiesModified )
2519     setDirty( true );
2520 
2521   return success;
2522 }
2523 
writeEntry(const QString & scope,const QString & key,const QString & value)2524 bool QgsProject::writeEntry( const QString &scope, const QString &key, const QString &value )
2525 {
2526   bool propertiesModified;
2527   bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
2528 
2529   if ( propertiesModified )
2530     setDirty( true );
2531 
2532   return success;
2533 }
2534 
writeEntry(const QString & scope,const QString & key,const QStringList & value)2535 bool QgsProject::writeEntry( const QString &scope, const QString &key, const QStringList &value )
2536 {
2537   bool propertiesModified;
2538   bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
2539 
2540   if ( propertiesModified )
2541     setDirty( true );
2542 
2543   return success;
2544 }
2545 
readListEntry(const QString & scope,const QString & key,const QStringList & def,bool * ok) const2546 QStringList QgsProject::readListEntry( const QString &scope,
2547                                        const QString &key,
2548                                        const QStringList &def,
2549                                        bool *ok ) const
2550 {
2551   QgsProjectProperty *property = findKey_( scope, key, mProperties );
2552 
2553   QVariant value;
2554 
2555   if ( property )
2556   {
2557     value = property->value();
2558 
2559     bool valid = QVariant::StringList == value.type();
2560     if ( ok )
2561       *ok = valid;
2562 
2563     if ( valid )
2564     {
2565       return value.toStringList();
2566     }
2567   }
2568   else if ( ok )
2569     *ok = false;
2570 
2571 
2572   return def;
2573 }
2574 
2575 
readEntry(const QString & scope,const QString & key,const QString & def,bool * ok) const2576 QString QgsProject::readEntry( const QString &scope,
2577                                const QString &key,
2578                                const QString &def,
2579                                bool *ok ) const
2580 {
2581   QgsProjectProperty *property = findKey_( scope, key, mProperties );
2582 
2583   QVariant value;
2584 
2585   if ( property )
2586   {
2587     value = property->value();
2588 
2589     bool valid = value.canConvert( QVariant::String );
2590     if ( ok )
2591       *ok = valid;
2592 
2593     if ( valid )
2594       return value.toString();
2595   }
2596   else if ( ok )
2597     *ok = false;
2598 
2599   return def;
2600 }
2601 
readNumEntry(const QString & scope,const QString & key,int def,bool * ok) const2602 int QgsProject::readNumEntry( const QString &scope, const QString &key, int def,
2603                               bool *ok ) const
2604 {
2605   QgsProjectProperty *property = findKey_( scope, key, mProperties );
2606 
2607   QVariant value;
2608 
2609   if ( property )
2610   {
2611     value = property->value();
2612   }
2613 
2614   bool valid = value.canConvert( QVariant::Int );
2615 
2616   if ( ok )
2617   {
2618     *ok = valid;
2619   }
2620 
2621   if ( valid )
2622   {
2623     return value.toInt();
2624   }
2625 
2626   return def;
2627 }
2628 
readDoubleEntry(const QString & scope,const QString & key,double def,bool * ok) const2629 double QgsProject::readDoubleEntry( const QString &scope, const QString &key,
2630                                     double def,
2631                                     bool *ok ) const
2632 {
2633   QgsProjectProperty *property = findKey_( scope, key, mProperties );
2634   if ( property )
2635   {
2636     QVariant value = property->value();
2637 
2638     bool valid = value.canConvert( QVariant::Double );
2639     if ( ok )
2640       *ok = valid;
2641 
2642     if ( valid )
2643       return value.toDouble();
2644   }
2645   else if ( ok )
2646     *ok = false;
2647 
2648   return def;
2649 }
2650 
readBoolEntry(const QString & scope,const QString & key,bool def,bool * ok) const2651 bool QgsProject::readBoolEntry( const QString &scope, const QString &key, bool def,
2652                                 bool *ok ) const
2653 {
2654   QgsProjectProperty *property = findKey_( scope, key, mProperties );
2655 
2656   if ( property )
2657   {
2658     QVariant value = property->value();
2659 
2660     bool valid = value.canConvert( QVariant::Bool );
2661     if ( ok )
2662       *ok = valid;
2663 
2664     if ( valid )
2665       return value.toBool();
2666   }
2667   else if ( ok )
2668     *ok = false;
2669 
2670   return def;
2671 }
2672 
removeEntry(const QString & scope,const QString & key)2673 bool QgsProject::removeEntry( const QString &scope, const QString &key )
2674 {
2675   if ( findKey_( scope, key, mProperties ) )
2676   {
2677     removeKey_( scope, key, mProperties );
2678     setDirty( true );
2679   }
2680 
2681   return !findKey_( scope, key, mProperties );
2682 }
2683 
2684 
entryList(const QString & scope,const QString & key) const2685 QStringList QgsProject::entryList( const QString &scope, const QString &key ) const
2686 {
2687   QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
2688 
2689   QStringList entries;
2690 
2691   if ( foundProperty )
2692   {
2693     QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
2694 
2695     if ( propertyKey )
2696     { propertyKey->entryList( entries ); }
2697   }
2698 
2699   return entries;
2700 }
2701 
subkeyList(const QString & scope,const QString & key) const2702 QStringList QgsProject::subkeyList( const QString &scope, const QString &key ) const
2703 {
2704   QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
2705 
2706   QStringList entries;
2707 
2708   if ( foundProperty )
2709   {
2710     QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
2711 
2712     if ( propertyKey )
2713     { propertyKey->subkeyList( entries ); }
2714   }
2715 
2716   return entries;
2717 }
2718 
dumpProperties() const2719 void QgsProject::dumpProperties() const
2720 {
2721   dump_( mProperties );
2722 }
2723 
pathResolver() const2724 QgsPathResolver QgsProject::pathResolver() const
2725 {
2726   bool absolutePaths = readBoolEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), false );
2727   QString filePath;
2728   if ( ! absolutePaths )
2729   {
2730     // for projects stored in a custom storage, we need to ask to the
2731     // storage for the path, if the storage returns an empty path
2732     // relative paths are not supported
2733     if ( QgsProjectStorage *storage = projectStorage() )
2734     {
2735       filePath = storage->filePath( mFile.fileName() );
2736     }
2737     else
2738     {
2739       filePath = fileName();
2740     }
2741   }
2742   return QgsPathResolver( filePath );
2743 }
2744 
readPath(const QString & src) const2745 QString QgsProject::readPath( const QString &src ) const
2746 {
2747   return pathResolver().readPath( src );
2748 }
2749 
writePath(const QString & src) const2750 QString QgsProject::writePath( const QString &src ) const
2751 {
2752   return pathResolver().writePath( src );
2753 }
2754 
setError(const QString & errorMessage)2755 void QgsProject::setError( const QString &errorMessage )
2756 {
2757   mErrorMessage = errorMessage;
2758 }
2759 
error() const2760 QString QgsProject::error() const
2761 {
2762   return mErrorMessage;
2763 }
2764 
clearError()2765 void QgsProject::clearError()
2766 {
2767   setError( QString() );
2768 }
2769 
setBadLayerHandler(QgsProjectBadLayerHandler * handler)2770 void QgsProject::setBadLayerHandler( QgsProjectBadLayerHandler *handler )
2771 {
2772   delete mBadLayerHandler;
2773   mBadLayerHandler = handler;
2774 }
2775 
layerIsEmbedded(const QString & id) const2776 QString QgsProject::layerIsEmbedded( const QString &id ) const
2777 {
2778   QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
2779   if ( it == mEmbeddedLayers.constEnd() )
2780   {
2781     return QString();
2782   }
2783   return it.value().first;
2784 }
2785 
createEmbeddedLayer(const QString & layerId,const QString & projectFilePath,QList<QDomNode> & brokenNodes,bool saveFlag,QgsProject::ReadFlags flags)2786 bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &projectFilePath, QList<QDomNode> &brokenNodes,
2787                                       bool saveFlag, QgsProject::ReadFlags flags )
2788 {
2789   QgsDebugCall;
2790 
2791   static QString sPrevProjectFilePath;
2792   static QDateTime sPrevProjectFileTimestamp;
2793   static QDomDocument sProjectDocument;
2794 
2795   QString qgsProjectFile = projectFilePath;
2796   QgsProjectArchive archive;
2797   if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
2798   {
2799     archive.unzip( projectFilePath );
2800     qgsProjectFile = archive.projectFile();
2801   }
2802 
2803   QDateTime projectFileTimestamp = QFileInfo( projectFilePath ).lastModified();
2804 
2805   if ( projectFilePath != sPrevProjectFilePath || projectFileTimestamp != sPrevProjectFileTimestamp )
2806   {
2807     sPrevProjectFilePath.clear();
2808 
2809     QFile projectFile( qgsProjectFile );
2810     if ( !projectFile.open( QIODevice::ReadOnly ) )
2811     {
2812       return false;
2813     }
2814 
2815     if ( !sProjectDocument.setContent( &projectFile ) )
2816     {
2817       return false;
2818     }
2819 
2820     sPrevProjectFilePath = projectFilePath;
2821     sPrevProjectFileTimestamp = projectFileTimestamp;
2822   }
2823 
2824   // does project store paths absolute or relative?
2825   bool useAbsolutePaths = true;
2826 
2827   QDomElement propertiesElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "properties" ) );
2828   if ( !propertiesElem.isNull() )
2829   {
2830     QDomElement absElem = propertiesElem.firstChildElement( QStringLiteral( "Paths" ) ).firstChildElement( QStringLiteral( "Absolute" ) );
2831     if ( !absElem.isNull() )
2832     {
2833       useAbsolutePaths = absElem.text().compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
2834     }
2835   }
2836 
2837   QgsReadWriteContext embeddedContext;
2838   if ( !useAbsolutePaths )
2839     embeddedContext.setPathResolver( QgsPathResolver( projectFilePath ) );
2840   embeddedContext.setProjectTranslator( this );
2841   embeddedContext.setTransformContext( transformContext() );
2842 
2843   QDomElement projectLayersElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "projectlayers" ) );
2844   if ( projectLayersElem.isNull() )
2845   {
2846     return false;
2847   }
2848 
2849   QDomNodeList mapLayerNodes = projectLayersElem.elementsByTagName( QStringLiteral( "maplayer" ) );
2850   for ( int i = 0; i < mapLayerNodes.size(); ++i )
2851   {
2852     // get layer id
2853     QDomElement mapLayerElem = mapLayerNodes.at( i ).toElement();
2854     QString id = mapLayerElem.firstChildElement( QStringLiteral( "id" ) ).text();
2855     if ( id == layerId )
2856     {
2857       // layer can be embedded only once
2858       if ( mapLayerElem.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
2859       {
2860         return false;
2861       }
2862 
2863       mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
2864 
2865       if ( addLayer( mapLayerElem, brokenNodes, embeddedContext, flags ) )
2866       {
2867         return true;
2868       }
2869       else
2870       {
2871         mEmbeddedLayers.remove( layerId );
2872         return false;
2873       }
2874     }
2875   }
2876 
2877   return false;
2878 }
2879 
2880 
createEmbeddedGroup(const QString & groupName,const QString & projectFilePath,const QStringList & invisibleLayers,QgsProject::ReadFlags flags)2881 QgsLayerTreeGroup *QgsProject::createEmbeddedGroup( const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers, QgsProject::ReadFlags flags )
2882 {
2883   QString qgsProjectFile = projectFilePath;
2884   QgsProjectArchive archive;
2885   if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
2886   {
2887     archive.unzip( projectFilePath );
2888     qgsProjectFile = archive.projectFile();
2889   }
2890 
2891   // open project file, get layer ids in group, add the layers
2892   QFile projectFile( qgsProjectFile );
2893   if ( !projectFile.open( QIODevice::ReadOnly ) )
2894   {
2895     return nullptr;
2896   }
2897 
2898   QDomDocument projectDocument;
2899   if ( !projectDocument.setContent( &projectFile ) )
2900   {
2901     return nullptr;
2902   }
2903 
2904   QgsReadWriteContext context;
2905   context.setPathResolver( pathResolver() );
2906   context.setProjectTranslator( this );
2907   context.setTransformContext( transformContext() );
2908 
2909   QgsLayerTreeGroup *root = new QgsLayerTreeGroup;
2910 
2911   QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
2912   if ( !layerTreeElem.isNull() )
2913   {
2914     root->readChildrenFromXml( layerTreeElem, context );
2915   }
2916   else
2917   {
2918     QgsLayerTreeUtils::readOldLegend( root, projectDocument.documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
2919   }
2920 
2921   QgsLayerTreeGroup *group = root->findGroup( groupName );
2922   if ( !group || group->customProperty( QStringLiteral( "embedded" ) ).toBool() )
2923   {
2924     // embedded groups cannot be embedded again
2925     delete root;
2926     return nullptr;
2927   }
2928 
2929   // clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
2930   QgsLayerTreeGroup *newGroup = QgsLayerTree::toGroup( group->clone() );
2931   delete root;
2932   root = nullptr;
2933 
2934   newGroup->setCustomProperty( QStringLiteral( "embedded" ), 1 );
2935   newGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectFilePath );
2936 
2937   // set "embedded" to all children + load embedded layers
2938   mLayerTreeRegistryBridge->setEnabled( false );
2939   initializeEmbeddedSubtree( projectFilePath, newGroup, flags );
2940   mLayerTreeRegistryBridge->setEnabled( true );
2941 
2942   // consider the layers might be identify disabled in its project
2943   const auto constFindLayerIds = newGroup->findLayerIds();
2944   for ( const QString &layerId : constFindLayerIds )
2945   {
2946     QgsLayerTreeLayer *layer = newGroup->findLayer( layerId );
2947     if ( layer )
2948     {
2949       layer->resolveReferences( this );
2950       layer->setItemVisibilityChecked( !invisibleLayers.contains( layerId ) );
2951     }
2952   }
2953 
2954   return newGroup;
2955 }
2956 
initializeEmbeddedSubtree(const QString & projectFilePath,QgsLayerTreeGroup * group,QgsProject::ReadFlags flags)2957 void QgsProject::initializeEmbeddedSubtree( const QString &projectFilePath, QgsLayerTreeGroup *group, QgsProject::ReadFlags flags )
2958 {
2959   const auto constChildren = group->children();
2960   for ( QgsLayerTreeNode *child : constChildren )
2961   {
2962     // all nodes in the subtree will have "embedded" custom property set
2963     child->setCustomProperty( QStringLiteral( "embedded" ), 1 );
2964 
2965     if ( QgsLayerTree::isGroup( child ) )
2966     {
2967       initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ), flags );
2968     }
2969     else if ( QgsLayerTree::isLayer( child ) )
2970     {
2971       // load the layer into our project
2972       QList<QDomNode> brokenNodes;
2973       createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, false, flags );
2974     }
2975   }
2976 }
2977 
evaluateDefaultValues() const2978 bool QgsProject::evaluateDefaultValues() const
2979 {
2980   return mEvaluateDefaultValues;
2981 }
2982 
setEvaluateDefaultValues(bool evaluateDefaultValues)2983 void QgsProject::setEvaluateDefaultValues( bool evaluateDefaultValues )
2984 {
2985   if ( evaluateDefaultValues == mEvaluateDefaultValues )
2986     return;
2987 
2988   const QMap<QString, QgsMapLayer *> layers = mapLayers();
2989   QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
2990   for ( ; layerIt != layers.constEnd(); ++layerIt )
2991   {
2992     QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() );
2993     if ( vl )
2994     {
2995       vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, evaluateDefaultValues );
2996     }
2997   }
2998 
2999   mEvaluateDefaultValues = evaluateDefaultValues;
3000 }
3001 
setTopologicalEditing(bool enabled)3002 void QgsProject::setTopologicalEditing( bool enabled )
3003 {
3004   writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), ( enabled ? 1 : 0 ) );
3005   emit topologicalEditingChanged();
3006 }
3007 
topologicalEditing() const3008 bool QgsProject::topologicalEditing() const
3009 {
3010   return readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), 0 );
3011 }
3012 
distanceUnits() const3013 QgsUnitTypes::DistanceUnit QgsProject::distanceUnits() const
3014 {
3015   QString distanceUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QString() );
3016   if ( !distanceUnitString.isEmpty() )
3017     return QgsUnitTypes::decodeDistanceUnit( distanceUnitString );
3018 
3019   //fallback to QGIS default measurement unit
3020   bool ok = false;
3021   QgsUnitTypes::DistanceUnit type = QgsUnitTypes::decodeDistanceUnit( mSettings.value( QStringLiteral( "/qgis/measure/displayunits" ) ).toString(), &ok );
3022   return ok ? type : QgsUnitTypes::DistanceMeters;
3023 }
3024 
setDistanceUnits(QgsUnitTypes::DistanceUnit unit)3025 void QgsProject::setDistanceUnits( QgsUnitTypes::DistanceUnit unit )
3026 {
3027   writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QgsUnitTypes::encodeUnit( unit ) );
3028 }
3029 
areaUnits() const3030 QgsUnitTypes::AreaUnit QgsProject::areaUnits() const
3031 {
3032   QString areaUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QString() );
3033   if ( !areaUnitString.isEmpty() )
3034     return QgsUnitTypes::decodeAreaUnit( areaUnitString );
3035 
3036   //fallback to QGIS default area unit
3037   bool ok = false;
3038   QgsUnitTypes::AreaUnit type = QgsUnitTypes::decodeAreaUnit( mSettings.value( QStringLiteral( "/qgis/measure/areaunits" ) ).toString(), &ok );
3039   return ok ? type : QgsUnitTypes::AreaSquareMeters;
3040 }
3041 
setAreaUnits(QgsUnitTypes::AreaUnit unit)3042 void QgsProject::setAreaUnits( QgsUnitTypes::AreaUnit unit )
3043 {
3044   writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QgsUnitTypes::encodeUnit( unit ) );
3045 }
3046 
homePath() const3047 QString QgsProject::homePath() const
3048 {
3049   if ( !mCachedHomePath.isEmpty() )
3050     return mCachedHomePath;
3051 
3052   QFileInfo pfi( fileName() );
3053 
3054   if ( !mHomePath.isEmpty() )
3055   {
3056     QFileInfo homeInfo( mHomePath );
3057     if ( !homeInfo.isRelative() )
3058     {
3059       mCachedHomePath = mHomePath;
3060       return mHomePath;
3061     }
3062   }
3063   else if ( !fileName().isEmpty() )
3064   {
3065     mCachedHomePath = pfi.path();
3066 
3067     return mCachedHomePath;
3068   }
3069 
3070   if ( !pfi.exists() )
3071   {
3072     mCachedHomePath = mHomePath;
3073     return mHomePath;
3074   }
3075 
3076   if ( !mHomePath.isEmpty() )
3077   {
3078     // path is relative to project file
3079     mCachedHomePath = QDir::cleanPath( pfi.path() + '/' + mHomePath );
3080   }
3081   else
3082   {
3083     mCachedHomePath = pfi.canonicalPath();
3084   }
3085   return mCachedHomePath;
3086 }
3087 
presetHomePath() const3088 QString QgsProject::presetHomePath() const
3089 {
3090   return mHomePath;
3091 }
3092 
relationManager() const3093 QgsRelationManager *QgsProject::relationManager() const
3094 {
3095   return mRelationManager;
3096 }
3097 
layoutManager() const3098 const QgsLayoutManager *QgsProject::layoutManager() const
3099 {
3100   return mLayoutManager.get();
3101 }
3102 
layoutManager()3103 QgsLayoutManager *QgsProject::layoutManager()
3104 {
3105   return mLayoutManager.get();
3106 }
3107 
bookmarkManager() const3108 const QgsBookmarkManager *QgsProject::bookmarkManager() const
3109 {
3110   return mBookmarkManager;
3111 }
3112 
bookmarkManager()3113 QgsBookmarkManager *QgsProject::bookmarkManager()
3114 {
3115   return mBookmarkManager;
3116 }
3117 
viewSettings() const3118 const QgsProjectViewSettings *QgsProject::viewSettings() const
3119 {
3120   return mViewSettings;
3121 }
3122 
viewSettings()3123 QgsProjectViewSettings *QgsProject::viewSettings()
3124 {
3125   return mViewSettings;
3126 }
3127 
timeSettings() const3128 const QgsProjectTimeSettings *QgsProject::timeSettings() const
3129 {
3130   return mTimeSettings;
3131 }
3132 
timeSettings()3133 QgsProjectTimeSettings *QgsProject::timeSettings()
3134 {
3135   return mTimeSettings;
3136 }
3137 
displaySettings() const3138 const QgsProjectDisplaySettings *QgsProject::displaySettings() const
3139 {
3140   return mDisplaySettings;
3141 }
3142 
displaySettings()3143 QgsProjectDisplaySettings *QgsProject::displaySettings()
3144 {
3145   return mDisplaySettings;
3146 }
3147 
layerTreeRoot() const3148 QgsLayerTree *QgsProject::layerTreeRoot() const
3149 {
3150   return mRootGroup;
3151 }
3152 
mapThemeCollection()3153 QgsMapThemeCollection *QgsProject::mapThemeCollection()
3154 {
3155   return mMapThemeCollection.get();
3156 }
3157 
annotationManager()3158 QgsAnnotationManager *QgsProject::annotationManager()
3159 {
3160   return mAnnotationManager.get();
3161 }
3162 
annotationManager() const3163 const QgsAnnotationManager *QgsProject::annotationManager() const
3164 {
3165   return mAnnotationManager.get();
3166 }
3167 
setNonIdentifiableLayers(const QList<QgsMapLayer * > & layers)3168 void QgsProject::setNonIdentifiableLayers( const QList<QgsMapLayer *> &layers )
3169 {
3170   const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
3171   for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
3172   {
3173     if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
3174       continue;
3175 
3176     if ( layers.contains( it.value() ) )
3177       it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Identifiable );
3178     else
3179       it.value()->setFlags( it.value()->flags() | QgsMapLayer::Identifiable );
3180   }
3181 
3182   Q_NOWARN_DEPRECATED_PUSH
3183   emit nonIdentifiableLayersChanged( nonIdentifiableLayers() );
3184   Q_NOWARN_DEPRECATED_POP
3185 }
3186 
setNonIdentifiableLayers(const QStringList & layerIds)3187 void QgsProject::setNonIdentifiableLayers( const QStringList &layerIds )
3188 {
3189   QList<QgsMapLayer *> nonIdentifiableLayers;
3190   nonIdentifiableLayers.reserve( layerIds.count() );
3191   for ( const QString &layerId : layerIds )
3192   {
3193     QgsMapLayer *layer = mapLayer( layerId );
3194     if ( layer )
3195       nonIdentifiableLayers << layer;
3196   }
3197   Q_NOWARN_DEPRECATED_PUSH
3198   setNonIdentifiableLayers( nonIdentifiableLayers );
3199   Q_NOWARN_DEPRECATED_POP
3200 }
3201 
nonIdentifiableLayers() const3202 QStringList QgsProject::nonIdentifiableLayers() const
3203 {
3204   QStringList nonIdentifiableLayers;
3205 
3206   const QMap<QString, QgsMapLayer *> &layers = mapLayers();
3207   for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
3208   {
3209     if ( !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
3210     {
3211       nonIdentifiableLayers.append( it.value()->id() );
3212     }
3213   }
3214   return nonIdentifiableLayers;
3215 }
3216 
autoTransaction() const3217 bool QgsProject::autoTransaction() const
3218 {
3219   return mAutoTransaction;
3220 }
3221 
setAutoTransaction(bool autoTransaction)3222 void QgsProject::setAutoTransaction( bool autoTransaction )
3223 {
3224   if ( autoTransaction != mAutoTransaction )
3225   {
3226     mAutoTransaction = autoTransaction;
3227 
3228     if ( autoTransaction )
3229       onMapLayersAdded( mapLayers().values() );
3230     else
3231       cleanTransactionGroups( true );
3232   }
3233 }
3234 
transactionGroups()3235 QMap<QPair<QString, QString>, QgsTransactionGroup *> QgsProject::transactionGroups()
3236 {
3237   return mTransactionGroups;
3238 }
3239 
3240 
3241 //
3242 // QgsMapLayerStore methods
3243 //
3244 
3245 
count() const3246 int QgsProject::count() const
3247 {
3248   return mLayerStore->count();
3249 }
3250 
validCount() const3251 int QgsProject::validCount() const
3252 {
3253   return mLayerStore->validCount();
3254 }
3255 
mapLayer(const QString & layerId) const3256 QgsMapLayer *QgsProject::mapLayer( const QString &layerId ) const
3257 {
3258   return mLayerStore->mapLayer( layerId );
3259 }
3260 
mapLayersByName(const QString & layerName) const3261 QList<QgsMapLayer *> QgsProject::mapLayersByName( const QString &layerName ) const
3262 {
3263   return mLayerStore->mapLayersByName( layerName );
3264 }
3265 
mapLayersByShortName(const QString & shortName) const3266 QList<QgsMapLayer *> QgsProject::mapLayersByShortName( const QString &shortName ) const
3267 {
3268   QList<QgsMapLayer *> layers;
3269   const auto constMapLayers { mLayerStore->mapLayers() };
3270   for ( const auto &l : constMapLayers )
3271   {
3272     if ( ! l->shortName().isEmpty() )
3273     {
3274       if ( l->shortName() == shortName )
3275         layers << l;
3276     }
3277     else if ( l->name() == shortName )
3278     {
3279       layers << l;
3280     }
3281   }
3282   return layers;
3283 }
3284 
unzip(const QString & filename,QgsProject::ReadFlags flags)3285 bool QgsProject::unzip( const QString &filename, QgsProject::ReadFlags flags )
3286 {
3287   clearError();
3288   std::unique_ptr<QgsProjectArchive> archive( new QgsProjectArchive() );
3289 
3290   // unzip the archive
3291   if ( !archive->unzip( filename ) )
3292   {
3293     setError( tr( "Unable to unzip file '%1'" ).arg( filename ) );
3294     return false;
3295   }
3296 
3297   // test if zip provides a .qgs file
3298   if ( archive->projectFile().isEmpty() )
3299   {
3300     setError( tr( "Zip archive does not provide a project file" ) );
3301     return false;
3302   }
3303 
3304   // load auxiliary storage
3305   if ( !archive->auxiliaryStorageFile().isEmpty() )
3306   {
3307     // database file is already a copy as it's been unzipped. So we don't open
3308     // auxiliary storage in copy mode in this case
3309     mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( archive->auxiliaryStorageFile(), false ) );
3310   }
3311   else
3312   {
3313     mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
3314   }
3315 
3316   // read the project file
3317   if ( ! readProjectFile( archive->projectFile(), flags ) )
3318   {
3319     setError( tr( "Cannot read unzipped qgs project file" ) );
3320     return false;
3321   }
3322 
3323   // keep the archive and remove the temporary .qgs file
3324   mArchive = std::move( archive );
3325   mArchive->clearProjectFile();
3326 
3327   return true;
3328 }
3329 
zip(const QString & filename)3330 bool QgsProject::zip( const QString &filename )
3331 {
3332   clearError();
3333 
3334   // save the current project in a temporary .qgs file
3335   std::unique_ptr<QgsProjectArchive> archive( new QgsProjectArchive() );
3336   const QString baseName = QFileInfo( filename ).baseName();
3337   const QString qgsFileName = QStringLiteral( "%1.qgs" ).arg( baseName );
3338   QFile qgsFile( QDir( archive->dir() ).filePath( qgsFileName ) );
3339 
3340   bool writeOk = false;
3341   if ( qgsFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
3342   {
3343     writeOk = writeProjectFile( qgsFile.fileName() );
3344     qgsFile.close();
3345   }
3346 
3347   // stop here with an error message
3348   if ( ! writeOk )
3349   {
3350     setError( tr( "Unable to write temporary qgs file" ) );
3351     return false;
3352   }
3353 
3354   // save auxiliary storage
3355   const QFileInfo info( qgsFile );
3356   const QString asFileName = info.path() + QDir::separator() + info.completeBaseName() + "." + QgsAuxiliaryStorage::extension();
3357 
3358   if ( ! saveAuxiliaryStorage( asFileName ) )
3359   {
3360     const QString err = mAuxiliaryStorage->errorString();
3361     setError( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
3362     return false;
3363   }
3364 
3365   // create the archive
3366   archive->addFile( qgsFile.fileName() );
3367   archive->addFile( asFileName );
3368 
3369   // zip
3370   if ( !archive->zip( filename ) )
3371   {
3372     setError( tr( "Unable to perform zip" ) );
3373     return false;
3374   }
3375 
3376   return true;
3377 }
3378 
isZipped() const3379 bool QgsProject::isZipped() const
3380 {
3381   return QgsZipUtils::isZipFile( mFile.fileName() );
3382 }
3383 
addMapLayers(const QList<QgsMapLayer * > & layers,bool addToLegend,bool takeOwnership)3384 QList<QgsMapLayer *> QgsProject::addMapLayers(
3385   const QList<QgsMapLayer *> &layers,
3386   bool addToLegend,
3387   bool takeOwnership )
3388 {
3389   const QList<QgsMapLayer *> myResultList { mLayerStore->addMapLayers( layers, takeOwnership ) };
3390   if ( !myResultList.isEmpty() )
3391   {
3392     // Update transform context
3393     for ( auto &l : myResultList )
3394     {
3395       l->setTransformContext( transformContext() );
3396     }
3397     if ( addToLegend )
3398     {
3399       emit legendLayersAdded( myResultList );
3400     }
3401   }
3402 
3403   if ( mAuxiliaryStorage )
3404   {
3405     for ( QgsMapLayer *mlayer : myResultList )
3406     {
3407       if ( mlayer->type() != QgsMapLayerType::VectorLayer )
3408         continue;
3409 
3410       QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mlayer );
3411       if ( vl )
3412       {
3413         vl->loadAuxiliaryLayer( *mAuxiliaryStorage );
3414       }
3415     }
3416   }
3417 
3418   mProjectScope.reset();
3419 
3420   return myResultList;
3421 }
3422 
3423 QgsMapLayer *
addMapLayer(QgsMapLayer * layer,bool addToLegend,bool takeOwnership)3424 QgsProject::addMapLayer( QgsMapLayer *layer,
3425                          bool addToLegend,
3426                          bool takeOwnership )
3427 {
3428   QList<QgsMapLayer *> addedLayers;
3429   addedLayers = addMapLayers( QList<QgsMapLayer *>() << layer, addToLegend, takeOwnership );
3430   return addedLayers.isEmpty() ? nullptr : addedLayers[0];
3431 }
3432 
removeMapLayers(const QStringList & layerIds)3433 void QgsProject::removeMapLayers( const QStringList &layerIds )
3434 {
3435   mProjectScope.reset();
3436   mLayerStore->removeMapLayers( layerIds );
3437 }
3438 
removeMapLayers(const QList<QgsMapLayer * > & layers)3439 void QgsProject::removeMapLayers( const QList<QgsMapLayer *> &layers )
3440 {
3441   mProjectScope.reset();
3442   mLayerStore->removeMapLayers( layers );
3443 }
3444 
removeMapLayer(const QString & layerId)3445 void QgsProject::removeMapLayer( const QString &layerId )
3446 {
3447   mProjectScope.reset();
3448   mLayerStore->removeMapLayer( layerId );
3449 }
3450 
removeMapLayer(QgsMapLayer * layer)3451 void QgsProject::removeMapLayer( QgsMapLayer *layer )
3452 {
3453   mProjectScope.reset();
3454   mLayerStore->removeMapLayer( layer );
3455 }
3456 
takeMapLayer(QgsMapLayer * layer)3457 QgsMapLayer *QgsProject::takeMapLayer( QgsMapLayer *layer )
3458 {
3459   mProjectScope.reset();
3460   return mLayerStore->takeMapLayer( layer );
3461 }
3462 
mainAnnotationLayer()3463 QgsAnnotationLayer *QgsProject::mainAnnotationLayer()
3464 {
3465   return mMainAnnotationLayer;
3466 }
3467 
removeAllMapLayers()3468 void QgsProject::removeAllMapLayers()
3469 {
3470   if ( mLayerStore->count() == 0 )
3471     return;
3472 
3473   ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
3474   mProjectScope.reset();
3475   mLayerStore->removeAllMapLayers();
3476 
3477   snapSingleBlocker.release();
3478   mSnappingConfig.clearIndividualLayerSettings();
3479   if ( !mBlockSnappingUpdates )
3480     emit snappingConfigChanged( mSnappingConfig );
3481 }
3482 
reloadAllLayers()3483 void QgsProject::reloadAllLayers()
3484 {
3485   QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
3486   QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin();
3487   for ( ; it != layers.constEnd(); ++it )
3488   {
3489     it.value()->reload();
3490   }
3491 }
3492 
mapLayers(const bool validOnly) const3493 QMap<QString, QgsMapLayer *> QgsProject::mapLayers( const bool validOnly ) const
3494 {
3495   return validOnly ? mLayerStore->validMapLayers() : mLayerStore->mapLayers();
3496 }
3497 
transactionGroup(const QString & providerKey,const QString & connString)3498 QgsTransactionGroup *QgsProject::transactionGroup( const QString &providerKey, const QString &connString )
3499 {
3500   return mTransactionGroups.value( qMakePair( providerKey, connString ) );
3501 }
3502 
defaultCrsForNewLayers() const3503 QgsCoordinateReferenceSystem QgsProject::defaultCrsForNewLayers() const
3504 {
3505   QgsCoordinateReferenceSystem defaultCrs;
3506 
3507   // TODO QGIS 4.0 -- remove this method, and place it somewhere in app (where it belongs)
3508   // in the meantime, we have a slightly hacky way to read the settings key using an enum which isn't available (since it lives in app)
3509   if ( mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), QStringLiteral( "NoAction" ), QgsSettings::App ).toString() == QStringLiteral( "UseProjectCrs" )
3510        || mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), 0, QgsSettings::App ).toString() == 2 )
3511   {
3512     // for new layers if the new layer crs method is set to either prompt or use project, then we use the project crs
3513     defaultCrs = crs();
3514   }
3515   else
3516   {
3517     // global crs
3518     QString layerDefaultCrs = mSettings.value( QStringLiteral( "/Projections/layerDefaultCrs" ), geoEpsgCrsAuthId() ).toString();
3519     defaultCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( layerDefaultCrs );
3520   }
3521 
3522   return defaultCrs;
3523 }
3524 
setTrustLayerMetadata(bool trust)3525 void QgsProject::setTrustLayerMetadata( bool trust )
3526 {
3527   mTrustLayerMetadata = trust;
3528 
3529   auto layers = mapLayers();
3530   for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
3531   {
3532     QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
3533     if ( vl )
3534     {
3535       vl->setReadExtentFromXml( trust );
3536     }
3537   }
3538 }
3539 
saveAuxiliaryStorage(const QString & filename)3540 bool QgsProject::saveAuxiliaryStorage( const QString &filename )
3541 {
3542   const QMap<QString, QgsMapLayer *> layers = mapLayers();
3543   bool empty = true;
3544   for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
3545   {
3546     if ( it.value()->type() != QgsMapLayerType::VectorLayer )
3547       continue;
3548 
3549     QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
3550     if ( vl && vl->auxiliaryLayer() )
3551     {
3552       vl->auxiliaryLayer()->save();
3553       empty &= vl->auxiliaryLayer()->auxiliaryFields().isEmpty();
3554     }
3555   }
3556 
3557   if ( !mAuxiliaryStorage->exists( *this ) && filename.isEmpty() && empty )
3558   {
3559     return true; // it's not an error
3560   }
3561   else if ( !filename.isEmpty() )
3562   {
3563     return mAuxiliaryStorage->saveAs( filename );
3564   }
3565   else
3566   {
3567     return mAuxiliaryStorage->saveAs( *this );
3568   }
3569 }
3570 
dataDefinedServerPropertyDefinitions()3571 QgsPropertiesDefinition &QgsProject::dataDefinedServerPropertyDefinitions()
3572 {
3573   static QgsPropertiesDefinition sPropertyDefinitions
3574   {
3575     {
3576       QgsProject::DataDefinedServerProperty::WMSOnlineResource,
3577       QgsPropertyDefinition( "WMSOnlineResource", QObject::tr( "WMS Online Resource" ), QgsPropertyDefinition::String )
3578     },
3579   };
3580   return sPropertyDefinitions;
3581 }
3582 
auxiliaryStorage() const3583 const QgsAuxiliaryStorage *QgsProject::auxiliaryStorage() const
3584 {
3585   return mAuxiliaryStorage.get();
3586 }
3587 
auxiliaryStorage()3588 QgsAuxiliaryStorage *QgsProject::auxiliaryStorage()
3589 {
3590   return mAuxiliaryStorage.get();
3591 }
3592 
metadata() const3593 const QgsProjectMetadata &QgsProject::metadata() const
3594 {
3595   return mMetadata;
3596 }
3597 
setMetadata(const QgsProjectMetadata & metadata)3598 void QgsProject::setMetadata( const QgsProjectMetadata &metadata )
3599 {
3600   if ( metadata == mMetadata )
3601     return;
3602 
3603   mMetadata = metadata;
3604   mProjectScope.reset();
3605 
3606   emit metadataChanged();
3607 
3608   setDirty( true );
3609 }
3610 
requiredLayers() const3611 QSet<QgsMapLayer *> QgsProject::requiredLayers() const
3612 {
3613   QSet<QgsMapLayer *> requiredLayers;
3614 
3615   const QMap<QString, QgsMapLayer *> &layers = mapLayers();
3616   for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
3617   {
3618     if ( !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
3619     {
3620       requiredLayers.insert( it.value() );
3621     }
3622   }
3623   return requiredLayers;
3624 }
3625 
setRequiredLayers(const QSet<QgsMapLayer * > & layers)3626 void QgsProject::setRequiredLayers( const QSet<QgsMapLayer *> &layers )
3627 {
3628   const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
3629   for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
3630   {
3631     if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
3632       continue;
3633 
3634     if ( layers.contains( it.value() ) )
3635       it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Removable );
3636     else
3637       it.value()->setFlags( it.value()->flags() | QgsMapLayer::Removable );
3638   }
3639 }
3640 
setProjectColors(const QgsNamedColorList & colors)3641 void QgsProject::setProjectColors( const QgsNamedColorList &colors )
3642 {
3643   // save colors to project
3644   QStringList customColors;
3645   QStringList customColorLabels;
3646 
3647   QgsNamedColorList::const_iterator colorIt = colors.constBegin();
3648   for ( ; colorIt != colors.constEnd(); ++colorIt )
3649   {
3650     QString color = QgsSymbolLayerUtils::encodeColor( ( *colorIt ).first );
3651     QString label = ( *colorIt ).second;
3652     customColors.append( color );
3653     customColorLabels.append( label );
3654   }
3655   writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ), customColors );
3656   writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ), customColorLabels );
3657   mProjectScope.reset();
3658   emit projectColorsChanged();
3659 }
3660 
setBackgroundColor(const QColor & color)3661 void QgsProject::setBackgroundColor( const QColor &color )
3662 {
3663   if ( mBackgroundColor == color )
3664     return;
3665 
3666   mBackgroundColor = color;
3667   emit backgroundColorChanged();
3668 }
3669 
backgroundColor() const3670 QColor QgsProject::backgroundColor() const
3671 {
3672   return mBackgroundColor;
3673 }
3674 
setSelectionColor(const QColor & color)3675 void QgsProject::setSelectionColor( const QColor &color )
3676 {
3677   if ( mSelectionColor == color )
3678     return;
3679 
3680   mSelectionColor = color;
3681   emit selectionColorChanged();
3682 }
3683 
selectionColor() const3684 QColor QgsProject::selectionColor() const
3685 {
3686   return mSelectionColor;
3687 }
3688 
setMapScales(const QVector<double> & scales)3689 void QgsProject::setMapScales( const QVector<double> &scales )
3690 {
3691   mViewSettings->setMapScales( scales );
3692 }
3693 
mapScales() const3694 QVector<double> QgsProject::mapScales() const
3695 {
3696   return mViewSettings->mapScales();
3697 }
3698 
setUseProjectScales(bool enabled)3699 void QgsProject::setUseProjectScales( bool enabled )
3700 {
3701   mViewSettings->setUseProjectScales( enabled );
3702 }
3703 
useProjectScales() const3704 bool QgsProject::useProjectScales() const
3705 {
3706   return mViewSettings->useProjectScales();
3707 }
3708 
generateTsFile(const QString & locale)3709 void QgsProject::generateTsFile( const QString &locale )
3710 {
3711   QgsTranslationContext translationContext;
3712   translationContext.setProject( this );
3713   translationContext.setFileName( QStringLiteral( "%1/%2.ts" ).arg( absolutePath(), baseName() ) );
3714 
3715   QgsApplication::instance()->collectTranslatableObjects( &translationContext );
3716 
3717   translationContext.writeTsFile( locale );
3718 }
3719 
translate(const QString & context,const QString & sourceText,const char * disambiguation,int n) const3720 QString QgsProject::translate( const QString &context, const QString &sourceText, const char *disambiguation, int n ) const
3721 {
3722   if ( !mTranslator )
3723   {
3724     return sourceText;
3725   }
3726 
3727   QString result = mTranslator->translate( context.toUtf8(), sourceText.toUtf8(), disambiguation, n );
3728 
3729   if ( result.isEmpty() )
3730   {
3731     return sourceText;
3732   }
3733   return result;
3734 }
3735 
accept(QgsStyleEntityVisitorInterface * visitor) const3736 bool QgsProject::accept( QgsStyleEntityVisitorInterface *visitor ) const
3737 {
3738   const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
3739   if ( !layers.empty() )
3740   {
3741     for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
3742     {
3743       // NOTE: if visitEnter returns false it means "don't visit this layer", not "abort all further visitations"
3744       if ( visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
3745       {
3746         if ( !( ( *it )->accept( visitor ) ) )
3747           return false;
3748 
3749         if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
3750           return false;
3751       }
3752     }
3753   }
3754 
3755   if ( !mLayoutManager->accept( visitor ) )
3756     return false;
3757 
3758   if ( !mAnnotationManager->accept( visitor ) )
3759     return false;
3760 
3761   return true;
3762 }
3763 
3764 /// @cond PRIVATE
GetNamedProjectColor(const QgsProject * project)3765 GetNamedProjectColor::GetNamedProjectColor( const QgsProject *project )
3766   : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
3767 {
3768   if ( !project )
3769     return;
3770 
3771   //build up color list from project. Do this in advance for speed
3772   QStringList colorStrings = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ) );
3773   QStringList colorLabels = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ) );
3774 
3775   //generate list from custom colors
3776   int colorIndex = 0;
3777   for ( QStringList::iterator it = colorStrings.begin();
3778         it != colorStrings.end(); ++it )
3779   {
3780     QColor color = QgsSymbolLayerUtils::decodeColor( *it );
3781     QString label;
3782     if ( colorLabels.length() > colorIndex )
3783     {
3784       label = colorLabels.at( colorIndex );
3785     }
3786 
3787     mColors.insert( label.toLower(), color );
3788     colorIndex++;
3789   }
3790 }
3791 
GetNamedProjectColor(const QHash<QString,QColor> & colors)3792 GetNamedProjectColor::GetNamedProjectColor( const QHash<QString, QColor> &colors )
3793   : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
3794   , mColors( colors )
3795 {
3796 }
3797 
func(const QVariantList & values,const QgsExpressionContext *,QgsExpression *,const QgsExpressionNodeFunction *)3798 QVariant GetNamedProjectColor::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
3799 {
3800   QString colorName = values.at( 0 ).toString().toLower();
3801   if ( mColors.contains( colorName ) )
3802   {
3803     return QStringLiteral( "%1,%2,%3" ).arg( mColors.value( colorName ).red() ).arg( mColors.value( colorName ).green() ).arg( mColors.value( colorName ).blue() );
3804   }
3805   else
3806     return QVariant();
3807 }
3808 
clone() const3809 QgsScopedExpressionFunction *GetNamedProjectColor::clone() const
3810 {
3811   return new GetNamedProjectColor( mColors );
3812 }
3813 ///@endcond
3814