QGIS API Documentation 3.39.0-Master (67e056379ed)
Loading...
Searching...
No Matches
qgsproject.cpp
Go to the documentation of this file.
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"
22#include "qgslayertree.h"
23#include "qgslayertreeutils.h"
25#include "qgslogger.h"
26#include "qgsmessagelog.h"
27#include "qgsmaplayerfactory.h"
30#include "qgssnappingconfig.h"
31#include "qgspathresolver.h"
32#include "qgsprojectstorage.h"
34#include "qgsprojectversion.h"
35#include "qgsrasterlayer.h"
36#include "qgsreadwritecontext.h"
37#include "qgsrelationmanager.h"
41#include "qgslayerdefinition.h"
42#include "qgsunittypes.h"
43#include "qgstransaction.h"
44#include "qgstransactiongroup.h"
47#include "qgsmeshlayer.h"
48#include "qgslayoutmanager.h"
49#include "qgsbookmarkmanager.h"
50#include "qgsmaplayerstore.h"
51#include "qgsziputils.h"
52#include "qgsauxiliarystorage.h"
53#include "qgscolorutils.h"
54#include "qgsapplication.h"
60#include "qgsvectortilelayer.h"
61#include "qgstiledscenelayer.h"
62#include "qgsruntimeprofiler.h"
63#include "qgsannotationlayer.h"
64#include "qgspointcloudlayer.h"
66#include "qgsgrouplayer.h"
67#include "qgsmapviewsmanager.h"
71#include "qgsthreadingutils.h"
72#include "qgssensormanager.h"
73#include "qgsproviderregistry.h"
76#include "qgspluginlayer.h"
77
78#include <algorithm>
79#include <QApplication>
80#include <QFileInfo>
81#include <QDomNode>
82#include <QObject>
83#include <QTextStream>
84#include <QTemporaryFile>
85#include <QDir>
86#include <QUrl>
87#include <QStandardPaths>
88#include <QUuid>
89#include <QRegularExpression>
90#include <QThreadPool>
91
92#ifdef _MSC_VER
93#include <sys/utime.h>
94#else
95#include <utime.h>
96#endif
97
98// canonical project instance
99QgsProject *QgsProject::sProject = nullptr;
100
109QStringList makeKeyTokens_( const QString &scope, const QString &key )
110{
111 QStringList keyTokens = QStringList( scope );
112 keyTokens += key.split( '/', Qt::SkipEmptyParts );
113
114 // be sure to include the canonical root node
115 keyTokens.push_front( QStringLiteral( "properties" ) );
116
117 //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.
118 for ( int i = 0; i < keyTokens.size(); ++i )
119 {
120 const QString keyToken = keyTokens.at( i );
121
122 //invalid chars in XML are found at http://www.w3.org/TR/REC-xml/#NT-NameChar
123 //note : it seems \x10000-\xEFFFF is valid, but it when added to the regexp, a lot of unwanted characters remain
124 const thread_local QRegularExpression sInvalidRegexp = QRegularExpression( QStringLiteral( "([^:A-Z_a-z\\x{C0}-\\x{D6}\\x{D8}-\\x{F6}\\x{F8}-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}\\-\\.0-9\\x{B7}\\x{0300}-\\x{036F}\\x{203F}-\\x{2040}]|^[^:A-Z_a-z\\x{C0}-\\x{D6}\\x{D8}-\\x{F6}\\x{F8}-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}])" ) );
125 if ( keyToken.contains( sInvalidRegexp ) )
126 {
127 const QString errorString = QObject::tr( "Entry token invalid : '%1'. The token will not be saved to file." ).arg( keyToken );
129 }
130 }
131
132 return keyTokens;
133}
134
135
136
146QgsProjectProperty *findKey_( const QString &scope,
147 const QString &key,
148 QgsProjectPropertyKey &rootProperty )
149{
150 QgsProjectPropertyKey *currentProperty = &rootProperty;
151 QgsProjectProperty *nextProperty; // link to next property down hierarchy
152
153 QStringList keySequence = makeKeyTokens_( scope, key );
154
155 while ( !keySequence.isEmpty() )
156 {
157 // if the current head of the sequence list matches the property name,
158 // then traverse down the property hierarchy
159 if ( keySequence.first() == currentProperty->name() )
160 {
161 // remove front key since we're traversing down a level
162 keySequence.pop_front();
163
164 if ( 1 == keySequence.count() )
165 {
166 // if we have only one key name left, then return the key found
167 return currentProperty->find( keySequence.front() );
168 }
169 else if ( keySequence.isEmpty() )
170 {
171 // if we're out of keys then the current property is the one we
172 // want; i.e., we're in the rate case of being at the top-most
173 // property node
174 return currentProperty;
175 }
176 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
177 {
178 if ( nextProperty->isKey() )
179 {
180 currentProperty = static_cast<QgsProjectPropertyKey *>( nextProperty );
181 }
182 else if ( nextProperty->isValue() && 1 == keySequence.count() )
183 {
184 // it may be that this may be one of several property value
185 // nodes keyed by QDict string; if this is the last remaining
186 // key token and the next property is a value node, then
187 // that's the situation, so return the currentProperty
188 return currentProperty;
189 }
190 else
191 {
192 // QgsProjectPropertyValue not Key, so return null
193 return nullptr;
194 }
195 }
196 else
197 {
198 // if the next key down isn't found
199 // then the overall key sequence doesn't exist
200 return nullptr;
201 }
202 }
203 else
204 {
205 return nullptr;
206 }
207 }
208
209 return nullptr;
210}
211
212
213
223QgsProjectProperty *addKey_( const QString &scope,
224 const QString &key,
225 QgsProjectPropertyKey *rootProperty,
226 const QVariant &value,
227 bool &propertiesModified )
228{
229 QStringList keySequence = makeKeyTokens_( scope, key );
230
231 // cursor through property key/value hierarchy
232 QgsProjectPropertyKey *currentProperty = rootProperty;
233 QgsProjectProperty *nextProperty; // link to next property down hierarchy
234 QgsProjectPropertyKey *newPropertyKey = nullptr;
235
236 propertiesModified = false;
237 while ( ! keySequence.isEmpty() )
238 {
239 // if the current head of the sequence list matches the property name,
240 // then traverse down the property hierarchy
241 if ( keySequence.first() == currentProperty->name() )
242 {
243 // remove front key since we're traversing down a level
244 keySequence.pop_front();
245
246 // if key sequence has one last element, then we use that as the
247 // name to store the value
248 if ( 1 == keySequence.count() )
249 {
250 QgsProjectProperty *property = currentProperty->find( keySequence.front() );
251 if ( !property || property->value() != value )
252 {
253 currentProperty->setValue( keySequence.front(), value );
254 propertiesModified = true;
255 }
256
257 return currentProperty;
258 }
259 // we're at the top element if popping the keySequence element
260 // will leave it empty; in that case, just add the key
261 else if ( keySequence.isEmpty() )
262 {
263 if ( currentProperty->value() != value )
264 {
265 currentProperty->setValue( value );
266 propertiesModified = true;
267 }
268
269 return currentProperty;
270 }
271 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
272 {
273 currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
274
275 if ( currentProperty )
276 {
277 continue;
278 }
279 else // QgsProjectPropertyValue not Key, so return null
280 {
281 return nullptr;
282 }
283 }
284 else // the next subkey doesn't exist, so add it
285 {
286 if ( ( newPropertyKey = currentProperty->addKey( keySequence.first() ) ) )
287 {
288 currentProperty = newPropertyKey;
289 }
290 continue;
291 }
292 }
293 else
294 {
295 return nullptr;
296 }
297 }
298
299 return nullptr;
300}
301
309void removeKey_( const QString &scope,
310 const QString &key,
311 QgsProjectPropertyKey &rootProperty )
312{
313 QgsProjectPropertyKey *currentProperty = &rootProperty;
314
315 QgsProjectProperty *nextProperty = nullptr; // link to next property down hierarchy
316 QgsProjectPropertyKey *previousQgsPropertyKey = nullptr; // link to previous property up hierarchy
317
318 QStringList keySequence = makeKeyTokens_( scope, key );
319
320 while ( ! keySequence.isEmpty() )
321 {
322 // if the current head of the sequence list matches the property name,
323 // then traverse down the property hierarchy
324 if ( keySequence.first() == currentProperty->name() )
325 {
326 // remove front key since we're traversing down a level
327 keySequence.pop_front();
328
329 // if we have only one key name left, then try to remove the key
330 // with that name
331 if ( 1 == keySequence.count() )
332 {
333 currentProperty->removeKey( keySequence.front() );
334 }
335 // if we're out of keys then the current property is the one we
336 // want to remove, but we can't delete it directly; we need to
337 // delete it from the parent property key container
338 else if ( keySequence.isEmpty() )
339 {
340 previousQgsPropertyKey->removeKey( currentProperty->name() );
341 }
342 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
343 {
344 previousQgsPropertyKey = currentProperty;
345 currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
346
347 if ( currentProperty )
348 {
349 continue;
350 }
351 else // QgsProjectPropertyValue not Key, so return null
352 {
353 return;
354 }
355 }
356 else // if the next key down isn't found
357 {
358 // then the overall key sequence doesn't exist
359 return;
360 }
361 }
362 else
363 {
364 return;
365 }
366 }
367}
368
369QgsProject::QgsProject( QObject *parent, Qgis::ProjectCapabilities capabilities )
370 : QObject( parent )
371 , mCapabilities( capabilities )
372 , mLayerStore( new QgsMapLayerStore( this ) )
373 , mBadLayerHandler( new QgsProjectBadLayerHandler() )
374 , mSnappingConfig( this )
375 , mRelationManager( new QgsRelationManager( this ) )
376 , mAnnotationManager( new QgsAnnotationManager( this ) )
377 , mLayoutManager( new QgsLayoutManager( this ) )
378 , m3DViewsManager( new QgsMapViewsManager( this ) )
379 , mBookmarkManager( QgsBookmarkManager::createProjectBasedManager( this ) )
380 , mSensorManager( new QgsSensorManager( this ) )
381 , mViewSettings( new QgsProjectViewSettings( this ) )
382 , mStyleSettings( new QgsProjectStyleSettings( this ) )
383 , mTimeSettings( new QgsProjectTimeSettings( this ) )
384 , mElevationProperties( new QgsProjectElevationProperties( this ) )
385 , mDisplaySettings( new QgsProjectDisplaySettings( this ) )
386 , mGpsSettings( new QgsProjectGpsSettings( this ) )
387 , mRootGroup( new QgsLayerTree )
388 , mLabelingEngineSettings( new QgsLabelingEngineSettings )
389 , mArchive( new QgsArchive() )
390 , mAuxiliaryStorage( new QgsAuxiliaryStorage() )
391{
392 mProperties.setName( QStringLiteral( "properties" ) );
393
394 mMainAnnotationLayer = new QgsAnnotationLayer( QObject::tr( "Annotations" ), QgsAnnotationLayer::LayerOptions( mTransformContext ) );
395 mMainAnnotationLayer->setParent( this );
396
397 clear();
398
399 // bind the layer tree to the map layer registry.
400 // whenever layers are added to or removed from the registry,
401 // layer tree will be updated
402 mLayerTreeRegistryBridge = new QgsLayerTreeRegistryBridge( mRootGroup, this, this );
403 connect( this, &QgsProject::layersAdded, this, &QgsProject::onMapLayersAdded );
404 connect( this, &QgsProject::layersRemoved, this, [this] { cleanTransactionGroups(); } );
405 connect( this, qOverload< const QList<QgsMapLayer *> & >( &QgsProject::layersWillBeRemoved ), this, &QgsProject::onMapLayersRemoved );
406
407 // proxy map layer store signals to this
408 connect( mLayerStore.get(), qOverload<const QStringList &>( &QgsMapLayerStore::layersWillBeRemoved ),
409 this, [this]( const QStringList & layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
410 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersWillBeRemoved ),
411 this, [this]( const QList<QgsMapLayer *> &layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
412 connect( mLayerStore.get(), qOverload< const QString & >( &QgsMapLayerStore::layerWillBeRemoved ),
413 this, [this]( const QString & layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
414 connect( mLayerStore.get(), qOverload< QgsMapLayer * >( &QgsMapLayerStore::layerWillBeRemoved ),
415 this, [this]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
416 connect( mLayerStore.get(), qOverload<const QStringList & >( &QgsMapLayerStore::layersRemoved ), this,
417 [this]( const QStringList & layers ) { mProjectScope.reset(); emit layersRemoved( layers ); } );
418 connect( mLayerStore.get(), &QgsMapLayerStore::layerRemoved, this,
419 [this]( const QString & layer ) { mProjectScope.reset(); emit layerRemoved( layer ); } );
420 connect( mLayerStore.get(), &QgsMapLayerStore::allLayersRemoved, this,
421 [this]() { mProjectScope.reset(); emit removeAll(); } );
422 connect( mLayerStore.get(), &QgsMapLayerStore::layersAdded, this,
423 [this]( const QList< QgsMapLayer * > &layers ) { mProjectScope.reset(); emit layersAdded( layers ); } );
424 connect( mLayerStore.get(), &QgsMapLayerStore::layerWasAdded, this,
425 [this]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWasAdded( layer ); } );
426
428 {
430 }
431
432 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersWillBeRemoved ), this,
433 [this]( const QList<QgsMapLayer *> &layers )
434 {
435 for ( const auto &layer : layers )
436 {
437 disconnect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager, &QgsRelationManager::updateRelationsStatus );
438 }
439 }
440 );
441 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersAdded ), this,
442 [this]( const QList<QgsMapLayer *> &layers )
443 {
444 for ( const auto &layer : layers )
445 {
446 connect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager, &QgsRelationManager::updateRelationsStatus );
447 }
448 }
449 );
450
454
455 mStyleSettings->combinedStyleModel()->addDefaultStyle();
456}
457
458
460{
461 mIsBeingDeleted = true;
462
463 clear();
464 releaseHandlesToProjectArchive();
465 delete mBadLayerHandler;
466 delete mRelationManager;
467 delete mLayerTreeRegistryBridge;
468 delete mRootGroup;
469 if ( this == sProject )
470 {
471 sProject = nullptr;
472 }
473}
474
476{
477 sProject = project;
478}
479
480
482{
483 if ( !sProject )
484 {
485 sProject = new QgsProject;
486
488 }
489 return sProject;
490}
491
492void QgsProject::setTitle( const QString &title )
493{
495
496 if ( title == mMetadata.title() )
497 return;
498
499 mMetadata.setTitle( title );
500 mProjectScope.reset();
501 emit metadataChanged();
502
503 setDirty( true );
504}
505
506QString QgsProject::title() const
507{
508 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
510
511 return mMetadata.title();
512}
513
515{
517
518 const bool oldEvaluateDefaultValues = mFlags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide;
519 const bool newEvaluateDefaultValues = flags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide;
520 if ( oldEvaluateDefaultValues != newEvaluateDefaultValues )
521 {
522 const QMap<QString, QgsMapLayer *> layers = mapLayers();
523 for ( auto layerIt = layers.constBegin(); layerIt != layers.constEnd(); ++layerIt )
524 {
525 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() ) )
526 if ( vl->dataProvider() )
527 vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, newEvaluateDefaultValues );
528 }
529 }
530
531 const bool oldTrustLayerMetadata = mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics;
532 const bool newTrustLayerMetadata = flags & Qgis::ProjectFlag::TrustStoredLayerStatistics;
533 if ( oldTrustLayerMetadata != newTrustLayerMetadata )
534 {
535 const QMap<QString, QgsMapLayer *> layers = mapLayers();
536 for ( auto layerIt = layers.constBegin(); layerIt != layers.constEnd(); ++layerIt )
537 {
538 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() ) )
539 {
540 vl->setReadExtentFromXml( newTrustLayerMetadata );
541 }
542 }
543 }
544
545 if ( mFlags != flags )
546 {
547 mFlags = flags;
548 setDirty( true );
549 }
550}
551
552void QgsProject::setFlag( Qgis::ProjectFlag flag, bool enabled )
553{
555
556 Qgis::ProjectFlags newFlags = mFlags;
557 if ( enabled )
558 newFlags |= flag;
559 else
560 newFlags &= ~( static_cast< int >( flag ) );
561 setFlags( newFlags );
562}
563
564QString QgsProject::saveUser() const
565{
567
568 return mSaveUser;
569}
570
572{
574
575 return mSaveUserFull;
576}
577
579{
581
582 return mSaveDateTime;
583}
584
591
593{
595
596 return mDirty;
597}
598
599void QgsProject::setDirty( const bool dirty )
600{
602
603 if ( dirty && mDirtyBlockCount > 0 )
604 return;
605
606 if ( dirty )
607 emit dirtySet();
608
609 if ( mDirty == dirty )
610 return;
611
612 mDirty = dirty;
613 emit isDirtyChanged( mDirty );
614}
615
616void QgsProject::setPresetHomePath( const QString &path )
617{
619
620 if ( path == mHomePath )
621 return;
622
623 mHomePath = path;
624 mCachedHomePath.clear();
625 mProjectScope.reset();
626
627 emit homePathChanged();
628
629 setDirty( true );
630}
631
632void QgsProject::registerTranslatableContainers( QgsTranslationContext *translationContext, QgsAttributeEditorContainer *parent, const QString &layerId )
633{
635
636 const QList<QgsAttributeEditorElement *> elements = parent->children();
637
638 for ( QgsAttributeEditorElement *element : elements )
639 {
640 if ( element->type() == Qgis::AttributeEditorType::Container )
641 {
642 QgsAttributeEditorContainer *container = dynamic_cast<QgsAttributeEditorContainer *>( element );
643
644 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:formcontainers" ).arg( layerId ), container->name() );
645
646 if ( !container->children().empty() )
647 registerTranslatableContainers( translationContext, container, layerId );
648 }
649 }
650}
651
653{
655
656 //register layers
657 const QList<QgsLayerTreeLayer *> layers = mRootGroup->findLayers();
658
659 for ( const QgsLayerTreeLayer *layer : layers )
660 {
661 translationContext->registerTranslation( QStringLiteral( "project:layers:%1" ).arg( layer->layerId() ), layer->name() );
662
663 QgsMapLayer *mapLayer = layer->layer();
665 {
666 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer );
667
668 //register aliases and fields
669 const QgsFields fields = vlayer->fields();
670 for ( const QgsField &field : fields )
671 {
672 QString fieldName;
673 if ( field.alias().isEmpty() )
674 fieldName = field.name();
675 else
676 fieldName = field.alias();
677
678 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:fieldaliases" ).arg( vlayer->id() ), fieldName );
679
680 if ( field.editorWidgetSetup().type() == QLatin1String( "ValueRelation" ) )
681 {
682 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:fields:%2:valuerelationvalue" ).arg( vlayer->id(), field.name() ), field.editorWidgetSetup().config().value( QStringLiteral( "Value" ) ).toString() );
683 }
684 }
685
686 //register formcontainers
687 registerTranslatableContainers( translationContext, vlayer->editFormConfig().invisibleRootContainer(), vlayer->id() );
688
689 }
690 }
691
692 //register layergroups
693 const QList<QgsLayerTreeGroup *> groupLayers = mRootGroup->findGroups();
694 for ( const QgsLayerTreeGroup *groupLayer : groupLayers )
695 {
696 translationContext->registerTranslation( QStringLiteral( "project:layergroups" ), groupLayer->name() );
697 }
698
699 //register relations
700 const QList<QgsRelation> &relations = mRelationManager->relations().values();
701 for ( const QgsRelation &relation : relations )
702 {
703 translationContext->registerTranslation( QStringLiteral( "project:relations" ), relation.name() );
704 }
705}
706
708{
710
711 mDataDefinedServerProperties = properties;
712}
713
715{
717
718 return mDataDefinedServerProperties;
719}
720
722{
724
725 switch ( mTransactionMode )
726 {
729 {
730 if ( ! vectorLayer )
731 return false;
732 return vectorLayer->startEditing();
733 }
734
736 return mEditBufferGroup.startEditing();
737 }
738
739 return false;
740}
741
742bool QgsProject::commitChanges( QStringList &commitErrors, bool stopEditing, QgsVectorLayer *vectorLayer )
743{
745
746 switch ( mTransactionMode )
747 {
750 {
751 if ( ! vectorLayer )
752 {
753 commitErrors.append( tr( "Trying to commit changes without a layer specified. This only works if the transaction mode is buffered" ) );
754 return false;
755 }
756 bool success = vectorLayer->commitChanges( stopEditing );
757 commitErrors = vectorLayer->commitErrors();
758 return success;
759 }
760
762 return mEditBufferGroup.commitChanges( commitErrors, stopEditing );
763 }
764
765 return false;
766}
767
768bool QgsProject::rollBack( QStringList &rollbackErrors, bool stopEditing, QgsVectorLayer *vectorLayer )
769{
771
772 switch ( mTransactionMode )
773 {
776 {
777 if ( ! vectorLayer )
778 {
779 rollbackErrors.append( tr( "Trying to roll back changes without a layer specified. This only works if the transaction mode is buffered" ) );
780 return false;
781 }
782 bool success = vectorLayer->rollBack( stopEditing );
783 rollbackErrors = vectorLayer->commitErrors();
784 return success;
785 }
786
788 return mEditBufferGroup.rollBack( rollbackErrors, stopEditing );
789 }
790
791 return false;
792}
793
794void QgsProject::setFileName( const QString &name )
795{
797
798 if ( name == mFile.fileName() )
799 return;
800
801 const QString oldHomePath = homePath();
802
803 mFile.setFileName( name );
804 mCachedHomePath.clear();
805 mProjectScope.reset();
806
807 emit fileNameChanged();
808
809 const QString newHomePath = homePath();
810 if ( newHomePath != oldHomePath )
811 emit homePathChanged();
812
813 setDirty( true );
814}
815
816QString QgsProject::fileName() const
817{
818 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
820
821 return mFile.fileName();
822}
823
824void QgsProject::setOriginalPath( const QString &path )
825{
827
828 mOriginalPath = path;
829}
830
832{
834
835 return mOriginalPath;
836}
837
838QFileInfo QgsProject::fileInfo() const
839{
841
842 return QFileInfo( mFile );
843}
844
846{
847 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
849
851}
852
854{
856
857 if ( QgsProjectStorage *storage = projectStorage() )
858 {
860 storage->readProjectStorageMetadata( mFile.fileName(), metadata );
861 return metadata.lastModified;
862 }
863 else
864 {
865 return QFileInfo( mFile.fileName() ).lastModified();
866 }
867}
868
870{
872
873 if ( projectStorage() )
874 return QString();
875
876 if ( mFile.fileName().isEmpty() )
877 return QString(); // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
878
879 return QFileInfo( mFile.fileName() ).absolutePath();
880}
881
883{
884 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
886
887 if ( projectStorage() )
888 return QString();
889
890 if ( mFile.fileName().isEmpty() )
891 return QString(); // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
892
893 return QFileInfo( mFile.fileName() ).absoluteFilePath();
894}
895
896QString QgsProject::baseName() const
897{
898 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
900
901 if ( QgsProjectStorage *storage = projectStorage() )
902 {
904 storage->readProjectStorageMetadata( mFile.fileName(), metadata );
905 return metadata.name;
906 }
907 else
908 {
909 return QFileInfo( mFile.fileName() ).completeBaseName();
910 }
911}
912
914{
916
917 const bool absolutePaths = readBoolEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), false );
919}
920
922{
924
925 switch ( type )
926 {
928 writeEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), true );
929 break;
931 writeEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), false );
932 break;
933 }
934}
935
937{
938 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
940
941 return mCrs;
942}
943
945{
947
948 return mCrs3D.isValid() ? mCrs3D : mCrs;
949}
950
951void QgsProject::setCrs( const QgsCoordinateReferenceSystem &crs, bool adjustEllipsoid )
952{
954
955 if ( crs != mCrs )
956 {
957 const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
958 const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
959 mCrs = crs;
960 writeEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), crs.isValid() ? 1 : 0 );
961 mProjectScope.reset();
962
963 // if annotation layer doesn't have a crs (i.e. in a newly created project), it should
964 // initially inherit the project CRS
965 if ( !mMainAnnotationLayer->crs().isValid() || mMainAnnotationLayer->isEmpty() )
966 mMainAnnotationLayer->setCrs( crs );
967
968 rebuildCrs3D();
969
970 setDirty( true );
971 emit crsChanged();
972 // Did vertical crs also change as a result of this? If so, emit signal
973 if ( oldVerticalCrs != verticalCrs() )
974 emit verticalCrsChanged();
975 if ( oldCrs3D != mCrs3D )
976 emit crs3DChanged();
977 }
978
979 if ( adjustEllipsoid )
981}
982
984{
985 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
987
988 if ( !crs().isValid() )
989 return geoNone();
990
991 return readEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ), geoNone() );
992}
993
994void QgsProject::setEllipsoid( const QString &ellipsoid )
995{
997
998 if ( ellipsoid == readEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ) ) )
999 return;
1000
1001 mProjectScope.reset();
1002 writeEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ), ellipsoid );
1004}
1005
1007{
1008 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
1010
1011 switch ( mCrs.type() )
1012 {
1013 case Qgis::CrsType::Vertical: // would hope this never happens!
1014 QgsDebugError( QStringLiteral( "Project has a vertical CRS set as the horizontal CRS!" ) );
1015 return mCrs;
1016
1018 return mCrs.verticalCrs();
1019
1031 break;
1032 }
1033 return mVerticalCrs;
1034}
1035
1037{
1039 bool res = true;
1040 if ( crs.isValid() )
1041 {
1042 // validate that passed crs is a vertical crs
1043 switch ( crs.type() )
1044 {
1046 break;
1047
1060 if ( errorMessage )
1061 *errorMessage = QObject::tr( "Specified CRS is a %1 CRS, not a Vertical CRS" ).arg( qgsEnumValueToKey( crs.type() ) );
1062 return false;
1063 }
1064 }
1065
1066 if ( crs != mVerticalCrs )
1067 {
1068 const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
1069 const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
1070
1071 switch ( mCrs.type() )
1072 {
1074 if ( crs != oldVerticalCrs )
1075 {
1076 if ( errorMessage )
1077 *errorMessage = QObject::tr( "Project CRS is a Compound CRS, specified Vertical CRS will be ignored" );
1078 return false;
1079 }
1080 break;
1081
1083 if ( crs != oldVerticalCrs )
1084 {
1085 if ( errorMessage )
1086 *errorMessage = QObject::tr( "Project CRS is a Geographic 3D CRS, specified Vertical CRS will be ignored" );
1087 return false;
1088 }
1089 break;
1090
1092 if ( crs != oldVerticalCrs )
1093 {
1094 if ( errorMessage )
1095 *errorMessage = QObject::tr( "Project CRS is a Geocentric CRS, specified Vertical CRS will be ignored" );
1096 return false;
1097 }
1098 break;
1099
1101 if ( mCrs.hasVerticalAxis() && crs != oldVerticalCrs )
1102 {
1103 if ( errorMessage )
1104 *errorMessage = QObject::tr( "Project CRS is a Projected 3D CRS, specified Vertical CRS will be ignored" );
1105 return false;
1106 }
1107 break;
1108
1118 break;
1119 }
1120
1121 mVerticalCrs = crs;
1122 res = rebuildCrs3D( errorMessage );
1123 mProjectScope.reset();
1124
1125 setDirty( true );
1126 // only emit signal if vertical crs was actually changed, so eg if mCrs is compound
1127 // then we haven't actually changed the vertical crs by this call!
1128 if ( verticalCrs() != oldVerticalCrs )
1129 emit verticalCrsChanged();
1130 if ( mCrs3D != oldCrs3D )
1131 emit crs3DChanged();
1132 }
1133 return res;
1134}
1135
1137{
1138 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
1140
1141 return mTransformContext;
1142}
1143
1145{
1147
1148 if ( context == mTransformContext )
1149 return;
1150
1151 mTransformContext = context;
1152 mProjectScope.reset();
1153
1154 mMainAnnotationLayer->setTransformContext( context );
1155 for ( auto &layer : mLayerStore.get()->mapLayers() )
1156 {
1157 layer->setTransformContext( context );
1158 }
1160}
1161
1163{
1165
1166 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
1167
1168 emit aboutToBeCleared();
1169
1170 mProjectScope.reset();
1171 mFile.setFileName( QString() );
1172 mProperties.clearKeys();
1173 mSaveUser.clear();
1174 mSaveUserFull.clear();
1175 mSaveDateTime = QDateTime();
1176 mSaveVersion = QgsProjectVersion();
1177 mHomePath.clear();
1178 mCachedHomePath.clear();
1179 mTransactionMode = Qgis::TransactionMode::Disabled;
1180 mFlags = Qgis::ProjectFlags();
1181 mDirty = false;
1182 mCustomVariables.clear();
1184 mVerticalCrs = QgsCoordinateReferenceSystem();
1186 mMetadata = QgsProjectMetadata();
1187 mElevationShadingRenderer = QgsElevationShadingRenderer();
1188 if ( !mSettings.value( QStringLiteral( "projects/anonymize_new_projects" ), false, QgsSettings::Core ).toBool() )
1189 {
1190 mMetadata.setCreationDateTime( QDateTime::currentDateTime() );
1192 }
1193 emit metadataChanged();
1194
1196 context.readSettings();
1197 setTransformContext( context );
1198
1199 //fallback to QGIS default measurement unit
1200 bool ok = false;
1201 const Qgis::DistanceUnit distanceUnit = QgsUnitTypes::decodeDistanceUnit( mSettings.value( QStringLiteral( "/qgis/measure/displayunits" ) ).toString(), &ok );
1202 setDistanceUnits( ok ? distanceUnit : Qgis::DistanceUnit::Meters );
1203 ok = false;
1204 const Qgis::AreaUnit areaUnits = QgsUnitTypes::decodeAreaUnit( mSettings.value( QStringLiteral( "/qgis/measure/areaunits" ) ).toString(), &ok );
1206
1207 mEmbeddedLayers.clear();
1208 mRelationManager->clear();
1209 mAnnotationManager->clear();
1210 mLayoutManager->clear();
1211 m3DViewsManager->clear();
1212 mBookmarkManager->clear();
1213 mSensorManager->clear();
1214 mViewSettings->reset();
1215 mTimeSettings->reset();
1216 mElevationProperties->reset();
1217 mDisplaySettings->reset();
1218 mGpsSettings->reset();
1219 mSnappingConfig.reset();
1220 mAvoidIntersectionsMode = Qgis::AvoidIntersectionsMode::AllowIntersections;
1223
1224 mMapThemeCollection.reset( new QgsMapThemeCollection( this ) );
1226
1227 mLabelingEngineSettings->clear();
1228
1229 // must happen BEFORE archive reset, because we need to release the hold on any files which
1230 // exists within the archive. Otherwise the archive can't be removed.
1231 releaseHandlesToProjectArchive();
1232
1233 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage() );
1234 mArchive.reset( new QgsArchive() );
1235
1236 // must happen AFTER archive reset, as it will populate a new style database within the new archive
1237 mStyleSettings->reset();
1238
1240
1241 if ( !mIsBeingDeleted )
1242 {
1243 // possibly other signals should also not be thrown on destruction -- e.g. labelEngineSettingsChanged, etc.
1244 emit projectColorsChanged();
1245 }
1246
1247 // reset some default project properties
1248 // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
1249 writeEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/Automatic" ), true );
1250 writeEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DecimalPlaces" ), 2 );
1251
1252 const bool defaultRelativePaths = mSettings.value( QStringLiteral( "/qgis/defaultProjectPathsRelative" ), true ).toBool();
1254
1255 int red = mSettings.value( QStringLiteral( "qgis/default_canvas_color_red" ), 255 ).toInt();
1256 int green = mSettings.value( QStringLiteral( "qgis/default_canvas_color_green" ), 255 ).toInt();
1257 int blue = mSettings.value( QStringLiteral( "qgis/default_canvas_color_blue" ), 255 ).toInt();
1258 setBackgroundColor( QColor( red, green, blue ) );
1259
1260 red = mSettings.value( QStringLiteral( "qgis/default_selection_color_red" ), 255 ).toInt();
1261 green = mSettings.value( QStringLiteral( "qgis/default_selection_color_green" ), 255 ).toInt();
1262 blue = mSettings.value( QStringLiteral( "qgis/default_selection_color_blue" ), 0 ).toInt();
1263 const int alpha = mSettings.value( QStringLiteral( "qgis/default_selection_color_alpha" ), 255 ).toInt();
1264 setSelectionColor( QColor( red, green, blue, alpha ) );
1265
1266 mSnappingConfig.clearIndividualLayerSettings();
1267
1269 mRootGroup->clear();
1270 if ( mMainAnnotationLayer )
1271 mMainAnnotationLayer->reset();
1272
1273 snapSingleBlocker.release();
1274
1275 if ( !mBlockSnappingUpdates )
1276 emit snappingConfigChanged( mSnappingConfig );
1277
1278 setDirty( false );
1279 emit homePathChanged();
1280 if ( !mBlockChangeSignalsDuringClear )
1281 {
1282 emit verticalCrsChanged();
1283 emit crs3DChanged();
1284 }
1285 emit cleared();
1286}
1287
1288// basically a debugging tool to dump property list values
1289void dump_( const QgsProjectPropertyKey &topQgsPropertyKey )
1290{
1291 QgsDebugMsgLevel( QStringLiteral( "current properties:" ), 3 );
1292 topQgsPropertyKey.dump();
1293}
1294
1323void _getProperties( const QDomDocument &doc, QgsProjectPropertyKey &project_properties )
1324{
1325 const QDomElement propertiesElem = doc.documentElement().firstChildElement( QStringLiteral( "properties" ) );
1326
1327 if ( propertiesElem.isNull() ) // no properties found, so we're done
1328 {
1329 return;
1330 }
1331
1332 const QDomNodeList scopes = propertiesElem.childNodes();
1333
1334 if ( propertiesElem.firstChild().isNull() )
1335 {
1336 QgsDebugError( QStringLiteral( "empty ``properties'' XML tag ... bailing" ) );
1337 return;
1338 }
1339
1340 if ( ! project_properties.readXml( propertiesElem ) )
1341 {
1342 QgsDebugError( QStringLiteral( "Project_properties.readXml() failed" ) );
1343 }
1344}
1345
1352QgsPropertyCollection getDataDefinedServerProperties( const QDomDocument &doc, const QgsPropertiesDefinition &dataDefinedServerPropertyDefinitions )
1353{
1354 QgsPropertyCollection ddServerProperties;
1355 // Read data defined server properties
1356 const QDomElement ddElem = doc.documentElement().firstChildElement( QStringLiteral( "dataDefinedServerProperties" ) );
1357 if ( !ddElem.isNull() )
1358 {
1359 if ( !ddServerProperties.readXml( ddElem, dataDefinedServerPropertyDefinitions ) )
1360 {
1361 QgsDebugError( QStringLiteral( "dataDefinedServerProperties.readXml() failed" ) );
1362 }
1363 }
1364 return ddServerProperties;
1365}
1366
1371static void _getTitle( const QDomDocument &doc, QString &title )
1372{
1373 const QDomElement titleNode = doc.documentElement().firstChildElement( QStringLiteral( "title" ) );
1374
1375 title.clear(); // by default the title will be empty
1376
1377 if ( titleNode.isNull() )
1378 {
1379 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1380 return;
1381 }
1382
1383 if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text
1384 {
1385 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1386 return;
1387 }
1388
1389 const QDomNode titleTextNode = titleNode.firstChild(); // should only have one child
1390
1391 if ( !titleTextNode.isText() )
1392 {
1393 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1394 return;
1395 }
1396
1397 const QDomText titleText = titleTextNode.toText();
1398
1399 title = titleText.data();
1400
1401}
1402
1403static void readProjectFileMetadata( const QDomDocument &doc, QString &lastUser, QString &lastUserFull, QDateTime &lastSaveDateTime )
1404{
1405 const QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
1406
1407 if ( !nl.count() )
1408 {
1409 QgsDebugError( QStringLiteral( "unable to find qgis element" ) );
1410 return;
1411 }
1412
1413 const QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1414
1415 const QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1416 lastUser = qgisElement.attribute( QStringLiteral( "saveUser" ), QString() );
1417 lastUserFull = qgisElement.attribute( QStringLiteral( "saveUserFull" ), QString() );
1418 lastSaveDateTime = QDateTime::fromString( qgisElement.attribute( QStringLiteral( "saveDateTime" ), QString() ), Qt::ISODate );
1419}
1420
1421QgsProjectVersion getVersion( const QDomDocument &doc )
1422{
1423 const QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
1424
1425 if ( !nl.count() )
1426 {
1427 QgsDebugError( QStringLiteral( " unable to find qgis element in project file" ) );
1428 return QgsProjectVersion( 0, 0, 0, QString() );
1429 }
1430
1431 const QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1432
1433 const QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1434 QgsProjectVersion projectVersion( qgisElement.attribute( QStringLiteral( "version" ) ) );
1435 return projectVersion;
1436}
1437
1439{
1441
1442 return mSnappingConfig;
1443}
1444
1446{
1448
1449 if ( mSnappingConfig == snappingConfig )
1450 return;
1451
1452 mSnappingConfig = snappingConfig;
1453 setDirty( true );
1454 emit snappingConfigChanged( mSnappingConfig );
1455}
1456
1458{
1460
1461 if ( mAvoidIntersectionsMode == mode )
1462 return;
1463
1464 mAvoidIntersectionsMode = mode;
1466}
1467
1468static QgsMapLayer::ReadFlags projectFlagsToLayerReadFlags( Qgis::ProjectReadFlags projectReadFlags, Qgis::ProjectFlags projectFlags )
1469{
1471 // Propagate don't resolve layers
1472 if ( projectReadFlags & Qgis::ProjectReadFlag::DontResolveLayers )
1474 // Propagate trust layer metadata flag
1475 // Propagate read extent from XML based trust layer metadata flag
1476 if ( ( projectFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics ) || ( projectReadFlags & Qgis::ProjectReadFlag::TrustLayerMetadata ) )
1477 {
1480 }
1481 // Propagate open layers in read-only mode
1482 if ( ( projectReadFlags & Qgis::ProjectReadFlag::ForceReadOnlyLayers ) )
1483 layerFlags |= QgsMapLayer::FlagForceReadOnly;
1484
1485 return layerFlags;
1486}
1487
1497
1498void QgsProject::preloadProviders( const QVector<QDomNode> &parallelLayerNodes,
1499 const QgsReadWriteContext &context,
1500 QMap<QString, QgsDataProvider *> &loadedProviders,
1501 QgsMapLayer::ReadFlags layerReadFlags,
1502 int totalProviderCount )
1503{
1504 int i = 0;
1505 QEventLoop loop;
1506
1507 QMap<QString, LayerToLoad> layersToLoad;
1508
1509 for ( const QDomNode &node : parallelLayerNodes )
1510 {
1511 LayerToLoad layerToLoad;
1512
1513 const QDomElement layerElement = node.toElement();
1514 layerToLoad.layerElement = layerElement;
1515 layerToLoad.layerId = layerElement.namedItem( QStringLiteral( "id" ) ).toElement().text();
1516 layerToLoad.provider = layerElement.namedItem( QStringLiteral( "provider" ) ).toElement().text();
1517 layerToLoad.dataSource = layerElement.namedItem( QStringLiteral( "datasource" ) ).toElement().text();
1518
1519 layerToLoad.dataSource = QgsProviderRegistry::instance()->relativeToAbsoluteUri( layerToLoad.provider, layerToLoad.dataSource, context );
1520
1521 layerToLoad.options = QgsDataProvider::ProviderOptions( {context.transformContext()} );
1522 layerToLoad.flags = QgsMapLayer::providerReadFlags( node, layerReadFlags );
1523
1524 // Requesting credential from worker thread could lead to deadlocks because the main thread is waiting for worker thread to fininsh
1525 layerToLoad.flags.setFlag( QgsDataProvider::SkipCredentialsRequest, true );
1526 layerToLoad.flags.setFlag( QgsDataProvider::ParallelThreadLoading, true );
1527
1528 layersToLoad.insert( layerToLoad.layerId, layerToLoad );
1529 }
1530
1531 while ( !layersToLoad.isEmpty() )
1532 {
1533 const QList<LayerToLoad> layersToAttemptInParallel = layersToLoad.values();
1534 QString layerToAttemptInMainThread;
1535
1536 QHash<QString, QgsRunnableProviderCreator *> runnables;
1537 QThreadPool threadPool;
1538 threadPool.setMaxThreadCount( QgsSettingsRegistryCore::settingsLayerParallelLoadingMaxCount->value() );
1539
1540 for ( const LayerToLoad &lay : layersToAttemptInParallel )
1541 {
1542 QgsRunnableProviderCreator *run = new QgsRunnableProviderCreator( lay.layerId, lay.provider, lay.dataSource, lay.options, lay.flags );
1543 runnables.insert( lay.layerId, run );
1544
1545 QObject::connect( run, &QgsRunnableProviderCreator::providerCreated, run, [&]( bool isValid, const QString & layId )
1546 {
1547 if ( isValid )
1548 {
1549 layersToLoad.remove( layId );
1550 i++;
1551 QgsRunnableProviderCreator *finishedRun = runnables.value( layId, nullptr );
1552 Q_ASSERT( finishedRun );
1553
1554 std::unique_ptr<QgsDataProvider> provider( finishedRun->dataProvider() );
1555 Q_ASSERT( provider && provider->isValid() );
1556
1557 loadedProviders.insert( layId, provider.release() );
1558 emit layerLoaded( i, totalProviderCount );
1559 }
1560 else
1561 {
1562 if ( layerToAttemptInMainThread.isEmpty() )
1563 layerToAttemptInMainThread = layId;
1564 threadPool.clear(); //we have to stop all loading provider to try this layer in main thread and maybe have credentials
1565 }
1566
1567 if ( i == parallelLayerNodes.count() || !isValid )
1568 loop.quit();
1569 } );
1570 threadPool.start( run );
1571 }
1572 loop.exec();
1573
1574 threadPool.waitForDone(); // to be sure all threads are finished
1575
1576 qDeleteAll( runnables );
1577
1578 // We try with the first layer returned invalid but this time in the main thread to maybe have credentials and continue with others not loaded in parallel
1579 auto it = layersToLoad.find( layerToAttemptInMainThread );
1580 if ( it != layersToLoad.end() )
1581 {
1582 std::unique_ptr<QgsDataProvider> provider;
1583 QString layerId;
1584 {
1585 const LayerToLoad &lay = it.value();
1586 QgsDataProvider::ReadFlags providerFlags = lay.flags;
1587 providerFlags.setFlag( QgsDataProvider::SkipCredentialsRequest, false );
1588 providerFlags.setFlag( QgsDataProvider::ParallelThreadLoading, false );
1589 QgsScopedRuntimeProfile profile( "Create data providers/" + lay.layerId, QStringLiteral( "projectload" ) );
1590 provider.reset( QgsProviderRegistry::instance()->createProvider( lay.provider, lay.dataSource, lay.options, providerFlags ) );
1591 i++;
1592 if ( provider && provider->isValid() )
1593 {
1594 emit layerLoaded( i, totalProviderCount );
1595 }
1596 layerId = lay.layerId;
1597 layersToLoad.erase( it );
1598 // can't access "lay" anymore -- it's now been freed
1599 }
1600 loadedProviders.insert( layerId, provider.release() );
1601 }
1602
1603 // if there still are some not loaded providers or some invalid in parallel thread we start again
1604 }
1605
1606}
1607
1608void QgsProject::releaseHandlesToProjectArchive()
1609{
1610 mStyleSettings->removeProjectStyle();
1611}
1612
1613bool QgsProject::rebuildCrs3D( QString *error )
1614{
1615 bool res = true;
1616 if ( !mCrs.isValid() )
1617 {
1619 }
1620 else if ( !mVerticalCrs.isValid() )
1621 {
1622 mCrs3D = mCrs;
1623 }
1624 else
1625 {
1626 switch ( mCrs.type() )
1627 {
1631 mCrs3D = mCrs;
1632 break;
1633
1635 {
1636 QString tempError;
1637 mCrs3D = mCrs.hasVerticalAxis() ? mCrs : QgsCoordinateReferenceSystem::createCompoundCrs( mCrs, mVerticalCrs, error ? *error : tempError );
1638 res = mCrs3D.isValid();
1639 break;
1640 }
1641
1643 // nonsense situation
1645 res = false;
1646 break;
1647
1656 {
1657 QString tempError;
1658 mCrs3D = QgsCoordinateReferenceSystem::createCompoundCrs( mCrs, mVerticalCrs, error ? *error : tempError );
1659 res = mCrs3D.isValid();
1660 break;
1661 }
1662 }
1663 }
1664 return res;
1665}
1666
1667bool QgsProject::_getMapLayers( const QDomDocument &doc, QList<QDomNode> &brokenNodes, Qgis::ProjectReadFlags flags )
1668{
1670
1671 // Layer order is set by the restoring the legend settings from project file.
1672 // This is done on the 'readProject( ... )' signal
1673
1674 QDomElement layerElement = doc.documentElement().firstChildElement( QStringLiteral( "projectlayers" ) ).firstChildElement( QStringLiteral( "maplayer" ) );
1675
1676 // process the map layer nodes
1677
1678 if ( layerElement.isNull() ) // if we have no layers to process, bail
1679 {
1680 return true; // Decided to return "true" since it's
1681 // possible for there to be a project with no
1682 // layers; but also, more imporantly, this
1683 // would cause the tests/qgsproject to fail
1684 // since the test suite doesn't currently
1685 // support test layers
1686 }
1687
1688 bool returnStatus = true;
1689 int numLayers = 0;
1690
1691 while ( ! layerElement.isNull() )
1692 {
1693 numLayers++;
1694 layerElement = layerElement.nextSiblingElement( QStringLiteral( "maplayer" ) );
1695 }
1696
1697 // order layers based on their dependencies
1698 QgsScopedRuntimeProfile profile( tr( "Sorting layers" ), QStringLiteral( "projectload" ) );
1699 const QgsLayerDefinition::DependencySorter depSorter( doc );
1700 if ( depSorter.hasCycle() )
1701 return false;
1702
1703 // Missing a dependency? We still load all the layers, otherwise the project is completely broken!
1704 if ( depSorter.hasMissingDependency() )
1705 returnStatus = false;
1706
1707 emit layerLoaded( 0, numLayers );
1708
1709 const QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
1710 const int totalLayerCount = sortedLayerNodes.count();
1711
1712 QVector<QDomNode> parallelLoading;
1713 QMap<QString, QgsDataProvider *> loadedProviders;
1714
1716 {
1717 profile.switchTask( tr( "Load providers in parallel" ) );
1718 for ( const QDomNode &node : sortedLayerNodes )
1719 {
1720 const QDomElement element = node.toElement();
1721 if ( element.attribute( QStringLiteral( "embedded" ) ) != QLatin1String( "1" ) )
1722 {
1723 const QString layerId = node.namedItem( QStringLiteral( "id" ) ).toElement().text();
1724 if ( !depSorter.isLayerDependent( layerId ) )
1725 {
1726 const QDomNode mnl = element.namedItem( QStringLiteral( "provider" ) );
1727 const QDomElement mne = mnl.toElement();
1728 const QString provider = mne.text();
1730 if ( meta && meta->providerCapabilities().testFlag( QgsProviderMetadata::ParallelCreateProvider ) )
1731 {
1732 parallelLoading.append( node );
1733 continue;
1734 }
1735 }
1736 }
1737 }
1738
1739 QgsReadWriteContext context;
1740 context.setPathResolver( pathResolver() );
1741 if ( !parallelLoading.isEmpty() )
1742 preloadProviders( parallelLoading, context, loadedProviders, projectFlagsToLayerReadFlags( flags, mFlags ), sortedLayerNodes.count() );
1743 }
1744
1745 int i = loadedProviders.count();
1746 for ( const QDomNode &node : std::as_const( sortedLayerNodes ) )
1747 {
1748 const QDomElement element = node.toElement();
1749 const QString name = translate( QStringLiteral( "project:layers:%1" ).arg( node.namedItem( QStringLiteral( "id" ) ).toElement().text() ), node.namedItem( QStringLiteral( "layername" ) ).toElement().text() );
1750 if ( !name.isNull() )
1751 emit loadingLayer( tr( "Loading layer %1" ).arg( name ) );
1752
1753 profile.switchTask( name );
1754 if ( element.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
1755 {
1756 createEmbeddedLayer( element.attribute( QStringLiteral( "id" ) ), readPath( element.attribute( QStringLiteral( "project" ) ) ), brokenNodes, true, flags );
1757 }
1758 else
1759 {
1760 QgsReadWriteContext context;
1761 context.setPathResolver( pathResolver() );
1762 context.setProjectTranslator( this );
1764 QString layerId = element.namedItem( QStringLiteral( "id" ) ).toElement().text();
1765
1766 if ( !addLayer( element, brokenNodes, context, flags, loadedProviders.take( layerId ) ) )
1767 {
1768 returnStatus = false;
1769 }
1770 const auto messages = context.takeMessages();
1771 if ( !messages.isEmpty() )
1772 {
1773 emit loadingLayerMessageReceived( tr( "Loading layer %1" ).arg( name ), messages );
1774 }
1775 }
1776 emit layerLoaded( i + 1, totalLayerCount );
1777 i++;
1778 }
1779
1780 return returnStatus;
1781}
1782
1783bool QgsProject::addLayer( const QDomElement &layerElem,
1784 QList<QDomNode> &brokenNodes,
1785 QgsReadWriteContext &context,
1787 QgsDataProvider *provider )
1788{
1790
1791 const QString type = layerElem.attribute( QStringLiteral( "type" ) );
1792 QgsDebugMsgLevel( "Layer type is " + type, 4 );
1793 std::unique_ptr<QgsMapLayer> mapLayer;
1794
1795 QgsScopedRuntimeProfile profile( tr( "Create layer" ), QStringLiteral( "projectload" ) );
1796
1797 bool ok = false;
1798 const Qgis::LayerType layerType( QgsMapLayerFactory::typeFromString( type, ok ) );
1799 if ( !ok )
1800 {
1801 QgsDebugError( QStringLiteral( "Unknown layer type \"%1\"" ).arg( type ) );
1802 return false;
1803 }
1804
1805 switch ( layerType )
1806 {
1808 mapLayer = std::make_unique<QgsVectorLayer>();
1809 break;
1810
1812 mapLayer = std::make_unique<QgsRasterLayer>();
1813 break;
1814
1816 mapLayer = std::make_unique<QgsMeshLayer>();
1817 break;
1818
1820 mapLayer = std::make_unique<QgsVectorTileLayer>();
1821 break;
1822
1824 mapLayer = std::make_unique<QgsPointCloudLayer>();
1825 break;
1826
1828 mapLayer = std::make_unique<QgsTiledSceneLayer>();
1829 break;
1830
1832 {
1833 const QString typeName = layerElem.attribute( QStringLiteral( "name" ) );
1834 mapLayer.reset( QgsApplication::pluginLayerRegistry()->createLayer( typeName ) );
1835 break;
1836 }
1837
1839 {
1840 const QgsAnnotationLayer::LayerOptions options( mTransformContext );
1841 mapLayer = std::make_unique<QgsAnnotationLayer>( QString(), options );
1842 break;
1843 }
1844
1846 {
1847 const QgsGroupLayer::LayerOptions options( mTransformContext );
1848 mapLayer = std::make_unique<QgsGroupLayer>( QString(), options );
1849 break;
1850 }
1851 }
1852
1853 if ( !mapLayer )
1854 {
1855 QgsDebugError( QStringLiteral( "Unable to create layer" ) );
1856 return false;
1857 }
1858
1859 Q_CHECK_PTR( mapLayer ); // NOLINT
1860
1861 // This is tricky: to avoid a leak we need to check if the layer was already in the store
1862 // because if it was, the newly created layer will not be added to the store and it would leak.
1863 const QString layerId { layerElem.namedItem( QStringLiteral( "id" ) ).toElement().text() };
1864 Q_ASSERT( ! layerId.isEmpty() );
1865 const bool layerWasStored { layerStore()->mapLayer( layerId ) != nullptr };
1866
1867 // have the layer restore state that is stored in Dom node
1868 QgsMapLayer::ReadFlags layerFlags = projectFlagsToLayerReadFlags( flags, mFlags );
1869
1870 profile.switchTask( tr( "Load layer source" ) );
1871 const bool layerIsValid = mapLayer->readLayerXml( layerElem, context, layerFlags, provider ) && mapLayer->isValid();
1872
1873 // apply specific settings to vector layer
1874 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1875 {
1876 vl->setReadExtentFromXml( layerFlags & QgsMapLayer::FlagReadExtentFromXml );
1877 if ( vl->dataProvider() )
1878 {
1880 vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, evaluateDefaultValues );
1881 }
1882 }
1883
1884 profile.switchTask( tr( "Add layer to project" ) );
1885 QList<QgsMapLayer *> newLayers;
1886 newLayers << mapLayer.get();
1887 if ( layerIsValid || flags & Qgis::ProjectReadFlag::DontResolveLayers )
1888 {
1889 emit readMapLayer( mapLayer.get(), layerElem );
1890 addMapLayers( newLayers );
1891 // Try to resolve references here (this is necessary to set up joined fields that will be possibly used by
1892 // virtual layers that point to this layer's joined field in their query otherwise they won't be valid ),
1893 // a second attempt to resolve references will be done after all layers are loaded
1894 // see https://github.com/qgis/QGIS/issues/46834
1895 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1896 {
1897 vLayer->joinBuffer()->resolveReferences( this );
1898 }
1899 }
1900 else
1901 {
1902 // It's a bad layer: do not add to legend (the user will decide if she wants to do so)
1903 addMapLayers( newLayers, false );
1904 newLayers.first();
1905 QgsDebugError( "Unable to load " + type + " layer" );
1906 brokenNodes.push_back( layerElem );
1907 }
1908
1909 const bool wasEditable = layerElem.attribute( QStringLiteral( "editable" ), QStringLiteral( "0" ) ).toInt();
1910 if ( wasEditable )
1911 {
1912 mapLayer->setCustomProperty( QStringLiteral( "_layer_was_editable" ), true );
1913 }
1914 else
1915 {
1916 mapLayer->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
1917 }
1918
1919 // It should be safe to delete the layer now if layer was stored, because all the store
1920 // had to to was to reset the data source in case the validity changed.
1921 if ( ! layerWasStored )
1922 {
1923 mapLayer.release();
1924 }
1925
1926 return layerIsValid;
1927}
1928
1929bool QgsProject::read( const QString &filename, Qgis::ProjectReadFlags flags )
1930{
1932
1933 mFile.setFileName( filename );
1934 mCachedHomePath.clear();
1935 mProjectScope.reset();
1936
1937 return read( flags );
1938}
1939
1941{
1943
1944 const QString filename = mFile.fileName();
1945 bool returnValue;
1946
1947 if ( QgsProjectStorage *storage = projectStorage() )
1948 {
1949 QTemporaryFile inDevice;
1950 if ( !inDevice.open() )
1951 {
1952 setError( tr( "Unable to open %1" ).arg( inDevice.fileName() ) );
1953 return false;
1954 }
1955
1956 QgsReadWriteContext context;
1957 context.setProjectTranslator( this );
1958 if ( !storage->readProject( filename, &inDevice, context ) )
1959 {
1960 QString err = tr( "Unable to open %1" ).arg( filename );
1961 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
1962 if ( !messages.isEmpty() )
1963 err += QStringLiteral( "\n\n" ) + messages.last().message();
1964 setError( err );
1965 return false;
1966 }
1967 returnValue = unzip( inDevice.fileName(), flags ); // calls setError() if returning false
1968 }
1969 else
1970 {
1971 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
1972 {
1973 returnValue = unzip( mFile.fileName(), flags );
1974 }
1975 else
1976 {
1977 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
1978 const QFileInfo finfo( mFile.fileName() );
1979 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
1980 if ( QFile( attachmentsZip ).exists() )
1981 {
1982 std::unique_ptr<QgsArchive> archive( new QgsArchive() );
1983 if ( archive->unzip( attachmentsZip ) )
1984 {
1985 releaseHandlesToProjectArchive();
1986 mArchive = std::move( archive );
1987 }
1988 }
1989 returnValue = readProjectFile( mFile.fileName(), flags );
1990 }
1991
1992 //on translation we should not change the filename back
1993 if ( !mTranslator )
1994 {
1995 mFile.setFileName( filename );
1996 mCachedHomePath.clear();
1997 mProjectScope.reset();
1998 }
1999 else
2000 {
2001 //but delete the translator
2002 mTranslator.reset( nullptr );
2003 }
2004 }
2005 emit homePathChanged();
2006 return returnValue;
2007}
2008
2009bool QgsProject::readProjectFile( const QString &filename, Qgis::ProjectReadFlags flags )
2010{
2012
2013 // avoid multiple emission of snapping updated signals
2014 ScopedIntIncrementor snapSignalBlock( &mBlockSnappingUpdates );
2015
2016 QFile projectFile( filename );
2017 clearError();
2018
2019 QgsApplication::profiler()->clear( QStringLiteral( "projectload" ) );
2020 QgsScopedRuntimeProfile profile( tr( "Setting up translations" ), QStringLiteral( "projectload" ) );
2021
2022 const QString localeFileName = QStringLiteral( "%1_%2" ).arg( QFileInfo( projectFile.fileName() ).baseName(), QgsApplication::settingsLocaleUserLocale->value() );
2023
2024 if ( QFile( QStringLiteral( "%1/%2.qm" ).arg( QFileInfo( projectFile.fileName() ).absolutePath(), localeFileName ) ).exists() )
2025 {
2026 mTranslator.reset( new QTranslator() );
2027 ( void )mTranslator->load( localeFileName, QFileInfo( projectFile.fileName() ).absolutePath() );
2028 }
2029
2030 profile.switchTask( tr( "Reading project file" ) );
2031 std::unique_ptr<QDomDocument> doc( new QDomDocument( QStringLiteral( "qgis" ) ) );
2032
2033 if ( !projectFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
2034 {
2035 projectFile.close();
2036
2037 setError( tr( "Unable to open %1" ).arg( projectFile.fileName() ) );
2038
2039 return false;
2040 }
2041
2042 QTextStream textStream( &projectFile );
2043#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2044 textStream.setCodec( "UTF-8" );
2045#endif
2046 QString projectString = textStream.readAll();
2047 projectFile.close();
2048
2049 for ( int i = 0; i < 32; i++ )
2050 {
2051 if ( i == 9 || i == 10 || i == 13 )
2052 {
2053 continue;
2054 }
2055 projectString.replace( QChar( i ), QStringLiteral( "%1%2%1" ).arg( FONTMARKER_CHR_FIX, QString::number( i ) ) );
2056 }
2057
2058 // location of problem associated with errorMsg
2059 int line, column;
2060 QString errorMsg;
2061 if ( !doc->setContent( projectString, &errorMsg, &line, &column ) )
2062 {
2063 const QString errorString = tr( "Project file read error in file %1: %2 at line %3 column %4" )
2064 .arg( projectFile.fileName(), errorMsg ).arg( line ).arg( column );
2065 QgsDebugError( errorString );
2066 setError( errorString );
2067
2068 return false;
2069 }
2070
2071 projectFile.close();
2072
2073 QgsDebugMsgLevel( "Opened document " + projectFile.fileName(), 2 );
2074
2075 // get project version string, if any
2076 const QgsProjectVersion fileVersion = getVersion( *doc );
2077 const QgsProjectVersion thisVersion( Qgis::version() );
2078
2079 profile.switchTask( tr( "Updating project file" ) );
2080 if ( thisVersion > fileVersion )
2081 {
2082 const bool isOlderMajorVersion = fileVersion.majorVersion() < thisVersion.majorVersion();
2083
2084 if ( isOlderMajorVersion )
2085 {
2086 QgsLogger::warning( "Loading a file that was saved with an older "
2087 "version of qgis (saved in " + fileVersion.text() +
2088 ", loaded in " + Qgis::version() +
2089 "). Problems may occur." );
2090 }
2091
2092 QgsProjectFileTransform projectFile( *doc, fileVersion );
2093
2094 // Shows a warning when an old project file is read.
2096 emit oldProjectVersionWarning( fileVersion.text() );
2098 emit readVersionMismatchOccurred( fileVersion.text() );
2099
2100 projectFile.updateRevision( thisVersion );
2101 }
2102 else if ( fileVersion > thisVersion )
2103 {
2104 QgsLogger::warning( "Loading a file that was saved with a newer "
2105 "version of qgis (saved in " + fileVersion.text() +
2106 ", loaded in " + Qgis::version() +
2107 "). Problems may occur." );
2108
2109 emit readVersionMismatchOccurred( fileVersion.text() );
2110 }
2111
2112 // start new project, just keep the file name and auxiliary storage
2113 profile.switchTask( tr( "Creating auxiliary storage" ) );
2114 const QString fileName = mFile.fileName();
2115
2116 const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
2117 const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
2118
2119 // NOTE [ND] -- I suspect this is wrong, as the archive may contain any number of non-auxiliary
2120 // storage related files from the previously loaded project.
2121 std::unique_ptr<QgsAuxiliaryStorage> aStorage = std::move( mAuxiliaryStorage );
2122 std::unique_ptr<QgsArchive> archive = std::move( mArchive );
2123
2124 // don't emit xxxChanged signals during the clear() call, as we'll be emitting
2125 // them again after reading the properties from the project file
2126 mBlockChangeSignalsDuringClear = true;
2127 clear();
2128 mBlockChangeSignalsDuringClear = false;
2129
2130 // this is ugly, but clear() will have created a new archive and started populating it. We
2131 // need to release handles to this archive now as the subsequent call to move will need
2132 // to delete it, and requires free access to do so.
2133 releaseHandlesToProjectArchive();
2134
2135 mAuxiliaryStorage = std::move( aStorage );
2136 mArchive = std::move( archive );
2137
2138 mFile.setFileName( fileName );
2139 mCachedHomePath.clear();
2140 mProjectScope.reset();
2141 mSaveVersion = fileVersion;
2142
2143 // now get any properties
2144 profile.switchTask( tr( "Reading properties" ) );
2145 _getProperties( *doc, mProperties );
2146
2147 // now get the data defined server properties
2148 mDataDefinedServerProperties = getDataDefinedServerProperties( *doc, dataDefinedServerPropertyDefinitions() );
2149
2150 QgsDebugMsgLevel( QString::number( mProperties.count() ) + " properties read", 2 );
2151
2152#if 0
2153 dump_( mProperties );
2154#endif
2155
2156 // get older style project title
2157 QString oldTitle;
2158 _getTitle( *doc, oldTitle );
2159
2160 readProjectFileMetadata( *doc, mSaveUser, mSaveUserFull, mSaveDateTime );
2161
2162 const QDomNodeList homePathNl = doc->elementsByTagName( QStringLiteral( "homePath" ) );
2163 if ( homePathNl.count() > 0 )
2164 {
2165 const QDomElement homePathElement = homePathNl.at( 0 ).toElement();
2166 const QString homePath = homePathElement.attribute( QStringLiteral( "path" ) );
2167 if ( !homePath.isEmpty() )
2169 }
2170 else
2171 {
2172 emit homePathChanged();
2173 }
2174
2175 const QColor backgroundColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), 255 ),
2176 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), 255 ),
2177 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), 255 ) );
2179 const QColor selectionColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), 255 ),
2180 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), 255 ),
2181 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), 255 ),
2182 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), 255 ) );
2184
2185
2186 const QString distanceUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QString() );
2187 if ( !distanceUnitString.isEmpty() )
2188 setDistanceUnits( QgsUnitTypes::decodeDistanceUnit( distanceUnitString ) );
2189
2190 const QString areaUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QString() );
2191 if ( !areaUnitString.isEmpty() )
2192 setAreaUnits( QgsUnitTypes::decodeAreaUnit( areaUnitString ) );
2193
2194 QgsReadWriteContext context;
2195 context.setPathResolver( pathResolver() );
2196 context.setProjectTranslator( this );
2197
2198 //crs
2200 if ( readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), 0 ) )
2201 {
2202 // first preference - dedicated projectCrs node
2203 const QDomNode srsNode = doc->documentElement().namedItem( QStringLiteral( "projectCrs" ) );
2204 if ( !srsNode.isNull() )
2205 {
2206 projectCrs.readXml( srsNode );
2207 }
2208
2209 if ( !projectCrs.isValid() )
2210 {
2211 const QString projCrsString = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSProj4String" ) );
2212 const long currentCRS = readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSID" ), -1 );
2213 const QString authid = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCrs" ) );
2214
2215 // authid should be prioritized over all
2216 const bool isUserAuthId = authid.startsWith( QLatin1String( "USER:" ), Qt::CaseInsensitive );
2217 if ( !authid.isEmpty() && !isUserAuthId )
2218 projectCrs = QgsCoordinateReferenceSystem( authid );
2219
2220 // try the CRS
2221 if ( !projectCrs.isValid() && currentCRS >= 0 )
2222 {
2223 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2224 }
2225
2226 // if that didn't produce a match, try the proj.4 string
2227 if ( !projCrsString.isEmpty() && ( authid.isEmpty() || isUserAuthId ) && ( !projectCrs.isValid() || projectCrs.toProj() != projCrsString ) )
2228 {
2229 projectCrs = QgsCoordinateReferenceSystem::fromProj( projCrsString );
2230 }
2231
2232 // last just take the given id
2233 if ( !projectCrs.isValid() )
2234 {
2235 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2236 }
2237 }
2238 }
2239 mCrs = projectCrs;
2240
2241 //vertical CRS
2242 {
2244 const QDomNode verticalCrsNode = doc->documentElement().namedItem( QStringLiteral( "verticalCrs" ) );
2245 if ( !verticalCrsNode.isNull() )
2246 {
2247 verticalCrs.readXml( verticalCrsNode );
2248 }
2249 mVerticalCrs = verticalCrs;
2250 }
2251 rebuildCrs3D();
2252
2253 QStringList datumErrors;
2254 if ( !mTransformContext.readXml( doc->documentElement(), context, datumErrors ) && !datumErrors.empty() )
2255 {
2256 emit missingDatumTransforms( datumErrors );
2257 }
2259
2260 // map shading
2261 const QDomNode elevationShadingNode = doc->documentElement().namedItem( QStringLiteral( "elevation-shading-renderer" ) );
2262 if ( !elevationShadingNode.isNull() )
2263 {
2264 mElevationShadingRenderer.readXml( elevationShadingNode.toElement(), context );
2265 }
2267
2268
2269 //add variables defined in project file - do this early in the reading cycle, as other components
2270 //(e.g. layouts) may depend on these variables
2271 const QStringList variableNames = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ) );
2272 const QStringList variableValues = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ) );
2273
2274 mCustomVariables.clear();
2275 if ( variableNames.length() == variableValues.length() )
2276 {
2277 for ( int i = 0; i < variableNames.length(); ++i )
2278 {
2279 mCustomVariables.insert( variableNames.at( i ), variableValues.at( i ) );
2280 }
2281 }
2282 else
2283 {
2284 QgsMessageLog::logMessage( tr( "Project Variables Invalid" ), tr( "The project contains invalid variable settings." ) );
2285 }
2286
2287 QDomElement element = doc->documentElement().firstChildElement( QStringLiteral( "projectMetadata" ) );
2288
2289 if ( !element.isNull() )
2290 {
2291 mMetadata.readMetadataXml( element );
2292 }
2293 else
2294 {
2295 // older project, no metadata => remove auto generated metadata which is populated on QgsProject::clear()
2296 mMetadata = QgsProjectMetadata();
2297 }
2298 if ( mMetadata.title().isEmpty() && !oldTitle.isEmpty() )
2299 {
2300 // upgrade older title storage to storing within project metadata.
2301 mMetadata.setTitle( oldTitle );
2302 }
2303 emit metadataChanged();
2304
2305 // Transaction mode
2306 element = doc->documentElement().firstChildElement( QStringLiteral( "transaction" ) );
2307 if ( !element.isNull() )
2308 {
2309 mTransactionMode = qgsEnumKeyToValue( element.attribute( QStringLiteral( "mode" ) ), Qgis::TransactionMode::Disabled );
2310 }
2311 else
2312 {
2313 // maybe older project => try read autotransaction
2314 element = doc->documentElement().firstChildElement( QStringLiteral( "autotransaction" ) );
2315 if ( ! element.isNull() )
2316 {
2317 mTransactionMode = static_cast<Qgis::TransactionMode>( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() );
2318 }
2319 }
2320
2321 // read the layer tree from project file
2322 profile.switchTask( tr( "Loading layer tree" ) );
2323 mRootGroup->setCustomProperty( QStringLiteral( "loading" ), 1 );
2324
2325 QDomElement layerTreeElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
2326 if ( !layerTreeElem.isNull() )
2327 {
2328 // Use a temporary tree to read the nodes to prevent signals being delivered to the models
2329 QgsLayerTree tempTree;
2330 tempTree.readChildrenFromXml( layerTreeElem, context );
2331 mRootGroup->insertChildNodes( -1, tempTree.abandonChildren() );
2332 }
2333 else
2334 {
2335 QgsLayerTreeUtils::readOldLegend( mRootGroup, doc->documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
2336 }
2337
2338 mLayerTreeRegistryBridge->setEnabled( false );
2339
2340 // get the map layers
2341 profile.switchTask( tr( "Reading map layers" ) );
2342
2343 loadProjectFlags( doc.get() );
2344
2345 QList<QDomNode> brokenNodes;
2346 const bool clean = _getMapLayers( *doc, brokenNodes, flags );
2347
2348 // review the integrity of the retrieved map layers
2349 if ( !clean && !( flags & Qgis::ProjectReadFlag::DontResolveLayers ) )
2350 {
2351 QgsDebugError( QStringLiteral( "Unable to get map layers from project file." ) );
2352
2353 if ( !brokenNodes.isEmpty() )
2354 {
2355 QgsDebugError( "there are " + QString::number( brokenNodes.size() ) + " broken layers" );
2356 }
2357
2358 // we let a custom handler decide what to do with missing layers
2359 // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
2360 mBadLayerHandler->handleBadLayers( brokenNodes );
2361 }
2362
2363 mMainAnnotationLayer->readLayerXml( doc->documentElement().firstChildElement( QStringLiteral( "main-annotation-layer" ) ), context );
2364 mMainAnnotationLayer->setTransformContext( mTransformContext );
2365
2366 // load embedded groups and layers
2367 profile.switchTask( tr( "Loading embedded layers" ) );
2368 loadEmbeddedNodes( mRootGroup, flags );
2369
2370 // Resolve references to other layers
2371 // Needs to be done here once all dependent layers are loaded
2372 profile.switchTask( tr( "Resolving layer references" ) );
2373 QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
2374 for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
2375 {
2376 it.value()->resolveReferences( this );
2377 }
2378
2379 mLayerTreeRegistryBridge->setEnabled( true );
2380
2381 // now that layers are loaded, we can resolve layer tree's references to the layers
2382 profile.switchTask( tr( "Resolving references" ) );
2383 mRootGroup->resolveReferences( this );
2384
2385 // we need to migrate old fashion designed QgsSymbolLayerReference to new ones
2386 if ( QgsProjectVersion( 3, 28, 0 ) > mSaveVersion )
2387 {
2391 }
2392
2393 if ( !layerTreeElem.isNull() )
2394 {
2395 mRootGroup->readLayerOrderFromXml( layerTreeElem );
2396 }
2397
2398 // Load pre 3.0 configuration
2399 const QDomElement layerTreeCanvasElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-canvas" ) );
2400 if ( !layerTreeCanvasElem.isNull( ) )
2401 {
2402 mRootGroup->readLayerOrderFromXml( layerTreeCanvasElem );
2403 }
2404
2405 // Convert pre 3.4 to create layers flags
2406 if ( QgsProjectVersion( 3, 4, 0 ) > mSaveVersion )
2407 {
2408 const QStringList requiredLayerIds = readListEntry( QStringLiteral( "RequiredLayers" ), QStringLiteral( "Layers" ) );
2409 for ( const QString &layerId : requiredLayerIds )
2410 {
2411 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2412 {
2413 layer->setFlags( layer->flags() & ~QgsMapLayer::Removable );
2414 }
2415 }
2416 const QStringList disabledLayerIds = readListEntry( QStringLiteral( "Identify" ), QStringLiteral( "/disabledLayers" ) );
2417 for ( const QString &layerId : disabledLayerIds )
2418 {
2419 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2420 {
2421 layer->setFlags( layer->flags() & ~QgsMapLayer::Identifiable );
2422 }
2423 }
2424 }
2425
2426 // Convert pre 3.26 default styles
2427 if ( QgsProjectVersion( 3, 26, 0 ) > mSaveVersion )
2428 {
2429 // Convert default symbols
2430 QString styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Marker" ) );
2431 if ( !styleName.isEmpty() )
2432 {
2433 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2435 }
2436 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Line" ) );
2437 if ( !styleName.isEmpty() )
2438 {
2439 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2441 }
2442 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Fill" ) );
2443 if ( !styleName.isEmpty() )
2444 {
2445 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2447 }
2448 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ) );
2449 if ( !styleName.isEmpty() )
2450 {
2451 std::unique_ptr<QgsColorRamp> colorRamp( QgsStyle::defaultStyle()->colorRamp( styleName ) );
2452 styleSettings()->setDefaultColorRamp( colorRamp.get() );
2453 }
2454
2455 // Convert randomize default symbol fill color
2456 styleSettings()->setRandomizeDefaultSymbolColor( readBoolEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ), true ) );
2457
2458 // Convert default symbol opacity
2459 double opacity = 1.0;
2460 bool ok = false;
2461 // upgrade old setting
2462 double alpha = readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/AlphaInt" ), 255, &ok );
2463 if ( ok )
2464 opacity = alpha / 255.0;
2465 double newOpacity = readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Opacity" ), 1.0, &ok );
2466 if ( ok )
2467 opacity = newOpacity;
2469
2470 // Cleanup
2471 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Marker" ) );
2472 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Line" ) );
2473 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Fill" ) );
2474 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ) );
2475 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ) );
2476 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/AlphaInt" ) );
2477 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Opacity" ) );
2478 }
2479
2480 // After bad layer handling we might still have invalid layers,
2481 // store them in case the user wanted to handle them later
2482 // or wanted to pass them through when saving
2484 {
2485 profile.switchTask( tr( "Storing original layer properties" ) );
2486 QgsLayerTreeUtils::storeOriginalLayersProperties( mRootGroup, doc.get() );
2487 }
2488
2489 mRootGroup->removeCustomProperty( QStringLiteral( "loading" ) );
2490
2491 profile.switchTask( tr( "Loading map themes" ) );
2492 mMapThemeCollection.reset( new QgsMapThemeCollection( this ) );
2494 mMapThemeCollection->readXml( *doc );
2495
2496 profile.switchTask( tr( "Loading label settings" ) );
2497 mLabelingEngineSettings->readSettingsFromProject( this );
2499
2500 profile.switchTask( tr( "Loading annotations" ) );
2501 mAnnotationManager->readXml( doc->documentElement(), context );
2503 {
2504 profile.switchTask( tr( "Loading layouts" ) );
2505 mLayoutManager->readXml( doc->documentElement(), *doc );
2506 }
2507
2509 {
2510 profile.switchTask( tr( "Loading 3D Views" ) );
2511 m3DViewsManager->readXml( doc->documentElement(), *doc );
2512 }
2513
2514 profile.switchTask( tr( "Loading bookmarks" ) );
2515 mBookmarkManager->readXml( doc->documentElement(), *doc );
2516
2517 profile.switchTask( tr( "Loading sensors" ) );
2518 mSensorManager->readXml( doc->documentElement(), *doc );
2519
2520 // reassign change dependencies now that all layers are loaded
2521 QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2522 for ( QMap<QString, QgsMapLayer *>::iterator it = existingMaps.begin(); it != existingMaps.end(); ++it )
2523 {
2524 it.value()->setDependencies( it.value()->dependencies() );
2525 }
2526
2527 profile.switchTask( tr( "Loading snapping settings" ) );
2528 mSnappingConfig.readProject( *doc );
2529 mAvoidIntersectionsMode = static_cast<Qgis::AvoidIntersectionsMode>( readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( Qgis::AvoidIntersectionsMode::AvoidIntersectionsLayers ) ) );
2530
2531 profile.switchTask( tr( "Loading view settings" ) );
2532 // restore older project scales settings
2533 mViewSettings->setUseProjectScales( readBoolEntry( QStringLiteral( "Scales" ), QStringLiteral( "/useProjectScales" ) ) );
2534 const QStringList scales = readListEntry( QStringLiteral( "Scales" ), QStringLiteral( "/ScalesList" ) );
2535 QVector<double> res;
2536 for ( const QString &scale : scales )
2537 {
2538 const QStringList parts = scale.split( ':' );
2539 if ( parts.size() != 2 )
2540 continue;
2541
2542 bool ok = false;
2543 const double denominator = QLocale().toDouble( parts[1], &ok );
2544 if ( ok )
2545 {
2546 res << denominator;
2547 }
2548 }
2549 mViewSettings->setMapScales( res );
2550 const QDomElement viewSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectViewSettings" ) );
2551 if ( !viewSettingsElement.isNull() )
2552 mViewSettings->readXml( viewSettingsElement, context );
2553
2554 // restore style settings
2555 profile.switchTask( tr( "Loading style properties" ) );
2556 const QDomElement styleSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectStyleSettings" ) );
2557 if ( !styleSettingsElement.isNull() )
2558 {
2559 mStyleSettings->removeProjectStyle();
2560 mStyleSettings->readXml( styleSettingsElement, context, flags );
2561 }
2562
2563 // restore time settings
2564 profile.switchTask( tr( "Loading temporal settings" ) );
2565 const QDomElement timeSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectTimeSettings" ) );
2566 if ( !timeSettingsElement.isNull() )
2567 mTimeSettings->readXml( timeSettingsElement, context );
2568
2569
2570 profile.switchTask( tr( "Loading elevation properties" ) );
2571 const QDomElement elevationPropertiesElement = doc->documentElement().firstChildElement( QStringLiteral( "ElevationProperties" ) );
2572 if ( !elevationPropertiesElement.isNull() )
2573 mElevationProperties->readXml( elevationPropertiesElement, context );
2574 mElevationProperties->resolveReferences( this );
2575
2576 profile.switchTask( tr( "Loading display settings" ) );
2577 {
2578 const QDomElement displaySettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectDisplaySettings" ) );
2579 if ( !displaySettingsElement.isNull() )
2580 mDisplaySettings->readXml( displaySettingsElement, context );
2581 }
2582
2583 profile.switchTask( tr( "Loading GPS settings" ) );
2584 {
2585 const QDomElement gpsSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectGpsSettings" ) );
2586 if ( !gpsSettingsElement.isNull() )
2587 mGpsSettings->readXml( gpsSettingsElement, context );
2588 mGpsSettings->resolveReferences( this );
2589 }
2590
2591 profile.switchTask( tr( "Updating variables" ) );
2593 profile.switchTask( tr( "Updating CRS" ) );
2594 emit crsChanged();
2595 if ( verticalCrs() != oldVerticalCrs )
2596 emit verticalCrsChanged();
2597 if ( mCrs3D != oldCrs3D )
2598 emit crs3DChanged();
2599 emit ellipsoidChanged( ellipsoid() );
2600
2601 // read the project: used by map canvas and legend
2602 profile.switchTask( tr( "Reading external settings" ) );
2603 emit readProject( *doc );
2604 emit readProjectWithContext( *doc, context );
2605
2606 profile.switchTask( tr( "Updating interface" ) );
2607
2608 snapSignalBlock.release();
2609 if ( !mBlockSnappingUpdates )
2610 emit snappingConfigChanged( mSnappingConfig );
2611
2614 emit projectColorsChanged();
2615
2616 // if all went well, we're allegedly in pristine state
2617 if ( clean )
2618 setDirty( false );
2619
2620 QgsDebugMsgLevel( QStringLiteral( "Project save user: %1" ).arg( mSaveUser ), 2 );
2621 QgsDebugMsgLevel( QStringLiteral( "Project save user: %1" ).arg( mSaveUserFull ), 2 );
2622
2626
2627 if ( mTranslator )
2628 {
2629 //project possibly translated -> rename it with locale postfix
2630 const QString newFileName( QStringLiteral( "%1/%2.qgs" ).arg( QFileInfo( projectFile.fileName() ).absolutePath(), localeFileName ) );
2631 setFileName( newFileName );
2632
2633 if ( write() )
2634 {
2635 setTitle( localeFileName );
2636 QgsMessageLog::logMessage( tr( "Translated project saved with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Success );
2637 }
2638 else
2639 {
2640 QgsMessageLog::logMessage( tr( "Error saving translated project with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Critical );
2641 }
2642 }
2643
2644 // lastly, make any previously editable layers editable
2645 const QMap<QString, QgsMapLayer *> loadedLayers = mapLayers();
2646 for ( auto it = loadedLayers.constBegin(); it != loadedLayers.constEnd(); ++it )
2647 {
2648 if ( it.value()->isValid() && it.value()->customProperty( QStringLiteral( "_layer_was_editable" ) ).toBool() )
2649 {
2650 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( it.value() ) )
2651 vl->startEditing();
2652 it.value()->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
2653 }
2654 }
2655
2656 return true;
2657}
2658
2659bool QgsProject::loadEmbeddedNodes( QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
2660{
2662
2663 bool valid = true;
2664 const auto constChildren = group->children();
2665 for ( QgsLayerTreeNode *child : constChildren )
2666 {
2667 if ( QgsLayerTree::isGroup( child ) )
2668 {
2669 QgsLayerTreeGroup *childGroup = QgsLayerTree::toGroup( child );
2670 if ( childGroup->customProperty( QStringLiteral( "embedded" ) ).toInt() )
2671 {
2672 // make sure to convert the path from relative to absolute
2673 const QString projectPath = readPath( childGroup->customProperty( QStringLiteral( "embedded_project" ) ).toString() );
2674 childGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectPath );
2675 QgsLayerTreeGroup *newGroup = createEmbeddedGroup( childGroup->name(), projectPath, childGroup->customProperty( QStringLiteral( "embedded-invisible-layers" ) ).toStringList(), flags );
2676 if ( newGroup )
2677 {
2678 QList<QgsLayerTreeNode *> clonedChildren;
2679 const QList<QgsLayerTreeNode *> constChildren = newGroup->children();
2680 clonedChildren.reserve( constChildren.size() );
2681 for ( QgsLayerTreeNode *newGroupChild : constChildren )
2682 clonedChildren << newGroupChild->clone();
2683 delete newGroup;
2684
2685 childGroup->insertChildNodes( 0, clonedChildren );
2686 }
2687 }
2688 else
2689 {
2690 loadEmbeddedNodes( childGroup, flags );
2691 }
2692 }
2693 else if ( QgsLayerTree::isLayer( child ) )
2694 {
2695 if ( child->customProperty( QStringLiteral( "embedded" ) ).toInt() )
2696 {
2697 QList<QDomNode> brokenNodes;
2698 if ( ! createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), readPath( child->customProperty( QStringLiteral( "embedded_project" ) ).toString() ), brokenNodes, true, flags ) )
2699 {
2700 valid = valid && false;
2701 }
2702 }
2703 }
2704
2705 }
2706
2707 return valid;
2708}
2709
2711{
2712 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
2714
2715 return mCustomVariables;
2716}
2717
2718void QgsProject::setCustomVariables( const QVariantMap &variables )
2719{
2721
2722 if ( variables == mCustomVariables )
2723 return;
2724
2725 //write variable to project
2726 QStringList variableNames;
2727 QStringList variableValues;
2728
2729 QVariantMap::const_iterator it = variables.constBegin();
2730 for ( ; it != variables.constEnd(); ++it )
2731 {
2732 variableNames << it.key();
2733 variableValues << it.value().toString();
2734 }
2735
2736 writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ), variableNames );
2737 writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ), variableValues );
2738
2739 mCustomVariables = variables;
2740 mProjectScope.reset();
2741
2743}
2744
2746{
2748
2749 *mLabelingEngineSettings = settings;
2751}
2752
2754{
2756
2757 return *mLabelingEngineSettings;
2758}
2759
2761{
2763
2764 mProjectScope.reset();
2765 return mLayerStore.get();
2766}
2767
2769{
2771
2772 return mLayerStore.get();
2773}
2774
2775QList<QgsVectorLayer *> QgsProject::avoidIntersectionsLayers() const
2776{
2778
2779 QList<QgsVectorLayer *> layers;
2780 const QStringList layerIds = readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), QStringList() );
2781 const auto constLayerIds = layerIds;
2782 for ( const QString &layerId : constLayerIds )
2783 {
2784 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer( layerId ) ) )
2785 layers << vlayer;
2786 }
2787 return layers;
2788}
2789
2790void QgsProject::setAvoidIntersectionsLayers( const QList<QgsVectorLayer *> &layers )
2791{
2793
2794 QStringList list;
2795 list.reserve( layers.size() );
2796 for ( QgsVectorLayer *layer : layers )
2797 list << layer->id();
2798 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), list );
2800}
2801
2813
2815{
2816 // this method is called quite extensively using QgsProject::instance()
2818
2819 // MUCH cheaper to clone than build
2820 if ( mProjectScope )
2821 {
2822 std::unique_ptr< QgsExpressionContextScope > projectScope = std::make_unique< QgsExpressionContextScope >( *mProjectScope );
2823
2824 // we can't cache these variables
2825 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_distance_units" ), QgsUnitTypes::toString( distanceUnits() ), true, true ) );
2826 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_area_units" ), QgsUnitTypes::toString( areaUnits() ), true, true ) );
2827
2828 // neither this function
2829 projectScope->addFunction( QStringLiteral( "sensor_data" ), new GetSensorData( sensorManager()->sensorsData() ) );
2830
2831 return projectScope.release();
2832 }
2833
2834 mProjectScope = std::make_unique< QgsExpressionContextScope >( QObject::tr( "Project" ) );
2835
2836 const QVariantMap vars = customVariables();
2837
2838 QVariantMap::const_iterator it = vars.constBegin();
2839
2840 for ( ; it != vars.constEnd(); ++it )
2841 {
2842 mProjectScope->setVariable( it.key(), it.value(), true );
2843 }
2844
2845 QString projectPath = projectStorage() ? fileName() : absoluteFilePath();
2846 if ( projectPath.isEmpty() )
2847 projectPath = mOriginalPath;
2848 const QString projectFolder = QFileInfo( projectPath ).path();
2849 const QString projectFilename = QFileInfo( projectPath ).fileName();
2850 const QString projectBasename = baseName();
2851
2852 //add other known project variables
2853 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_title" ), title(), true, true ) );
2854 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_path" ), QDir::toNativeSeparators( projectPath ), true, true ) );
2855 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_folder" ), QDir::toNativeSeparators( projectFolder ), true, true ) );
2856 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_filename" ), projectFilename, true, true ) );
2857 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_basename" ), projectBasename, true, true ) );
2858 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_home" ), QDir::toNativeSeparators( homePath() ), true, true ) );
2859 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_last_saved" ), mSaveDateTime.isNull() ? QVariant() : QVariant( mSaveDateTime ), true, true ) );
2860
2861 const QgsCoordinateReferenceSystem projectCrs = crs();
2862 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs" ), projectCrs.authid(), true, true ) );
2863 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_definition" ), projectCrs.toProj(), true, true ) );
2864 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_description" ), projectCrs.description(), true, true ) );
2865 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_acronym" ), projectCrs.projectionAcronym(), true ) );
2866 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_ellipsoid" ), projectCrs.ellipsoidAcronym(), true ) );
2867 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_proj4" ), projectCrs.toProj(), true ) );
2868 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_wkt" ), projectCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
2869
2870 const QgsCoordinateReferenceSystem projectVerticalCrs = QgsProject::verticalCrs();
2871 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_vertical_crs" ), projectVerticalCrs.authid(), true, true ) );
2872 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_vertical_crs_definition" ), projectVerticalCrs.toProj(), true, true ) );
2873 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_vertical_crs_description" ), projectVerticalCrs.description(), true, true ) );
2874 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_vertical_crs_wkt" ), projectVerticalCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
2875
2876 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_ellipsoid" ), ellipsoid(), true, true ) );
2877 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "_project_transform_context" ), QVariant::fromValue<QgsCoordinateTransformContext>( transformContext() ), true, true ) );
2878 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_units" ), QgsUnitTypes::toString( projectCrs.mapUnits() ), true ) );
2879
2880 // metadata
2881 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_author" ), metadata().author(), true, true ) );
2882 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_abstract" ), metadata().abstract(), true, true ) );
2883 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_creation_date" ), metadata().creationDateTime(), true, true ) );
2884 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_identifier" ), metadata().identifier(), true, true ) );
2885
2886 // keywords
2887 QVariantMap keywords;
2888 const QgsAbstractMetadataBase::KeywordMap metadataKeywords = metadata().keywords();
2889 for ( auto it = metadataKeywords.constBegin(); it != metadataKeywords.constEnd(); ++it )
2890 {
2891 keywords.insert( it.key(), it.value() );
2892 }
2893 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_keywords" ), keywords, true, true ) );
2894
2895 // layers
2896 QVariantList layersIds;
2897 QVariantList layers;
2898 const QMap<QString, QgsMapLayer *> layersInProject = mLayerStore->mapLayers();
2899 layersIds.reserve( layersInProject.count() );
2900 layers.reserve( layersInProject.count() );
2901 for ( auto it = layersInProject.constBegin(); it != layersInProject.constEnd(); ++it )
2902 {
2903 layersIds << it.value()->id();
2904 layers << QVariant::fromValue<QgsWeakMapLayerPointer>( QgsWeakMapLayerPointer( it.value() ) );
2905 }
2906 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer_ids" ), layersIds, true ) );
2907 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layers" ), layers, true ) );
2908
2909 mProjectScope->addFunction( QStringLiteral( "project_color" ), new GetNamedProjectColor( this ) );
2910
2912}
2913
2914void QgsProject::onMapLayersAdded( const QList<QgsMapLayer *> &layers )
2915{
2917
2918 const QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2919
2920 const auto constLayers = layers;
2921 for ( QgsMapLayer *layer : constLayers )
2922 {
2923 if ( ! layer->isValid() )
2924 return;
2925
2926 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
2927 {
2928 vlayer->setReadExtentFromXml( mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics );
2929 if ( vlayer->dataProvider() )
2930 vlayer->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues,
2932 }
2933
2934 connect( layer, &QgsMapLayer::configChanged, this, [this] { setDirty(); } );
2935
2936 // check if we have to update connections for layers with dependencies
2937 for ( QMap<QString, QgsMapLayer *>::const_iterator it = existingMaps.cbegin(); it != existingMaps.cend(); ++it )
2938 {
2939 const QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
2940 if ( deps.contains( layer->id() ) )
2941 {
2942 // reconnect to change signals
2943 it.value()->setDependencies( deps );
2944 }
2945 }
2946 }
2947
2948 updateTransactionGroups();
2949
2950 if ( !mBlockSnappingUpdates && mSnappingConfig.addLayers( layers ) )
2951 emit snappingConfigChanged( mSnappingConfig );
2952}
2953
2954void QgsProject::onMapLayersRemoved( const QList<QgsMapLayer *> &layers )
2955{
2957
2958 if ( !mBlockSnappingUpdates && mSnappingConfig.removeLayers( layers ) )
2959 emit snappingConfigChanged( mSnappingConfig );
2960}
2961
2962void QgsProject::cleanTransactionGroups( bool force )
2963{
2965
2966 bool changed = false;
2967 for ( QMap< QPair< QString, QString>, QgsTransactionGroup *>::Iterator tg = mTransactionGroups.begin(); tg != mTransactionGroups.end(); )
2968 {
2969 if ( tg.value()->isEmpty() || force )
2970 {
2971 delete tg.value();
2972 tg = mTransactionGroups.erase( tg );
2973 changed = true;
2974 }
2975 else
2976 {
2977 ++tg;
2978 }
2979 }
2980 if ( changed )
2982}
2983
2984void QgsProject::updateTransactionGroups()
2985{
2987
2988 mEditBufferGroup.clear();
2989
2990 switch ( mTransactionMode )
2991 {
2993 {
2994 cleanTransactionGroups( true );
2995 return;
2996 }
2997 break;
2999 cleanTransactionGroups( true );
3000 break;
3002 cleanTransactionGroups( false );
3003 break;
3004 }
3005
3006 bool tgChanged = false;
3007 const auto constLayers = mapLayers().values();
3008 for ( QgsMapLayer *layer : constLayers )
3009 {
3010 if ( ! layer->isValid() )
3011 continue;
3012
3013 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
3014 if ( ! vlayer )
3015 continue;
3016
3017 switch ( mTransactionMode )
3018 {
3020 Q_ASSERT( false );
3021 break;
3023 {
3025 {
3026 const QString connString = QgsTransaction::connectionString( vlayer->source() );
3027 const QString key = vlayer->providerType();
3028
3029 QgsTransactionGroup *tg = mTransactionGroups.value( qMakePair( key, connString ) );
3030
3031 if ( !tg )
3032 {
3033 tg = new QgsTransactionGroup();
3034 mTransactionGroups.insert( qMakePair( key, connString ), tg );
3035 tgChanged = true;
3036 }
3037 tg->addLayer( vlayer );
3038 }
3039 }
3040 break;
3042 {
3043 if ( vlayer->supportsEditing() )
3044 mEditBufferGroup.addLayer( vlayer );
3045 }
3046 break;
3047 }
3048 }
3049
3050 if ( tgChanged )
3052}
3053
3054bool QgsProject::readLayer( const QDomNode &layerNode )
3055{
3057
3058 QgsReadWriteContext context;
3059 context.setPathResolver( pathResolver() );
3060 context.setProjectTranslator( this );
3061 context.setTransformContext( transformContext() );
3062 QList<QDomNode> brokenNodes;
3063 if ( addLayer( layerNode.toElement(), brokenNodes, context ) )
3064 {
3065 // have to try to update joins for all layers now - a previously added layer may be dependent on this newly
3066 // added layer for joins
3067 const QVector<QgsVectorLayer *> vectorLayers = layers<QgsVectorLayer *>();
3068 for ( QgsVectorLayer *layer : vectorLayers )
3069 {
3070 // TODO: should be only done later - and with all layers (other layers may have referenced this layer)
3071 layer->resolveReferences( this );
3072
3073 if ( layer->isValid() && layer->customProperty( QStringLiteral( "_layer_was_editable" ) ).toBool() )
3074 {
3075 layer->startEditing();
3076 layer->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
3077 }
3078 }
3079 return true;
3080 }
3081 return false;
3082}
3083
3084bool QgsProject::write( const QString &filename )
3085{
3087
3088 mFile.setFileName( filename );
3089 mCachedHomePath.clear();
3090 return write();
3091}
3092
3094{
3096
3097 mProjectScope.reset();
3098 if ( QgsProjectStorage *storage = projectStorage() )
3099 {
3100 QgsReadWriteContext context;
3101 // for projects stored in a custom storage, we have to check for the support
3102 // of relative paths since the storage most likely will not be in a file system
3103 const QString storageFilePath { storage->filePath( mFile.fileName() ) };
3104 if ( storageFilePath.isEmpty() )
3105 {
3107 }
3108 context.setPathResolver( pathResolver() );
3109
3110 const QString tempPath = QStandardPaths::standardLocations( QStandardPaths::TempLocation ).at( 0 );
3111 const QString tmpZipFilename( tempPath + QDir::separator() + QUuid::createUuid().toString() );
3112
3113 if ( !zip( tmpZipFilename ) )
3114 return false; // zip() already calls setError() when returning false
3115
3116 QFile tmpZipFile( tmpZipFilename );
3117 if ( !tmpZipFile.open( QIODevice::ReadOnly ) )
3118 {
3119 setError( tr( "Unable to read file %1" ).arg( tmpZipFilename ) );
3120 return false;
3121 }
3122
3124 if ( !storage->writeProject( mFile.fileName(), &tmpZipFile, context ) )
3125 {
3126 QString err = tr( "Unable to save project to storage %1" ).arg( mFile.fileName() );
3127 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
3128 if ( !messages.isEmpty() )
3129 err += QStringLiteral( "\n\n" ) + messages.last().message();
3130 setError( err );
3131 return false;
3132 }
3133
3134 tmpZipFile.close();
3135 QFile::remove( tmpZipFilename );
3136
3137 return true;
3138 }
3139
3140 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
3141 {
3142 return zip( mFile.fileName() );
3143 }
3144 else
3145 {
3146 // write project file even if the auxiliary storage is not correctly
3147 // saved
3148 const bool asOk = saveAuxiliaryStorage();
3149 const bool writeOk = writeProjectFile( mFile.fileName() );
3150 bool attachmentsOk = true;
3151 if ( !mArchive->files().isEmpty() )
3152 {
3153 const QFileInfo finfo( mFile.fileName() );
3154 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
3155 attachmentsOk = mArchive->zip( attachmentsZip );
3156 }
3157
3158 // errors raised during writing project file are more important
3159 if ( ( !asOk || !attachmentsOk ) && writeOk )
3160 {
3161 QStringList errorMessage;
3162 if ( !asOk )
3163 {
3164 const QString err = mAuxiliaryStorage->errorString();
3165 errorMessage.append( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
3166 }
3167 if ( !attachmentsOk )
3168 {
3169 errorMessage.append( tr( "Unable to save attachments archive" ) );
3170 }
3171 setError( errorMessage.join( '\n' ) );
3172 }
3173
3174 return asOk && writeOk && attachmentsOk;
3175 }
3176}
3177
3178bool QgsProject::writeProjectFile( const QString &filename )
3179{
3181
3182 QFile projectFile( filename );
3183 clearError();
3184
3185 // if we have problems creating or otherwise writing to the project file,
3186 // let's find out up front before we go through all the hand-waving
3187 // necessary to create all the Dom objects
3188 const QFileInfo myFileInfo( projectFile );
3189 if ( myFileInfo.exists() && !myFileInfo.isWritable() )
3190 {
3191 setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." )
3192 .arg( projectFile.fileName() ) );
3193 return false;
3194 }
3195
3196 QgsReadWriteContext context;
3197 context.setPathResolver( pathResolver() );
3199
3200 QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
3201
3202 const QDomDocumentType documentType =
3203 QDomImplementation().createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ),
3204 QStringLiteral( "SYSTEM" ) );
3205 std::unique_ptr<QDomDocument> doc( new QDomDocument( documentType ) );
3206
3207 QDomElement qgisNode = doc->createElement( QStringLiteral( "qgis" ) );
3208 qgisNode.setAttribute( QStringLiteral( "projectname" ), title() );
3209 qgisNode.setAttribute( QStringLiteral( "version" ), Qgis::version() );
3210
3211 if ( !mSettings.value( QStringLiteral( "projects/anonymize_saved_projects" ), false, QgsSettings::Core ).toBool() )
3212 {
3213 const QString newSaveUser = QgsApplication::userLoginName();
3214 const QString newSaveUserFull = QgsApplication::userFullName();
3215 qgisNode.setAttribute( QStringLiteral( "saveUser" ), newSaveUser );
3216 qgisNode.setAttribute( QStringLiteral( "saveUserFull" ), newSaveUserFull );
3217 mSaveUser = newSaveUser;
3218 mSaveUserFull = newSaveUserFull;
3219 mSaveDateTime = QDateTime::currentDateTime();
3220 qgisNode.setAttribute( QStringLiteral( "saveDateTime" ), mSaveDateTime.toString( Qt::ISODate ) );
3221 }
3222 else
3223 {
3224 mSaveUser.clear();
3225 mSaveUserFull.clear();
3226 mSaveDateTime = QDateTime();
3227 }
3228 doc->appendChild( qgisNode );
3229 mSaveVersion = QgsProjectVersion( Qgis::version() );
3230
3231 QDomElement homePathNode = doc->createElement( QStringLiteral( "homePath" ) );
3232 homePathNode.setAttribute( QStringLiteral( "path" ), mHomePath );
3233 qgisNode.appendChild( homePathNode );
3234
3235 // title
3236 QDomElement titleNode = doc->createElement( QStringLiteral( "title" ) );
3237 qgisNode.appendChild( titleNode );
3238
3239 QDomElement transactionNode = doc->createElement( QStringLiteral( "transaction" ) );
3240 transactionNode.setAttribute( QStringLiteral( "mode" ), qgsEnumValueToKey( mTransactionMode ) );
3241 qgisNode.appendChild( transactionNode );
3242
3243 QDomElement flagsNode = doc->createElement( QStringLiteral( "projectFlags" ) );
3244 flagsNode.setAttribute( QStringLiteral( "set" ), qgsFlagValueToKeys( mFlags ) );
3245 qgisNode.appendChild( flagsNode );
3246
3247 const QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
3248 titleNode.appendChild( titleText );
3249
3250 // write project CRS
3251 {
3252 QDomElement srsNode = doc->createElement( QStringLiteral( "projectCrs" ) );
3253 mCrs.writeXml( srsNode, *doc );
3254 qgisNode.appendChild( srsNode );
3255 }
3256 {
3257 QDomElement verticalSrsNode = doc->createElement( QStringLiteral( "verticalCrs" ) );
3258 mVerticalCrs.writeXml( verticalSrsNode, *doc );
3259 qgisNode.appendChild( verticalSrsNode );
3260 }
3261
3262 QDomElement elevationShadingNode = doc->createElement( QStringLiteral( "elevation-shading-renderer" ) );
3263 mElevationShadingRenderer.writeXml( elevationShadingNode, context );
3264 qgisNode.appendChild( elevationShadingNode );
3265
3266 // write layer tree - make sure it is without embedded subgroups
3267 QgsLayerTreeNode *clonedRoot = mRootGroup->clone();
3269 QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ), this ); // convert absolute paths to relative paths if required
3270
3271 clonedRoot->writeXml( qgisNode, context );
3272 delete clonedRoot;
3273
3274 mSnappingConfig.writeProject( *doc );
3275 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( mAvoidIntersectionsMode ) );
3276
3277 // let map canvas and legend write their information
3278 emit writeProject( *doc );
3279
3280 // within top level node save list of layers
3281 const QMap<QString, QgsMapLayer *> layers = mapLayers();
3282
3283 QDomElement annotationLayerNode = doc->createElement( QStringLiteral( "main-annotation-layer" ) );
3284 mMainAnnotationLayer->writeLayerXml( annotationLayerNode, *doc, context );
3285 qgisNode.appendChild( annotationLayerNode );
3286
3287 // Iterate over layers in zOrder
3288 // Call writeXml() on each
3289 QDomElement projectLayersNode = doc->createElement( QStringLiteral( "projectlayers" ) );
3290
3291 QMap<QString, QgsMapLayer *>::ConstIterator li = layers.constBegin();
3292 while ( li != layers.end() )
3293 {
3294 QgsMapLayer *ml = li.value();
3295
3296 if ( ml )
3297 {
3298 const QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.constFind( ml->id() );
3299 if ( emIt == mEmbeddedLayers.constEnd() )
3300 {
3301 QDomElement maplayerElem;
3302 // If layer is not valid, prefer to restore saved properties from invalidLayerProperties. But if that's
3303 // not available, just write what we DO have
3304 if ( ml->isValid() || ml->originalXmlProperties().isEmpty() )
3305 {
3306 // general layer metadata
3307 maplayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
3308 ml->writeLayerXml( maplayerElem, *doc, context );
3309
3311 maplayerElem.setAttribute( QStringLiteral( "editable" ), QStringLiteral( "1" ) );
3312 }
3313 else if ( ! ml->originalXmlProperties().isEmpty() )
3314 {
3315 QDomDocument document;
3316 if ( document.setContent( ml->originalXmlProperties() ) )
3317 {
3318 maplayerElem = document.firstChildElement();
3319 }
3320 else
3321 {
3322 QgsDebugError( QStringLiteral( "Could not restore layer properties for layer %1" ).arg( ml->id() ) );
3323 }
3324 }
3325
3326 emit writeMapLayer( ml, maplayerElem, *doc );
3327
3328 projectLayersNode.appendChild( maplayerElem );
3329 }
3330 else
3331 {
3332 // layer defined in an external project file
3333 // only save embedded layer if not managed by a legend group
3334 if ( emIt.value().second )
3335 {
3336 QDomElement mapLayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
3337 mapLayerElem.setAttribute( QStringLiteral( "embedded" ), 1 );
3338 mapLayerElem.setAttribute( QStringLiteral( "project" ), writePath( emIt.value().first ) );
3339 mapLayerElem.setAttribute( QStringLiteral( "id" ), ml->id() );
3340 projectLayersNode.appendChild( mapLayerElem );
3341 }
3342 }
3343 }
3344 li++;
3345 }
3346
3347 qgisNode.appendChild( projectLayersNode );
3348
3349 QDomElement layerOrderNode = doc->createElement( QStringLiteral( "layerorder" ) );
3350 const auto constCustomLayerOrder = mRootGroup->customLayerOrder();
3351 for ( QgsMapLayer *layer : constCustomLayerOrder )
3352 {
3353 QDomElement mapLayerElem = doc->createElement( QStringLiteral( "layer" ) );
3354 mapLayerElem.setAttribute( QStringLiteral( "id" ), layer->id() );
3355 layerOrderNode.appendChild( mapLayerElem );
3356 }
3357 qgisNode.appendChild( layerOrderNode );
3358
3359 mLabelingEngineSettings->writeSettingsToProject( this );
3360
3361 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), mBackgroundColor.red() );
3362 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), mBackgroundColor.green() );
3363 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), mBackgroundColor.blue() );
3364
3365 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), mSelectionColor.red() );
3366 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), mSelectionColor.green() );
3367 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), mSelectionColor.blue() );
3368 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), mSelectionColor.alpha() );
3369
3370 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QgsUnitTypes::encodeUnit( mDistanceUnits ) );
3371 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QgsUnitTypes::encodeUnit( mAreaUnits ) );
3372
3373 // now add the optional extra properties
3374#if 0
3375 dump_( mProperties );
3376#endif
3377
3378 QgsDebugMsgLevel( QStringLiteral( "there are %1 property scopes" ).arg( static_cast<int>( mProperties.count() ) ), 2 );
3379
3380 if ( !mProperties.isEmpty() ) // only worry about properties if we
3381 // actually have any properties
3382 {
3383 mProperties.writeXml( QStringLiteral( "properties" ), qgisNode, *doc );
3384 }
3385
3386 QDomElement ddElem = doc->createElement( QStringLiteral( "dataDefinedServerProperties" ) );
3387 mDataDefinedServerProperties.writeXml( ddElem, dataDefinedServerPropertyDefinitions() );
3388 qgisNode.appendChild( ddElem );
3389
3390 mMapThemeCollection->writeXml( *doc );
3391
3392 mTransformContext.writeXml( qgisNode, context );
3393
3394 QDomElement metadataElem = doc->createElement( QStringLiteral( "projectMetadata" ) );
3395 mMetadata.writeMetadataXml( metadataElem, *doc );
3396 qgisNode.appendChild( metadataElem );
3397
3398 {
3399 const QDomElement annotationsElem = mAnnotationManager->writeXml( *doc, context );
3400 qgisNode.appendChild( annotationsElem );
3401 }
3402
3403 {
3404 const QDomElement layoutElem = mLayoutManager->writeXml( *doc );
3405 qgisNode.appendChild( layoutElem );
3406 }
3407
3408 {
3409 const QDomElement views3DElem = m3DViewsManager->writeXml( *doc );
3410 qgisNode.appendChild( views3DElem );
3411 }
3412
3413 {
3414 const QDomElement bookmarkElem = mBookmarkManager->writeXml( *doc );
3415 qgisNode.appendChild( bookmarkElem );
3416 }
3417
3418 {
3419 const QDomElement sensorElem = mSensorManager->writeXml( *doc );
3420 qgisNode.appendChild( sensorElem );
3421 }
3422
3423 {
3424 const QDomElement viewSettingsElem = mViewSettings->writeXml( *doc, context );
3425 qgisNode.appendChild( viewSettingsElem );
3426 }
3427
3428 {
3429 const QDomElement styleSettingsElem = mStyleSettings->writeXml( *doc, context );
3430 qgisNode.appendChild( styleSettingsElem );
3431 }
3432
3433 {
3434 const QDomElement timeSettingsElement = mTimeSettings->writeXml( *doc, context );
3435 qgisNode.appendChild( timeSettingsElement );
3436 }
3437
3438 {
3439 const QDomElement elevationPropertiesElement = mElevationProperties->writeXml( *doc, context );
3440 qgisNode.appendChild( elevationPropertiesElement );
3441 }
3442
3443 {
3444 const QDomElement displaySettingsElem = mDisplaySettings->writeXml( *doc, context );
3445 qgisNode.appendChild( displaySettingsElem );
3446 }
3447
3448 {
3449 const QDomElement gpsSettingsElem = mGpsSettings->writeXml( *doc, context );
3450 qgisNode.appendChild( gpsSettingsElem );
3451 }
3452
3453 // now wrap it up and ship it to the project file
3454 doc->normalize(); // XXX I'm not entirely sure what this does
3455
3456 // Create backup file
3457 if ( QFile::exists( fileName() ) )
3458 {
3459 QFile backupFile( QStringLiteral( "%1~" ).arg( filename ) );
3460 bool ok = true;
3461 ok &= backupFile.open( QIODevice::WriteOnly | QIODevice::Truncate );
3462 ok &= projectFile.open( QIODevice::ReadOnly );
3463
3464 QByteArray ba;
3465 while ( ok && !projectFile.atEnd() )
3466 {
3467 ba = projectFile.read( 10240 );
3468 ok &= backupFile.write( ba ) == ba.size();
3469 }
3470
3471 projectFile.close();
3472 backupFile.close();
3473
3474 if ( !ok )
3475 {
3476 setError( tr( "Unable to create backup file %1" ).arg( backupFile.fileName() ) );
3477 return false;
3478 }
3479
3480 const QFileInfo fi( fileName() );
3481 struct utimbuf tb = { static_cast<time_t>( fi.lastRead().toSecsSinceEpoch() ), static_cast<time_t>( fi.lastModified().toSecsSinceEpoch() ) };
3482 utime( backupFile.fileName().toUtf8().constData(), &tb );
3483 }
3484
3485 if ( !projectFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
3486 {
3487 projectFile.close(); // even though we got an error, let's make
3488 // sure it's closed anyway
3489
3490 setError( tr( "Unable to save to file %1" ).arg( projectFile.fileName() ) );
3491 return false;
3492 }
3493
3494 QTemporaryFile tempFile;
3495 bool ok = tempFile.open();
3496 if ( ok )
3497 {
3498 QTextStream projectFileStream( &tempFile );
3499 doc->save( projectFileStream, 2 ); // save as utf-8
3500 ok &= projectFileStream.pos() > -1;
3501
3502 ok &= tempFile.seek( 0 );
3503
3504 QByteArray ba;
3505 while ( ok && !tempFile.atEnd() )
3506 {
3507 ba = tempFile.read( 10240 );
3508 ok &= projectFile.write( ba ) == ba.size();
3509 }
3510
3511 ok &= projectFile.error() == QFile::NoError;
3512
3513 projectFile.close();
3514 }
3515
3516 tempFile.close();
3517
3518 if ( !ok )
3519 {
3520 setError( tr( "Unable to save to file %1. Your project "
3521 "may be corrupted on disk. Try clearing some space on the volume and "
3522 "check file permissions before pressing save again." )
3523 .arg( projectFile.fileName() ) );
3524 return false;
3525 }
3526
3527 setDirty( false ); // reset to pristine state
3528
3529 emit projectSaved();
3530 return true;
3531}
3532
3533bool QgsProject::writeEntry( const QString &scope, QString const &key, bool value )
3534{
3536
3537 bool propertiesModified;
3538 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3539
3540 if ( propertiesModified )
3541 setDirty( true );
3542
3543 return success;
3544}
3545
3546bool QgsProject::writeEntry( const QString &scope, const QString &key, double value )
3547{
3549
3550 bool propertiesModified;
3551 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3552
3553 if ( propertiesModified )
3554 setDirty( true );
3555
3556 return success;
3557}
3558
3559bool QgsProject::writeEntry( const QString &scope, QString const &key, int value )
3560{
3562
3563 bool propertiesModified;
3564 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3565
3566 if ( propertiesModified )
3567 setDirty( true );
3568
3569 return success;
3570}
3571
3572bool QgsProject::writeEntry( const QString &scope, const QString &key, const QString &value )
3573{
3575
3576 bool propertiesModified;
3577 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3578
3579 if ( propertiesModified )
3580 setDirty( true );
3581
3582 return success;
3583}
3584
3585bool QgsProject::writeEntry( const QString &scope, const QString &key, const QStringList &value )
3586{
3588
3589 bool propertiesModified;
3590 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3591
3592 if ( propertiesModified )
3593 setDirty( true );
3594
3595 return success;
3596}
3597
3598QStringList QgsProject::readListEntry( const QString &scope,
3599 const QString &key,
3600 const QStringList &def,
3601 bool *ok ) const
3602{
3603 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
3605
3606 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3607
3608 QVariant value;
3609
3610 if ( property )
3611 {
3612 value = property->value();
3613
3614 const bool valid = QMetaType::Type::QStringList == value.userType();
3615 if ( ok )
3616 *ok = valid;
3617
3618 if ( valid )
3619 {
3620 return value.toStringList();
3621 }
3622 }
3623 else if ( ok )
3624 *ok = false;
3625
3626
3627 return def;
3628}
3629
3630QString QgsProject::readEntry( const QString &scope,
3631 const QString &key,
3632 const QString &def,
3633 bool *ok ) const
3634{
3636
3637 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3638
3639 QVariant value;
3640
3641 if ( property )
3642 {
3643 value = property->value();
3644
3645 const bool valid = value.canConvert( QMetaType::Type::QString );
3646 if ( ok )
3647 *ok = valid;
3648
3649 if ( valid )
3650 return value.toString();
3651 }
3652 else if ( ok )
3653 *ok = false;
3654
3655 return def;
3656}
3657
3658int QgsProject::readNumEntry( const QString &scope, const QString &key, int def,
3659 bool *ok ) const
3660{
3662
3663 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3664
3665 QVariant value;
3666
3667 if ( property )
3668 {
3669 value = property->value();
3670 }
3671
3672 const bool valid = value.canConvert( QMetaType::Type::Int );
3673
3674 if ( ok )
3675 {
3676 *ok = valid;
3677 }
3678
3679 if ( valid )
3680 {
3681 return value.toInt();
3682 }
3683
3684 return def;
3685}
3686
3687double QgsProject::readDoubleEntry( const QString &scope, const QString &key,
3688 double def,
3689 bool *ok ) const
3690{
3692
3693 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3694 if ( property )
3695 {
3696 const QVariant value = property->value();
3697
3698 const bool valid = value.canConvert( QMetaType::Type::Double );
3699 if ( ok )
3700 *ok = valid;
3701
3702 if ( valid )
3703 return value.toDouble();
3704 }
3705 else if ( ok )
3706 *ok = false;
3707
3708 return def;
3709}
3710
3711bool QgsProject::readBoolEntry( const QString &scope, const QString &key, bool def,
3712 bool *ok ) const
3713{
3715
3716 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3717
3718 if ( property )
3719 {
3720 const QVariant value = property->value();
3721
3722 const bool valid = value.canConvert( QMetaType::Type::Bool );
3723 if ( ok )
3724 *ok = valid;
3725
3726 if ( valid )
3727 return value.toBool();
3728 }
3729 else if ( ok )
3730 *ok = false;
3731
3732 return def;
3733}
3734
3735bool QgsProject::removeEntry( const QString &scope, const QString &key )
3736{
3738
3739 if ( findKey_( scope, key, mProperties ) )
3740 {
3741 removeKey_( scope, key, mProperties );
3742 setDirty( true );
3743 }
3744
3745 return !findKey_( scope, key, mProperties );
3746}
3747
3748QStringList QgsProject::entryList( const QString &scope, const QString &key ) const
3749{
3751
3752 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3753
3754 QStringList entries;
3755
3756 if ( foundProperty )
3757 {
3758 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3759
3760 if ( propertyKey )
3761 { propertyKey->entryList( entries ); }
3762 }
3763
3764 return entries;
3765}
3766
3767QStringList QgsProject::subkeyList( const QString &scope, const QString &key ) const
3768{
3770
3771 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3772
3773 QStringList entries;
3774
3775 if ( foundProperty )
3776 {
3777 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3778
3779 if ( propertyKey )
3780 { propertyKey->subkeyList( entries ); }
3781 }
3782
3783 return entries;
3784}
3785
3787{
3789
3790 dump_( mProperties );
3791}
3792
3794{
3796
3797 QString filePath;
3798 switch ( filePathStorage() )
3799 {
3801 break;
3802
3804 {
3805 // for projects stored in a custom storage, we need to ask to the
3806 // storage for the path, if the storage returns an empty path
3807 // relative paths are not supported
3808 if ( QgsProjectStorage *storage = projectStorage() )
3809 {
3810 filePath = storage->filePath( mFile.fileName() );
3811 }
3812 else
3813 {
3814 filePath = fileName();
3815 }
3816 break;
3817 }
3818 }
3819
3820 return QgsPathResolver( filePath, mArchive->dir() );
3821}
3822
3823QString QgsProject::readPath( const QString &src ) const
3824{
3826
3827 return pathResolver().readPath( src );
3828}
3829
3830QString QgsProject::writePath( const QString &src ) const
3831{
3833
3834 return pathResolver().writePath( src );
3835}
3836
3837void QgsProject::setError( const QString &errorMessage )
3838{
3840
3841 mErrorMessage = errorMessage;
3842}
3843
3844QString QgsProject::error() const
3845{
3847
3848 return mErrorMessage;
3849}
3850
3851void QgsProject::clearError()
3852{
3854
3855 setError( QString() );
3856}
3857
3859{
3861
3862 delete mBadLayerHandler;
3863 mBadLayerHandler = handler;
3864}
3865
3866QString QgsProject::layerIsEmbedded( const QString &id ) const
3867{
3869
3870 const QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
3871 if ( it == mEmbeddedLayers.constEnd() )
3872 {
3873 return QString();
3874 }
3875 return it.value().first;
3876}
3877
3878bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &projectFilePath, QList<QDomNode> &brokenNodes,
3879 bool saveFlag, Qgis::ProjectReadFlags flags )
3880{
3882
3884
3885 static QString sPrevProjectFilePath;
3886 static QDateTime sPrevProjectFileTimestamp;
3887 static QDomDocument sProjectDocument;
3888
3889 QString qgsProjectFile = projectFilePath;
3890 QgsProjectArchive archive;
3891 if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
3892 {
3893 archive.unzip( projectFilePath );
3894 qgsProjectFile = archive.projectFile();
3895 }
3896
3897 const QDateTime projectFileTimestamp = QFileInfo( projectFilePath ).lastModified();
3898
3899 if ( projectFilePath != sPrevProjectFilePath || projectFileTimestamp != sPrevProjectFileTimestamp )
3900 {
3901 sPrevProjectFilePath.clear();
3902
3903 QFile projectFile( qgsProjectFile );
3904 if ( !projectFile.open( QIODevice::ReadOnly ) )
3905 {
3906 return false;
3907 }
3908
3909 if ( !sProjectDocument.setContent( &projectFile ) )
3910 {
3911 return false;
3912 }
3913
3914 sPrevProjectFilePath = projectFilePath;
3915 sPrevProjectFileTimestamp = projectFileTimestamp;
3916 }
3917
3918 // does project store paths absolute or relative?
3919 bool useAbsolutePaths = true;
3920
3921 const QDomElement propertiesElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "properties" ) );
3922 if ( !propertiesElem.isNull() )
3923 {
3924 const QDomElement absElem = propertiesElem.firstChildElement( QStringLiteral( "Paths" ) ).firstChildElement( QStringLiteral( "Absolute" ) );
3925 if ( !absElem.isNull() )
3926 {
3927 useAbsolutePaths = absElem.text().compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
3928 }
3929 }
3930
3931 QgsReadWriteContext embeddedContext;
3932 if ( !useAbsolutePaths )
3933 embeddedContext.setPathResolver( QgsPathResolver( projectFilePath ) );
3934 embeddedContext.setProjectTranslator( this );
3935 embeddedContext.setTransformContext( transformContext() );
3936
3937 const QDomElement projectLayersElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "projectlayers" ) );
3938 if ( projectLayersElem.isNull() )
3939 {
3940 return false;
3941 }
3942
3943 QDomElement mapLayerElem = projectLayersElem.firstChildElement( QStringLiteral( "maplayer" ) );
3944 while ( ! mapLayerElem.isNull() )
3945 {
3946 // get layer id
3947 const QString id = mapLayerElem.firstChildElement( QStringLiteral( "id" ) ).text();
3948 if ( id == layerId )
3949 {
3950 // layer can be embedded only once
3951 if ( mapLayerElem.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
3952 {
3953 return false;
3954 }
3955
3956 mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
3957
3958 if ( addLayer( mapLayerElem, brokenNodes, embeddedContext, flags ) )
3959 {
3960 return true;
3961 }
3962 else
3963 {
3964 mEmbeddedLayers.remove( layerId );
3965 return false;
3966 }
3967 }
3968 mapLayerElem = mapLayerElem.nextSiblingElement( QStringLiteral( "maplayer" ) );
3969 }
3970
3971 return false;
3972}
3973
3974QgsLayerTreeGroup *QgsProject::createEmbeddedGroup( const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers, Qgis::ProjectReadFlags flags )
3975{
3977
3978 QString qgsProjectFile = projectFilePath;
3979 QgsProjectArchive archive;
3980 if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
3981 {
3982 archive.unzip( projectFilePath );
3983 qgsProjectFile = archive.projectFile();
3984 }
3985
3986 // open project file, get layer ids in group, add the layers
3987 QFile projectFile( qgsProjectFile );
3988 if ( !projectFile.open( QIODevice::ReadOnly ) )
3989 {
3990 return nullptr;
3991 }
3992
3993 QDomDocument projectDocument;
3994 if ( !projectDocument.setContent( &projectFile ) )
3995 {
3996 return nullptr;
3997 }
3998
3999 QgsReadWriteContext context;
4000 context.setPathResolver( pathResolver() );
4001 context.setProjectTranslator( this );
4003
4005
4006 QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
4007 if ( !layerTreeElem.isNull() )
4008 {
4009 root->readChildrenFromXml( layerTreeElem, context );
4010 }
4011 else
4012 {
4013 QgsLayerTreeUtils::readOldLegend( root, projectDocument.documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
4014 }
4015
4016 QgsLayerTreeGroup *group = root->findGroup( groupName );
4017 if ( !group || group->customProperty( QStringLiteral( "embedded" ) ).toBool() )
4018 {
4019 // embedded groups cannot be embedded again
4020 delete root;
4021 return nullptr;
4022 }
4023
4024 // clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
4025 QgsLayerTreeGroup *newGroup = QgsLayerTree::toGroup( group->clone() );
4026 delete root;
4027 root = nullptr;
4028
4029 newGroup->setCustomProperty( QStringLiteral( "embedded" ), 1 );
4030 newGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectFilePath );
4031
4032 // set "embedded" to all children + load embedded layers
4033 mLayerTreeRegistryBridge->setEnabled( false );
4034 initializeEmbeddedSubtree( projectFilePath, newGroup, flags );
4035 mLayerTreeRegistryBridge->setEnabled( true );
4036
4037 // consider the layers might be identify disabled in its project
4038 const auto constFindLayerIds = newGroup->findLayerIds();
4039 for ( const QString &layerId : constFindLayerIds )
4040 {
4041 QgsLayerTreeLayer *layer = newGroup->findLayer( layerId );
4042 if ( layer )
4043 {
4044 layer->resolveReferences( this );
4045 layer->setItemVisibilityChecked( !invisibleLayers.contains( layerId ) );
4046 }
4047 }
4048
4049 return newGroup;
4050}
4051
4052void QgsProject::initializeEmbeddedSubtree( const QString &projectFilePath, QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
4053{
4055
4056 const auto constChildren = group->children();
4057 for ( QgsLayerTreeNode *child : constChildren )
4058 {
4059 // all nodes in the subtree will have "embedded" custom property set
4060 child->setCustomProperty( QStringLiteral( "embedded" ), 1 );
4061
4062 if ( QgsLayerTree::isGroup( child ) )
4063 {
4064 initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ), flags );
4065 }
4066 else if ( QgsLayerTree::isLayer( child ) )
4067 {
4068 // load the layer into our project
4069 QList<QDomNode> brokenNodes;
4070 createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, false, flags );
4071 }
4072 }
4073}
4074
4081
4088
4090{
4092
4093 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), ( enabled ? 1 : 0 ) );
4095}
4096
4098{
4100
4101 return readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), 0 );
4102}
4103
4105{
4107
4108 if ( mDistanceUnits == unit )
4109 return;
4110
4111 mDistanceUnits = unit;
4112
4113 emit distanceUnitsChanged();
4114}
4115
4117{
4119
4120 if ( mAreaUnits == unit )
4121 return;
4122
4123 mAreaUnits = unit;
4124
4125 emit areaUnitsChanged();
4126}
4127
4129{
4130 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4132
4133 if ( !mCachedHomePath.isEmpty() )
4134 return mCachedHomePath;
4135
4136 const QFileInfo pfi( fileName() );
4137
4138 if ( !mHomePath.isEmpty() )
4139 {
4140 const QFileInfo homeInfo( mHomePath );
4141 if ( !homeInfo.isRelative() )
4142 {
4143 mCachedHomePath = mHomePath;
4144 return mHomePath;
4145 }
4146 }
4147 else if ( !fileName().isEmpty() )
4148 {
4149
4150 // If it's not stored in the file system, try to get the path from the storage
4151 if ( QgsProjectStorage *storage = projectStorage() )
4152 {
4153 const QString storagePath { storage->filePath( fileName() ) };
4154 if ( ! storagePath.isEmpty() && QFileInfo::exists( storagePath ) )
4155 {
4156 mCachedHomePath = QFileInfo( storagePath ).path();
4157 return mCachedHomePath;
4158 }
4159 }
4160
4161 mCachedHomePath = pfi.path();
4162 return mCachedHomePath;
4163 }
4164
4165 if ( !pfi.exists() )
4166 {
4167 mCachedHomePath = mHomePath;
4168 return mHomePath;
4169 }
4170
4171 if ( !mHomePath.isEmpty() )
4172 {
4173 // path is relative to project file
4174 mCachedHomePath = QDir::cleanPath( pfi.path() + '/' + mHomePath );
4175 }
4176 else
4177 {
4178 mCachedHomePath = pfi.canonicalPath();
4179 }
4180 return mCachedHomePath;
4181}
4182
4184{
4186
4187 return mHomePath;
4188}
4189
4191{
4192 // because relation aggregate functions are not thread safe
4194
4195 return mRelationManager;
4196}
4197
4199{
4201
4202 return mLayoutManager.get();
4203}
4204
4206{
4208
4209 return mLayoutManager.get();
4210}
4211
4213{
4215
4216 return m3DViewsManager.get();
4217}
4218
4220{
4222
4223 return m3DViewsManager.get();
4224}
4225
4227{
4229
4230 return mBookmarkManager;
4231}
4232
4234{
4236
4237 return mBookmarkManager;
4238}
4239
4241{
4243
4244 return mSensorManager;
4245}
4246
4248{
4250
4251 return mSensorManager;
4252}
4253
4255{
4257
4258 return mViewSettings;
4259}
4260
4267
4269{
4271
4272 return mStyleSettings;
4273}
4274
4276{
4277 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4279
4280 return mStyleSettings;
4281}
4282
4284{
4286
4287 return mTimeSettings;
4288}
4289
4296
4298{
4300
4301 return mElevationProperties;
4302}
4303
4310
4312{
4314
4315 return mDisplaySettings;
4316}
4317
4319{
4321
4322 return mDisplaySettings;
4323}
4324
4326{
4328
4329 return mGpsSettings;
4330}
4331
4338
4340{
4342
4343 return mRootGroup;
4344}
4345
4347{
4349
4350 return mMapThemeCollection.get();
4351}
4352
4354{
4356
4357 return mAnnotationManager.get();
4358}
4359
4361{
4363
4364 return mAnnotationManager.get();
4365}
4366
4367void QgsProject::setNonIdentifiableLayers( const QList<QgsMapLayer *> &layers )
4368{
4370
4371 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
4372 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
4373 {
4374 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4375 continue;
4376
4377 if ( layers.contains( it.value() ) )
4378 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Identifiable );
4379 else
4380 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Identifiable );
4381 }
4382
4386}
4387
4388void QgsProject::setNonIdentifiableLayers( const QStringList &layerIds )
4389{
4391
4392 QList<QgsMapLayer *> nonIdentifiableLayers;
4393 nonIdentifiableLayers.reserve( layerIds.count() );
4394 for ( const QString &layerId : layerIds )
4395 {
4396 QgsMapLayer *layer = mapLayer( layerId );
4397 if ( layer )
4398 nonIdentifiableLayers << layer;
4399 }
4403}
4404
4406{
4408
4409 QStringList nonIdentifiableLayers;
4410
4411 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
4412 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
4413 {
4414 if ( !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4415 {
4416 nonIdentifiableLayers.append( it.value()->id() );
4417 }
4418 }
4419 return nonIdentifiableLayers;
4420}
4421
4423{
4425
4426 return mTransactionMode == Qgis::TransactionMode::AutomaticGroups;
4427}
4428
4429void QgsProject::setAutoTransaction( bool autoTransaction )
4430{
4432
4433 if ( autoTransaction
4434 && mTransactionMode == Qgis::TransactionMode::AutomaticGroups )
4435 return;
4436
4437 if ( ! autoTransaction
4438 && mTransactionMode == Qgis::TransactionMode::Disabled )
4439 return;
4440
4441 if ( autoTransaction )
4443 else
4445
4446 updateTransactionGroups();
4447}
4448
4450{
4452
4453 return mTransactionMode;
4454}
4455
4457{
4459
4460 if ( transactionMode == mTransactionMode )
4461 return true;
4462
4463 // Check that all layer are not in edit mode
4464 const auto constLayers = mapLayers().values();
4465 for ( QgsMapLayer *layer : constLayers )
4466 {
4467 if ( layer->isEditable() )
4468 {
4469 QgsLogger::warning( tr( "Transaction mode can be changed only if all layers are not editable." ) );
4470 return false;
4471 }
4472 }
4473
4474 mTransactionMode = transactionMode;
4475 updateTransactionGroups();
4477 return true;
4478}
4479
4480QMap<QPair<QString, QString>, QgsTransactionGroup *> QgsProject::transactionGroups()
4481{
4483
4484 return mTransactionGroups;
4485}
4486
4487
4488//
4489// QgsMapLayerStore methods
4490//
4491
4492
4494{
4496
4497 return mLayerStore->count();
4498}
4499
4501{
4503
4504 return mLayerStore->validCount();
4505}
4506
4507QgsMapLayer *QgsProject::mapLayer( const QString &layerId ) const
4508{
4509 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
4511
4512 return mLayerStore->mapLayer( layerId );
4513}
4514
4515QList<QgsMapLayer *> QgsProject::mapLayersByName( const QString &layerName ) const
4516{
4518
4519 return mLayerStore->mapLayersByName( layerName );
4520}
4521
4522QList<QgsMapLayer *> QgsProject::mapLayersByShortName( const QString &shortName ) const
4523{
4525
4526 QList<QgsMapLayer *> layers;
4527 const auto constMapLayers { mLayerStore->mapLayers() };
4528 for ( const auto &l : constMapLayers )
4529 {
4530 if ( ! l->serverProperties()->shortName().isEmpty() )
4531 {
4532 if ( l->serverProperties()->shortName() == shortName )
4533 layers << l;
4534 }
4535 else if ( l->name() == shortName )
4536 {
4537 layers << l;
4538 }
4539 }
4540 return layers;
4541}
4542
4543bool QgsProject::unzip( const QString &filename, Qgis::ProjectReadFlags flags )
4544{
4546
4547 clearError();
4548 std::unique_ptr<QgsProjectArchive> archive( new QgsProjectArchive() );
4549
4550 // unzip the archive
4551 if ( !archive->unzip( filename ) )
4552 {
4553 setError( tr( "Unable to unzip file '%1'" ).arg( filename ) );
4554 return false;
4555 }
4556
4557 // test if zip provides a .qgs file
4558 if ( archive->projectFile().isEmpty() )
4559 {
4560 setError( tr( "Zip archive does not provide a project file" ) );
4561 return false;
4562 }
4563
4564 // Keep the archive
4565 releaseHandlesToProjectArchive();
4566 mArchive = std::move( archive );
4567
4568 // load auxiliary storage
4569 if ( !static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile().isEmpty() )
4570 {
4571 // database file is already a copy as it's been unzipped. So we don't open
4572 // auxiliary storage in copy mode in this case
4573 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile(), false ) );
4574 }
4575 else
4576 {
4577 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
4578 }
4579
4580 // read the project file
4581 if ( ! readProjectFile( static_cast<QgsProjectArchive *>( mArchive.get() )->projectFile(), flags ) )
4582 {
4583 setError( tr( "Cannot read unzipped qgs project file" ) + QStringLiteral( ": " ) + error() );
4584 return false;
4585 }
4586
4587 // Remove the temporary .qgs file
4588 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4589
4590 return true;
4591}
4592
4593bool QgsProject::zip( const QString &filename )
4594{
4596
4597 clearError();
4598
4599 // save the current project in a temporary .qgs file
4600 std::unique_ptr<QgsProjectArchive> archive( new QgsProjectArchive() );
4601 const QString baseName = QFileInfo( filename ).baseName();
4602 const QString qgsFileName = QStringLiteral( "%1.qgs" ).arg( baseName );
4603 QFile qgsFile( QDir( archive->dir() ).filePath( qgsFileName ) );
4604
4605 bool writeOk = false;
4606 if ( qgsFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
4607 {
4608 writeOk = writeProjectFile( qgsFile.fileName() );
4609 qgsFile.close();
4610 }
4611
4612 // stop here with an error message
4613 if ( ! writeOk )
4614 {
4615 setError( tr( "Unable to write temporary qgs file" ) );
4616 return false;
4617 }
4618
4619 // save auxiliary storage
4620 const QFileInfo info( qgsFile );
4621 const QString asExt = QStringLiteral( ".%1" ).arg( QgsAuxiliaryStorage::extension() );
4622 const QString asFileName = info.path() + QDir::separator() + info.completeBaseName() + asExt;
4623
4624 bool auxiliaryStorageSavedOk = true;
4625 if ( ! saveAuxiliaryStorage( asFileName ) )
4626 {
4627 const QString err = mAuxiliaryStorage->errorString();
4628 setError( tr( "Unable to save auxiliary storage file ('%1'). The project has been saved but the latest changes to auxiliary data cannot be recovered. It is recommended to reload the project." ).arg( err ) );
4629 auxiliaryStorageSavedOk = false;
4630
4631 // fixes the current archive and keep the previous version of qgd
4632 if ( !mArchive->exists() )
4633 {
4634 releaseHandlesToProjectArchive();
4635 mArchive.reset( new QgsProjectArchive() );
4636 mArchive->unzip( mFile.fileName() );
4637 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4638
4639 const QString auxiliaryStorageFile = static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile();
4640 if ( ! auxiliaryStorageFile.isEmpty() )
4641 {
4642 archive->addFile( auxiliaryStorageFile );
4643 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( auxiliaryStorageFile, false ) );
4644 }
4645 }
4646 }
4647 else
4648 {
4649 // in this case, an empty filename means that the auxiliary database is
4650 // empty, so we don't want to save it
4651 if ( QFile::exists( asFileName ) )
4652 {
4653 archive->addFile( asFileName );
4654 }
4655 }
4656
4657 // create the archive
4658 archive->addFile( qgsFile.fileName() );
4659
4660 // Add all other files
4661 const QStringList &files = mArchive->files();
4662 for ( const QString &file : files )
4663 {
4664 if ( !file.endsWith( QLatin1String( ".qgs" ), Qt::CaseInsensitive ) && !file.endsWith( asExt, Qt::CaseInsensitive ) )
4665 {
4666 archive->addFile( file );
4667 }
4668 }
4669
4670 // zip
4671 bool zipOk = true;
4672 if ( !archive->zip( filename ) )
4673 {
4674 setError( tr( "Unable to perform zip" ) );
4675 zipOk = false;
4676 }
4677
4678 return auxiliaryStorageSavedOk && zipOk;
4679}
4680
4682{
4684
4685 return QgsZipUtils::isZipFile( mFile.fileName() );
4686}
4687
4688QList<QgsMapLayer *> QgsProject::addMapLayers(
4689 const QList<QgsMapLayer *> &layers,
4690 bool addToLegend,
4691 bool takeOwnership )
4692{
4694
4695 const QList<QgsMapLayer *> myResultList { mLayerStore->addMapLayers( layers, takeOwnership ) };
4696 if ( !myResultList.isEmpty() )
4697 {
4698 // Update transform context
4699 for ( auto &l : myResultList )
4700 {
4701 l->setTransformContext( transformContext() );
4702 }
4703 if ( addToLegend )
4704 {
4705 emit legendLayersAdded( myResultList );
4706 }
4707 }
4708
4709 if ( mAuxiliaryStorage )
4710 {
4711 for ( QgsMapLayer *mlayer : myResultList )
4712 {
4713 if ( mlayer->type() != Qgis::LayerType::Vector )
4714 continue;
4715
4716 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mlayer );
4717 if ( vl )
4718 {
4719 vl->loadAuxiliaryLayer( *mAuxiliaryStorage );
4720 }
4721 }
4722 }
4723
4724 mProjectScope.reset();
4725
4726 return myResultList;
4727}
4728
4731 bool addToLegend,
4732 bool takeOwnership )
4733{
4735
4736 QList<QgsMapLayer *> addedLayers;
4737 addedLayers = addMapLayers( QList<QgsMapLayer *>() << layer, addToLegend, takeOwnership );
4738 return addedLayers.isEmpty() ? nullptr : addedLayers[0];
4739}
4740
4741void QgsProject::removeAuxiliaryLayer( const QgsMapLayer *ml )
4742{
4744
4745 if ( ! ml || ml->type() != Qgis::LayerType::Vector )
4746 return;
4747
4748 const QgsVectorLayer *vl = qobject_cast<const QgsVectorLayer *>( ml );
4749 if ( vl && vl->auxiliaryLayer() )
4750 {
4751 const QgsDataSourceUri uri( vl->auxiliaryLayer()->source() );
4753 }
4754}
4755
4756void QgsProject::removeMapLayers( const QStringList &layerIds )
4757{
4759
4760 for ( const auto &layerId : layerIds )
4761 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4762
4763 mProjectScope.reset();
4764 mLayerStore->removeMapLayers( layerIds );
4765}
4766
4767void QgsProject::removeMapLayers( const QList<QgsMapLayer *> &layers )
4768{
4770
4771 for ( const auto &layer : layers )
4772 removeAuxiliaryLayer( layer );
4773
4774 mProjectScope.reset();
4775 mLayerStore->removeMapLayers( layers );
4776}
4777
4778void QgsProject::removeMapLayer( const QString &layerId )
4779{
4781
4782 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4783 mProjectScope.reset();
4784 mLayerStore->removeMapLayer( layerId );
4785}
4786
4788{
4790
4791 removeAuxiliaryLayer( layer );
4792 mProjectScope.reset();
4793 mLayerStore->removeMapLayer( layer );
4794}
4795
4797{
4799
4800 mProjectScope.reset();
4801 return mLayerStore->takeMapLayer( layer );
4802}
4803
4805{
4807
4808 return mMainAnnotationLayer;
4809}
4810
4812{
4814
4815 if ( mLayerStore->count() == 0 )
4816 return;
4817
4818 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
4819 mProjectScope.reset();
4820 mLayerStore->removeAllMapLayers();
4821
4822 snapSingleBlocker.release();
4823 mSnappingConfig.clearIndividualLayerSettings();
4824 if ( !mBlockSnappingUpdates )
4825 emit snappingConfigChanged( mSnappingConfig );
4826}
4827
4829{
4831
4832 const QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
4833 QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin();
4834 for ( ; it != layers.constEnd(); ++it )
4835 {
4836 it.value()->reload();
4837 }
4838}
4839
4840QMap<QString, QgsMapLayer *> QgsProject::mapLayers( const bool validOnly ) const
4841{
4842 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
4844
4845 return validOnly ? mLayerStore->validMapLayers() : mLayerStore->mapLayers();
4846}
4847
4848QgsTransactionGroup *QgsProject::transactionGroup( const QString &providerKey, const QString &connString )
4849{
4851
4852 return mTransactionGroups.value( qMakePair( providerKey, connString ) );
4853}
4854
4861
4863{
4865
4867
4868 // TODO QGIS 4.0 -- remove this method, and place it somewhere in app (where it belongs)
4869 // 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)
4870 if ( mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), QStringLiteral( "NoAction" ), QgsSettings::App ).toString() == QStringLiteral( "UseProjectCrs" )
4871 || mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), 0, QgsSettings::App ).toString() == QLatin1String( "2" ) )
4872 {
4873 // for new layers if the new layer crs method is set to either prompt or use project, then we use the project crs
4874 defaultCrs = crs();
4875 }
4876 else
4877 {
4878 // global crs
4879 const QString layerDefaultCrs = mSettings.value( QStringLiteral( "/Projections/layerDefaultCrs" ), geoEpsgCrsAuthId() ).toString();
4880 defaultCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( layerDefaultCrs );
4881 }
4882
4883 return defaultCrs;
4884}
4885
4892
4899
4900bool QgsProject::saveAuxiliaryStorage( const QString &filename )
4901{
4903
4904 const QMap<QString, QgsMapLayer *> layers = mapLayers();
4905 bool empty = true;
4906 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
4907 {
4908 if ( it.value()->type() != Qgis::LayerType::Vector )
4909 continue;
4910
4911 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
4912 if ( vl && vl->auxiliaryLayer() )
4913 {
4914 vl->auxiliaryLayer()->save();
4915 empty &= vl->auxiliaryLayer()->auxiliaryFields().isEmpty();
4916 }
4917 }
4918
4919 if ( !mAuxiliaryStorage->exists( *this ) && empty )
4920 {
4921 return true; // it's not an error
4922 }
4923 else if ( !filename.isEmpty() )
4924 {
4925 return mAuxiliaryStorage->saveAs( filename );
4926 }
4927 else
4928 {
4929 return mAuxiliaryStorage->saveAs( *this );
4930 }
4931}
4932
4933QgsPropertiesDefinition &QgsProject::dataDefinedServerPropertyDefinitions()
4934{
4935 static QgsPropertiesDefinition sPropertyDefinitions
4936 {
4937 {
4939 QgsPropertyDefinition( "WMSOnlineResource", QObject::tr( "WMS Online Resource" ), QgsPropertyDefinition::String )
4940 },
4941 };
4942 return sPropertyDefinitions;
4943}
4944
4946{
4947 mElevationShadingRenderer = elevationShadingRenderer;
4949}
4950
4952{
4954
4955 return mAuxiliaryStorage.get();
4956}
4957
4959{
4961
4962 return mAuxiliaryStorage.get();
4963}
4964
4965QString QgsProject::createAttachedFile( const QString &nameTemplate )
4966{
4968
4969 const QDir archiveDir( mArchive->dir() );
4970 QTemporaryFile tmpFile( archiveDir.filePath( "XXXXXX_" + nameTemplate ), this );
4971 tmpFile.setAutoRemove( false );
4972 tmpFile.open();
4973 mArchive->addFile( tmpFile.fileName() );
4974 return tmpFile.fileName();
4975}
4976
4977QStringList QgsProject::attachedFiles() const
4978{
4980
4981 QStringList attachments;
4982 const QString baseName = QFileInfo( fileName() ).baseName();
4983 const QStringList files = mArchive->files();
4984 attachments.reserve( files.size() );
4985 for ( const QString &file : files )
4986 {
4987 if ( QFileInfo( file ).baseName() != baseName )
4988 {
4989 attachments.append( file );
4990 }
4991 }
4992 return attachments;
4993}
4994
4995bool QgsProject::removeAttachedFile( const QString &path )
4996{
4998
4999 return mArchive->removeFile( path );
5000}
5001
5002QString QgsProject::attachmentIdentifier( const QString &attachedFile ) const
5003{
5005
5006 return QStringLiteral( "attachment:///%1" ).arg( QFileInfo( attachedFile ).fileName() );
5007}
5008
5009QString QgsProject::resolveAttachmentIdentifier( const QString &identifier ) const
5010{
5012
5013 if ( identifier.startsWith( QLatin1String( "attachment:///" ) ) )
5014 {
5015 return QDir( mArchive->dir() ).absoluteFilePath( identifier.mid( 14 ) );
5016 }
5017 return QString();
5018}
5019
5021{
5022 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
5024
5025 return mMetadata;
5026}
5027
5029{
5031
5032 if ( metadata == mMetadata )
5033 return;
5034
5035 mMetadata = metadata;
5036 mProjectScope.reset();
5037
5038 emit metadataChanged();
5039
5040 setDirty( true );
5041}
5042
5043QSet<QgsMapLayer *> QgsProject::requiredLayers() const
5044{
5046
5047 QSet<QgsMapLayer *> requiredLayers;
5048
5049 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
5050 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
5051 {
5052 if ( !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
5053 {
5054 requiredLayers.insert( it.value() );
5055 }
5056 }
5057 return requiredLayers;
5058}
5059
5060void QgsProject::setRequiredLayers( const QSet<QgsMapLayer *> &layers )
5061{
5063
5064 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
5065 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
5066 {
5067 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
5068 continue;
5069
5070 if ( layers.contains( it.value() ) )
5071 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Removable );
5072 else
5073 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Removable );
5074 }
5075}
5076
5078{
5080
5081 // save colors to project
5082 QStringList customColors;
5083 QStringList customColorLabels;
5084
5085 QgsNamedColorList::const_iterator colorIt = colors.constBegin();
5086 for ( ; colorIt != colors.constEnd(); ++colorIt )
5087 {
5088 const QString color = QgsColorUtils::colorToString( ( *colorIt ).first );
5089 const QString label = ( *colorIt ).second;
5090 customColors.append( color );
5091 customColorLabels.append( label );
5092 }
5093 writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ), customColors );
5094 writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ), customColorLabels );
5095 mProjectScope.reset();
5096 emit projectColorsChanged();
5097}
5098
5099void QgsProject::setBackgroundColor( const QColor &color )
5100{
5102
5103 if ( mBackgroundColor == color )
5104 return;
5105
5106 mBackgroundColor = color;
5108}
5109
5111{
5113
5114 return mBackgroundColor;
5115}
5116
5117void QgsProject::setSelectionColor( const QColor &color )
5118{
5120
5121 if ( mSelectionColor == color )
5122 return;
5123
5124 mSelectionColor = color;
5125 emit selectionColorChanged();
5126}
5127
5129{
5131
5132 return mSelectionColor;
5133}
5134
5135void QgsProject::setMapScales( const QVector<double> &scales )
5136{
5138
5139 mViewSettings->setMapScales( scales );
5140}
5141
5142QVector<double> QgsProject::mapScales() const
5143{
5145
5146 return mViewSettings->mapScales();
5147}
5148
5150{
5152
5153 mViewSettings->setUseProjectScales( enabled );
5154}
5155
5157{
5159
5160 return mViewSettings->useProjectScales();
5161}
5162
5163void QgsProject::generateTsFile( const QString &locale )
5164{
5166
5167 QgsTranslationContext translationContext;
5168 translationContext.setProject( this );
5169 translationContext.setFileName( QStringLiteral( "%1/%2.ts" ).arg( absolutePath(), baseName() ) );
5170
5171 QgsApplication::instance()->collectTranslatableObjects( &translationContext );
5172
5173 translationContext.writeTsFile( locale );
5174}
5175
5176QString QgsProject::translate( const QString &context, const QString &sourceText, const char *disambiguation, int n ) const
5177{
5179
5180 if ( !mTranslator )
5181 {
5182 return sourceText;
5183 }
5184
5185 QString result = mTranslator->translate( context.toUtf8(), sourceText.toUtf8(), disambiguation, n );
5186
5187 if ( result.isEmpty() )
5188 {
5189 return sourceText;
5190 }
5191 return result;
5192}
5193
5195{
5197
5198 const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
5199 if ( !layers.empty() )
5200 {
5201 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5202 {
5203 // NOTE: if visitEnter returns false it means "don't visit this layer", not "abort all further visitations"
5204 if ( visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
5205 {
5206 if ( !( ( *it )->accept( visitor ) ) )
5207 return false;
5208
5209 if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
5210 return false;
5211 }
5212 }
5213 }
5214
5215 if ( !mLayoutManager->accept( visitor ) )
5216 return false;
5217
5218 if ( !mAnnotationManager->accept( visitor ) )
5219 return false;
5220
5221 return true;
5222}
5223
5225{
5226 return mElevationShadingRenderer;
5227}
5228
5229void QgsProject::loadProjectFlags( const QDomDocument *doc )
5230{
5232
5233 QDomElement element = doc->documentElement().firstChildElement( QStringLiteral( "projectFlags" ) );
5235 if ( !element.isNull() )
5236 {
5237 flags = qgsFlagKeysToValue( element.attribute( QStringLiteral( "set" ) ), Qgis::ProjectFlags() );
5238 }
5239 else
5240 {
5241 // older project compatibility
5242 element = doc->documentElement().firstChildElement( QStringLiteral( "evaluateDefaultValues" ) );
5243 if ( !element.isNull() )
5244 {
5245 if ( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
5247 }
5248
5249 // Read trust layer metadata config in the project
5250 element = doc->documentElement().firstChildElement( QStringLiteral( "trust" ) );
5251 if ( !element.isNull() )
5252 {
5253 if ( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
5255 }
5256 }
5257
5258 setFlags( flags );
5259}
5260
5262GetNamedProjectColor::GetNamedProjectColor( const QgsProject *project )
5263 : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
5264{
5265 if ( !project )
5266 return;
5267
5268 //build up color list from project. Do this in advance for speed
5269 QStringList colorStrings = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ) );
5270 const QStringList colorLabels = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ) );
5271
5272 //generate list from custom colors
5273 int colorIndex = 0;
5274 for ( QStringList::iterator it = colorStrings.begin();
5275 it != colorStrings.end(); ++it )
5276 {
5277 const QColor color = QgsColorUtils::colorFromString( *it );
5278 QString label;
5279 if ( colorLabels.length() > colorIndex )
5280 {
5281 label = colorLabels.at( colorIndex );
5282 }
5283
5284 mColors.insert( label.toLower(), color );
5285 colorIndex++;
5286 }
5287}
5288
5289GetNamedProjectColor::GetNamedProjectColor( const QHash<QString, QColor> &colors )
5290 : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
5291 , mColors( colors )
5292{
5293}
5294
5295QVariant GetNamedProjectColor::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5296{
5297 const QString colorName = values.at( 0 ).toString().toLower();
5298 if ( mColors.contains( colorName ) )
5299 {
5300 return QStringLiteral( "%1,%2,%3" ).arg( mColors.value( colorName ).red() ).arg( mColors.value( colorName ).green() ).arg( mColors.value( colorName ).blue() );
5301 }
5302 else
5303 return QVariant();
5304}
5305
5306QgsScopedExpressionFunction *GetNamedProjectColor::clone() const
5307{
5308 return new GetNamedProjectColor( mColors );
5309}
5310
5311// ----------------
5312
5313GetSensorData::GetSensorData( const QMap<QString, QgsAbstractSensor::SensorData> &sensorData )
5314 : QgsScopedExpressionFunction( QStringLiteral( "sensor_data" ),
5315 QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "name" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "expiration" ), true, 0 ),
5316 QStringLiteral( "Sensors" ) )
5317 , mSensorData( sensorData )
5318{
5319}
5320
5321QVariant GetSensorData::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5322{
5323 const QString sensorName = values.at( 0 ).toString();
5324 const int expiration = values.at( 1 ).toInt();
5325 const qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
5326 if ( mSensorData.contains( sensorName ) )
5327 {
5328 if ( expiration <= 0 || ( timestamp - mSensorData[sensorName].lastTimestamp.toMSecsSinceEpoch() ) < expiration )
5329 {
5330 return mSensorData[sensorName].lastValue;
5331 }
5332 }
5333
5334 return QVariant();
5335}
5336
5337QgsScopedExpressionFunction *GetSensorData::clone() const
5338{
5339 return new GetSensorData( mSensorData );
5340}
@ DontLoad3DViews
Skip loading 3D views (since QGIS 3.26)
@ DontStoreOriginalStyles
Skip the initial XML style storage for layers. Useful for minimising project load times in non-intera...
@ ForceReadOnlyLayers
Open layers in a read-only mode. (since QGIS 3.28)
@ TrustLayerMetadata
Trust layer metadata. Improves project read time. Do not use it if layers' extent is not fixed during...
@ DontLoadLayouts
Don't load print layouts. Improves project read time if layouts are not required, and allows projects...
@ DontResolveLayers
Don't resolve layer paths (i.e. don't load any layer content). Dramatically improves project read tim...
static QString version()
Version string.
Definition qgis.cpp:258
QFlags< ProjectCapability > ProjectCapabilities
Flags which control project capabilities.
Definition qgis.h:3755
QFlags< ProjectReadFlag > ProjectReadFlags
Project load flags.
Definition qgis.h:3733
DistanceUnit
Units of distance.
Definition qgis.h:4363
FilePathType
File path types.
Definition qgis.h:1346
@ Relative
Relative path.
@ Absolute
Absolute path.
TransactionMode
Transaction mode.
Definition qgis.h:3395
@ AutomaticGroups
Automatic transactional editing means that on supported datasources (postgres and geopackage database...
@ BufferedGroups
Buffered transactional editing means that all editable layers in the buffered transaction group are t...
@ Disabled
Edits are buffered locally and sent to the provider when toggling layer editing mode.
AreaUnit
Units of area.
Definition qgis.h:4401
@ SquareMeters
Square meters.
@ Critical
Critical/error message.
Definition qgis.h:102
@ Success
Used for reporting a successful operation.
Definition qgis.h:103
@ Vertical
Vertical CRS.
@ Temporal
Temporal CRS.
@ Compound
Compound (horizontal + vertical) CRS.
@ Projected
Projected CRS.
@ Other
Other type.
@ Bound
Bound CRS.
@ DerivedProjected
Derived projected CRS.
@ Unknown
Unknown type.
@ Engineering
Engineering CRS.
@ Geographic3d
3D geopraphic CRS
@ Geodetic
Geodetic CRS.
@ Geographic2d
2D geographic CRS
@ Geocentric
Geocentric CRS.
AvoidIntersectionsMode
Flags which control how intersections of pre-existing feature are handled when digitizing new feature...
Definition qgis.h:3686
@ AvoidIntersectionsLayers
Overlap with features from a specified list of layers when digitizing new features not allowed.
@ AllowIntersections
Overlap with any feature allowed when digitizing new features.
ProjectFlag
Flags which control the behavior of QgsProjects.
Definition qgis.h:3518
@ RememberLayerEditStatusBetweenSessions
If set, then any layers set to be editable will be stored in the project and immediately made editabl...
@ EvaluateDefaultValuesOnProviderSide
If set, default values for fields will be evaluated on the provider side when features from the proje...
@ TrustStoredLayerStatistics
If set, then layer statistics (such as the layer extent) will be read from values stored in the proje...
LayerType
Types of layers that can be added to a map.
Definition qgis.h:114
@ Group
Composite group layer. Added in QGIS 3.24.
@ Plugin
Plugin based layer.
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ Vector
Vector layer.
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
@ Mesh
Mesh layer. Added in QGIS 3.2.
@ Raster
Raster layer.
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
QFlags< ProjectFlag > ProjectFlags
Definition qgis.h:3525
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
QMap< QString, QStringList > KeywordMap
Map of vocabulary string to keyword list.
void setTitle(const QString &title)
Sets the human readable title (name) of the resource, typically displayed in search results.
QString title() const
Returns the human readable name of the resource, typically displayed in search results.
QgsAbstractMetadataBase::KeywordMap keywords() const
Returns the keywords map, which is a set of descriptive keywords associated with the resource.
virtual bool readXml(const QDomElement &collectionElem, const QgsPropertiesDefinition &definitions)
Reads property collection state from an XML element.
virtual bool writeXml(QDomElement &collectionElem, const QgsPropertiesDefinition &definitions) const
Writes the current state of the property collection into an XML element.
Represents a map layer containing a set of georeferenced annotations, e.g.
void setTransformContext(const QgsCoordinateTransformContext &context) override
Sets the coordinate transform context to transformContext.
void reset()
Resets the annotation layer to a default state, and clears all items from it.
bool isEmpty() const
Returns true if the annotation layer is empty and contains no annotations.
Manages storage of a set of QgsAnnotation annotation objects.
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
static QgsProjectStorageRegistry * projectStorageRegistry()
Returns registry of available project storage implementations.
static const QgsSettingsEntryString * settingsLocaleUserLocale
Settings entry locale user locale.
static QgsRuntimeProfiler * profiler()
Returns the application runtime profiler.
void collectTranslatableObjects(QgsTranslationContext *translationContext)
Emits the signal to collect all the strings of .qgs to be included in ts file.
static QgsPluginLayerRegistry * pluginLayerRegistry()
Returns the application's plugin layer registry, used for managing plugin layer types.
void requestForTranslatableObjects(QgsTranslationContext *translationContext)
Emitted when project strings which require translation are being collected for inclusion in a ....
static QString userFullName()
Returns the user's operating system login account full display name.
static QString userLoginName()
Returns the user's operating system login account name.
Class allowing to manage the zip/unzip actions.
Definition qgsarchive.h:35
void addFile(const QString &filename)
Add a new file to this archive.
This is a container for attribute editors, used to group them visually in the attribute form if it is...
QList< QgsAttributeEditorElement * > children() const
Gets a list of the children elements of this container.
This is an abstract base class for any elements of a drag and drop form.
QString name() const
Returns the name of this element.
QgsFields auxiliaryFields() const
Returns a list of all auxiliary fields currently managed by the layer.
bool save()
Commits changes and starts editing then.
Class providing some utility methods to manage auxiliary storage.
static QString extension()
Returns the extension used for auxiliary databases.
static bool deleteTable(const QgsDataSourceUri &uri)
Removes a table from the auxiliary storage.
Manages storage of a set of bookmarks.
bool readXml(const QDomElement &element, const QDomDocument &doc)
Reads the manager's state from a DOM element, restoring all bookmarks present in the XML document.
void clear()
Removes and deletes all bookmarks from the manager.
QDomElement writeXml(QDomDocument &doc) const
Returns a DOM element representing the state of the manager.
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
This class represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool hasVerticalAxis() const
Returns true if the CRS has a vertical axis.
QString toProj() const
Returns a Proj string representation of this CRS.
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
static QgsCoordinateReferenceSystem createCompoundCrs(const QgsCoordinateReferenceSystem &horizontalCrs, const QgsCoordinateReferenceSystem &verticalCrs, QString &error)
Given a horizontal and vertical CRS, attempts to create a compound CRS from them.
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
QString projectionAcronym() const
Returns the projection acronym for the projection used by the CRS.
QgsCoordinateReferenceSystem verticalCrs() const
Returns the vertical CRS associated with this CRS object.
static QgsCoordinateReferenceSystem fromProj(const QString &proj)
Creates a CRS from a proj style formatted string.
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
static QgsCoordinateReferenceSystem fromSrsId(long srsId)
Creates a CRS from a specified QGIS SRS ID.
Qgis::CrsType type() const
Returns the type of the CRS.
Contains information about the context in which a coordinate transform is executed.
void readSettings()
Reads the context's state from application settings.
void writeXml(QDomElement &element, const QgsReadWriteContext &context) const
Writes the context's state to a DOM element.
bool readXml(const QDomElement &element, const QgsReadWriteContext &context, QStringList &missingTransforms)
Reads the context's state from a DOM element.
Abstract base class for spatial data provider implementations.
@ ParallelThreadLoading
Skip credentials if the provided one are not valid, let the provider be invalid, avoiding to block th...
QFlags< ReadFlag > ReadFlags
@ EvaluateDefaultValues
Evaluate default values on provider side when calling QgsVectorDataProvider::defaultValue( int index ...
Class for storing the component parts of a RDBMS data source URI (e.g.
QgsAttributeEditorContainer * invisibleRootContainer()
Gets the invisible root container for the drag and drop designer form (EditorLayout::TabLayout).
This class can render elevation shading on an image with different methods (eye dome lighting,...
void writeXml(QDomElement &elem, const QgsReadWriteContext &context) const
Writes configuration on a DOM element.
void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads configuration from a DOM element.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
A abstract base class for defining QgsExpression functions.
An expression node for expression functions.
Class for parsing and evaluation of expressions (formerly called "search strings").
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:53
Container of fields for a vector layer.
Definition qgsfields.h:46
bool isEmpty
Definition qgsfields.h:49
Stores global configuration for labeling engine.
Class used to work with layer dependencies stored in a XML project or layer definition file.
Layer tree group node serves as a container for layers and further groups.
void resolveReferences(const QgsProject *project, bool looseMatching=false) override
Calls resolveReferences() on child tree nodes.
QgsLayerTreeGroup * findGroup(const QString &name)
Find group node with specified name.
QList< QgsLayerTreeGroup * > findGroups(bool recursive=false) const
Find group layer nodes.
QString name() const override
Returns the group's name.
QStringList findLayerIds() const
Find layer IDs used in all layer nodes.
void insertChildNodes(int index, const QList< QgsLayerTreeNode * > &nodes)
Insert existing nodes at specified position.
void readChildrenFromXml(QDomElement &element, const QgsReadWriteContext &context)
Read children from XML and append them to the group.
QgsLayerTreeGroup * clone() const override
Returns a clone of the group.
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
QgsLayerTreeLayer * findLayer(QgsMapLayer *layer) const
Find layer node representing the map layer.
Layer tree node points to a map layer.
void resolveReferences(const QgsProject *project, bool looseMatching=false) override
Resolves reference to layer from stored layer ID (if it has not been resolved already)
This class is a base class for nodes in a layer tree.
QList< QgsLayerTreeNode * > abandonChildren()
Removes the children, disconnect all the forwarded and external signals and sets their parent to null...
void setCustomProperty(const QString &key, const QVariant &value)
Sets a custom property for the node. Properties are stored in a map and saved in project file.
virtual void writeXml(QDomElement &parentElement, const QgsReadWriteContext &context)=0
Write layer tree to XML.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
void removeCustomProperty(const QString &key)
Remove a custom property from layer. Properties are stored in a map and saved in project file.
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file.
void setItemVisibilityChecked(bool checked)
Check or uncheck a node (independently of its ancestors or children)
Listens to the updates in map layer registry and does changes in layer tree.
static void replaceChildrenOfEmbeddedGroups(QgsLayerTreeGroup *group)
Remove subtree of embedded groups and replaces it with a custom property embedded-visible-layers.
static void storeOriginalLayersProperties(QgsLayerTreeGroup *group, const QDomDocument *doc)
Stores in a layer's originalXmlProperties the layer properties information.
static void updateEmbeddedGroupsProjectPath(QgsLayerTreeGroup *group, const QgsProject *project)
Updates an embedded group from a project.
static bool readOldLegend(QgsLayerTreeGroup *root, const QDomElement &legendElem)
Try to load layer tree from.
Namespace with helper functions for layer tree operations.
void readLayerOrderFromXml(const QDomElement &doc)
Load the layer order from an XML element.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
void clear()
Clear any information from this layer tree.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
QList< QgsMapLayer * > customLayerOrder() const
The order in which layers will be rendered on the canvas.
QgsLayerTree * clone() const override
Create a copy of the node. Returns new instance.
Manages storage of a set of layouts.
static void warning(const QString &msg)
Goes to qWarning.
static Qgis::LayerType typeFromString(const QString &string, bool &ok)
Returns the map layer type corresponding a string value.
A storage object for map layers, in which the layers are owned by the store and have their lifetime b...
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the store.
void layerWillBeRemoved(const QString &layerId)
Emitted when a layer is about to be removed from the store.
void layersRemoved(const QStringList &layerIds)
Emitted after one or more layers were removed from the store.
void allLayersRemoved()
Emitted when all layers are removed, before layersWillBeRemoved() and layerWillBeRemoved() signals ar...
void layerRemoved(const QString &layerId)
Emitted after a layer was removed from the store.
void layerWasAdded(QgsMapLayer *layer)
Emitted when a layer was added to the store.
QgsMapLayer * mapLayer(const QString &id) const
Retrieve a pointer to a layer by layer id.
void layersAdded(const QList< QgsMapLayer * > &layers)
Emitted when one or more layers were added to the store.
Base class for all map layer types.
Definition qgsmaplayer.h:75
QFlags< ReadFlag > ReadFlags
QString source() const
Returns the source for the layer.
QString providerType() const
Returns the provider type (provider key) for this layer.
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
void configChanged()
Emitted whenever the configuration is changed.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:82
QString id
Definition qgsmaplayer.h:78
QString originalXmlProperties() const
Returns the XML properties of the original layer as they were when the layer was first read from the ...
Qgis::LayerType type
Definition qgsmaplayer.h:85
Q_INVOKABLE void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
virtual bool isEditable() const
Returns true if the layer can be edited.
bool writeLayerXml(QDomElement &layerElement, QDomDocument &document, const QgsReadWriteContext &context) const
Stores state in DOM node.
@ Identifiable
If the layer is identifiable using the identify map tool and as a WMS layer.
@ Removable
If the layer can be removed from the project. The layer will not be removable from the legend menu en...
@ FlagReadExtentFromXml
Read extent from xml and skip get extent from provider.
@ FlagTrustLayerMetadata
Trust layer metadata. Improves layer load time by skipping expensive checks like primary key unicity,...
@ FlagForceReadOnly
Force open as read only.
@ FlagDontResolveLayers
Don't resolve layer paths or create data providers for layers.
Definition