QGIS API Documentation 3.99.0-Master (26c88405ac0)
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 <algorithm>
21
22#include "qgsannotationlayer.h"
24#include "qgsapplication.h"
26#include "qgsauxiliarystorage.h"
27#include "qgsbookmarkmanager.h"
28#include "qgscolorutils.h"
30#include "qgsdatasourceuri.h"
33#include "qgsgrouplayer.h"
35#include "qgslayerdefinition.h"
36#include "qgslayertree.h"
38#include "qgslayertreeutils.h"
39#include "qgslayoutmanager.h"
40#include "qgslogger.h"
41#include "qgsmaplayerfactory.h"
42#include "qgsmaplayerstore.h"
44#include "qgsmapviewsmanager.h"
45#include "qgsmeshlayer.h"
46#include "qgsmessagelog.h"
47#include "qgsobjectvisitor.h"
48#include "qgspathresolver.h"
49#include "qgspluginlayer.h"
51#include "qgspointcloudlayer.h"
56#include "qgsprojectstorage.h"
60#include "qgsprojectutils.h"
61#include "qgsprojectversion.h"
63#include "qgsproviderregistry.h"
64#include "qgspythonrunner.h"
65#include "qgsrasterlayer.h"
66#include "qgsreadwritecontext.h"
67#include "qgsrelationmanager.h"
69#include "qgsruntimeprofiler.h"
70#include "qgssensormanager.h"
72#include "qgssnappingconfig.h"
74#include "qgsthreadingutils.h"
75#include "qgstiledscenelayer.h"
76#include "qgstransaction.h"
77#include "qgstransactiongroup.h"
78#include "qgsunittypes.h"
81#include "qgsvectortilelayer.h"
82#include "qgsziputils.h"
83
84#include <QApplication>
85#include <QDir>
86#include <QDomNode>
87#include <QFileInfo>
88#include <QObject>
89#include <QRegularExpression>
90#include <QStandardPaths>
91#include <QTemporaryFile>
92#include <QTextStream>
93#include <QThreadPool>
94#include <QUrl>
95#include <QUuid>
96
97#include "moc_qgsproject.cpp"
98
99#ifdef _MSC_VER
100#include <sys/utime.h>
101#else
102#include <utime.h>
103#endif
104
105// canonical project instance
106QgsProject *QgsProject::sProject = nullptr;
107
116QStringList makeKeyTokens_( const QString &scope, const QString &key )
117{
118 QStringList keyTokens = QStringList( scope );
119 keyTokens += key.split( '/', Qt::SkipEmptyParts );
120
121 // be sure to include the canonical root node
122 keyTokens.push_front( QStringLiteral( "properties" ) );
123
124 return keyTokens;
125}
126
127
128
138QgsProjectProperty *findKey_( const QString &scope,
139 const QString &key,
140 QgsProjectPropertyKey &rootProperty )
141{
142 QgsProjectPropertyKey *currentProperty = &rootProperty;
143 QgsProjectProperty *nextProperty; // link to next property down hierarchy
144
145 QStringList keySequence = makeKeyTokens_( scope, key );
146
147 while ( !keySequence.isEmpty() )
148 {
149 // if the current head of the sequence list matches the property name,
150 // then traverse down the property hierarchy
151 if ( keySequence.first() == currentProperty->name() )
152 {
153 // remove front key since we're traversing down a level
154 keySequence.pop_front();
155
156 if ( 1 == keySequence.count() )
157 {
158 // if we have only one key name left, then return the key found
159 return currentProperty->find( keySequence.front() );
160 }
161 else if ( keySequence.isEmpty() )
162 {
163 // if we're out of keys then the current property is the one we
164 // want; i.e., we're in the rate case of being at the top-most
165 // property node
166 return currentProperty;
167 }
168 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
169 {
170 if ( nextProperty->isKey() )
171 {
172 currentProperty = static_cast<QgsProjectPropertyKey *>( nextProperty );
173 }
174 else if ( nextProperty->isValue() && 1 == keySequence.count() )
175 {
176 // it may be that this may be one of several property value
177 // nodes keyed by QDict string; if this is the last remaining
178 // key token and the next property is a value node, then
179 // that's the situation, so return the currentProperty
180 return currentProperty;
181 }
182 else
183 {
184 // QgsProjectPropertyValue not Key, so return null
185 return nullptr;
186 }
187 }
188 else
189 {
190 // if the next key down isn't found
191 // then the overall key sequence doesn't exist
192 return nullptr;
193 }
194 }
195 else
196 {
197 return nullptr;
198 }
199 }
200
201 return nullptr;
202}
203
204
205
215QgsProjectProperty *addKey_( const QString &scope,
216 const QString &key,
217 QgsProjectPropertyKey *rootProperty,
218 const QVariant &value,
219 bool &propertiesModified )
220{
221 QStringList keySequence = makeKeyTokens_( scope, key );
222
223 // cursor through property key/value hierarchy
224 QgsProjectPropertyKey *currentProperty = rootProperty;
225 QgsProjectProperty *nextProperty; // link to next property down hierarchy
226 QgsProjectPropertyKey *newPropertyKey = nullptr;
227
228 propertiesModified = false;
229 while ( ! keySequence.isEmpty() )
230 {
231 // if the current head of the sequence list matches the property name,
232 // then traverse down the property hierarchy
233 if ( keySequence.first() == currentProperty->name() )
234 {
235 // remove front key since we're traversing down a level
236 keySequence.pop_front();
237
238 // if key sequence has one last element, then we use that as the
239 // name to store the value
240 if ( 1 == keySequence.count() )
241 {
242 QgsProjectProperty *property = currentProperty->find( keySequence.front() );
243 if ( !property || property->value() != value )
244 {
245 currentProperty->setValue( keySequence.front(), value );
246 propertiesModified = true;
247 }
248
249 return currentProperty;
250 }
251 // we're at the top element if popping the keySequence element
252 // will leave it empty; in that case, just add the key
253 else if ( keySequence.isEmpty() )
254 {
255 if ( currentProperty->value() != value )
256 {
257 currentProperty->setValue( value );
258 propertiesModified = true;
259 }
260
261 return currentProperty;
262 }
263 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
264 {
265 currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
266
267 if ( currentProperty )
268 {
269 continue;
270 }
271 else // QgsProjectPropertyValue not Key, so return null
272 {
273 return nullptr;
274 }
275 }
276 else // the next subkey doesn't exist, so add it
277 {
278 if ( ( newPropertyKey = currentProperty->addKey( keySequence.first() ) ) )
279 {
280 currentProperty = newPropertyKey;
281 }
282 continue;
283 }
284 }
285 else
286 {
287 return nullptr;
288 }
289 }
290
291 return nullptr;
292}
293
301void removeKey_( const QString &scope,
302 const QString &key,
303 QgsProjectPropertyKey &rootProperty )
304{
305 QgsProjectPropertyKey *currentProperty = &rootProperty;
306
307 QgsProjectProperty *nextProperty = nullptr; // link to next property down hierarchy
308 QgsProjectPropertyKey *previousQgsPropertyKey = nullptr; // link to previous property up hierarchy
309
310 QStringList keySequence = makeKeyTokens_( scope, key );
311
312 while ( ! keySequence.isEmpty() )
313 {
314 // if the current head of the sequence list matches the property name,
315 // then traverse down the property hierarchy
316 if ( keySequence.first() == currentProperty->name() )
317 {
318 // remove front key since we're traversing down a level
319 keySequence.pop_front();
320
321 // if we have only one key name left, then try to remove the key
322 // with that name
323 if ( 1 == keySequence.count() )
324 {
325 currentProperty->removeKey( keySequence.front() );
326 }
327 // if we're out of keys then the current property is the one we
328 // want to remove, but we can't delete it directly; we need to
329 // delete it from the parent property key container
330 else if ( keySequence.isEmpty() )
331 {
332 previousQgsPropertyKey->removeKey( currentProperty->name() );
333 }
334 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
335 {
336 previousQgsPropertyKey = currentProperty;
337 currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
338
339 if ( currentProperty )
340 {
341 continue;
342 }
343 else // QgsProjectPropertyValue not Key, so return null
344 {
345 return;
346 }
347 }
348 else // if the next key down isn't found
349 {
350 // then the overall key sequence doesn't exist
351 return;
352 }
353 }
354 else
355 {
356 return;
357 }
358 }
359}
360
362 : QObject( parent )
363 , mCapabilities( capabilities )
364 , mLayerStore( new QgsMapLayerStore( this ) )
365 , mBadLayerHandler( std::make_unique<QgsProjectBadLayerHandler>() )
366 , mSnappingConfig( this )
367 , mRelationManager( std::make_unique<QgsRelationManager>( this ) )
368 , mAnnotationManager( new QgsAnnotationManager( this ) )
369 , mLayoutManager( new QgsLayoutManager( this ) )
370 , mElevationProfileManager( new QgsElevationProfileManager( this ) )
371 , m3DViewsManager( new QgsMapViewsManager( this ) )
372 , mBookmarkManager( QgsBookmarkManager::createProjectBasedManager( this ) )
373 , mSensorManager( new QgsSensorManager( this ) )
374 , mViewSettings( new QgsProjectViewSettings( this ) )
375 , mStyleSettings( new QgsProjectStyleSettings( this ) )
376 , mTimeSettings( new QgsProjectTimeSettings( this ) )
377 , mElevationProperties( new QgsProjectElevationProperties( this ) )
378 , mDisplaySettings( new QgsProjectDisplaySettings( this ) )
379 , mGpsSettings( new QgsProjectGpsSettings( this ) )
380 , mRootGroup( std::make_unique<QgsLayerTree>() )
381 , mLabelingEngineSettings( new QgsLabelingEngineSettings )
382 , mArchive( new QgsArchive() )
383 , mAuxiliaryStorage( new QgsAuxiliaryStorage() )
384{
385 mProperties.setName( QStringLiteral( "properties" ) );
386
387 mMainAnnotationLayer = new QgsAnnotationLayer( QObject::tr( "Annotations" ), QgsAnnotationLayer::LayerOptions( mTransformContext ) );
388 mMainAnnotationLayer->setParent( this );
389
390 clear();
391
392 // bind the layer tree to the map layer registry.
393 // whenever layers are added to or removed from the registry,
394 // layer tree will be updated
395 mLayerTreeRegistryBridge = std::make_unique<QgsLayerTreeRegistryBridge>( mRootGroup.get(), this, this );
396 connect( this, &QgsProject::layersAdded, this, &QgsProject::onMapLayersAdded );
397 connect( this, &QgsProject::layersRemoved, this, [this] { cleanTransactionGroups(); } );
398 connect( this, qOverload< const QList<QgsMapLayer *> & >( &QgsProject::layersWillBeRemoved ), this, &QgsProject::onMapLayersRemoved );
399
400 // proxy map layer store signals to this
401 connect( mLayerStore.get(), qOverload<const QStringList &>( &QgsMapLayerStore::layersWillBeRemoved ),
402 this, [this]( const QStringList & layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
403 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersWillBeRemoved ),
404 this, [this]( const QList<QgsMapLayer *> &layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
405 connect( mLayerStore.get(), qOverload< const QString & >( &QgsMapLayerStore::layerWillBeRemoved ),
406 this, [this]( const QString & layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
407 connect( mLayerStore.get(), qOverload< QgsMapLayer * >( &QgsMapLayerStore::layerWillBeRemoved ),
408 this, [this]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
409 connect( mLayerStore.get(), qOverload<const QStringList & >( &QgsMapLayerStore::layersRemoved ), this,
410 [this]( const QStringList & layers ) { mProjectScope.reset(); emit layersRemoved( layers ); } );
411 connect( mLayerStore.get(), &QgsMapLayerStore::layerRemoved, this,
412 [this]( const QString & layer ) { mProjectScope.reset(); emit layerRemoved( layer ); } );
413 connect( mLayerStore.get(), &QgsMapLayerStore::allLayersRemoved, this,
414 [this]() { mProjectScope.reset(); emit removeAll(); } );
415 connect( mLayerStore.get(), &QgsMapLayerStore::layersAdded, this,
416 [this]( const QList< QgsMapLayer * > &layers ) { mProjectScope.reset(); emit layersAdded( layers ); } );
417 connect( mLayerStore.get(), &QgsMapLayerStore::layerWasAdded, this,
418 [this]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWasAdded( layer ); } );
419
421 {
423 }
424
425 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersWillBeRemoved ), this,
426 [this]( const QList<QgsMapLayer *> &layers )
427 {
428 for ( const auto &layer : layers )
429 {
430 disconnect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager.get(), &QgsRelationManager::updateRelationsStatus );
431 }
432 }
433 );
434 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersAdded ), this,
435 [this]( const QList<QgsMapLayer *> &layers )
436 {
437 for ( const auto &layer : layers )
438 {
439 connect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager.get(), &QgsRelationManager::updateRelationsStatus );
440 }
441 }
442 );
443
447
448 mStyleSettings->combinedStyleModel()->addDefaultStyle();
449}
450
451
453{
454 mIsBeingDeleted = true;
455
456 clear();
457 releaseHandlesToProjectArchive();
458
459 if ( this == sProject )
460 {
461 sProject = nullptr;
462 }
463}
464
466{
467 sProject = project;
468}
469
470
471QgsProject *QgsProject::instance() // skip-keyword-check
472{
473 if ( !sProject )
474 {
475 sProject = new QgsProject;
476
478 }
479 return sProject;
480}
481
482void QgsProject::setTitle( const QString &title )
483{
485
486 if ( title == mMetadata.title() )
487 return;
488
489 mMetadata.setTitle( title );
490 mProjectScope.reset();
491 emit metadataChanged();
492 emit titleChanged();
493
494 setDirty( true );
495}
496
497QString QgsProject::title() const
498{
499 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
501
502 return mMetadata.title();
503}
504
506{
508
509 const bool oldEvaluateDefaultValues = mFlags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide;
510 const bool newEvaluateDefaultValues = flags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide;
511 if ( oldEvaluateDefaultValues != newEvaluateDefaultValues )
512 {
513 const QMap<QString, QgsMapLayer *> layers = mapLayers();
514 for ( auto layerIt = layers.constBegin(); layerIt != layers.constEnd(); ++layerIt )
515 {
516 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() ) )
517 if ( vl->dataProvider() )
518 vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, newEvaluateDefaultValues );
519 }
520 }
521
522 const bool oldTrustLayerMetadata = mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics;
523 const bool newTrustLayerMetadata = flags & Qgis::ProjectFlag::TrustStoredLayerStatistics;
524 if ( oldTrustLayerMetadata != newTrustLayerMetadata )
525 {
526 const QMap<QString, QgsMapLayer *> layers = mapLayers();
527 for ( auto layerIt = layers.constBegin(); layerIt != layers.constEnd(); ++layerIt )
528 {
529 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() ) )
530 {
531 vl->setReadExtentFromXml( newTrustLayerMetadata );
532 }
533 }
534 }
535
536 if ( mFlags != flags )
537 {
538 mFlags = flags;
539 setDirty( true );
540 }
541}
542
543void QgsProject::setFlag( Qgis::ProjectFlag flag, bool enabled )
544{
546
547 Qgis::ProjectFlags newFlags = mFlags;
548 if ( enabled )
549 newFlags |= flag;
550 else
551 newFlags &= ~( static_cast< int >( flag ) );
552 setFlags( newFlags );
553}
554
555QString QgsProject::saveUser() const
556{
558
559 return mSaveUser;
560}
561
563{
565
566 return mSaveUserFull;
567}
568
570{
572
573 return mSaveDateTime;
574}
575
582
584{
586
587 return mDirty;
588}
589
590void QgsProject::setDirty( const bool dirty )
591{
593
594 if ( dirty && mDirtyBlockCount > 0 )
595 return;
596
597 if ( dirty )
598 emit dirtySet();
599
600 if ( mDirty == dirty )
601 return;
602
603 mDirty = dirty;
604 emit isDirtyChanged( mDirty );
605}
606
607void QgsProject::setPresetHomePath( const QString &path )
608{
610
611 if ( path == mHomePath )
612 return;
613
614 mHomePath = path;
615 mCachedHomePath.clear();
616 mProjectScope.reset();
617
618 emit homePathChanged();
619
620 setDirty( true );
621}
622
623void QgsProject::registerTranslatableContainers( QgsTranslationContext *translationContext, QgsAttributeEditorContainer *parent, const QString &layerId )
624{
626
627 const QList<QgsAttributeEditorElement *> elements = parent->children();
628
629 for ( QgsAttributeEditorElement *element : elements )
630 {
631 if ( element->type() == Qgis::AttributeEditorType::Container )
632 {
633 QgsAttributeEditorContainer *container = qgis::down_cast<QgsAttributeEditorContainer *>( element );
634
635 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:formcontainers" ).arg( layerId ), container->name() );
636
637 if ( !container->children().empty() )
638 registerTranslatableContainers( translationContext, container, layerId );
639 }
640 }
641}
642
644{
646
647 //register layers
648 const QList<QgsLayerTreeLayer *> layers = mRootGroup->findLayers();
649
650 for ( const QgsLayerTreeLayer *layer : layers )
651 {
652 translationContext->registerTranslation( QStringLiteral( "project:layers:%1" ).arg( layer->layerId() ), layer->name() );
653
654 if ( QgsMapLayer *mapLayer = layer->layer() )
655 {
656 switch ( mapLayer->type() )
657 {
659 {
660 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer );
661
662 //register aliases and widget settings
663 const QgsFields fields = vlayer->fields();
664 for ( const QgsField &field : fields )
665 {
666 QString fieldName;
667 if ( field.alias().isEmpty() )
668 fieldName = field.name();
669 else
670 fieldName = field.alias();
671
672 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:fieldaliases" ).arg( vlayer->id() ), fieldName );
673
674 if ( field.editorWidgetSetup().type() == QLatin1String( "ValueRelation" ) )
675 {
676 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:fields:%2:valuerelationvalue" ).arg( vlayer->id(), field.name() ), field.editorWidgetSetup().config().value( QStringLiteral( "Value" ) ).toString() );
677 }
678 if ( field.editorWidgetSetup().type() == QLatin1String( "ValueMap" ) )
679 {
680 if ( field.editorWidgetSetup().config().value( QStringLiteral( "map" ) ).canConvert<QList<QVariant>>() )
681 {
682 const QList<QVariant> valueList = field.editorWidgetSetup().config().value( QStringLiteral( "map" ) ).toList();
683
684 for ( int i = 0, row = 0; i < valueList.count(); i++, row++ )
685 {
686 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:fields:%2:valuemapdescriptions" ).arg( vlayer->id(), field.name() ), valueList[i].toMap().constBegin().key() );
687 }
688 }
689 }
690 }
691
692 //register formcontainers
693 registerTranslatableContainers( translationContext, vlayer->editFormConfig().invisibleRootContainer(), vlayer->id() );
694 break;
695 }
696
705 break;
706 }
707
708 //register metadata
709 mapLayer->metadata().registerTranslations( translationContext );
710 }
711 }
712
713 //register layergroups
714 const QList<QgsLayerTreeGroup *> groupLayers = mRootGroup->findGroups();
715 for ( const QgsLayerTreeGroup *groupLayer : groupLayers )
716 {
717 translationContext->registerTranslation( QStringLiteral( "project:layergroups" ), groupLayer->name() );
718 }
719
720 //register relations
721 const QList<QgsRelation> &relations = mRelationManager->relations().values();
722 for ( const QgsRelation &relation : relations )
723 {
724 translationContext->registerTranslation( QStringLiteral( "project:relations" ), relation.name() );
725 }
726
727 //register metadata
728 mMetadata.registerTranslations( translationContext );
729}
730
732{
734
735 mDataDefinedServerProperties = properties;
736}
737
739{
741
742 return mDataDefinedServerProperties;
743}
744
746{
748
749 switch ( mTransactionMode )
750 {
753 {
754 if ( ! vectorLayer )
755 return false;
756 return vectorLayer->startEditing();
757 }
758
760 return mEditBufferGroup.startEditing();
761 }
762
763 return false;
764}
765
766bool QgsProject::commitChanges( QStringList &commitErrors, bool stopEditing, QgsVectorLayer *vectorLayer )
767{
769
770 switch ( mTransactionMode )
771 {
774 {
775 if ( ! vectorLayer )
776 {
777 commitErrors.append( tr( "Trying to commit changes without a layer specified. This only works if the transaction mode is buffered" ) );
778 return false;
779 }
780 bool success = vectorLayer->commitChanges( stopEditing );
781 commitErrors = vectorLayer->commitErrors();
782 return success;
783 }
784
786 return mEditBufferGroup.commitChanges( commitErrors, stopEditing );
787 }
788
789 return false;
790}
791
792bool QgsProject::rollBack( QStringList &rollbackErrors, bool stopEditing, QgsVectorLayer *vectorLayer )
793{
795
796 switch ( mTransactionMode )
797 {
800 {
801 if ( ! vectorLayer )
802 {
803 rollbackErrors.append( tr( "Trying to roll back changes without a layer specified. This only works if the transaction mode is buffered" ) );
804 return false;
805 }
806 bool success = vectorLayer->rollBack( stopEditing );
807 rollbackErrors = vectorLayer->commitErrors();
808 return success;
809 }
810
812 return mEditBufferGroup.rollBack( rollbackErrors, stopEditing );
813 }
814
815 return false;
816}
817
818void QgsProject::setFileName( const QString &name )
819{
821
822 if ( name == mFile.fileName() )
823 return;
824
825 const QString oldHomePath = homePath();
826
827 mFile.setFileName( name );
828 mCachedHomePath.clear();
829 mProjectScope.reset();
830
831 emit fileNameChanged();
832
833 const QString newHomePath = homePath();
834 if ( newHomePath != oldHomePath )
835 emit homePathChanged();
836
837 setDirty( true );
838}
839
840QString QgsProject::fileName() const
841{
842 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
844
845 return mFile.fileName();
846}
847
848void QgsProject::setOriginalPath( const QString &path )
849{
851
852 mOriginalPath = path;
853}
854
856{
858
859 return mOriginalPath;
860}
861
862QFileInfo QgsProject::fileInfo() const
863{
865
866 return QFileInfo( mFile );
867}
868
870{
871 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
873
875}
876
878{
880
881 if ( QgsProjectStorage *storage = projectStorage() )
882 {
884 storage->readProjectStorageMetadata( mFile.fileName(), metadata );
885 return metadata.lastModified;
886 }
887 else
888 {
889 return QFileInfo( mFile.fileName() ).lastModified();
890 }
891}
892
894{
896
897 if ( projectStorage() )
898 return QString();
899
900 if ( mFile.fileName().isEmpty() )
901 return QString(); // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
902
903 return QFileInfo( mFile.fileName() ).absolutePath();
904}
905
907{
908 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
910
911 if ( projectStorage() )
912 return QString();
913
914 if ( mFile.fileName().isEmpty() )
915 return QString(); // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
916
917 return QFileInfo( mFile.fileName() ).absoluteFilePath();
918}
919
920QString QgsProject::baseName() const
921{
922 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
924
925 if ( QgsProjectStorage *storage = projectStorage() )
926 {
928 storage->readProjectStorageMetadata( mFile.fileName(), metadata );
929 return metadata.name;
930 }
931 else
932 {
933 return QFileInfo( mFile.fileName() ).completeBaseName();
934 }
935}
936
938{
940
941 const bool absolutePaths = readBoolEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), false );
943}
944
946{
948
949 switch ( type )
950 {
952 writeEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), true );
953 break;
955 writeEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), false );
956 break;
957 }
958}
959
961{
962 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
964
965 return mCrs;
966}
967
969{
971
972 return mCrs3D.isValid() ? mCrs3D : mCrs;
973}
974
975void QgsProject::setCrs( const QgsCoordinateReferenceSystem &crs, bool adjustEllipsoid )
976{
978
979 if ( crs != mCrs )
980 {
981 const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
982 const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
983 mCrs = crs;
984 writeEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), crs.isValid() ? 1 : 0 );
985 mProjectScope.reset();
986
987 // if annotation layer doesn't have a crs (i.e. in a newly created project), it should
988 // initially inherit the project CRS
989 if ( !mMainAnnotationLayer->crs().isValid() || mMainAnnotationLayer->isEmpty() )
990 mMainAnnotationLayer->setCrs( crs );
991
992 rebuildCrs3D();
993
994 setDirty( true );
995 emit crsChanged();
996 // Did vertical crs also change as a result of this? If so, emit signal
997 if ( oldVerticalCrs != verticalCrs() )
998 emit verticalCrsChanged();
999 if ( oldCrs3D != mCrs3D )
1000 emit crs3DChanged();
1001 }
1002
1003 if ( adjustEllipsoid )
1004 setEllipsoid( crs.ellipsoidAcronym() );
1005}
1006
1008{
1009 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
1011
1012 if ( !crs().isValid() )
1013 return Qgis::geoNone();
1014
1015 return readEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ), Qgis::geoNone() );
1016}
1017
1019{
1021
1022 if ( ellipsoid == readEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ) ) )
1023 return;
1024
1025 mProjectScope.reset();
1026 writeEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ), ellipsoid );
1028}
1029
1031{
1032 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
1034
1035 switch ( mCrs.type() )
1036 {
1037 case Qgis::CrsType::Vertical: // would hope this never happens!
1038 QgsDebugError( QStringLiteral( "Project has a vertical CRS set as the horizontal CRS!" ) );
1039 return mCrs;
1040
1042 return mCrs.verticalCrs();
1043
1055 break;
1056 }
1057 return mVerticalCrs;
1058}
1059
1061{
1063 bool res = true;
1064 if ( crs.isValid() )
1065 {
1066 // validate that passed crs is a vertical crs
1067 switch ( crs.type() )
1068 {
1070 break;
1071
1084 if ( errorMessage )
1085 *errorMessage = QObject::tr( "Specified CRS is a %1 CRS, not a Vertical CRS" ).arg( qgsEnumValueToKey( crs.type() ) );
1086 return false;
1087 }
1088 }
1089
1090 if ( crs != mVerticalCrs )
1091 {
1092 const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
1093 const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
1094
1095 switch ( mCrs.type() )
1096 {
1098 if ( crs != oldVerticalCrs )
1099 {
1100 if ( errorMessage )
1101 *errorMessage = QObject::tr( "Project CRS is a Compound CRS, specified Vertical CRS will be ignored" );
1102 return false;
1103 }
1104 break;
1105
1107 if ( crs != oldVerticalCrs )
1108 {
1109 if ( errorMessage )
1110 *errorMessage = QObject::tr( "Project CRS is a Geographic 3D CRS, specified Vertical CRS will be ignored" );
1111 return false;
1112 }
1113 break;
1114
1116 if ( crs != oldVerticalCrs )
1117 {
1118 if ( errorMessage )
1119 *errorMessage = QObject::tr( "Project CRS is a Geocentric CRS, specified Vertical CRS will be ignored" );
1120 return false;
1121 }
1122 break;
1123
1125 if ( mCrs.hasVerticalAxis() && crs != oldVerticalCrs )
1126 {
1127 if ( errorMessage )
1128 *errorMessage = QObject::tr( "Project CRS is a Projected 3D CRS, specified Vertical CRS will be ignored" );
1129 return false;
1130 }
1131 break;
1132
1142 break;
1143 }
1144
1145 mVerticalCrs = crs;
1146 res = rebuildCrs3D( errorMessage );
1147 mProjectScope.reset();
1148
1149 setDirty( true );
1150 // only emit signal if vertical crs was actually changed, so eg if mCrs is compound
1151 // then we haven't actually changed the vertical crs by this call!
1152 if ( verticalCrs() != oldVerticalCrs )
1153 emit verticalCrsChanged();
1154 if ( mCrs3D != oldCrs3D )
1155 emit crs3DChanged();
1156 }
1157 return res;
1158}
1159
1161{
1162 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
1164
1165 return mTransformContext;
1166}
1167
1169{
1171
1172 if ( context == mTransformContext )
1173 return;
1174
1175 mTransformContext = context;
1176 mProjectScope.reset();
1177
1178 mMainAnnotationLayer->setTransformContext( context );
1179 for ( auto &layer : mLayerStore.get()->mapLayers() )
1180 {
1181 layer->setTransformContext( context );
1182 }
1184}
1185
1187{
1189
1190 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
1191
1192 emit aboutToBeCleared();
1193
1194 if ( !mIsBeingDeleted )
1195 {
1196 // Unregister expression functions stored in the project.
1197 // If we clean on destruction we may end-up with a non-valid
1198 // mPythonUtils, so be safe and only clean when not destroying.
1199 // This should be called before calling mProperties.clearKeys().
1201 }
1202
1203 mProjectScope.reset();
1204 mFile.setFileName( QString() );
1205 mProperties.clearKeys();
1206 mSaveUser.clear();
1207 mSaveUserFull.clear();
1208 mSaveDateTime = QDateTime();
1209 mSaveVersion = QgsProjectVersion();
1210 mHomePath.clear();
1211 mCachedHomePath.clear();
1212 mTransactionMode = Qgis::TransactionMode::Disabled;
1213 mFlags = Qgis::ProjectFlags();
1214 mDirty = false;
1215 mCustomVariables.clear();
1217 mVerticalCrs = QgsCoordinateReferenceSystem();
1219 mMetadata = QgsProjectMetadata();
1220 mElevationShadingRenderer = QgsElevationShadingRenderer();
1221 if ( !mSettings.value( QStringLiteral( "projects/anonymize_new_projects" ), false, QgsSettings::Core ).toBool() )
1222 {
1223 mMetadata.setCreationDateTime( QDateTime::currentDateTime() );
1224 mMetadata.setAuthor( QgsApplication::userFullName() );
1225 }
1226 emit metadataChanged();
1227
1229 context.readSettings();
1230 setTransformContext( context );
1231
1232 //fallback to QGIS default measurement unit
1233 bool ok = false;
1234 const Qgis::DistanceUnit distanceUnit = QgsUnitTypes::decodeDistanceUnit( mSettings.value( QStringLiteral( "/qgis/measure/displayunits" ) ).toString(), &ok );
1235 setDistanceUnits( ok ? distanceUnit : Qgis::DistanceUnit::Meters );
1236 ok = false;
1237 const Qgis::AreaUnit areaUnits = QgsUnitTypes::decodeAreaUnit( mSettings.value( QStringLiteral( "/qgis/measure/areaunits" ) ).toString(), &ok );
1239
1241
1242 mEmbeddedLayers.clear();
1243 mRelationManager->clear();
1244 mAnnotationManager->clear();
1245 mLayoutManager->clear();
1246 mElevationProfileManager->clear();
1247 m3DViewsManager->clear();
1248 mBookmarkManager->clear();
1249 mSensorManager->clear();
1250 mViewSettings->reset();
1251 mTimeSettings->reset();
1252 mElevationProperties->reset();
1253 mDisplaySettings->reset();
1254 mGpsSettings->reset();
1255 mSnappingConfig.reset();
1256 mAvoidIntersectionsMode = Qgis::AvoidIntersectionsMode::AllowIntersections;
1259
1260 mMapThemeCollection = std::make_unique< QgsMapThemeCollection >( this );
1262
1263 mLabelingEngineSettings->clear();
1264
1265 // must happen BEFORE archive reset, because we need to release the hold on any files which
1266 // exists within the archive. Otherwise the archive can't be removed.
1267 releaseHandlesToProjectArchive();
1268
1269 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >();
1270 mArchive = std::make_unique< QgsArchive >();
1271
1272 // must happen AFTER archive reset, as it will populate a new style database within the new archive
1273 mStyleSettings->reset();
1274
1276
1277 if ( !mIsBeingDeleted )
1278 {
1279 // possibly other signals should also not be thrown on destruction -- e.g. labelEngineSettingsChanged, etc.
1280 emit projectColorsChanged();
1281 }
1282
1283 // reset some default project properties
1284 // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
1285 writeEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/Automatic" ), true );
1286 writeEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DecimalPlaces" ), 2 );
1287
1288 const bool defaultRelativePaths = mSettings.value( QStringLiteral( "/qgis/defaultProjectPathsRelative" ), true ).toBool();
1290
1291 int red = mSettings.value( QStringLiteral( "qgis/default_canvas_color_red" ), 255 ).toInt();
1292 int green = mSettings.value( QStringLiteral( "qgis/default_canvas_color_green" ), 255 ).toInt();
1293 int blue = mSettings.value( QStringLiteral( "qgis/default_canvas_color_blue" ), 255 ).toInt();
1294 setBackgroundColor( QColor( red, green, blue ) );
1295
1296 red = mSettings.value( QStringLiteral( "qgis/default_selection_color_red" ), 255 ).toInt();
1297 green = mSettings.value( QStringLiteral( "qgis/default_selection_color_green" ), 255 ).toInt();
1298 blue = mSettings.value( QStringLiteral( "qgis/default_selection_color_blue" ), 0 ).toInt();
1299 const int alpha = mSettings.value( QStringLiteral( "qgis/default_selection_color_alpha" ), 255 ).toInt();
1300 setSelectionColor( QColor( red, green, blue, alpha ) );
1301
1302 mSnappingConfig.clearIndividualLayerSettings();
1303
1305 mRootGroup->clear();
1306 if ( mMainAnnotationLayer )
1307 mMainAnnotationLayer->reset();
1308
1309 snapSingleBlocker.release();
1310
1311 if ( !mBlockSnappingUpdates )
1312 emit snappingConfigChanged( mSnappingConfig );
1313
1314 setDirty( false );
1315 emit homePathChanged();
1316 emit fileNameChanged();
1317 if ( !mBlockChangeSignalsDuringClear )
1318 {
1319 emit verticalCrsChanged();
1320 emit crs3DChanged();
1321 }
1322 emit cleared();
1323}
1324
1325// basically a debugging tool to dump property list values
1326void dump_( const QgsProjectPropertyKey &topQgsPropertyKey )
1327{
1328 QgsDebugMsgLevel( QStringLiteral( "current properties:" ), 3 );
1329 topQgsPropertyKey.dump();
1330}
1331
1360void _getProperties( const QDomDocument &doc, QgsProjectPropertyKey &project_properties )
1361{
1362 const QDomElement propertiesElem = doc.documentElement().firstChildElement( QStringLiteral( "properties" ) );
1363
1364 if ( propertiesElem.isNull() ) // no properties found, so we're done
1365 {
1366 return;
1367 }
1368
1369 const QDomNodeList scopes = propertiesElem.childNodes();
1370
1371 if ( propertiesElem.firstChild().isNull() )
1372 {
1373 QgsDebugError( QStringLiteral( "empty ``properties'' XML tag ... bailing" ) );
1374 return;
1375 }
1376
1377 if ( ! project_properties.readXml( propertiesElem ) )
1378 {
1379 QgsDebugError( QStringLiteral( "Project_properties.readXml() failed" ) );
1380 }
1381}
1382
1389QgsPropertyCollection getDataDefinedServerProperties( const QDomDocument &doc, const QgsPropertiesDefinition &dataDefinedServerPropertyDefinitions )
1390{
1391 QgsPropertyCollection ddServerProperties;
1392 // Read data defined server properties
1393 const QDomElement ddElem = doc.documentElement().firstChildElement( QStringLiteral( "dataDefinedServerProperties" ) );
1394 if ( !ddElem.isNull() )
1395 {
1396 if ( !ddServerProperties.readXml( ddElem, dataDefinedServerPropertyDefinitions ) )
1397 {
1398 QgsDebugError( QStringLiteral( "dataDefinedServerProperties.readXml() failed" ) );
1399 }
1400 }
1401 return ddServerProperties;
1402}
1403
1408static void _getTitle( const QDomDocument &doc, QString &title )
1409{
1410 const QDomElement titleNode = doc.documentElement().firstChildElement( QStringLiteral( "title" ) );
1411
1412 title.clear(); // by default the title will be empty
1413
1414 if ( titleNode.isNull() )
1415 {
1416 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1417 return;
1418 }
1419
1420 if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text
1421 {
1422 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1423 return;
1424 }
1425
1426 const QDomNode titleTextNode = titleNode.firstChild(); // should only have one child
1427
1428 if ( !titleTextNode.isText() )
1429 {
1430 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1431 return;
1432 }
1433
1434 const QDomText titleText = titleTextNode.toText();
1435
1436 title = titleText.data();
1437
1438}
1439
1440static void readProjectFileMetadata( const QDomDocument &doc, QString &lastUser, QString &lastUserFull, QDateTime &lastSaveDateTime )
1441{
1442 const QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
1443
1444 if ( !nl.count() )
1445 {
1446 QgsDebugError( QStringLiteral( "unable to find qgis element" ) );
1447 return;
1448 }
1449
1450 const QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1451
1452 const QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1453 lastUser = qgisElement.attribute( QStringLiteral( "saveUser" ), QString() );
1454 lastUserFull = qgisElement.attribute( QStringLiteral( "saveUserFull" ), QString() );
1455 lastSaveDateTime = QDateTime::fromString( qgisElement.attribute( QStringLiteral( "saveDateTime" ), QString() ), Qt::ISODate );
1456}
1457
1458QgsProjectVersion getVersion( const QDomDocument &doc )
1459{
1460 const QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
1461
1462 if ( !nl.count() )
1463 {
1464 QgsDebugError( QStringLiteral( " unable to find qgis element in project file" ) );
1465 return QgsProjectVersion( 0, 0, 0, QString() );
1466 }
1467
1468 const QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1469
1470 const QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1471 QgsProjectVersion projectVersion( qgisElement.attribute( QStringLiteral( "version" ) ) );
1472 return projectVersion;
1473}
1474
1476{
1478
1479 return mSnappingConfig;
1480}
1481
1483{
1485
1486 if ( mSnappingConfig == snappingConfig )
1487 return;
1488
1489 mSnappingConfig = snappingConfig;
1490 setDirty( true );
1491 emit snappingConfigChanged( mSnappingConfig );
1492}
1493
1495{
1497
1498 if ( mAvoidIntersectionsMode == mode )
1499 return;
1500
1501 mAvoidIntersectionsMode = mode;
1503}
1504
1505static QgsMapLayer::ReadFlags projectFlagsToLayerReadFlags( Qgis::ProjectReadFlags projectReadFlags, Qgis::ProjectFlags projectFlags )
1506{
1508 // Propagate don't resolve layers
1509 if ( projectReadFlags & Qgis::ProjectReadFlag::DontResolveLayers )
1511 // Propagate trust layer metadata flag
1512 // Propagate read extent from XML based trust layer metadata flag
1513 if ( ( projectFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics ) || ( projectReadFlags & Qgis::ProjectReadFlag::TrustLayerMetadata ) )
1514 {
1517 }
1518 // Propagate open layers in read-only mode
1519 if ( ( projectReadFlags & Qgis::ProjectReadFlag::ForceReadOnlyLayers ) )
1520 layerFlags |= QgsMapLayer::FlagForceReadOnly;
1521
1522 return layerFlags;
1523}
1524
1534
1535void QgsProject::preloadProviders( const QVector<QDomNode> &parallelLayerNodes,
1536 const QgsReadWriteContext &context,
1537 QMap<QString, QgsDataProvider *> &loadedProviders,
1538 QgsMapLayer::ReadFlags layerReadFlags,
1539 int totalProviderCount )
1540{
1541 int i = 0;
1542 QEventLoop loop;
1543
1544 QMap<QString, LayerToLoad> layersToLoad;
1545
1546 for ( const QDomNode &node : parallelLayerNodes )
1547 {
1548 LayerToLoad layerToLoad;
1549
1550 const QDomElement layerElement = node.toElement();
1551 layerToLoad.layerElement = layerElement;
1552 layerToLoad.layerId = layerElement.namedItem( QStringLiteral( "id" ) ).toElement().text();
1553 layerToLoad.provider = layerElement.namedItem( QStringLiteral( "provider" ) ).toElement().text();
1554 layerToLoad.dataSource = layerElement.namedItem( QStringLiteral( "datasource" ) ).toElement().text();
1555
1556 layerToLoad.dataSource = QgsProviderRegistry::instance()->relativeToAbsoluteUri( layerToLoad.provider, layerToLoad.dataSource, context );
1557
1558 layerToLoad.options = QgsDataProvider::ProviderOptions( {context.transformContext()} );
1559 layerToLoad.flags = QgsMapLayer::providerReadFlags( node, layerReadFlags );
1560
1561 // Requesting credential from worker thread could lead to deadlocks because the main thread is waiting for worker thread to fininsh
1562 layerToLoad.flags.setFlag( Qgis::DataProviderReadFlag::SkipCredentialsRequest, true );
1563 layerToLoad.flags.setFlag( Qgis::DataProviderReadFlag::ParallelThreadLoading, true );
1564
1565 layersToLoad.insert( layerToLoad.layerId, layerToLoad );
1566 }
1567
1568 while ( !layersToLoad.isEmpty() )
1569 {
1570 const QList<LayerToLoad> layersToAttemptInParallel = layersToLoad.values();
1571 QString layerToAttemptInMainThread;
1572
1573 QHash<QString, QgsRunnableProviderCreator *> runnables;
1574 QThreadPool threadPool;
1575 threadPool.setMaxThreadCount( QgsSettingsRegistryCore::settingsLayerParallelLoadingMaxCount->value() );
1576
1577 for ( const LayerToLoad &lay : layersToAttemptInParallel )
1578 {
1579 QgsRunnableProviderCreator *run = new QgsRunnableProviderCreator( lay.layerId, lay.provider, lay.dataSource, lay.options, lay.flags );
1580 runnables.insert( lay.layerId, run );
1581
1582 QObject::connect( run, &QgsRunnableProviderCreator::providerCreated, run, [&]( bool isValid, const QString & layId )
1583 {
1584 if ( isValid )
1585 {
1586 layersToLoad.remove( layId );
1587 i++;
1588 QgsRunnableProviderCreator *finishedRun = runnables.value( layId, nullptr );
1589 Q_ASSERT( finishedRun );
1590
1591 std::unique_ptr<QgsDataProvider> provider( finishedRun->dataProvider() );
1592 Q_ASSERT( provider && provider->isValid() );
1593
1594 loadedProviders.insert( layId, provider.release() );
1595 emit layerLoaded( i, totalProviderCount );
1596 }
1597 else
1598 {
1599 if ( layerToAttemptInMainThread.isEmpty() )
1600 layerToAttemptInMainThread = layId;
1601 threadPool.clear(); //we have to stop all loading provider to try this layer in main thread and maybe have credentials
1602 }
1603
1604 if ( i == parallelLayerNodes.count() || !isValid )
1605 loop.quit();
1606 } );
1607 threadPool.start( run );
1608 }
1609 loop.exec();
1610
1611 threadPool.waitForDone(); // to be sure all threads are finished
1612
1613 qDeleteAll( runnables );
1614
1615 // 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
1616 auto it = layersToLoad.find( layerToAttemptInMainThread );
1617 if ( it != layersToLoad.end() )
1618 {
1619 std::unique_ptr<QgsDataProvider> provider;
1620 QString layerId;
1621 {
1622 const LayerToLoad &lay = it.value();
1623 Qgis::DataProviderReadFlags providerFlags = lay.flags;
1624 providerFlags.setFlag( Qgis::DataProviderReadFlag::SkipCredentialsRequest, false );
1625 providerFlags.setFlag( Qgis::DataProviderReadFlag::ParallelThreadLoading, false );
1626 QgsScopedRuntimeProfile profile( "Create data providers/" + lay.layerId, QStringLiteral( "projectload" ) );
1627 provider.reset( QgsProviderRegistry::instance()->createProvider( lay.provider, lay.dataSource, lay.options, providerFlags ) );
1628 i++;
1629 if ( provider && provider->isValid() )
1630 {
1631 emit layerLoaded( i, totalProviderCount );
1632 }
1633 layerId = lay.layerId;
1634 layersToLoad.erase( it );
1635 // can't access "lay" anymore -- it's now been freed
1636 }
1637 loadedProviders.insert( layerId, provider.release() );
1638 }
1639
1640 // if there still are some not loaded providers or some invalid in parallel thread we start again
1641 }
1642
1643}
1644
1645void QgsProject::releaseHandlesToProjectArchive()
1646{
1647 mStyleSettings->removeProjectStyle();
1648}
1649
1650bool QgsProject::rebuildCrs3D( QString *error )
1651{
1652 bool res = true;
1653 if ( !mCrs.isValid() )
1654 {
1655 mCrs3D = QgsCoordinateReferenceSystem();
1656 }
1657 else if ( !mVerticalCrs.isValid() )
1658 {
1659 mCrs3D = mCrs;
1660 }
1661 else
1662 {
1663 switch ( mCrs.type() )
1664 {
1668 mCrs3D = mCrs;
1669 break;
1670
1672 {
1673 QString tempError;
1674 mCrs3D = mCrs.hasVerticalAxis() ? mCrs : QgsCoordinateReferenceSystem::createCompoundCrs( mCrs, mVerticalCrs, error ? *error : tempError );
1675 res = mCrs3D.isValid();
1676 break;
1677 }
1678
1680 // nonsense situation
1681 mCrs3D = QgsCoordinateReferenceSystem();
1682 res = false;
1683 break;
1684
1693 {
1694 QString tempError;
1695 mCrs3D = QgsCoordinateReferenceSystem::createCompoundCrs( mCrs, mVerticalCrs, error ? *error : tempError );
1696 res = mCrs3D.isValid();
1697 break;
1698 }
1699 }
1700 }
1701 return res;
1702}
1703
1704bool QgsProject::_getMapLayers( const QDomDocument &doc, QList<QDomNode> &brokenNodes, Qgis::ProjectReadFlags flags )
1705{
1707
1708 // Layer order is set by the restoring the legend settings from project file.
1709 // This is done on the 'readProject( ... )' signal
1710
1711 QDomElement layerElement = doc.documentElement().firstChildElement( QStringLiteral( "projectlayers" ) ).firstChildElement( QStringLiteral( "maplayer" ) );
1712
1713 // process the map layer nodes
1714
1715 if ( layerElement.isNull() ) // if we have no layers to process, bail
1716 {
1717 return true; // Decided to return "true" since it's
1718 // possible for there to be a project with no
1719 // layers; but also, more imporantly, this
1720 // would cause the tests/qgsproject to fail
1721 // since the test suite doesn't currently
1722 // support test layers
1723 }
1724
1725 bool returnStatus = true;
1726 int numLayers = 0;
1727
1728 while ( ! layerElement.isNull() )
1729 {
1730 numLayers++;
1731 layerElement = layerElement.nextSiblingElement( QStringLiteral( "maplayer" ) );
1732 }
1733
1734 // order layers based on their dependencies
1735 QgsScopedRuntimeProfile profile( tr( "Sorting layers" ), QStringLiteral( "projectload" ) );
1736 const QgsLayerDefinition::DependencySorter depSorter( doc );
1737 if ( depSorter.hasCycle() )
1738 return false;
1739
1740 // Missing a dependency? We still load all the layers, otherwise the project is completely broken!
1741 if ( depSorter.hasMissingDependency() )
1742 returnStatus = false;
1743
1744 emit layerLoaded( 0, numLayers );
1745
1746 const QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
1747 const int totalLayerCount = sortedLayerNodes.count();
1748
1749 QVector<QDomNode> parallelLoading;
1750 QMap<QString, QgsDataProvider *> loadedProviders;
1751
1754 {
1755 profile.switchTask( tr( "Load providers in parallel" ) );
1756 for ( const QDomNode &node : sortedLayerNodes )
1757 {
1758 const QDomElement element = node.toElement();
1759 if ( element.attribute( QStringLiteral( "embedded" ) ) != QLatin1String( "1" ) )
1760 {
1761 const QString layerId = node.namedItem( QStringLiteral( "id" ) ).toElement().text();
1762 if ( !depSorter.isLayerDependent( layerId ) )
1763 {
1764 const QDomNode mnl = element.namedItem( QStringLiteral( "provider" ) );
1765 const QDomElement mne = mnl.toElement();
1766 const QString provider = mne.text();
1767 QgsProviderMetadata *meta = QgsProviderRegistry::instance()->providerMetadata( provider );
1768 if ( meta && meta->providerCapabilities().testFlag( QgsProviderMetadata::ParallelCreateProvider ) )
1769 {
1770 parallelLoading.append( node );
1771 continue;
1772 }
1773 }
1774 }
1775 }
1776
1777 QgsReadWriteContext context;
1778 context.setPathResolver( pathResolver() );
1779 if ( !parallelLoading.isEmpty() )
1780 preloadProviders( parallelLoading, context, loadedProviders, projectFlagsToLayerReadFlags( flags, mFlags ), sortedLayerNodes.count() );
1781 }
1782
1783 int i = loadedProviders.count();
1784 for ( const QDomNode &node : std::as_const( sortedLayerNodes ) )
1785 {
1786 const QDomElement element = node.toElement();
1787 const QString name = translate( QStringLiteral( "project:layers:%1" ).arg( node.namedItem( QStringLiteral( "id" ) ).toElement().text() ), node.namedItem( QStringLiteral( "layername" ) ).toElement().text() );
1788 if ( !name.isNull() )
1789 emit loadingLayer( tr( "Loading layer %1" ).arg( name ) );
1790
1791 profile.switchTask( name );
1792 if ( element.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
1793 {
1794 createEmbeddedLayer( element.attribute( QStringLiteral( "id" ) ), readPath( element.attribute( QStringLiteral( "project" ) ) ), brokenNodes, true, flags );
1795 }
1796 else
1797 {
1798 QgsReadWriteContext context;
1799 context.setPathResolver( pathResolver() );
1800 context.setProjectTranslator( this );
1802 QString layerId = element.namedItem( QStringLiteral( "id" ) ).toElement().text();
1803
1804 if ( !addLayer( element, brokenNodes, context, flags, loadedProviders.take( layerId ) ) )
1805 {
1806 returnStatus = false;
1807 }
1808 const auto messages = context.takeMessages();
1809 if ( !messages.isEmpty() )
1810 {
1811 emit loadingLayerMessageReceived( tr( "Loading layer %1" ).arg( name ), messages );
1812 }
1813 }
1814 emit layerLoaded( i + 1, totalLayerCount );
1815 i++;
1816 }
1817
1818 return returnStatus;
1819}
1820
1821bool QgsProject::addLayer( const QDomElement &layerElem,
1822 QList<QDomNode> &brokenNodes,
1823 QgsReadWriteContext &context,
1825 QgsDataProvider *provider )
1826{
1828
1829 const QString type = layerElem.attribute( QStringLiteral( "type" ) );
1830 QgsDebugMsgLevel( "Layer type is " + type, 4 );
1831 std::unique_ptr<QgsMapLayer> mapLayer;
1832
1833 QgsScopedRuntimeProfile profile( tr( "Create layer" ), QStringLiteral( "projectload" ) );
1834
1835 bool ok = false;
1836 const Qgis::LayerType layerType( QgsMapLayerFactory::typeFromString( type, ok ) );
1837 if ( !ok )
1838 {
1839 QgsDebugError( QStringLiteral( "Unknown layer type \"%1\"" ).arg( type ) );
1840 return false;
1841 }
1842
1843 switch ( layerType )
1844 {
1846 mapLayer = std::make_unique<QgsVectorLayer>();
1847 break;
1848
1850 mapLayer = std::make_unique<QgsRasterLayer>();
1851 break;
1852
1854 mapLayer = std::make_unique<QgsMeshLayer>();
1855 break;
1856
1858 mapLayer = std::make_unique<QgsVectorTileLayer>();
1859 break;
1860
1862 mapLayer = std::make_unique<QgsPointCloudLayer>();
1863 break;
1864
1866 mapLayer = std::make_unique<QgsTiledSceneLayer>();
1867 break;
1868
1870 {
1871 const QString typeName = layerElem.attribute( QStringLiteral( "name" ) );
1872 mapLayer.reset( QgsApplication::pluginLayerRegistry()->createLayer( typeName ) );
1873 break;
1874 }
1875
1877 {
1878 const QgsAnnotationLayer::LayerOptions options( mTransformContext );
1879 mapLayer = std::make_unique<QgsAnnotationLayer>( QString(), options );
1880 break;
1881 }
1882
1884 {
1885 const QgsGroupLayer::LayerOptions options( mTransformContext );
1886 mapLayer = std::make_unique<QgsGroupLayer>( QString(), options );
1887 break;
1888 }
1889 }
1890
1891 if ( !mapLayer )
1892 {
1893 QgsDebugError( QStringLiteral( "Unable to create layer" ) );
1894 return false;
1895 }
1896
1897 Q_CHECK_PTR( mapLayer ); // NOLINT
1898
1899 // This is tricky: to avoid a leak we need to check if the layer was already in the store
1900 // because if it was, the newly created layer will not be added to the store and it would leak.
1901 const QString layerId { layerElem.namedItem( QStringLiteral( "id" ) ).toElement().text() };
1902 Q_ASSERT( ! layerId.isEmpty() );
1903 const bool layerWasStored = layerStore()->mapLayer( layerId );
1904
1905 // have the layer restore state that is stored in Dom node
1906 QgsMapLayer::ReadFlags layerFlags = projectFlagsToLayerReadFlags( flags, mFlags );
1907
1908 profile.switchTask( tr( "Load layer source" ) );
1909 const bool layerIsValid = mapLayer->readLayerXml( layerElem, context, layerFlags, provider ) && mapLayer->isValid();
1910
1911 // apply specific settings to vector layer
1912 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1913 {
1914 vl->setReadExtentFromXml( layerFlags & QgsMapLayer::FlagReadExtentFromXml );
1915 if ( vl->dataProvider() )
1916 {
1918 vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, evaluateDefaultValues );
1919 }
1920 }
1921
1922 profile.switchTask( tr( "Add layer to project" ) );
1923 QList<QgsMapLayer *> newLayers;
1924 newLayers << mapLayer.get();
1925 if ( layerIsValid || flags & Qgis::ProjectReadFlag::DontResolveLayers )
1926 {
1927 emit readMapLayer( mapLayer.get(), layerElem );
1928 addMapLayers( newLayers );
1929 // Try to resolve references here (this is necessary to set up joined fields that will be possibly used by
1930 // virtual layers that point to this layer's joined field in their query otherwise they won't be valid ),
1931 // a second attempt to resolve references will be done after all layers are loaded
1932 // see https://github.com/qgis/QGIS/issues/46834
1933 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1934 {
1935 vLayer->joinBuffer()->resolveReferences( this );
1936 }
1937 }
1938 else
1939 {
1940 // It's a bad layer: do not add to legend (the user will decide if she wants to do so)
1941 addMapLayers( newLayers, false );
1942 newLayers.first();
1943 QgsDebugError( "Unable to load " + type + " layer" );
1944 brokenNodes.push_back( layerElem );
1945 }
1946
1947 const bool wasEditable = layerElem.attribute( QStringLiteral( "editable" ), QStringLiteral( "0" ) ).toInt();
1948 if ( wasEditable )
1949 {
1950 mapLayer->setCustomProperty( QStringLiteral( "_layer_was_editable" ), true );
1951 }
1952 else
1953 {
1954 mapLayer->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
1955 }
1956
1957 // It should be safe to delete the layer now if layer was stored, because all the store
1958 // had to to was to reset the data source in case the validity changed.
1959 if ( ! layerWasStored )
1960 {
1961 mapLayer.release();
1962 }
1963
1964 return layerIsValid;
1965}
1966
1967bool QgsProject::read( const QString &filename, Qgis::ProjectReadFlags flags )
1968{
1970
1971 mFile.setFileName( filename );
1972 mCachedHomePath.clear();
1973 mProjectScope.reset();
1974
1975 return read( flags );
1976}
1977
1979{
1981
1982 const QString filename = mFile.fileName();
1983 bool returnValue;
1984
1985 if ( QgsProjectStorage *storage = projectStorage() )
1986 {
1987 QTemporaryFile inDevice;
1988 if ( !inDevice.open() )
1989 {
1990 setError( tr( "Unable to open %1" ).arg( inDevice.fileName() ) );
1991 return false;
1992 }
1993
1994 QgsReadWriteContext context;
1995 context.setProjectTranslator( this );
1996 if ( !storage->readProject( filename, &inDevice, context ) )
1997 {
1998 QString err = tr( "Unable to open %1" ).arg( filename );
1999 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
2000 if ( !messages.isEmpty() )
2001 err += QStringLiteral( "\n\n" ) + messages.last().message();
2002 setError( err );
2003 return false;
2004 }
2005 returnValue = unzip( inDevice.fileName(), flags ); // calls setError() if returning false
2006 }
2007 else
2008 {
2009 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
2010 {
2011 returnValue = unzip( mFile.fileName(), flags );
2012 }
2013 else
2014 {
2015 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( *this );
2016 const QFileInfo finfo( mFile.fileName() );
2017 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
2018 if ( QFile( attachmentsZip ).exists() )
2019 {
2020 auto archive = std::make_unique<QgsArchive>();
2021 if ( archive->unzip( attachmentsZip ) )
2022 {
2023 releaseHandlesToProjectArchive();
2024 mArchive = std::move( archive );
2025 }
2026 }
2027 returnValue = readProjectFile( mFile.fileName(), flags );
2028 }
2029
2030 //on translation we should not change the filename back
2031 if ( !mTranslator )
2032 {
2033 mFile.setFileName( filename );
2034 mCachedHomePath.clear();
2035 mProjectScope.reset();
2036 }
2037 else
2038 {
2039 //but delete the translator
2040 mTranslator.reset( nullptr );
2041 }
2042 }
2043 emit fileNameChanged();
2044 emit homePathChanged();
2045 return returnValue;
2046}
2047
2048bool QgsProject::readProjectFile( const QString &filename, Qgis::ProjectReadFlags flags )
2049{
2051
2052 // avoid multiple emission of snapping updated signals
2053 ScopedIntIncrementor snapSignalBlock( &mBlockSnappingUpdates );
2054
2055 QFile projectFile( filename );
2056 clearError();
2057
2058 QgsApplication::profiler()->clear( QStringLiteral( "projectload" ) );
2059 QgsScopedRuntimeProfile profile( tr( "Setting up translations" ), QStringLiteral( "projectload" ) );
2060
2061 const QString localeFileName = QStringLiteral( "%1_%2" ).arg( QFileInfo( mFile ).baseName(), QgsApplication::settingsLocaleUserLocale->value() );
2062
2063 if ( QFile( QStringLiteral( "%1/%2.qm" ).arg( QFileInfo( mFile ).absolutePath(), localeFileName ) ).exists() )
2064 {
2065 mTranslator = std::make_unique< QTranslator >();
2066 ( void )mTranslator->load( localeFileName, QFileInfo( mFile ).absolutePath() );
2067 }
2068
2069 profile.switchTask( tr( "Reading project file" ) );
2070 auto doc = std::make_unique<QDomDocument>( QStringLiteral( "qgis" ) );
2071
2072 if ( !projectFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
2073 {
2074 projectFile.close();
2075
2076 setError( tr( "Unable to open %1" ).arg( projectFile.fileName() ) );
2077
2078 return false;
2079 }
2080
2081 QTextStream textStream( &projectFile );
2082#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2083 textStream.setCodec( "UTF-8" );
2084#endif
2085 QString projectString = textStream.readAll();
2086 projectFile.close();
2087
2088 for ( int i = 0; i < 32; i++ )
2089 {
2090 if ( i == 9 || i == 10 || i == 13 )
2091 {
2092 continue;
2093 }
2094 projectString.replace( QChar( i ), QStringLiteral( "%1%2%1" ).arg( FONTMARKER_CHR_FIX, QString::number( i ) ) );
2095 }
2096
2097 // location of problem associated with errorMsg
2098 int line, column;
2099 QString errorMsg;
2100 if ( !doc->setContent( projectString, &errorMsg, &line, &column ) )
2101 {
2102 const QString errorString = tr( "Project file read error in file %1: %2 at line %3 column %4" )
2103 .arg( projectFile.fileName(), errorMsg ).arg( line ).arg( column );
2104 QgsDebugError( errorString );
2105 setError( errorString );
2106
2107 return false;
2108 }
2109
2110 projectFile.close();
2111
2112 QgsDebugMsgLevel( "Opened document " + projectFile.fileName(), 2 );
2113
2114 // get project version string, if any
2115 const QgsProjectVersion fileVersion = getVersion( *doc );
2116 const QgsProjectVersion thisVersion( Qgis::version() );
2117
2118 profile.switchTask( tr( "Updating project file" ) );
2119 if ( thisVersion > fileVersion )
2120 {
2121 const bool isOlderMajorVersion = fileVersion.majorVersion() < thisVersion.majorVersion();
2122
2123 if ( isOlderMajorVersion )
2124 {
2125 QgsLogger::warning( "Loading a file that was saved with an older "
2126 "version of qgis (saved in " + fileVersion.text() +
2127 ", loaded in " + Qgis::version() +
2128 "). Problems may occur." );
2129 }
2130
2131 QgsProjectFileTransform projectFile( *doc, fileVersion );
2132
2133 // Shows a warning when an old project file is read.
2135 emit oldProjectVersionWarning( fileVersion.text() );
2137 emit readVersionMismatchOccurred( fileVersion.text() );
2138
2139 projectFile.updateRevision( thisVersion );
2140 }
2141 else if ( fileVersion > thisVersion )
2142 {
2143 QgsLogger::warning( "Loading a file that was saved with a newer "
2144 "version of qgis (saved in " + fileVersion.text() +
2145 ", loaded in " + Qgis::version() +
2146 "). Problems may occur." );
2147
2148 emit readVersionMismatchOccurred( fileVersion.text() );
2149 }
2150
2151 // start new project, just keep the file name and auxiliary storage
2152 profile.switchTask( tr( "Creating auxiliary storage" ) );
2153 const QString fileName = mFile.fileName();
2154
2155 const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
2156 const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
2157
2158 // NOTE [ND] -- I suspect this is wrong, as the archive may contain any number of non-auxiliary
2159 // storage related files from the previously loaded project.
2160 std::unique_ptr<QgsAuxiliaryStorage> aStorage = std::move( mAuxiliaryStorage );
2161 std::unique_ptr<QgsArchive> archive = std::move( mArchive );
2162
2163 // don't emit xxxChanged signals during the clear() call, as we'll be emitting
2164 // them again after reading the properties from the project file
2165 mBlockChangeSignalsDuringClear = true;
2166 clear();
2167 mBlockChangeSignalsDuringClear = false;
2168
2169 // this is ugly, but clear() will have created a new archive and started populating it. We
2170 // need to release handles to this archive now as the subsequent call to move will need
2171 // to delete it, and requires free access to do so.
2172 releaseHandlesToProjectArchive();
2173
2174 mAuxiliaryStorage = std::move( aStorage );
2175 mArchive = std::move( archive );
2176
2177 mFile.setFileName( fileName );
2178 mCachedHomePath.clear();
2179 mProjectScope.reset();
2180 mSaveVersion = fileVersion;
2181
2182 // now get any properties
2183 profile.switchTask( tr( "Reading properties" ) );
2184 _getProperties( *doc, mProperties );
2185
2186 // now get the data defined server properties
2187 mDataDefinedServerProperties = getDataDefinedServerProperties( *doc, dataDefinedServerPropertyDefinitions() );
2188
2189 QgsDebugMsgLevel( QString::number( mProperties.count() ) + " properties read", 2 );
2190
2191#if 0
2192 dump_( mProperties );
2193#endif
2194
2195 // get older style project title
2196 QString oldTitle;
2197 _getTitle( *doc, oldTitle );
2198
2199 readProjectFileMetadata( *doc, mSaveUser, mSaveUserFull, mSaveDateTime );
2200
2201 const QDomNodeList homePathNl = doc->elementsByTagName( QStringLiteral( "homePath" ) );
2202 if ( homePathNl.count() > 0 )
2203 {
2204 const QDomElement homePathElement = homePathNl.at( 0 ).toElement();
2205 const QString homePath = homePathElement.attribute( QStringLiteral( "path" ) );
2206 if ( !homePath.isEmpty() )
2208 }
2209 else
2210 {
2211 emit homePathChanged();
2212 }
2213
2214 const QColor backgroundColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), 255 ),
2215 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), 255 ),
2216 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), 255 ) );
2218 const QColor selectionColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), 255 ),
2219 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), 255 ),
2220 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), 255 ),
2221 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), 255 ) );
2223
2224
2225 const QString distanceUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QString() );
2226 if ( !distanceUnitString.isEmpty() )
2227 setDistanceUnits( QgsUnitTypes::decodeDistanceUnit( distanceUnitString ) );
2228
2229 const QString areaUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QString() );
2230 if ( !areaUnitString.isEmpty() )
2231 setAreaUnits( QgsUnitTypes::decodeAreaUnit( areaUnitString ) );
2232
2233 setScaleMethod( qgsEnumKeyToValue( readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/ScaleMethod" ), QString() ), Qgis::ScaleCalculationMethod::HorizontalMiddle ) );
2234
2235 QgsReadWriteContext context;
2236 context.setPathResolver( pathResolver() );
2237 context.setProjectTranslator( this );
2238
2239 //crs
2240 QgsCoordinateReferenceSystem projectCrs;
2241 if ( readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), 0 ) )
2242 {
2243 // first preference - dedicated projectCrs node
2244 const QDomNode srsNode = doc->documentElement().namedItem( QStringLiteral( "projectCrs" ) );
2245 if ( !srsNode.isNull() )
2246 {
2247 projectCrs.readXml( srsNode );
2248 }
2249
2250 if ( !projectCrs.isValid() )
2251 {
2252 const QString projCrsString = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSProj4String" ) );
2253 const long currentCRS = readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSID" ), -1 );
2254 const QString authid = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCrs" ) );
2255
2256 // authid should be prioritized over all
2257 const bool isUserAuthId = authid.startsWith( QLatin1String( "USER:" ), Qt::CaseInsensitive );
2258 if ( !authid.isEmpty() && !isUserAuthId )
2259 projectCrs = QgsCoordinateReferenceSystem( authid );
2260
2261 // try the CRS
2262 if ( !projectCrs.isValid() && currentCRS >= 0 )
2263 {
2264 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2265 }
2266
2267 // if that didn't produce a match, try the proj.4 string
2268 if ( !projCrsString.isEmpty() && ( authid.isEmpty() || isUserAuthId ) && ( !projectCrs.isValid() || projectCrs.toProj() != projCrsString ) )
2269 {
2270 projectCrs = QgsCoordinateReferenceSystem::fromProj( projCrsString );
2271 }
2272
2273 // last just take the given id
2274 if ( !projectCrs.isValid() )
2275 {
2276 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2277 }
2278 }
2279 }
2280 mCrs = projectCrs;
2281
2282 //vertical CRS
2283 {
2284 QgsCoordinateReferenceSystem verticalCrs;
2285 const QDomNode verticalCrsNode = doc->documentElement().namedItem( QStringLiteral( "verticalCrs" ) );
2286 if ( !verticalCrsNode.isNull() )
2287 {
2288 verticalCrs.readXml( verticalCrsNode );
2289 }
2290 mVerticalCrs = verticalCrs;
2291 }
2292 rebuildCrs3D();
2293
2294 QStringList datumErrors;
2295 if ( !mTransformContext.readXml( doc->documentElement(), context, datumErrors ) && !datumErrors.empty() )
2296 {
2297 emit missingDatumTransforms( datumErrors );
2298 }
2300
2301 // map shading
2302 const QDomNode elevationShadingNode = doc->documentElement().namedItem( QStringLiteral( "elevation-shading-renderer" ) );
2303 if ( !elevationShadingNode.isNull() )
2304 {
2305 mElevationShadingRenderer.readXml( elevationShadingNode.toElement(), context );
2306 }
2308
2309
2310 //add variables defined in project file - do this early in the reading cycle, as other components
2311 //(e.g. layouts) may depend on these variables
2312 const QStringList variableNames = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ) );
2313 const QStringList variableValues = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ) );
2314
2315 mCustomVariables.clear();
2316 if ( variableNames.length() == variableValues.length() )
2317 {
2318 for ( int i = 0; i < variableNames.length(); ++i )
2319 {
2320 mCustomVariables.insert( variableNames.at( i ), variableValues.at( i ) );
2321 }
2322 }
2323 else
2324 {
2325 QgsMessageLog::logMessage( tr( "Project Variables Invalid" ), tr( "The project contains invalid variable settings." ) );
2326 }
2327
2328 // Register expression functions stored in the project.
2329 // They might be using project variables and might be
2330 // in turn being used by other components (e.g., layouts).
2332
2333 QDomElement element = doc->documentElement().firstChildElement( QStringLiteral( "projectMetadata" ) );
2334
2335 if ( !element.isNull() )
2336 {
2337 mMetadata.readMetadataXml( element, context );
2338 }
2339 else
2340 {
2341 // older project, no metadata => remove auto generated metadata which is populated on QgsProject::clear()
2342 mMetadata = QgsProjectMetadata();
2343 }
2344 if ( mMetadata.title().isEmpty() && !oldTitle.isEmpty() )
2345 {
2346 // upgrade older title storage to storing within project metadata.
2347 mMetadata.setTitle( oldTitle );
2348 }
2349 emit metadataChanged();
2350 emit titleChanged();
2351
2352 // Transaction mode
2353 element = doc->documentElement().firstChildElement( QStringLiteral( "transaction" ) );
2354 if ( !element.isNull() )
2355 {
2356 mTransactionMode = qgsEnumKeyToValue( element.attribute( QStringLiteral( "mode" ) ), Qgis::TransactionMode::Disabled );
2357 }
2358 else
2359 {
2360 // maybe older project => try read autotransaction
2361 element = doc->documentElement().firstChildElement( QStringLiteral( "autotransaction" ) );
2362 if ( ! element.isNull() )
2363 {
2364 mTransactionMode = static_cast<Qgis::TransactionMode>( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() );
2365 }
2366 }
2367
2368 // read the layer tree from project file
2369 profile.switchTask( tr( "Loading layer tree" ) );
2370 mRootGroup->setCustomProperty( QStringLiteral( "loading" ), 1 );
2371
2372 QDomElement layerTreeElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
2373 if ( !layerTreeElem.isNull() )
2374 {
2375 // Use a temporary tree to read the nodes to prevent signals being delivered to the models
2376 QgsLayerTree tempTree;
2377 tempTree.readChildrenFromXml( layerTreeElem, context );
2378 mRootGroup->insertChildNodes( -1, tempTree.abandonChildren() );
2379 }
2380 else
2381 {
2382 QgsLayerTreeUtils::readOldLegend( mRootGroup.get(), doc->documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
2383 }
2384
2385 mLayerTreeRegistryBridge->setEnabled( false );
2386
2387 // get the map layers
2388 profile.switchTask( tr( "Reading map layers" ) );
2389
2390 loadProjectFlags( doc.get() );
2391
2392 QList<QDomNode> brokenNodes;
2393 const bool clean = _getMapLayers( *doc, brokenNodes, flags );
2394
2395 // review the integrity of the retrieved map layers
2396 if ( !clean && !( flags & Qgis::ProjectReadFlag::DontResolveLayers ) )
2397 {
2398 QgsDebugError( QStringLiteral( "Unable to get map layers from project file." ) );
2399
2400 if ( !brokenNodes.isEmpty() )
2401 {
2402 QgsDebugError( "there are " + QString::number( brokenNodes.size() ) + " broken layers" );
2403 }
2404
2405 // we let a custom handler decide what to do with missing layers
2406 // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
2407 mBadLayerHandler->handleBadLayers( brokenNodes );
2408 }
2409
2410 mMainAnnotationLayer->readLayerXml( doc->documentElement().firstChildElement( QStringLiteral( "main-annotation-layer" ) ), context );
2411 mMainAnnotationLayer->setTransformContext( mTransformContext );
2412
2413 // load embedded groups and layers
2414 profile.switchTask( tr( "Loading embedded layers" ) );
2415 loadEmbeddedNodes( mRootGroup.get(), flags );
2416
2417 // Resolve references to other layers
2418 // Needs to be done here once all dependent layers are loaded
2419 profile.switchTask( tr( "Resolving layer references" ) );
2420 QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
2421 for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
2422 {
2423 it.value()->resolveReferences( this );
2424 }
2425 mMainAnnotationLayer->resolveReferences( this );
2426
2427 mLayerTreeRegistryBridge->setEnabled( true );
2428
2429 // now that layers are loaded, we can resolve layer tree's references to the layers
2430 profile.switchTask( tr( "Resolving references" ) );
2431 mRootGroup->resolveReferences( this );
2432
2433 // we need to migrate old fashion designed QgsSymbolLayerReference to new ones
2434 if ( QgsProjectVersion( 3, 28, 0 ) > mSaveVersion )
2435 {
2439 }
2440
2441 if ( !layerTreeElem.isNull() )
2442 {
2443 mRootGroup->readLayerOrderFromXml( layerTreeElem );
2444 }
2445
2446 // Load pre 3.0 configuration
2447 const QDomElement layerTreeCanvasElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-canvas" ) );
2448 if ( !layerTreeCanvasElem.isNull( ) )
2449 {
2450 mRootGroup->readLayerOrderFromXml( layerTreeCanvasElem );
2451 }
2452
2453 // Convert pre 3.4 to create layers flags
2454 if ( QgsProjectVersion( 3, 4, 0 ) > mSaveVersion )
2455 {
2456 const QStringList requiredLayerIds = readListEntry( QStringLiteral( "RequiredLayers" ), QStringLiteral( "Layers" ) );
2457 for ( const QString &layerId : requiredLayerIds )
2458 {
2459 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2460 {
2461 layer->setFlags( layer->flags() & ~QgsMapLayer::Removable );
2462 }
2463 }
2464 const QStringList disabledLayerIds = readListEntry( QStringLiteral( "Identify" ), QStringLiteral( "/disabledLayers" ) );
2465 for ( const QString &layerId : disabledLayerIds )
2466 {
2467 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2468 {
2469 layer->setFlags( layer->flags() & ~QgsMapLayer::Identifiable );
2470 }
2471 }
2472 }
2473
2474 // Convert pre 3.26 default styles
2475 if ( QgsProjectVersion( 3, 26, 0 ) > mSaveVersion )
2476 {
2477 // Convert default symbols
2478 QString styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Marker" ) );
2479 if ( !styleName.isEmpty() )
2480 {
2481 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2483 }
2484 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Line" ) );
2485 if ( !styleName.isEmpty() )
2486 {
2487 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2489 }
2490 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Fill" ) );
2491 if ( !styleName.isEmpty() )
2492 {
2493 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2495 }
2496 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ) );
2497 if ( !styleName.isEmpty() )
2498 {
2499 std::unique_ptr<QgsColorRamp> colorRamp( QgsStyle::defaultStyle()->colorRamp( styleName ) );
2500 styleSettings()->setDefaultColorRamp( colorRamp.get() );
2501 }
2502
2503 // Convert randomize default symbol fill color
2504 styleSettings()->setRandomizeDefaultSymbolColor( readBoolEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ), true ) );
2505
2506 // Convert default symbol opacity
2507 double opacity = 1.0;
2508 bool ok = false;
2509 // upgrade old setting
2510 double alpha = readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/AlphaInt" ), 255, &ok );
2511 if ( ok )
2512 opacity = alpha / 255.0;
2513 double newOpacity = readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Opacity" ), 1.0, &ok );
2514 if ( ok )
2515 opacity = newOpacity;
2517
2518 // Cleanup
2519 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Marker" ) );
2520 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Line" ) );
2521 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Fill" ) );
2522 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ) );
2523 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ) );
2524 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/AlphaInt" ) );
2525 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Opacity" ) );
2526 }
2527
2528 // After bad layer handling we might still have invalid layers,
2529 // store them in case the user wanted to handle them later
2530 // or wanted to pass them through when saving
2532 {
2533 profile.switchTask( tr( "Storing original layer properties" ) );
2534 QgsLayerTreeUtils::storeOriginalLayersProperties( mRootGroup.get(), doc.get() );
2535 }
2536
2537 mRootGroup->removeCustomProperty( QStringLiteral( "loading" ) );
2538
2539 profile.switchTask( tr( "Loading map themes" ) );
2540 mMapThemeCollection = std::make_unique< QgsMapThemeCollection >( this );
2542 mMapThemeCollection->readXml( *doc );
2543
2544 profile.switchTask( tr( "Loading label settings" ) );
2545 mLabelingEngineSettings->readSettingsFromProject( this );
2546 {
2547 const QDomElement labelEngineSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "labelEngineSettings" ) );
2548 mLabelingEngineSettings->readXml( labelEngineSettingsElement, context );
2549 }
2550 mLabelingEngineSettings->resolveReferences( this );
2551
2553
2554 profile.switchTask( tr( "Loading annotations" ) );
2556 {
2557 mAnnotationManager->readXml( doc->documentElement(), context );
2558 }
2559 else
2560 {
2561 mAnnotationManager->readXmlAndUpgradeToAnnotationLayerItems( doc->documentElement(), context, mMainAnnotationLayer, mTransformContext );
2562 }
2564 {
2565 profile.switchTask( tr( "Loading layouts" ) );
2566 mLayoutManager->readXml( doc->documentElement(), *doc );
2567 }
2568
2569 {
2570 profile.switchTask( tr( "Loading elevation profiles" ) );
2571 mElevationProfileManager->readXml( doc->documentElement(), *doc, context );
2572 mElevationProfileManager->resolveReferences( this );
2573 }
2574
2576 {
2577 profile.switchTask( tr( "Loading 3D Views" ) );
2578 m3DViewsManager->readXml( doc->documentElement(), *doc );
2579 }
2580
2581 profile.switchTask( tr( "Loading bookmarks" ) );
2582 mBookmarkManager->readXml( doc->documentElement(), *doc );
2583
2584 profile.switchTask( tr( "Loading sensors" ) );
2585 mSensorManager->readXml( doc->documentElement(), *doc );
2586
2587 // reassign change dependencies now that all layers are loaded
2588 QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2589 for ( QMap<QString, QgsMapLayer *>::iterator it = existingMaps.begin(); it != existingMaps.end(); ++it )
2590 {
2591 it.value()->setDependencies( it.value()->dependencies() );
2592 }
2593
2594 profile.switchTask( tr( "Loading snapping settings" ) );
2595 mSnappingConfig.readProject( *doc );
2596 mAvoidIntersectionsMode = static_cast<Qgis::AvoidIntersectionsMode>( readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( Qgis::AvoidIntersectionsMode::AvoidIntersectionsLayers ) ) );
2597
2598 profile.switchTask( tr( "Loading view settings" ) );
2599 // restore older project scales settings
2600 mViewSettings->setUseProjectScales( readBoolEntry( QStringLiteral( "Scales" ), QStringLiteral( "/useProjectScales" ) ) );
2601 const QStringList scales = readListEntry( QStringLiteral( "Scales" ), QStringLiteral( "/ScalesList" ) );
2602 QVector<double> res;
2603 for ( const QString &scale : scales )
2604 {
2605 const QStringList parts = scale.split( ':' );
2606 if ( parts.size() != 2 )
2607 continue;
2608
2609 bool ok = false;
2610 const double denominator = QLocale().toDouble( parts[1], &ok );
2611 if ( ok )
2612 {
2613 res << denominator;
2614 }
2615 }
2616 mViewSettings->setMapScales( res );
2617 const QDomElement viewSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectViewSettings" ) );
2618 if ( !viewSettingsElement.isNull() )
2619 mViewSettings->readXml( viewSettingsElement, context );
2620
2621 // restore style settings
2622 profile.switchTask( tr( "Loading style properties" ) );
2623 const QDomElement styleSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectStyleSettings" ) );
2624 if ( !styleSettingsElement.isNull() )
2625 {
2626 mStyleSettings->removeProjectStyle();
2627 mStyleSettings->readXml( styleSettingsElement, context, flags );
2628 }
2629
2630 // restore time settings
2631 profile.switchTask( tr( "Loading temporal settings" ) );
2632 const QDomElement timeSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectTimeSettings" ) );
2633 if ( !timeSettingsElement.isNull() )
2634 mTimeSettings->readXml( timeSettingsElement, context );
2635
2636
2637 profile.switchTask( tr( "Loading elevation properties" ) );
2638 const QDomElement elevationPropertiesElement = doc->documentElement().firstChildElement( QStringLiteral( "ElevationProperties" ) );
2639 if ( !elevationPropertiesElement.isNull() )
2640 mElevationProperties->readXml( elevationPropertiesElement, context );
2641 mElevationProperties->resolveReferences( this );
2642
2643 profile.switchTask( tr( "Loading display settings" ) );
2644 {
2645 const QDomElement displaySettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectDisplaySettings" ) );
2646 if ( !displaySettingsElement.isNull() )
2647 mDisplaySettings->readXml( displaySettingsElement, context );
2648 }
2649
2650 profile.switchTask( tr( "Loading GPS settings" ) );
2651 {
2652 const QDomElement gpsSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectGpsSettings" ) );
2653 if ( !gpsSettingsElement.isNull() )
2654 mGpsSettings->readXml( gpsSettingsElement, context );
2655 mGpsSettings->resolveReferences( this );
2656 }
2657
2658 profile.switchTask( tr( "Updating variables" ) );
2660 profile.switchTask( tr( "Updating CRS" ) );
2661 emit crsChanged();
2662 if ( verticalCrs() != oldVerticalCrs )
2663 emit verticalCrsChanged();
2664 if ( mCrs3D != oldCrs3D )
2665 emit crs3DChanged();
2666 emit ellipsoidChanged( ellipsoid() );
2667
2668 // read the project: used by map canvas and legend
2669 profile.switchTask( tr( "Reading external settings" ) );
2670 emit readProject( *doc );
2671 emit readProjectWithContext( *doc, context );
2672
2673 profile.switchTask( tr( "Updating interface" ) );
2674
2675 snapSignalBlock.release();
2676 if ( !mBlockSnappingUpdates )
2677 emit snappingConfigChanged( mSnappingConfig );
2678
2681 emit projectColorsChanged();
2682
2683 // if all went well, we're allegedly in pristine state
2684 if ( clean )
2685 setDirty( false );
2686
2687 QgsDebugMsgLevel( QStringLiteral( "Project save user: %1" ).arg( mSaveUser ), 2 );
2688 QgsDebugMsgLevel( QStringLiteral( "Project save user: %1" ).arg( mSaveUserFull ), 2 );
2689
2693
2694 if ( mTranslator )
2695 {
2696 //project possibly translated -> rename it with locale postfix
2697 const QString newFileName( QStringLiteral( "%1/%2.qgs" ).arg( QFileInfo( mFile ).absolutePath(), localeFileName ) );
2698 setFileName( newFileName );
2699
2700 if ( write() )
2701 {
2702 QgsMessageLog::logMessage( tr( "Translated project saved with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Success );
2703 }
2704 else
2705 {
2706 QgsMessageLog::logMessage( tr( "Error saving translated project with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Critical );
2707 }
2708 }
2709
2710 // lastly, make any previously editable layers editable
2711 const QMap<QString, QgsMapLayer *> loadedLayers = mapLayers();
2712 for ( auto it = loadedLayers.constBegin(); it != loadedLayers.constEnd(); ++it )
2713 {
2714 if ( it.value()->isValid() && it.value()->customProperty( QStringLiteral( "_layer_was_editable" ) ).toBool() )
2715 {
2716 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( it.value() ) )
2717 vl->startEditing();
2718 it.value()->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
2719 }
2720 }
2721
2722 return true;
2723}
2724
2725bool QgsProject::loadEmbeddedNodes( QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
2726{
2728
2729 bool valid = true;
2730 const auto constChildren = group->children();
2731 for ( QgsLayerTreeNode *child : constChildren )
2732 {
2733 if ( QgsLayerTree::isGroup( child ) )
2734 {
2735 QgsLayerTreeGroup *childGroup = QgsLayerTree::toGroup( child );
2736 if ( childGroup->customProperty( QStringLiteral( "embedded" ) ).toInt() )
2737 {
2738 // make sure to convert the path from relative to absolute
2739 const QString projectPath = readPath( childGroup->customProperty( QStringLiteral( "embedded_project" ) ).toString() );
2740 childGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectPath );
2741 std::unique_ptr< QgsLayerTreeGroup > newGroup = createEmbeddedGroup( childGroup->name(), projectPath, childGroup->customProperty( QStringLiteral( "embedded-invisible-layers" ) ).toStringList(), flags );
2742 if ( newGroup )
2743 {
2744 QList<QgsLayerTreeNode *> clonedChildren;
2745 const QList<QgsLayerTreeNode *> constChildren = newGroup->children();
2746 clonedChildren.reserve( constChildren.size() );
2747 for ( QgsLayerTreeNode *newGroupChild : constChildren )
2748 clonedChildren << newGroupChild->clone();
2749
2750 childGroup->insertChildNodes( 0, clonedChildren );
2751 }
2752 }
2753 else
2754 {
2755 loadEmbeddedNodes( childGroup, flags );
2756 }
2757 }
2758 else if ( QgsLayerTree::isLayer( child ) )
2759 {
2760 if ( child->customProperty( QStringLiteral( "embedded" ) ).toInt() )
2761 {
2762 QList<QDomNode> brokenNodes;
2763 if ( ! createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), readPath( child->customProperty( QStringLiteral( "embedded_project" ) ).toString() ), brokenNodes, true, flags ) )
2764 {
2765 valid = valid && false;
2766 }
2767 }
2768 }
2769
2770 }
2771
2772 return valid;
2773}
2774
2776{
2777 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
2779
2780 return mCustomVariables;
2781}
2782
2783void QgsProject::setCustomVariables( const QVariantMap &variables )
2784{
2786
2787 if ( variables == mCustomVariables )
2788 return;
2789
2790 //write variable to project
2791 QStringList variableNames;
2792 QStringList variableValues;
2793
2794 QVariantMap::const_iterator it = variables.constBegin();
2795 for ( ; it != variables.constEnd(); ++it )
2796 {
2797 variableNames << it.key();
2798 variableValues << it.value().toString();
2799 }
2800
2801 writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ), variableNames );
2802 writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ), variableValues );
2803
2804 mCustomVariables = variables;
2805 mProjectScope.reset();
2806
2808}
2809
2811{
2813
2814 *mLabelingEngineSettings = settings;
2816}
2817
2819{
2821
2822 return *mLabelingEngineSettings;
2823}
2824
2826{
2828
2829 mProjectScope.reset();
2830 return mLayerStore.get();
2831}
2832
2834{
2836
2837 return mLayerStore.get();
2838}
2839
2840QList<QgsVectorLayer *> QgsProject::avoidIntersectionsLayers() const
2841{
2843
2844 QList<QgsVectorLayer *> layers;
2845 const QStringList layerIds = readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), QStringList() );
2846 const auto constLayerIds = layerIds;
2847 for ( const QString &layerId : constLayerIds )
2848 {
2849 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer( layerId ) ) )
2850 layers << vlayer;
2851 }
2852 return layers;
2853}
2854
2855void QgsProject::setAvoidIntersectionsLayers( const QList<QgsVectorLayer *> &layers )
2856{
2858
2859 QStringList list;
2860 list.reserve( layers.size() );
2861
2862 for ( QgsVectorLayer *layer : layers )
2863 {
2864 if ( layer->geometryType() == Qgis::GeometryType::Polygon )
2865 list << layer->id();
2866 }
2867
2868 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), list );
2870}
2871
2883
2885{
2886 // this method is called quite extensively using QgsProject::instance() skip-keyword-check
2888
2889 // MUCH cheaper to clone than build
2890 if ( mProjectScope )
2891 {
2892 auto projectScope = std::make_unique< QgsExpressionContextScope >( *mProjectScope );
2893
2894 // we can't cache these variables
2895 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_distance_units" ), QgsUnitTypes::toString( distanceUnits() ), true, true ) );
2896 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_area_units" ), QgsUnitTypes::toString( areaUnits() ), true, true ) );
2897
2898 // neither this function
2899 projectScope->addFunction( QStringLiteral( "sensor_data" ), new GetSensorData( sensorManager()->sensorsData() ) );
2900
2901 return projectScope.release();
2902 }
2903
2904 mProjectScope = std::make_unique< QgsExpressionContextScope >( QObject::tr( "Project" ) );
2905
2906 const QVariantMap vars = customVariables();
2907
2908 QVariantMap::const_iterator it = vars.constBegin();
2909
2910 for ( ; it != vars.constEnd(); ++it )
2911 {
2912 mProjectScope->setVariable( it.key(), it.value(), true );
2913 }
2914
2915 QString projectPath = projectStorage() ? fileName() : absoluteFilePath();
2916 if ( projectPath.isEmpty() )
2917 projectPath = mOriginalPath;
2918 const QString projectFolder = QFileInfo( projectPath ).path();
2919 const QString projectFilename = QFileInfo( projectPath ).fileName();
2920 const QString projectBasename = baseName();
2921
2922 //add other known project variables
2923 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_title" ), title(), true, true ) );
2924 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_path" ), QDir::toNativeSeparators( projectPath ), true, true ) );
2925 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_folder" ), QDir::toNativeSeparators( projectFolder ), true, true ) );
2926 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_filename" ), projectFilename, true, true ) );
2927 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_basename" ), projectBasename, true, true ) );
2928 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_home" ), QDir::toNativeSeparators( homePath() ), true, true ) );
2929 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_last_saved" ), mSaveDateTime.isNull() ? QVariant() : QVariant( mSaveDateTime ), true, true ) );
2930
2931 const QgsCoordinateReferenceSystem projectCrs = crs();
2932 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs" ), projectCrs.authid(), true, true ) );
2933 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_definition" ), projectCrs.toProj(), true, true ) );
2934 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_description" ), projectCrs.description(), true, true ) );
2935 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_acronym" ), projectCrs.projectionAcronym(), true ) );
2936 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_ellipsoid" ), projectCrs.ellipsoidAcronym(), true ) );
2937 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_proj4" ), projectCrs.toProj(), true ) );
2938 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_wkt" ), projectCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
2939
2940 const QgsCoordinateReferenceSystem projectVerticalCrs = QgsProject::verticalCrs();
2941 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_vertical_crs" ), projectVerticalCrs.authid(), true, true ) );
2942 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_vertical_crs_definition" ), projectVerticalCrs.toProj(), true, true ) );
2943 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_vertical_crs_description" ), projectVerticalCrs.description(), true, true ) );
2944 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_vertical_crs_wkt" ), projectVerticalCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
2945
2946 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_ellipsoid" ), ellipsoid(), true, true ) );
2947 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "_project_transform_context" ), QVariant::fromValue<QgsCoordinateTransformContext>( transformContext() ), true, true ) );
2948 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_units" ), QgsUnitTypes::toString( projectCrs.mapUnits() ), true ) );
2949
2950 // metadata
2951 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_author" ), metadata().author(), true, true ) );
2952 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_abstract" ), metadata().abstract(), true, true ) );
2953 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_creation_date" ), metadata().creationDateTime(), true, true ) );
2954 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_identifier" ), metadata().identifier(), true, true ) );
2955
2956 // keywords
2957 QVariantMap keywords;
2958 const QgsAbstractMetadataBase::KeywordMap metadataKeywords = metadata().keywords();
2959 for ( auto it = metadataKeywords.constBegin(); it != metadataKeywords.constEnd(); ++it )
2960 {
2961 keywords.insert( it.key(), it.value() );
2962 }
2963 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_keywords" ), keywords, true, true ) );
2964
2965 // layers
2966 QVariantList layersIds;
2967 QVariantList layers;
2968 const QMap<QString, QgsMapLayer *> layersInProject = mLayerStore->mapLayers();
2969 layersIds.reserve( layersInProject.count() );
2970 layers.reserve( layersInProject.count() );
2971 for ( auto it = layersInProject.constBegin(); it != layersInProject.constEnd(); ++it )
2972 {
2973 layersIds << it.value()->id();
2975 }
2976 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer_ids" ), layersIds, true ) );
2977 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layers" ), layers, true ) );
2978
2979 mProjectScope->addFunction( QStringLiteral( "project_color" ), new GetNamedProjectColor( this ) );
2980 mProjectScope->addFunction( QStringLiteral( "project_color_object" ), new GetNamedProjectColorObject( this ) );
2981
2983}
2984
2985void QgsProject::onMapLayersAdded( const QList<QgsMapLayer *> &layers )
2986{
2988
2989 const QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2990
2991 const auto constLayers = layers;
2992 for ( QgsMapLayer *layer : constLayers )
2993 {
2994 if ( ! layer->isValid() )
2995 return;
2996
2997 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
2998 {
2999 vlayer->setReadExtentFromXml( mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics );
3000 if ( vlayer->dataProvider() )
3001 vlayer->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues,
3003 }
3004
3005 connect( layer, &QgsMapLayer::configChanged, this, [this] { setDirty(); } );
3006
3007 // check if we have to update connections for layers with dependencies
3008 for ( QMap<QString, QgsMapLayer *>::const_iterator it = existingMaps.cbegin(); it != existingMaps.cend(); ++it )
3009 {
3010 const QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
3011 if ( deps.contains( layer->id() ) )
3012 {
3013 // reconnect to change signals
3014 it.value()->setDependencies( deps );
3015 }
3016 }
3017 }
3018
3019 updateTransactionGroups();
3020
3021 if ( !mBlockSnappingUpdates && mSnappingConfig.addLayers( layers ) )
3022 emit snappingConfigChanged( mSnappingConfig );
3023}
3024
3025void QgsProject::onMapLayersRemoved( const QList<QgsMapLayer *> &layers )
3026{
3028
3029 if ( !mBlockSnappingUpdates && mSnappingConfig.removeLayers( layers ) )
3030 emit snappingConfigChanged( mSnappingConfig );
3031
3032 for ( QgsMapLayer *layer : layers )
3033 {
3034 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
3035 if ( ! vlayer )
3036 continue;
3037
3038 mEditBufferGroup.removeLayer( vlayer );
3039 }
3040}
3041
3042void QgsProject::cleanTransactionGroups( bool force )
3043{
3045
3046 bool changed = false;
3047 for ( QMap< QPair< QString, QString>, QgsTransactionGroup *>::Iterator tg = mTransactionGroups.begin(); tg != mTransactionGroups.end(); )
3048 {
3049 if ( tg.value()->isEmpty() || force )
3050 {
3051 delete tg.value();
3052 tg = mTransactionGroups.erase( tg );
3053 changed = true;
3054 }
3055 else
3056 {
3057 ++tg;
3058 }
3059 }
3060 if ( changed )
3062}
3063
3064void QgsProject::updateTransactionGroups()
3065{
3067
3068 mEditBufferGroup.clear();
3069
3070 switch ( mTransactionMode )
3071 {
3073 {
3074 cleanTransactionGroups( true );
3075 return;
3076 }
3077 break;
3079 cleanTransactionGroups( true );
3080 break;
3082 cleanTransactionGroups( false );
3083 break;
3084 }
3085
3086 bool tgChanged = false;
3087 const auto constLayers = mapLayers().values();
3088 for ( QgsMapLayer *layer : constLayers )
3089 {
3090 if ( ! layer->isValid() )
3091 continue;
3092
3093 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
3094 if ( ! vlayer )
3095 continue;
3096
3097 switch ( mTransactionMode )
3098 {
3100 Q_ASSERT( false );
3101 break;
3103 {
3105 {
3106 const QString connString = QgsTransaction::connectionString( vlayer->source() );
3107 const QString key = vlayer->providerType();
3108
3109 QgsTransactionGroup *tg = mTransactionGroups.value( qMakePair( key, connString ) );
3110
3111 if ( !tg )
3112 {
3113 tg = new QgsTransactionGroup();
3114 mTransactionGroups.insert( qMakePair( key, connString ), tg );
3115 tgChanged = true;
3116 }
3117 tg->addLayer( vlayer );
3118 }
3119 }
3120 break;
3122 {
3123 if ( vlayer->supportsEditing() )
3124 mEditBufferGroup.addLayer( vlayer );
3125 }
3126 break;
3127 }
3128 }
3129
3130 if ( tgChanged )
3132}
3133
3134bool QgsProject::readLayer( const QDomNode &layerNode )
3135{
3137
3138 QgsReadWriteContext context;
3139 context.setPathResolver( pathResolver() );
3140 context.setProjectTranslator( this );
3141 context.setTransformContext( transformContext() );
3142 QList<QDomNode> brokenNodes;
3143 if ( addLayer( layerNode.toElement(), brokenNodes, context ) )
3144 {
3145 // have to try to update joins for all layers now - a previously added layer may be dependent on this newly
3146 // added layer for joins
3147 const QVector<QgsVectorLayer *> vectorLayers = layers<QgsVectorLayer *>();
3148 for ( QgsVectorLayer *layer : vectorLayers )
3149 {
3150 // TODO: should be only done later - and with all layers (other layers may have referenced this layer)
3151 layer->resolveReferences( this );
3152
3153 if ( layer->isValid() && layer->customProperty( QStringLiteral( "_layer_was_editable" ) ).toBool() )
3154 {
3155 layer->startEditing();
3156 layer->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
3157 }
3158 }
3159 return true;
3160 }
3161 return false;
3162}
3163
3164bool QgsProject::write( const QString &filename )
3165{
3167
3168 mFile.setFileName( filename );
3169 emit fileNameChanged();
3170 mCachedHomePath.clear();
3171 return write();
3172}
3173
3175{
3177
3178 mProjectScope.reset();
3179 if ( QgsProjectStorage *storage = projectStorage() )
3180 {
3181 QgsReadWriteContext context;
3182 // for projects stored in a custom storage, we have to check for the support
3183 // of relative paths since the storage most likely will not be in a file system
3184 const QString storageFilePath { storage->filePath( mFile.fileName() ) };
3185 if ( storageFilePath.isEmpty() )
3186 {
3188 }
3189 context.setPathResolver( pathResolver() );
3190
3191 const QString tempPath = QStandardPaths::standardLocations( QStandardPaths::TempLocation ).at( 0 );
3192 const QString tmpZipFilename( tempPath + QDir::separator() + QUuid::createUuid().toString() );
3193
3194 if ( !zip( tmpZipFilename ) )
3195 return false; // zip() already calls setError() when returning false
3196
3197 QFile tmpZipFile( tmpZipFilename );
3198 if ( !tmpZipFile.open( QIODevice::ReadOnly ) )
3199 {
3200 setError( tr( "Unable to read file %1" ).arg( tmpZipFilename ) );
3201 return false;
3202 }
3203
3205 if ( !storage->writeProject( mFile.fileName(), &tmpZipFile, context ) )
3206 {
3207 QString err = tr( "Unable to save project to storage %1" ).arg( mFile.fileName() );
3208 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
3209 if ( !messages.isEmpty() )
3210 err += QStringLiteral( "\n\n" ) + messages.last().message();
3211 setError( err );
3212 return false;
3213 }
3214
3215 tmpZipFile.close();
3216 QFile::remove( tmpZipFilename );
3217
3218 return true;
3219 }
3220
3221 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
3222 {
3223 return zip( mFile.fileName() );
3224 }
3225 else
3226 {
3227 // write project file even if the auxiliary storage is not correctly
3228 // saved
3229 const bool asOk = saveAuxiliaryStorage();
3230 const bool writeOk = writeProjectFile( mFile.fileName() );
3231 bool attachmentsOk = true;
3232 if ( !mArchive->files().isEmpty() )
3233 {
3234 const QFileInfo finfo( mFile.fileName() );
3235 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
3236 attachmentsOk = mArchive->zip( attachmentsZip );
3237 }
3238
3239 // errors raised during writing project file are more important
3240 if ( ( !asOk || !attachmentsOk ) && writeOk )
3241 {
3242 QStringList errorMessage;
3243 if ( !asOk )
3244 {
3245 const QString err = mAuxiliaryStorage->errorString();
3246 errorMessage.append( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
3247 }
3248 if ( !attachmentsOk )
3249 {
3250 errorMessage.append( tr( "Unable to save attachments archive" ) );
3251 }
3252 setError( errorMessage.join( '\n' ) );
3253 }
3254
3255 return asOk && writeOk && attachmentsOk;
3256 }
3257}
3258
3259bool QgsProject::writeProjectFile( const QString &filename )
3260{
3262
3263 QFile projectFile( filename );
3264 clearError();
3265
3266 // if we have problems creating or otherwise writing to the project file,
3267 // let's find out up front before we go through all the hand-waving
3268 // necessary to create all the Dom objects
3269 const QFileInfo myFileInfo( projectFile );
3270 if ( myFileInfo.exists() && !myFileInfo.isWritable() )
3271 {
3272 setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." )
3273 .arg( projectFile.fileName() ) );
3274 return false;
3275 }
3276
3277 QgsReadWriteContext context;
3278 context.setPathResolver( pathResolver() );
3280
3281 QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
3282
3283 const QDomDocumentType documentType =
3284 QDomImplementation().createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ),
3285 QStringLiteral( "SYSTEM" ) );
3286 auto doc = std::make_unique<QDomDocument>( documentType );
3287
3288 QDomElement qgisNode = doc->createElement( QStringLiteral( "qgis" ) );
3289 qgisNode.setAttribute( QStringLiteral( "projectname" ), title() );
3290 qgisNode.setAttribute( QStringLiteral( "version" ), Qgis::version() );
3291
3292 if ( !mSettings.value( QStringLiteral( "projects/anonymize_saved_projects" ), false, QgsSettings::Core ).toBool() )
3293 {
3294 const QString newSaveUser = QgsApplication::userLoginName();
3295 const QString newSaveUserFull = QgsApplication::userFullName();
3296 qgisNode.setAttribute( QStringLiteral( "saveUser" ), newSaveUser );
3297 qgisNode.setAttribute( QStringLiteral( "saveUserFull" ), newSaveUserFull );
3298 mSaveUser = newSaveUser;
3299 mSaveUserFull = newSaveUserFull;
3300 if ( mMetadata.author().isEmpty() )
3301 {
3302 mMetadata.setAuthor( QgsApplication::userFullName() );
3303 }
3304 if ( !mMetadata.creationDateTime().isValid() )
3305 {
3306 mMetadata.setCreationDateTime( QDateTime( QDateTime::currentDateTime() ) );
3307 }
3308 mSaveDateTime = QDateTime::currentDateTime();
3309 qgisNode.setAttribute( QStringLiteral( "saveDateTime" ), mSaveDateTime.toString( Qt::ISODate ) );
3310 }
3311 else
3312 {
3313 mSaveUser.clear();
3314 mSaveUserFull.clear();
3315 mMetadata.setAuthor( QString() );
3316 mMetadata.setCreationDateTime( QDateTime() );
3317 mSaveDateTime = QDateTime();
3318 }
3319 doc->appendChild( qgisNode );
3320 mSaveVersion = QgsProjectVersion( Qgis::version() );
3321
3322 QDomElement homePathNode = doc->createElement( QStringLiteral( "homePath" ) );
3323 homePathNode.setAttribute( QStringLiteral( "path" ), mHomePath );
3324 qgisNode.appendChild( homePathNode );
3325
3326 // title
3327 QDomElement titleNode = doc->createElement( QStringLiteral( "title" ) );
3328 qgisNode.appendChild( titleNode );
3329
3330 QDomElement transactionNode = doc->createElement( QStringLiteral( "transaction" ) );
3331 transactionNode.setAttribute( QStringLiteral( "mode" ), qgsEnumValueToKey( mTransactionMode ) );
3332 qgisNode.appendChild( transactionNode );
3333
3334 QDomElement flagsNode = doc->createElement( QStringLiteral( "projectFlags" ) );
3335 flagsNode.setAttribute( QStringLiteral( "set" ), qgsFlagValueToKeys( mFlags ) );
3336 qgisNode.appendChild( flagsNode );
3337
3338 const QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
3339 titleNode.appendChild( titleText );
3340
3341 // write project CRS
3342 {
3343 QDomElement srsNode = doc->createElement( QStringLiteral( "projectCrs" ) );
3344 mCrs.writeXml( srsNode, *doc );
3345 qgisNode.appendChild( srsNode );
3346 }
3347 {
3348 QDomElement verticalSrsNode = doc->createElement( QStringLiteral( "verticalCrs" ) );
3349 mVerticalCrs.writeXml( verticalSrsNode, *doc );
3350 qgisNode.appendChild( verticalSrsNode );
3351 }
3352
3353 QDomElement elevationShadingNode = doc->createElement( QStringLiteral( "elevation-shading-renderer" ) );
3354 mElevationShadingRenderer.writeXml( elevationShadingNode, context );
3355 qgisNode.appendChild( elevationShadingNode );
3356
3357 // write layer tree - make sure it is without embedded subgroups
3358 std::unique_ptr< QgsLayerTreeNode > clonedRoot( mRootGroup->clone() );
3360 QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot.get() ), this ); // convert absolute paths to relative paths if required
3361
3362 clonedRoot->writeXml( qgisNode, context );
3363 clonedRoot.reset();
3364
3365 mSnappingConfig.writeProject( *doc );
3366 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( mAvoidIntersectionsMode ) );
3367
3368 // let map canvas and legend write their information
3369 emit writeProject( *doc );
3370
3371 // within top level node save list of layers
3372 const QMap<QString, QgsMapLayer *> layers = mapLayers();
3373
3374 QDomElement annotationLayerNode = doc->createElement( QStringLiteral( "main-annotation-layer" ) );
3375 mMainAnnotationLayer->writeLayerXml( annotationLayerNode, *doc, context );
3376 qgisNode.appendChild( annotationLayerNode );
3377
3378 // Iterate over layers in zOrder
3379 // Call writeXml() on each
3380 QDomElement projectLayersNode = doc->createElement( QStringLiteral( "projectlayers" ) );
3381
3382 QMap<QString, QgsMapLayer *>::ConstIterator li = layers.constBegin();
3383 while ( li != layers.end() )
3384 {
3385 QgsMapLayer *ml = li.value();
3386
3387 if ( ml )
3388 {
3389 const QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.constFind( ml->id() );
3390 if ( emIt == mEmbeddedLayers.constEnd() )
3391 {
3392 QDomElement maplayerElem;
3393 // If layer is not valid, prefer to restore saved properties from invalidLayerProperties. But if that's
3394 // not available, just write what we DO have
3395 if ( ml->isValid() || ml->originalXmlProperties().isEmpty() )
3396 {
3397 // general layer metadata
3398 maplayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
3399 ml->writeLayerXml( maplayerElem, *doc, context );
3400
3402 maplayerElem.setAttribute( QStringLiteral( "editable" ), QStringLiteral( "1" ) );
3403 }
3404 else if ( ! ml->originalXmlProperties().isEmpty() )
3405 {
3406 QDomDocument document;
3407 if ( document.setContent( ml->originalXmlProperties() ) )
3408 {
3409 maplayerElem = document.firstChildElement();
3410 }
3411 else
3412 {
3413 QgsDebugError( QStringLiteral( "Could not restore layer properties for layer %1" ).arg( ml->id() ) );
3414 }
3415 }
3416
3417 emit writeMapLayer( ml, maplayerElem, *doc );
3418
3419 projectLayersNode.appendChild( maplayerElem );
3420 }
3421 else
3422 {
3423 // layer defined in an external project file
3424 // only save embedded layer if not managed by a legend group
3425 if ( emIt.value().second )
3426 {
3427 QDomElement mapLayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
3428 mapLayerElem.setAttribute( QStringLiteral( "embedded" ), 1 );
3429 mapLayerElem.setAttribute( QStringLiteral( "project" ), writePath( emIt.value().first ) );
3430 mapLayerElem.setAttribute( QStringLiteral( "id" ), ml->id() );
3431 projectLayersNode.appendChild( mapLayerElem );
3432 }
3433 }
3434 }
3435 li++;
3436 }
3437
3438 qgisNode.appendChild( projectLayersNode );
3439
3440 QDomElement layerOrderNode = doc->createElement( QStringLiteral( "layerorder" ) );
3441 const auto constCustomLayerOrder = mRootGroup->customLayerOrder();
3442 for ( QgsMapLayer *layer : constCustomLayerOrder )
3443 {
3444 QDomElement mapLayerElem = doc->createElement( QStringLiteral( "layer" ) );
3445 mapLayerElem.setAttribute( QStringLiteral( "id" ), layer->id() );
3446 layerOrderNode.appendChild( mapLayerElem );
3447 }
3448 qgisNode.appendChild( layerOrderNode );
3449
3450 mLabelingEngineSettings->writeSettingsToProject( this );
3451 {
3452 QDomElement labelEngineSettingsElement = doc->createElement( QStringLiteral( "labelEngineSettings" ) );
3453 mLabelingEngineSettings->writeXml( *doc, labelEngineSettingsElement, context );
3454 qgisNode.appendChild( labelEngineSettingsElement );
3455 }
3456
3457 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), mBackgroundColor.red() );
3458 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), mBackgroundColor.green() );
3459 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), mBackgroundColor.blue() );
3460
3461 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), mSelectionColor.red() );
3462 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), mSelectionColor.green() );
3463 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), mSelectionColor.blue() );
3464 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), mSelectionColor.alpha() );
3465
3466 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QgsUnitTypes::encodeUnit( mDistanceUnits ) );
3467 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QgsUnitTypes::encodeUnit( mAreaUnits ) );
3468 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/ScaleMethod" ), qgsEnumValueToKey( mScaleMethod ) );
3469
3470 // now add the optional extra properties
3471#if 0
3472 dump_( mProperties );
3473#endif
3474
3475 QgsDebugMsgLevel( QStringLiteral( "there are %1 property scopes" ).arg( static_cast<int>( mProperties.count() ) ), 2 );
3476
3477 if ( !mProperties.isEmpty() ) // only worry about properties if we
3478 // actually have any properties
3479 {
3480 mProperties.writeXml( QStringLiteral( "properties" ), qgisNode, *doc );
3481 }
3482
3483 QDomElement ddElem = doc->createElement( QStringLiteral( "dataDefinedServerProperties" ) );
3484 mDataDefinedServerProperties.writeXml( ddElem, dataDefinedServerPropertyDefinitions() );
3485 qgisNode.appendChild( ddElem );
3486
3487 mMapThemeCollection->writeXml( *doc );
3488
3489 mTransformContext.writeXml( qgisNode, context );
3490
3491 QDomElement metadataElem = doc->createElement( QStringLiteral( "projectMetadata" ) );
3492 mMetadata.writeMetadataXml( metadataElem, *doc );
3493 qgisNode.appendChild( metadataElem );
3494
3495 {
3496 const QDomElement annotationsElem = mAnnotationManager->writeXml( *doc, context );
3497 qgisNode.appendChild( annotationsElem );
3498 }
3499
3500 {
3501 const QDomElement layoutElem = mLayoutManager->writeXml( *doc );
3502 qgisNode.appendChild( layoutElem );
3503 }
3504
3505 {
3506 const QDomElement elevationProfileElem = mElevationProfileManager->writeXml( *doc, context );
3507 qgisNode.appendChild( elevationProfileElem );
3508 }
3509
3510 {
3511 const QDomElement views3DElem = m3DViewsManager->writeXml( *doc );
3512 qgisNode.appendChild( views3DElem );
3513 }
3514
3515 {
3516 const QDomElement bookmarkElem = mBookmarkManager->writeXml( *doc );
3517 qgisNode.appendChild( bookmarkElem );
3518 }
3519
3520 {
3521 const QDomElement sensorElem = mSensorManager->writeXml( *doc );
3522 qgisNode.appendChild( sensorElem );
3523 }
3524
3525 {
3526 const QDomElement viewSettingsElem = mViewSettings->writeXml( *doc, context );
3527 qgisNode.appendChild( viewSettingsElem );
3528 }
3529
3530 {
3531 const QDomElement styleSettingsElem = mStyleSettings->writeXml( *doc, context );
3532 qgisNode.appendChild( styleSettingsElem );
3533 }
3534
3535 {
3536 const QDomElement timeSettingsElement = mTimeSettings->writeXml( *doc, context );
3537 qgisNode.appendChild( timeSettingsElement );
3538 }
3539
3540 {
3541 const QDomElement elevationPropertiesElement = mElevationProperties->writeXml( *doc, context );
3542 qgisNode.appendChild( elevationPropertiesElement );
3543 }
3544
3545 {
3546 const QDomElement displaySettingsElem = mDisplaySettings->writeXml( *doc, context );
3547 qgisNode.appendChild( displaySettingsElem );
3548 }
3549
3550 {
3551 const QDomElement gpsSettingsElem = mGpsSettings->writeXml( *doc, context );
3552 qgisNode.appendChild( gpsSettingsElem );
3553 }
3554
3555 // now wrap it up and ship it to the project file
3556 doc->normalize(); // XXX I'm not entirely sure what this does
3557
3558 // Create backup file
3559 if ( QFile::exists( fileName() ) )
3560 {
3561 QFile backupFile( QStringLiteral( "%1~" ).arg( filename ) );
3562 bool ok = true;
3563 ok &= backupFile.open( QIODevice::WriteOnly | QIODevice::Truncate );
3564 ok &= projectFile.open( QIODevice::ReadOnly );
3565
3566 QByteArray ba;
3567 while ( ok && !projectFile.atEnd() )
3568 {
3569 ba = projectFile.read( 10240 );
3570 ok &= backupFile.write( ba ) == ba.size();
3571 }
3572
3573 projectFile.close();
3574 backupFile.close();
3575
3576 if ( !ok )
3577 {
3578 setError( tr( "Unable to create backup file %1" ).arg( backupFile.fileName() ) );
3579 return false;
3580 }
3581
3582 const QFileInfo fi( fileName() );
3583 struct utimbuf tb = { static_cast<time_t>( fi.lastRead().toSecsSinceEpoch() ), static_cast<time_t>( fi.lastModified().toSecsSinceEpoch() ) };
3584 utime( backupFile.fileName().toUtf8().constData(), &tb );
3585 }
3586
3587 if ( !projectFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
3588 {
3589 projectFile.close(); // even though we got an error, let's make
3590 // sure it's closed anyway
3591
3592 setError( tr( "Unable to save to file %1" ).arg( projectFile.fileName() ) );
3593 return false;
3594 }
3595
3596 QTemporaryFile tempFile;
3597 bool ok = tempFile.open();
3598 if ( ok )
3599 {
3600 QTextStream projectFileStream( &tempFile );
3601 doc->save( projectFileStream, 2 ); // save as utf-8
3602 ok &= projectFileStream.pos() > -1;
3603
3604 ok &= tempFile.seek( 0 );
3605
3606 QByteArray ba;
3607 while ( ok && !tempFile.atEnd() )
3608 {
3609 ba = tempFile.read( 10240 );
3610 ok &= projectFile.write( ba ) == ba.size();
3611 }
3612
3613 ok &= projectFile.error() == QFile::NoError;
3614
3615 projectFile.close();
3616 }
3617
3618 tempFile.close();
3619
3620 if ( !ok )
3621 {
3622 setError( tr( "Unable to save to file %1. Your project "
3623 "may be corrupted on disk. Try clearing some space on the volume and "
3624 "check file permissions before pressing save again." )
3625 .arg( projectFile.fileName() ) );
3626 return false;
3627 }
3628
3629 setDirty( false ); // reset to pristine state
3630
3631 emit projectSaved();
3632 return true;
3633}
3634
3635bool QgsProject::writeEntry( const QString &scope, QString const &key, bool value )
3636{
3638
3639 bool propertiesModified;
3640 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3641
3642 if ( propertiesModified )
3643 setDirty( true );
3644
3645 return success;
3646}
3647
3648bool QgsProject::writeEntry( const QString &scope, const QString &key, double value )
3649{
3651
3652 bool propertiesModified;
3653 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3654
3655 if ( propertiesModified )
3656 setDirty( true );
3657
3658 return success;
3659}
3660
3661bool QgsProject::writeEntry( const QString &scope, QString const &key, int value )
3662{
3664
3665 bool propertiesModified;
3666 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3667
3668 if ( propertiesModified )
3669 setDirty( true );
3670
3671 return success;
3672}
3673
3674bool QgsProject::writeEntry( const QString &scope, const QString &key, const QString &value )
3675{
3677
3678 bool propertiesModified;
3679 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3680
3681 if ( propertiesModified )
3682 setDirty( true );
3683
3684 return success;
3685}
3686
3687bool QgsProject::writeEntry( const QString &scope, const QString &key, const QStringList &value )
3688{
3690
3691 bool propertiesModified;
3692 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3693
3694 if ( propertiesModified )
3695 setDirty( true );
3696
3697 return success;
3698}
3699
3700QStringList QgsProject::readListEntry( const QString &scope,
3701 const QString &key,
3702 const QStringList &def,
3703 bool *ok ) const
3704{
3705 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
3707
3708 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3709
3710 QVariant value;
3711
3712 if ( property )
3713 {
3714 value = property->value();
3715
3716 const bool valid = QMetaType::Type::QStringList == value.userType();
3717 if ( ok )
3718 *ok = valid;
3719
3720 if ( valid )
3721 {
3722 return value.toStringList();
3723 }
3724 }
3725 else if ( ok )
3726 *ok = false;
3727
3728
3729 return def;
3730}
3731
3732QString QgsProject::readEntry( const QString &scope,
3733 const QString &key,
3734 const QString &def,
3735 bool *ok ) const
3736{
3738
3739 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3740
3741 QVariant value;
3742
3743 if ( property )
3744 {
3745 value = property->value();
3746
3747 const bool valid = value.canConvert( QMetaType::Type::QString );
3748 if ( ok )
3749 *ok = valid;
3750
3751 if ( valid )
3752 return value.toString();
3753 }
3754 else if ( ok )
3755 *ok = false;
3756
3757 return def;
3758}
3759
3760int QgsProject::readNumEntry( const QString &scope, const QString &key, int def,
3761 bool *ok ) const
3762{
3764
3765 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3766
3767 QVariant value;
3768
3769 if ( property )
3770 {
3771 value = property->value();
3772 }
3773
3774 const bool valid = value.canConvert( QMetaType::Type::Int );
3775
3776 if ( ok )
3777 {
3778 *ok = valid;
3779 }
3780
3781 if ( valid )
3782 {
3783 return value.toInt();
3784 }
3785
3786 return def;
3787}
3788
3789double QgsProject::readDoubleEntry( const QString &scope, const QString &key,
3790 double def,
3791 bool *ok ) const
3792{
3794
3795 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3796 if ( property )
3797 {
3798 const QVariant value = property->value();
3799
3800 const bool valid = value.canConvert( QMetaType::Type::Double );
3801 if ( ok )
3802 *ok = valid;
3803
3804 if ( valid )
3805 return value.toDouble();
3806 }
3807 else if ( ok )
3808 *ok = false;
3809
3810 return def;
3811}
3812
3813bool QgsProject::readBoolEntry( const QString &scope, const QString &key, bool def,
3814 bool *ok ) const
3815{
3817
3818 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3819
3820 if ( property )
3821 {
3822 const QVariant value = property->value();
3823
3824 const bool valid = value.canConvert( QMetaType::Type::Bool );
3825 if ( ok )
3826 *ok = valid;
3827
3828 if ( valid )
3829 return value.toBool();
3830 }
3831 else if ( ok )
3832 *ok = false;
3833
3834 return def;
3835}
3836
3837bool QgsProject::removeEntry( const QString &scope, const QString &key )
3838{
3840
3841 if ( findKey_( scope, key, mProperties ) )
3842 {
3843 removeKey_( scope, key, mProperties );
3844 setDirty( true );
3845 }
3846
3847 return !findKey_( scope, key, mProperties );
3848}
3849
3850QStringList QgsProject::entryList( const QString &scope, const QString &key ) const
3851{
3853
3854 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3855
3856 QStringList entries;
3857
3858 if ( foundProperty )
3859 {
3860 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3861
3862 if ( propertyKey )
3863 { propertyKey->entryList( entries ); }
3864 }
3865
3866 return entries;
3867}
3868
3869QStringList QgsProject::subkeyList( const QString &scope, const QString &key ) const
3870{
3872
3873 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3874
3875 QStringList entries;
3876
3877 if ( foundProperty )
3878 {
3879 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3880
3881 if ( propertyKey )
3882 { propertyKey->subkeyList( entries ); }
3883 }
3884
3885 return entries;
3886}
3887
3889{
3891
3892 dump_( mProperties );
3893}
3894
3896{
3898
3899 QString filePath;
3900 switch ( filePathStorage() )
3901 {
3903 break;
3904
3906 {
3907 // for projects stored in a custom storage, we need to ask to the
3908 // storage for the path, if the storage returns an empty path
3909 // relative paths are not supported
3910 if ( QgsProjectStorage *storage = projectStorage() )
3911 {
3912 filePath = storage->filePath( mFile.fileName() );
3913 }
3914 else
3915 {
3916 filePath = fileName();
3917 }
3918 break;
3919 }
3920 }
3921
3922 return QgsPathResolver( filePath, mArchive->dir() );
3923}
3924
3925QString QgsProject::readPath( const QString &src ) const
3926{
3928
3929 return pathResolver().readPath( src );
3930}
3931
3932QString QgsProject::writePath( const QString &src ) const
3933{
3935
3936 return pathResolver().writePath( src );
3937}
3938
3939void QgsProject::setError( const QString &errorMessage )
3940{
3942
3943 mErrorMessage = errorMessage;
3944}
3945
3946QString QgsProject::error() const
3947{
3949
3950 return mErrorMessage;
3951}
3952
3953void QgsProject::clearError()
3954{
3956
3957 setError( QString() );
3958}
3959
3961{
3963
3964 mBadLayerHandler.reset( handler );
3965}
3966
3967QString QgsProject::layerIsEmbedded( const QString &id ) const
3968{
3970
3971 const QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
3972 if ( it == mEmbeddedLayers.constEnd() )
3973 {
3974 return QString();
3975 }
3976 return it.value().first;
3977}
3978
3979bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &projectFilePath, QList<QDomNode> &brokenNodes,
3980 bool saveFlag, Qgis::ProjectReadFlags flags )
3981{
3983
3985
3986 static QString sPrevProjectFilePath;
3987 static QDateTime sPrevProjectFileTimestamp;
3988 static QDomDocument sProjectDocument;
3989
3990 QString qgsProjectFile = projectFilePath;
3991 QgsProjectArchive archive;
3992 if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
3993 {
3994 archive.unzip( projectFilePath );
3995 qgsProjectFile = archive.projectFile();
3996 }
3997
3998 const QDateTime projectFileTimestamp = QFileInfo( projectFilePath ).lastModified();
3999
4000 if ( projectFilePath != sPrevProjectFilePath || projectFileTimestamp != sPrevProjectFileTimestamp )
4001 {
4002 sPrevProjectFilePath.clear();
4003
4004 QFile projectFile( qgsProjectFile );
4005 if ( !projectFile.open( QIODevice::ReadOnly ) )
4006 {
4007 return false;
4008 }
4009
4010 if ( !sProjectDocument.setContent( &projectFile ) )
4011 {
4012 return false;
4013 }
4014
4015 sPrevProjectFilePath = projectFilePath;
4016 sPrevProjectFileTimestamp = projectFileTimestamp;
4017 }
4018
4019 // does project store paths absolute or relative?
4020 bool useAbsolutePaths = true;
4021
4022 const QDomElement propertiesElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "properties" ) );
4023 if ( !propertiesElem.isNull() )
4024 {
4025 QDomElement e = propertiesElem.firstChildElement( QStringLiteral( "Paths" ) );
4026 if ( e.isNull() )
4027 {
4028 e = propertiesElem.firstChildElement( QStringLiteral( "properties" ) );
4029 while ( !e.isNull() && e.attribute( QStringLiteral( "name" ) ) != QLatin1String( "Paths" ) )
4030 e = e.nextSiblingElement( QStringLiteral( "properties" ) );
4031
4032 e = e.firstChildElement( QStringLiteral( "properties" ) );
4033 while ( !e.isNull() && e.attribute( QStringLiteral( "name" ) ) != QLatin1String( "Absolute" ) )
4034 e = e.nextSiblingElement( QStringLiteral( "properties" ) );
4035 }
4036 else
4037 {
4038 e = e.firstChildElement( QStringLiteral( "Absolute" ) );
4039 }
4040
4041 if ( !e.isNull() )
4042 {
4043 useAbsolutePaths = e.text().compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
4044 }
4045 }
4046
4047 QgsReadWriteContext embeddedContext;
4048 if ( !useAbsolutePaths )
4049 embeddedContext.setPathResolver( QgsPathResolver( projectFilePath ) );
4050 embeddedContext.setProjectTranslator( this );
4051 embeddedContext.setTransformContext( transformContext() );
4052
4053 const QDomElement projectLayersElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "projectlayers" ) );
4054 if ( projectLayersElem.isNull() )
4055 {
4056 return false;
4057 }
4058
4059 QDomElement mapLayerElem = projectLayersElem.firstChildElement( QStringLiteral( "maplayer" ) );
4060 while ( ! mapLayerElem.isNull() )
4061 {
4062 // get layer id
4063 const QString id = mapLayerElem.firstChildElement( QStringLiteral( "id" ) ).text();
4064 if ( id == layerId )
4065 {
4066 // layer can be embedded only once
4067 if ( mapLayerElem.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
4068 {
4069 return false;
4070 }
4071
4072 mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
4073
4074 if ( addLayer( mapLayerElem, brokenNodes, embeddedContext, flags ) )
4075 {
4076 return true;
4077 }
4078 else
4079 {
4080 mEmbeddedLayers.remove( layerId );
4081 return false;
4082 }
4083 }
4084 mapLayerElem = mapLayerElem.nextSiblingElement( QStringLiteral( "maplayer" ) );
4085 }
4086
4087 return false;
4088}
4089
4090std::unique_ptr<QgsLayerTreeGroup> QgsProject::createEmbeddedGroup( const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers, Qgis::ProjectReadFlags flags )
4091{
4093
4094 QString qgsProjectFile = projectFilePath;
4095 QgsProjectArchive archive;
4096 if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
4097 {
4098 archive.unzip( projectFilePath );
4099 qgsProjectFile = archive.projectFile();
4100 }
4101
4102 // open project file, get layer ids in group, add the layers
4103 QFile projectFile( qgsProjectFile );
4104 if ( !projectFile.open( QIODevice::ReadOnly ) )
4105 {
4106 return nullptr;
4107 }
4108
4109 QDomDocument projectDocument;
4110 if ( !projectDocument.setContent( &projectFile ) )
4111 {
4112 return nullptr;
4113 }
4114
4115 QgsReadWriteContext context;
4116 context.setPathResolver( pathResolver() );
4117 context.setProjectTranslator( this );
4119
4120 auto root = std::make_unique< QgsLayerTreeGroup >();
4121
4122 QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
4123 if ( !layerTreeElem.isNull() )
4124 {
4125 root->readChildrenFromXml( layerTreeElem, context );
4126 }
4127 else
4128 {
4129 QgsLayerTreeUtils::readOldLegend( root.get(), projectDocument.documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
4130 }
4131
4132 QgsLayerTreeGroup *group = root->findGroup( groupName );
4133 if ( !group || group->customProperty( QStringLiteral( "embedded" ) ).toBool() )
4134 {
4135 // embedded groups cannot be embedded again
4136 return nullptr;
4137 }
4138
4139 // clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
4140 std::unique_ptr< QgsLayerTreeGroup > newGroup( QgsLayerTree::toGroup( group->clone() ) );
4141 root.reset();
4142
4143 newGroup->setCustomProperty( QStringLiteral( "embedded" ), 1 );
4144 newGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectFilePath );
4145
4146 // set "embedded" to all children + load embedded layers
4147 mLayerTreeRegistryBridge->setEnabled( false );
4148 initializeEmbeddedSubtree( projectFilePath, newGroup.get(), flags );
4149 mLayerTreeRegistryBridge->setEnabled( true );
4150
4151 // consider the layers might be identify disabled in its project
4152 const QStringList constFindLayerIds = newGroup->findLayerIds();
4153 for ( const QString &layerId : constFindLayerIds )
4154 {
4155 QgsLayerTreeLayer *layer = newGroup->findLayer( layerId );
4156 if ( layer )
4157 {
4158 layer->resolveReferences( this );
4159 layer->setItemVisibilityChecked( !invisibleLayers.contains( layerId ) );
4160 }
4161 }
4162
4163 return newGroup;
4164}
4165
4166void QgsProject::initializeEmbeddedSubtree( const QString &projectFilePath, QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
4167{
4169
4170 const auto constChildren = group->children();
4171 for ( QgsLayerTreeNode *child : constChildren )
4172 {
4173 // all nodes in the subtree will have "embedded" custom property set
4174 child->setCustomProperty( QStringLiteral( "embedded" ), 1 );
4175
4176 if ( QgsLayerTree::isGroup( child ) )
4177 {
4178 initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ), flags );
4179 }
4180 else if ( QgsLayerTree::isLayer( child ) )
4181 {
4182 // load the layer into our project
4183 QList<QDomNode> brokenNodes;
4184 createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, false, flags );
4185 }
4186 }
4187}
4188
4195
4202
4204{
4206
4207 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), ( enabled ? 1 : 0 ) );
4209}
4210
4212{
4214
4215 return readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), 0 );
4216}
4217
4219{
4221
4222 if ( mDistanceUnits == unit )
4223 return;
4224
4225 mDistanceUnits = unit;
4226
4227 emit distanceUnitsChanged();
4228}
4229
4231{
4233
4234 if ( mAreaUnits == unit )
4235 return;
4236
4237 mAreaUnits = unit;
4238
4239 emit areaUnitsChanged();
4240}
4241
4243{
4245
4246 if ( mScaleMethod == method )
4247 return;
4248
4249 mScaleMethod = method;
4250
4251 emit scaleMethodChanged();
4252}
4253
4255{
4256 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4258
4259 if ( !mCachedHomePath.isEmpty() )
4260 return mCachedHomePath;
4261
4262 const QFileInfo pfi( fileName() );
4263
4264 if ( !mHomePath.isEmpty() )
4265 {
4266 const QFileInfo homeInfo( mHomePath );
4267 if ( !homeInfo.isRelative() )
4268 {
4269 mCachedHomePath = mHomePath;
4270 return mHomePath;
4271 }
4272 }
4273 else if ( !fileName().isEmpty() )
4274 {
4275
4276 // If it's not stored in the file system, try to get the path from the storage
4277 if ( QgsProjectStorage *storage = projectStorage() )
4278 {
4279 const QString storagePath { storage->filePath( fileName() ) };
4280 if ( ! storagePath.isEmpty() && QFileInfo::exists( storagePath ) )
4281 {
4282 mCachedHomePath = QFileInfo( storagePath ).path();
4283 return mCachedHomePath;
4284 }
4285 }
4286
4287 mCachedHomePath = pfi.path();
4288 return mCachedHomePath;
4289 }
4290
4291 if ( !pfi.exists() )
4292 {
4293 mCachedHomePath = mHomePath;
4294 return mHomePath;
4295 }
4296
4297 if ( !mHomePath.isEmpty() )
4298 {
4299 // path is relative to project file
4300 mCachedHomePath = QDir::cleanPath( pfi.path() + '/' + mHomePath );
4301 }
4302 else
4303 {
4304 mCachedHomePath = pfi.canonicalPath();
4305 }
4306 return mCachedHomePath;
4307}
4308
4310{
4312
4313 return mHomePath;
4314}
4315
4317{
4318 // because relation aggregate functions are not thread safe
4320
4321 return mRelationManager.get();
4322}
4323
4325{
4327
4328 return mLayoutManager.get();
4329}
4330
4332{
4334
4335 return mLayoutManager.get();
4336}
4337
4339{
4341
4342 return mElevationProfileManager.get();
4343}
4344
4346{
4348
4349 return mElevationProfileManager.get();
4350}
4351
4353{
4355
4356 return m3DViewsManager.get();
4357}
4358
4360{
4362
4363 return m3DViewsManager.get();
4364}
4365
4367{
4369
4370 return mBookmarkManager;
4371}
4372
4374{
4376
4377 return mBookmarkManager;
4378}
4379
4381{
4383
4384 return mSensorManager;
4385}
4386
4388{
4390
4391 return mSensorManager;
4392}
4393
4395{
4397
4398 return mViewSettings;
4399}
4400
4407
4409{
4411
4412 return mStyleSettings;
4413}
4414
4416{
4417 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4419
4420 return mStyleSettings;
4421}
4422
4424{
4426
4427 return mTimeSettings;
4428}
4429
4436
4438{
4440
4441 return mElevationProperties;
4442}
4443
4450
4452{
4454
4455 return mDisplaySettings;
4456}
4457
4459{
4461
4462 return mDisplaySettings;
4463}
4464
4466{
4468
4469 return mGpsSettings;
4470}
4471
4478
4480{
4482
4483 return mRootGroup.get();
4484}
4485
4487{
4489
4490 return mMapThemeCollection.get();
4491}
4492
4494{
4496
4497 return mAnnotationManager.get();
4498}
4499
4501{
4503
4504 return mAnnotationManager.get();
4505}
4506
4507void QgsProject::setNonIdentifiableLayers( const QList<QgsMapLayer *> &layers )
4508{
4510
4511 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
4512 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
4513 {
4514 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4515 continue;
4516
4517 if ( layers.contains( it.value() ) )
4518 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Identifiable );
4519 else
4520 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Identifiable );
4521 }
4522
4526}
4527
4528void QgsProject::setNonIdentifiableLayers( const QStringList &layerIds )
4529{
4531
4532 QList<QgsMapLayer *> nonIdentifiableLayers;
4533 nonIdentifiableLayers.reserve( layerIds.count() );
4534 for ( const QString &layerId : layerIds )
4535 {
4536 QgsMapLayer *layer = mapLayer( layerId );
4537 if ( layer )
4538 nonIdentifiableLayers << layer;
4539 }
4543}
4544
4546{
4548
4549 QStringList nonIdentifiableLayers;
4550
4551 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
4552 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
4553 {
4554 if ( !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4555 {
4556 nonIdentifiableLayers.append( it.value()->id() );
4557 }
4558 }
4559 return nonIdentifiableLayers;
4560}
4561
4563{
4565
4566 return mTransactionMode == Qgis::TransactionMode::AutomaticGroups;
4567}
4568
4570{
4572
4573 if ( autoTransaction
4574 && mTransactionMode == Qgis::TransactionMode::AutomaticGroups )
4575 return;
4576
4577 if ( ! autoTransaction
4578 && mTransactionMode == Qgis::TransactionMode::Disabled )
4579 return;
4580
4581 if ( autoTransaction )
4583 else
4585
4586 updateTransactionGroups();
4587}
4588
4590{
4592
4593 return mTransactionMode;
4594}
4595
4597{
4599
4600 if ( transactionMode == mTransactionMode )
4601 return true;
4602
4603 // Check that all layer are not in edit mode
4604 const auto constLayers = mapLayers().values();
4605 for ( QgsMapLayer *layer : constLayers )
4606 {
4607 if ( layer->isEditable() )
4608 {
4609 QgsLogger::warning( tr( "Transaction mode can be changed only if all layers are not editable." ) );
4610 return false;
4611 }
4612 }
4613
4614 mTransactionMode = transactionMode;
4615 updateTransactionGroups();
4617 return true;
4618}
4619
4620QMap<QPair<QString, QString>, QgsTransactionGroup *> QgsProject::transactionGroups()
4621{
4623
4624 return mTransactionGroups;
4625}
4626
4627
4628//
4629// QgsMapLayerStore methods
4630//
4631
4632
4634{
4636
4637 return mLayerStore->count();
4638}
4639
4641{
4643
4644 return mLayerStore->validCount();
4645}
4646
4647QgsMapLayer *QgsProject::mapLayer( const QString &layerId ) const
4648{
4649 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
4651
4652 if ( mMainAnnotationLayer && layerId == mMainAnnotationLayer->id() )
4653 return mMainAnnotationLayer;
4654
4655 return mLayerStore->mapLayer( layerId );
4656}
4657
4658QList<QgsMapLayer *> QgsProject::mapLayersByName( const QString &layerName ) const
4659{
4661
4662 return mLayerStore->mapLayersByName( layerName );
4663}
4664
4665QList<QgsMapLayer *> QgsProject::mapLayersByShortName( const QString &shortName ) const
4666{
4668
4669 QList<QgsMapLayer *> layers;
4670 const auto constMapLayers { mLayerStore->mapLayers() };
4671 for ( const auto &l : constMapLayers )
4672 {
4673 if ( ! l->serverProperties()->shortName().isEmpty() )
4674 {
4675 if ( l->serverProperties()->shortName() == shortName )
4676 layers << l;
4677 }
4678 else if ( l->name() == shortName )
4679 {
4680 layers << l;
4681 }
4682 }
4683 return layers;
4684}
4685
4686bool QgsProject::unzip( const QString &filename, Qgis::ProjectReadFlags flags )
4687{
4689
4690 clearError();
4691 auto archive = std::make_unique<QgsProjectArchive>();
4692
4693 // unzip the archive
4694 if ( !archive->unzip( filename ) )
4695 {
4696 setError( tr( "Unable to unzip file '%1'" ).arg( filename ) );
4697 return false;
4698 }
4699
4700 // test if zip provides a .qgs file
4701 if ( archive->projectFile().isEmpty() )
4702 {
4703 setError( tr( "Zip archive does not provide a project file" ) );
4704 return false;
4705 }
4706
4707 // Keep the archive
4708 releaseHandlesToProjectArchive();
4709 mArchive = std::move( archive );
4710
4711 // load auxiliary storage
4712 if ( !static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile().isEmpty() )
4713 {
4714 // database file is already a copy as it's been unzipped. So we don't open
4715 // auxiliary storage in copy mode in this case
4716 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile(), false );
4717 }
4718 else
4719 {
4720 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( *this );
4721 }
4722
4723 // read the project file
4724 if ( ! readProjectFile( static_cast<QgsProjectArchive *>( mArchive.get() )->projectFile(), flags ) )
4725 {
4726 setError( tr( "Cannot read unzipped qgs project file" ) + QStringLiteral( ": " ) + error() );
4727 return false;
4728 }
4729
4730 // Remove the temporary .qgs file
4731 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4732
4733 return true;
4734}
4735
4736bool QgsProject::zip( const QString &filename )
4737{
4739
4740 clearError();
4741
4742 // save the current project in a temporary .qgs file
4743 auto archive = std::make_unique<QgsProjectArchive>();
4744 const QString baseName = QFileInfo( filename ).baseName();
4745 const QString qgsFileName = QStringLiteral( "%1.qgs" ).arg( baseName );
4746 QFile qgsFile( QDir( archive->dir() ).filePath( qgsFileName ) );
4747
4748 bool writeOk = false;
4749 if ( qgsFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
4750 {
4751 writeOk = writeProjectFile( qgsFile.fileName() );
4752 qgsFile.close();
4753 }
4754
4755 // stop here with an error message
4756 if ( ! writeOk )
4757 {
4758 setError( tr( "Unable to write temporary qgs file" ) );
4759 return false;
4760 }
4761
4762 // save auxiliary storage
4763 const QFileInfo info( qgsFile );
4764 const QString asExt = QStringLiteral( ".%1" ).arg( QgsAuxiliaryStorage::extension() );
4765 const QString asFileName = info.path() + QDir::separator() + info.completeBaseName() + asExt;
4766
4767 bool auxiliaryStorageSavedOk = true;
4768 if ( ! saveAuxiliaryStorage( asFileName ) )
4769 {
4770 const QString err = mAuxiliaryStorage->errorString();
4771 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 ) );
4772 auxiliaryStorageSavedOk = false;
4773
4774 // fixes the current archive and keep the previous version of qgd
4775 if ( !mArchive->exists() )
4776 {
4777 releaseHandlesToProjectArchive();
4778 mArchive = std::make_unique< QgsProjectArchive >();
4779 mArchive->unzip( mFile.fileName() );
4780 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4781
4782 const QString auxiliaryStorageFile = static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile();
4783 if ( ! auxiliaryStorageFile.isEmpty() )
4784 {
4785 archive->addFile( auxiliaryStorageFile );
4786 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( auxiliaryStorageFile, false );
4787 }
4788 }
4789 }
4790 else
4791 {
4792 // in this case, an empty filename means that the auxiliary database is
4793 // empty, so we don't want to save it
4794 if ( QFile::exists( asFileName ) )
4795 {
4796 archive->addFile( asFileName );
4797 }
4798 }
4799
4800 // create the archive
4801 archive->addFile( qgsFile.fileName() );
4802
4803 // Add all other files
4804 const QStringList &files = mArchive->files();
4805 for ( const QString &file : files )
4806 {
4807 if ( !file.endsWith( QLatin1String( ".qgs" ), Qt::CaseInsensitive ) && !file.endsWith( asExt, Qt::CaseInsensitive ) )
4808 {
4809 archive->addFile( file );
4810 }
4811 }
4812
4813 // zip
4814 bool zipOk = true;
4815 if ( !archive->zip( filename ) )
4816 {
4817 setError( tr( "Unable to perform zip" ) );
4818 zipOk = false;
4819 }
4820
4821 return auxiliaryStorageSavedOk && zipOk;
4822}
4823
4825{
4827
4828 return QgsZipUtils::isZipFile( mFile.fileName() );
4829}
4830
4831QList<QgsMapLayer *> QgsProject::addMapLayers(
4832 const QList<QgsMapLayer *> &layers,
4833 bool addToLegend,
4834 bool takeOwnership )
4835{
4837
4838 const QList<QgsMapLayer *> myResultList { mLayerStore->addMapLayers( layers, takeOwnership ) };
4839 if ( !myResultList.isEmpty() )
4840 {
4841 // Update transform context
4842 for ( auto &l : myResultList )
4843 {
4844 l->setTransformContext( transformContext() );
4845 }
4846 if ( addToLegend )
4847 {
4848 emit legendLayersAdded( myResultList );
4849 }
4850 }
4851
4852 if ( mAuxiliaryStorage )
4853 {
4854 for ( QgsMapLayer *mlayer : myResultList )
4855 {
4856 if ( mlayer->type() != Qgis::LayerType::Vector )
4857 continue;
4858
4859 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mlayer );
4860 if ( vl )
4861 {
4862 vl->loadAuxiliaryLayer( *mAuxiliaryStorage );
4863 }
4864 }
4865 }
4866
4867 mProjectScope.reset();
4868
4869 return myResultList;
4870}
4871
4874 bool addToLegend,
4875 bool takeOwnership )
4876{
4878
4879 QList<QgsMapLayer *> addedLayers;
4880 addedLayers = addMapLayers( QList<QgsMapLayer *>() << layer, addToLegend, takeOwnership );
4881 return addedLayers.isEmpty() ? nullptr : addedLayers[0];
4882}
4883
4884void QgsProject::removeAuxiliaryLayer( const QgsMapLayer *ml )
4885{
4887
4888 if ( ! ml || ml->type() != Qgis::LayerType::Vector )
4889 return;
4890
4891 const QgsVectorLayer *vl = qobject_cast<const QgsVectorLayer *>( ml );
4892 if ( vl && vl->auxiliaryLayer() )
4893 {
4894 const QgsDataSourceUri uri( vl->auxiliaryLayer()->source() );
4896 }
4897}
4898
4899void QgsProject::removeMapLayers( const QStringList &layerIds )
4900{
4902
4903 for ( const auto &layerId : layerIds )
4904 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4905
4906 mProjectScope.reset();
4907 mLayerStore->removeMapLayers( layerIds );
4908}
4909
4910void QgsProject::removeMapLayers( const QList<QgsMapLayer *> &layers )
4911{
4913
4914 for ( const auto &layer : layers )
4915 removeAuxiliaryLayer( layer );
4916
4917 mProjectScope.reset();
4918 mLayerStore->removeMapLayers( layers );
4919}
4920
4921void QgsProject::removeMapLayer( const QString &layerId )
4922{
4924
4925 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4926 mProjectScope.reset();
4927 mLayerStore->removeMapLayer( layerId );
4928}
4929
4931{
4933
4934 removeAuxiliaryLayer( layer );
4935 mProjectScope.reset();
4936 mLayerStore->removeMapLayer( layer );
4937}
4938
4940{
4942
4943 mProjectScope.reset();
4944 return mLayerStore->takeMapLayer( layer );
4945}
4946
4948{
4950
4951 return mMainAnnotationLayer;
4952}
4953
4955{
4957
4958 if ( mLayerStore->count() == 0 )
4959 return;
4960
4961 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
4962 mProjectScope.reset();
4963 mLayerStore->removeAllMapLayers();
4964
4965 snapSingleBlocker.release();
4966 mSnappingConfig.clearIndividualLayerSettings();
4967 if ( !mBlockSnappingUpdates )
4968 emit snappingConfigChanged( mSnappingConfig );
4969}
4970
4972{
4974
4975 const QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
4976 QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin();
4977 for ( ; it != layers.constEnd(); ++it )
4978 {
4979 it.value()->reload();
4980 }
4981}
4982
4983QMap<QString, QgsMapLayer *> QgsProject::mapLayers( const bool validOnly ) const
4984{
4985 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
4987
4988 return validOnly ? mLayerStore->validMapLayers() : mLayerStore->mapLayers();
4989}
4990
4991QgsTransactionGroup *QgsProject::transactionGroup( const QString &providerKey, const QString &connString )
4992{
4994
4995 return mTransactionGroups.value( qMakePair( providerKey, connString ) );
4996}
4997
5004
5006{
5008
5010
5011 // TODO QGIS 4.0 -- remove this method, and place it somewhere in app (where it belongs)
5012 // 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)
5013 if ( mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), QStringLiteral( "NoAction" ), QgsSettings::App ).toString() == QStringLiteral( "UseProjectCrs" )
5014 || mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), 0, QgsSettings::App ).toString() == QLatin1String( "2" ) )
5015 {
5016 // for new layers if the new layer crs method is set to either prompt or use project, then we use the project crs
5017 defaultCrs = crs();
5018 }
5019 else
5020 {
5021 // global crs
5022 const QString layerDefaultCrs = mSettings.value( QStringLiteral( "/Projections/layerDefaultCrs" ), QStringLiteral( "EPSG:4326" ) ).toString();
5023 defaultCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( layerDefaultCrs );
5024 }
5025
5026 return defaultCrs;
5027}
5028
5035
5042
5043bool QgsProject::saveAuxiliaryStorage( const QString &filename )
5044{
5046
5047 const QMap<QString, QgsMapLayer *> layers = mapLayers();
5048 bool empty = true;
5049 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5050 {
5051 if ( it.value()->type() != Qgis::LayerType::Vector )
5052 continue;
5053
5054 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
5055 if ( vl && vl->auxiliaryLayer() )
5056 {
5057 vl->auxiliaryLayer()->save();
5058 empty &= vl->auxiliaryLayer()->auxiliaryFields().isEmpty();
5059 }
5060 }
5061
5062 if ( !mAuxiliaryStorage->exists( *this ) && empty )
5063 {
5064 return true; // it's not an error
5065 }
5066 else if ( !filename.isEmpty() )
5067 {
5068 return mAuxiliaryStorage->saveAs( filename );
5069 }
5070 else
5071 {
5072 return mAuxiliaryStorage->saveAs( *this );
5073 }
5074}
5075
5076QgsPropertiesDefinition &QgsProject::dataDefinedServerPropertyDefinitions()
5077{
5078 static QgsPropertiesDefinition sPropertyDefinitions
5079 {
5080 {
5082 QgsPropertyDefinition( "WMSOnlineResource", QObject::tr( "WMS Online Resource" ), QgsPropertyDefinition::String )
5083 },
5084 };
5085 return sPropertyDefinitions;
5086}
5087
5093
5095{
5097
5098 return mAuxiliaryStorage.get();
5099}
5100
5102{
5104
5105 return mAuxiliaryStorage.get();
5106}
5107
5108QString QgsProject::createAttachedFile( const QString &nameTemplate )
5109{
5111
5112 const QDir archiveDir( mArchive->dir() );
5113 QTemporaryFile tmpFile( archiveDir.filePath( "XXXXXX_" + nameTemplate ), this );
5114 tmpFile.setAutoRemove( false );
5115 if ( !tmpFile.open() )
5116 {
5117 setError( tr( "Unable to open %1" ).arg( tmpFile.fileName() ) );
5118 return QString();
5119 }
5120 mArchive->addFile( tmpFile.fileName() );
5121 return tmpFile.fileName();
5122}
5123
5124QStringList QgsProject::attachedFiles() const
5125{
5127
5128 QStringList attachments;
5129 const QString baseName = QFileInfo( fileName() ).baseName();
5130 const QStringList files = mArchive->files();
5131 attachments.reserve( files.size() );
5132 for ( const QString &file : files )
5133 {
5134 if ( QFileInfo( file ).baseName() != baseName )
5135 {
5136 attachments.append( file );
5137 }
5138 }
5139 return attachments;
5140}
5141
5142bool QgsProject::removeAttachedFile( const QString &path )
5143{
5145
5146 return mArchive->removeFile( path );
5147}
5148
5149QString QgsProject::attachmentIdentifier( const QString &attachedFile ) const
5150{
5152
5153 return QStringLiteral( "attachment:///%1" ).arg( QFileInfo( attachedFile ).fileName() );
5154}
5155
5156QString QgsProject::resolveAttachmentIdentifier( const QString &identifier ) const
5157{
5159
5160 if ( identifier.startsWith( QLatin1String( "attachment:///" ) ) )
5161 {
5162 return QDir( mArchive->dir() ).absoluteFilePath( identifier.mid( 14 ) );
5163 }
5164 return QString();
5165}
5166
5168{
5169 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
5171
5172 return mMetadata;
5173}
5174
5176{
5178
5179 if ( metadata == mMetadata )
5180 return;
5181
5182 mMetadata = metadata;
5183 mProjectScope.reset();
5184
5185 emit metadataChanged();
5186 emit titleChanged();
5187
5188 setDirty( true );
5189}
5190
5191QSet<QgsMapLayer *> QgsProject::requiredLayers() const
5192{
5194
5195 QSet<QgsMapLayer *> requiredLayers;
5196
5197 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
5198 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
5199 {
5200 if ( !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
5201 {
5202 requiredLayers.insert( it.value() );
5203 }
5204 }
5205 return requiredLayers;
5206}
5207
5208void QgsProject::setRequiredLayers( const QSet<QgsMapLayer *> &layers )
5209{
5211
5212 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
5213 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
5214 {
5215 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
5216 continue;
5217
5218 if ( layers.contains( it.value() ) )
5219 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Removable );
5220 else
5221 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Removable );
5222 }
5223}
5224
5226{
5228
5229 // save colors to project
5230 QStringList customColors;
5231 QStringList customColorLabels;
5232
5233 QgsNamedColorList::const_iterator colorIt = colors.constBegin();
5234 for ( ; colorIt != colors.constEnd(); ++colorIt )
5235 {
5236 const QString color = QgsColorUtils::colorToString( ( *colorIt ).first );
5237 const QString label = ( *colorIt ).second;
5238 customColors.append( color );
5239 customColorLabels.append( label );
5240 }
5241 writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ), customColors );
5242 writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ), customColorLabels );
5243 mProjectScope.reset();
5244 emit projectColorsChanged();
5245}
5246
5247void QgsProject::setBackgroundColor( const QColor &color )
5248{
5250
5251 if ( mBackgroundColor == color )
5252 return;
5253
5254 mBackgroundColor = color;
5256}
5257
5259{
5261
5262 return mBackgroundColor;
5263}
5264
5265void QgsProject::setSelectionColor( const QColor &color )
5266{
5268
5269 if ( mSelectionColor == color )
5270 return;
5271
5272 mSelectionColor = color;
5273 emit selectionColorChanged();
5274}
5275
5277{
5279
5280 return mSelectionColor;
5281}
5282
5283void QgsProject::setMapScales( const QVector<double> &scales )
5284{
5286
5287 mViewSettings->setMapScales( scales );
5288}
5289
5290QVector<double> QgsProject::mapScales() const
5291{
5293
5294 return mViewSettings->mapScales();
5295}
5296
5298{
5300
5301 mViewSettings->setUseProjectScales( enabled );
5302}
5303
5305{
5307
5308 return mViewSettings->useProjectScales();
5309}
5310
5311void QgsProject::generateTsFile( const QString &locale )
5312{
5314
5315 QgsTranslationContext translationContext;
5316 translationContext.setProject( this );
5317 translationContext.setFileName( QStringLiteral( "%1/%2.ts" ).arg( absolutePath(), baseName() ) );
5318
5319 QgsApplication::instance()->collectTranslatableObjects( &translationContext );
5320
5321 translationContext.writeTsFile( locale );
5322}
5323
5324QString QgsProject::translate( const QString &context, const QString &sourceText, const char *disambiguation, int n ) const
5325{
5327
5328 if ( !mTranslator )
5329 {
5330 return sourceText;
5331 }
5332
5333 QString result = mTranslator->translate( context.toUtf8(), sourceText.toUtf8(), disambiguation, n );
5334
5335 if ( result.isEmpty() )
5336 {
5337 return sourceText;
5338 }
5339 return result;
5340}
5341
5343{
5345
5346 const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
5347 if ( !layers.empty() )
5348 {
5349 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5350 {
5351 // NOTE: if visitEnter returns false it means "don't visit this layer", not "abort all further visitations"
5352 if ( visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
5353 {
5354 if ( !( ( *it )->accept( visitor ) ) )
5355 return false;
5356
5357 if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
5358 return false;
5359 }
5360 }
5361 }
5362
5363 if ( !mLayoutManager->accept( visitor ) )
5364 return false;
5365
5366 if ( !mAnnotationManager->accept( visitor ) )
5367 return false;
5368
5369 return true;
5370}
5371
5373{
5375
5376 const QString macros = readEntry( QStringLiteral( "Macros" ), QStringLiteral( "/pythonCode" ), QString() );
5377 if ( !macros.isEmpty() )
5378 {
5379 QgsEmbeddedScriptEntity entity( Qgis::EmbeddedScriptType::Macro, tr( "Macros" ), macros );
5380 if ( !visitor->visitEmbeddedScript( entity, context ) )
5381 {
5382 return false;
5383 }
5384 }
5385
5386 const QString expressionFunctions = readEntry( QStringLiteral( "ExpressionFunctions" ), QStringLiteral( "/pythonCode" ) );
5387 if ( !expressionFunctions.isEmpty() )
5388 {
5389 QgsEmbeddedScriptEntity entity( Qgis::EmbeddedScriptType::ExpressionFunction, tr( "Expression functions" ), expressionFunctions );
5390 if ( !visitor->visitEmbeddedScript( entity, context ) )
5391 {
5392 return false;
5393 }
5394 }
5395
5396 const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
5397 if ( !layers.empty() )
5398 {
5399 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5400 {
5401 if ( !( ( *it )->accept( visitor, context ) ) )
5402 {
5403 return false;
5404 }
5405 }
5406 }
5407
5408 return true;
5409}
5410
5412{
5413 return mElevationShadingRenderer;
5414}
5415
5416void QgsProject::loadProjectFlags( const QDomDocument *doc )
5417{
5419
5420 QDomElement element = doc->documentElement().firstChildElement( QStringLiteral( "projectFlags" ) );
5422 if ( !element.isNull() )
5423 {
5424 flags = qgsFlagKeysToValue( element.attribute( QStringLiteral( "set" ) ), Qgis::ProjectFlags() );
5425 }
5426 else
5427 {
5428 // older project compatibility
5429 element = doc->documentElement().firstChildElement( QStringLiteral( "evaluateDefaultValues" ) );
5430 if ( !element.isNull() )
5431 {
5432 if ( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
5434 }
5435
5436 // Read trust layer metadata config in the project
5437 element = doc->documentElement().firstChildElement( QStringLiteral( "trust" ) );
5438 if ( !element.isNull() )
5439 {
5440 if ( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
5442 }
5443 }
5444
5445 setFlags( flags );
5446}
5447
5449{
5451 {
5453 {
5454 const QString projectFunctions = readEntry( QStringLiteral( "ExpressionFunctions" ), QStringLiteral( "/pythonCode" ), QString() );
5455 if ( !projectFunctions.isEmpty() )
5456 {
5457 QgsPythonRunner::run( projectFunctions );
5458 return true;
5459 }
5460 }
5461 }
5462 return false;
5463}
5464
5466{
5468 {
5469 QgsPythonRunner::run( "qgis.utils.clean_project_expression_functions()" );
5470 }
5471}
5472
5474
5475QHash< QString, QColor > loadColorsFromProject( const QgsProject *project )
5476{
5477 QHash< QString, QColor > colors;
5478
5479 //build up color list from project. Do this in advance for speed
5480 QStringList colorStrings = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ) );
5481 const QStringList colorLabels = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ) );
5482
5483 //generate list from custom colors
5484 int colorIndex = 0;
5485 for ( QStringList::iterator it = colorStrings.begin();
5486 it != colorStrings.end(); ++it )
5487 {
5488 const QColor color = QgsColorUtils::colorFromString( *it );
5489 QString label;
5490 if ( colorLabels.length() > colorIndex )
5491 {
5492 label = colorLabels.at( colorIndex );
5493 }
5494
5495 colors.insert( label.toLower(), color );
5496 colorIndex++;
5497 }
5498
5499 return colors;
5500}
5501
5502
5503GetNamedProjectColor::GetNamedProjectColor( const QgsProject *project )
5504 : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
5505{
5506 if ( !project )
5507 return;
5508
5509 mColors = loadColorsFromProject( project );
5510}
5511
5512GetNamedProjectColor::GetNamedProjectColor( const QHash<QString, QColor> &colors )
5513 : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
5514 , mColors( colors )
5515{
5516}
5517
5518QVariant GetNamedProjectColor::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5519{
5520 const QString colorName = values.at( 0 ).toString().toLower();
5521 if ( mColors.contains( colorName ) )
5522 {
5523 return QStringLiteral( "%1,%2,%3" ).arg( mColors.value( colorName ).red() ).arg( mColors.value( colorName ).green() ).arg( mColors.value( colorName ).blue() );
5524 }
5525 else
5526 return QVariant();
5527}
5528
5529QgsScopedExpressionFunction *GetNamedProjectColor::clone() const
5530{
5531 return new GetNamedProjectColor( mColors );
5532}
5533
5534GetNamedProjectColorObject::GetNamedProjectColorObject( const QgsProject *project )
5535 : QgsScopedExpressionFunction( QStringLiteral( "project_color_object" ), 1, QStringLiteral( "Color" ) )
5536{
5537 if ( !project )
5538 return;
5539
5540 mColors = loadColorsFromProject( project );
5541}
5542
5543GetNamedProjectColorObject::GetNamedProjectColorObject( const QHash<QString, QColor> &colors )
5544 : QgsScopedExpressionFunction( QStringLiteral( "project_color_object" ), 1, QStringLiteral( "Color" ) )
5545 , mColors( colors )
5546{
5547}
5548
5549QVariant GetNamedProjectColorObject::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5550{
5551 const QString colorName = values.at( 0 ).toString().toLower();
5552 if ( mColors.contains( colorName ) )
5553 {
5554 return mColors.value( colorName );
5555 }
5556 else
5557 return QVariant();
5558}
5559
5560QgsScopedExpressionFunction *GetNamedProjectColorObject::clone() const
5561{
5562 return new GetNamedProjectColorObject( mColors );
5563}
5564
5565// ----------------
5566
5567GetSensorData::GetSensorData( const QMap<QString, QgsAbstractSensor::SensorData> &sensorData )
5568 : QgsScopedExpressionFunction( QStringLiteral( "sensor_data" ),
5569 QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "name" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "expiration" ), true, 0 ),
5570 QStringLiteral( "Sensors" ) )
5571 , mSensorData( sensorData )
5572{
5573}
5574
5575QVariant GetSensorData::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5576{
5577 const QString sensorName = values.at( 0 ).toString();
5578 const int expiration = values.at( 1 ).toInt();
5579 const qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
5580 if ( mSensorData.contains( sensorName ) )
5581 {
5582 if ( expiration <= 0 || ( timestamp - mSensorData[sensorName].lastTimestamp.toMSecsSinceEpoch() ) < expiration )
5583 {
5584 return mSensorData[sensorName].lastValue;
5585 }
5586 }
5587
5588 return QVariant();
5589}
5590
5591QgsScopedExpressionFunction *GetSensorData::clone() const
5592{
5593 return new GetSensorData( mSensorData );
5594}
@ ExpressionFunction
Project macros.
Definition qgis.h:441
@ DontLoad3DViews
Skip loading 3D views.
Definition qgis.h:4307
@ DontStoreOriginalStyles
Skip the initial XML style storage for layers. Useful for minimising project load times in non-intera...
Definition qgis.h:4306
@ ForceReadOnlyLayers
Open layers in a read-only mode.
Definition qgis.h:4309
@ TrustLayerMetadata
Trust layer metadata. Improves project read time. Do not use it if layers' extent is not fixed during...
Definition qgis.h:4305
@ DontUpgradeAnnotations
Don't upgrade old annotation items to QgsAnnotationItem.
Definition qgis.h:4310
@ DontLoadLayouts
Don't load print layouts. Improves project read time if layouts are not required, and allows projects...
Definition qgis.h:4304
@ DontResolveLayers
Don't resolve layer paths (i.e. don't load any layer content). Dramatically improves project read tim...
Definition qgis.h:4303
static QString version()
Version string.
Definition qgis.cpp:677
@ Trusted
The project trust has not yet been determined by the user.
Definition qgis.h:454
QFlags< ProjectCapability > ProjectCapabilities
Flags which control project capabilities.
Definition qgis.h:4343
QFlags< ProjectReadFlag > ProjectReadFlags
Project load flags.
Definition qgis.h:4321
DistanceUnit
Units of distance.
Definition qgis.h:5013
@ Meters
Meters.
Definition qgis.h:5014
FilePathType
File path types.
Definition qgis.h:1676
@ Relative
Relative path.
Definition qgis.h:1678
@ Absolute
Absolute path.
Definition qgis.h:1677
TransactionMode
Transaction mode.
Definition qgis.h:3969
@ AutomaticGroups
Automatic transactional editing means that on supported datasources (postgres and geopackage database...
Definition qgis.h:3971
@ BufferedGroups
Buffered transactional editing means that all editable layers in the buffered transaction group are t...
Definition qgis.h:3972
@ Disabled
Edits are buffered locally and sent to the provider when toggling layer editing mode.
Definition qgis.h:3970
AreaUnit
Units of area.
Definition qgis.h:5090
@ SquareMeters
Square meters.
Definition qgis.h:5091
@ Critical
Critical/error message.
Definition qgis.h:159
@ Success
Used for reporting a successful operation.
Definition qgis.h:160
@ Vertical
Vertical CRS.
Definition qgis.h:2333
@ Temporal
Temporal CRS.
Definition qgis.h:2336
@ Compound
Compound (horizontal + vertical) CRS.
Definition qgis.h:2335
@ Projected
Projected CRS.
Definition qgis.h:2334
@ Other
Other type.
Definition qgis.h:2339
@ Bound
Bound CRS.
Definition qgis.h:2338
@ DerivedProjected
Derived projected CRS.
Definition qgis.h:2340
@ Unknown
Unknown type.
Definition qgis.h:2328
@ Engineering
Engineering CRS.
Definition qgis.h:2337
@ Geographic3d
3D geopraphic CRS
Definition qgis.h:2332
@ Geodetic
Geodetic CRS.
Definition qgis.h:2329
@ Geographic2d
2D geographic CRS
Definition qgis.h:2331
@ Geocentric
Geocentric CRS.
Definition qgis.h:2330
AvoidIntersectionsMode
Flags which control how intersections of pre-existing feature are handled when digitizing new feature...
Definition qgis.h:4273
@ AvoidIntersectionsLayers
Overlap with features from a specified list of layers when digitizing new features not allowed.
Definition qgis.h:4276
@ AllowIntersections
Overlap with any feature allowed when digitizing new features.
Definition qgis.h:4274
ProjectFlag
Flags which control the behavior of QgsProjects.
Definition qgis.h:4092
@ RememberLayerEditStatusBetweenSessions
If set, then any layers set to be editable will be stored in the project and immediately made editabl...
Definition qgis.h:4095
@ EvaluateDefaultValuesOnProviderSide
If set, default values for fields will be evaluated on the provider side when features from the proje...
Definition qgis.h:4093
@ TrustStoredLayerStatistics
If set, then layer statistics (such as the layer extent) will be read from values stored in the proje...
Definition qgis.h:4094
@ Polygon
Polygons.
Definition qgis.h:361
QFlags< DataProviderReadFlag > DataProviderReadFlags
Flags which control data provider construction.
Definition qgis.h:486
LayerType
Types of layers that can be added to a map.
Definition qgis.h:190
@ Group
Composite group layer. Added in QGIS 3.24.
Definition qgis.h:198
@ Plugin
Plugin based layer.
Definition qgis.h:193
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:199
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
Definition qgis.h:196
@ Vector
Vector layer.
Definition qgis.h:191
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:195
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:194
@ Raster
Raster layer.
Definition qgis.h:192
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Definition qgis.h:197
ScaleCalculationMethod
Scale calculation logic.
Definition qgis.h:5285
@ HorizontalMiddle
Calculate horizontally, across midle of map.
Definition qgis.h:5287
@ SkipCredentialsRequest
Skip credentials if the provided one are not valid, let the provider be invalid, avoiding to block th...
Definition qgis.h:474
@ ParallelThreadLoading
Provider is created in a parallel thread than the one where it will live.
Definition qgis.h:475
QFlags< ProjectFlag > ProjectFlags
Definition qgis.h:4099
@ Container
A container.
Definition qgis.h:5452
@ Marker
Marker symbol.
Definition qgis.h:611
@ Line
Line symbol.
Definition qgis.h:612
@ Fill
Fill symbol.
Definition qgis.h:613
static QString geoNone()
Constant that holds the string representation for "No ellipse/No CRS".
Definition qgis.h:6323
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
Definition qgis.h:2439
QMap< QString, QStringList > KeywordMap
Map of vocabulary string to keyword list.
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.
Represents a map layer containing a set of georeferenced annotations, e.g.
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.
Manages zip/unzip operations for an archive.
Definition qgsarchive.h:36
A container for attribute editors, used to group them visually in the attribute form if it is set to ...
QList< QgsAttributeEditorElement * > children() const
Gets a list of the children elements of this container.
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.
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.
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.
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.
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.
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.
static QgsCoordinateReferenceSystem fromSrsId(long srsId)
Creates a CRS from a specified QGIS SRS ID.
Contains information about the context in which a coordinate transform is executed.
void readSettings()
Reads the context's state from application settings.
Abstract base class for spatial data provider implementations.
@ EvaluateDefaultValues
Evaluate default values on provider side when calling QgsVectorDataProvider::defaultValue( int index ...
Stores the component parts of a data source URI (e.g.
QgsAttributeEditorContainer * invisibleRootContainer()
Gets the invisible root container for the drag and drop designer form (EditorLayout::TabLayout).
Manages storage of a set of elevation profiles.
Renders elevation shading on an image with different methods (eye dome lighting, hillshading,...
A embedded script entity for QgsObjectEntityVisitorInterface.
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...
An abstract base class for defining QgsExpression functions.
An expression node for expression functions.
Handles parsing and evaluation of expressions (formerly called "search strings").
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:54
Container of fields for a vector layer.
Definition qgsfields.h:46
bool isEmpty
Definition qgsfields.h:49
Stores global configuration for labeling engine.
Layer tree group node serves as a container for layers and further groups.
QgsLayerTreeGroup * findGroup(const QString &name)
Find group node with specified name.
void readChildrenFromXml(const QDomElement &element, const QgsReadWriteContext &context)
Read children from XML and append them to the group.
QString name() const override
Returns the group's name.
void insertChildNodes(int index, const QList< QgsLayerTreeNode * > &nodes)
Insert existing nodes at specified position.
QgsLayerTreeGroup * clone() const override
Returns a clone of the group.
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).
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.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
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).
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.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
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.
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:80
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 configChanged()
Emitted whenever the configuration is changed.
static Qgis::DataProviderReadFlags providerReadFlags(const QDomNode &layerNode, QgsMapLayer::ReadFlags layerReadFlags)
Returns provider read flag deduced from layer read flags layerReadFlags and a dom node layerNode that...
QString id
Definition qgsmaplayer.h:83
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:90
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.
Container class that allows storage of map themes consisting of visible map layers and layer styles.
Manages storage of a set of views.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
An interface for classes which can visit various object entity (e.g.
virtual bool visitEmbeddedScript(const QgsEmbeddedScriptEntity &entity, const QgsObjectVisitorContext &context)
Called when the visitor will visit an embedded script entity.
A QgsObjectEntityVisitorInterface context object.
Resolves relative paths into absolute paths and vice versa.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
Allows managing the zip/unzip actions on project files.
Definition qgsarchive.h:112
QString projectFile() const
Returns the current .qgs project file or an empty string if there's none.
bool unzip(const QString &zipFilename) override
Clear the current content of this archive and unzip.
Interface for classes that handle missing layer files when reading project files.
Contains settings and properties relating to how a QgsProject should display values such as map coord...
Contains elevation properties for a QgsProject.
static Q_DECL_DEPRECATED void fixOldSymbolLayerReferences(const QMap< QString, QgsMapLayer * > &mapLayers)
QgsSymbolLayerReference uses QgsSymbolLayer unique uuid identifier since QGIS 3.30,...
Contains settings and properties relating to how a QgsProject should interact with a GPS device.
A structured metadata store for a project.
Project property key node.
QString name() const
The name of the property is used as identifier.
QgsProjectProperty * find(const QString &propertyName) const
Attempts to find a property with a matching sub-key name.
void removeKey(const QString &keyName)
Removes the specified key.
void dump(int tabs=0) const override
Dumps out the keys and values.
void subkeyList(QStringList &entries) const
Returns any sub-keys contained by this property which themselves contain other keys.
QgsProjectPropertyKey * addKey(const QString &keyName)
Adds the specified property key as a sub-key.
QVariant value() const override
If this key has a value, it will be stored by its name in its properties.
QgsProjectPropertyValue * setValue(const QString &name, const QVariant &value)
Sets the value associated with this key.
void entryList(QStringList &entries) const
Returns any sub-keys contained by this property that do not contain other keys.
bool readXml(const QDomNode &keyNode) override
Restores the property hierarchy from a specified DOM node.
An abstract base class for QGIS project property hierarchys.
virtual bool isKey() const =0
Returns true if the property is a QgsProjectPropertyKey.
virtual bool isValue() const =0
Returns true if the property is a QgsProjectPropertyValue.
QgsProjectStorage * projectStorageFromUri(const QString &uri)
Returns storage implementation if the URI matches one. Returns nullptr otherwise (it is a normal file...
Metadata associated with a project.
Abstract interface for project storage - to be implemented by various backends and registered in QgsP...
Contains settings and properties relating to how a QgsProject should handle styling.
void setDefaultSymbol(Qgis::SymbolType symbolType, QgsSymbol *symbol)
Sets the project default symbol for a given type.
void setRandomizeDefaultSymbolColor(bool randomized)
Sets whether the default symbol fill color is randomized.
void setDefaultColorRamp(QgsColorRamp *colorRamp)
Sets the project default color ramp.
void setDefaultSymbolOpacity(double opacity)
Sets the default symbol opacity.
Contains temporal settings and properties for the project, this may be used when animating maps or sh...
static Qgis::ProjectTrustStatus checkUserTrust(QgsProject *project)
Returns the current trust status of the specified project.
Describes the version of a project.
QString text() const
Returns a string representation of the version.
int majorVersion() const
Returns the major version number.
Contains settings and properties relating to how a QgsProject should be displayed inside map canvas,...
void mapScalesChanged()
Emitted when the list of custom project map scales changes.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:109
bool isZipped() const
Returns true if the project comes from a zip archive, false otherwise.
bool removeAttachedFile(const QString &path)
Removes the attached file.
QgsRelationManager * relationManager
Definition qgsproject.h:120
bool write()
Writes the project to its current associated file (see fileName() ).
QgsProject(QObject *parent=nullptr, Qgis::ProjectCapabilities capabilities=Qgis::ProjectCapability::ProjectStyles)
Create a new QgsProject.
void removeMapLayer(const QString &layerId)
Remove a layer from the registry by layer ID.
Q_DECL_DEPRECATED void oldProjectVersionWarning(const QString &warning)
Emitted when an old project file is read.
Q_DECL_DEPRECATED bool evaluateDefaultValues() const
Should default values be evaluated on provider side when requested and not when committed.
Qgis::DistanceUnit distanceUnits
Definition qgsproject.h:127
void layersRemoved(const QStringList &layerIds)
Emitted after one or more layers were removed from the registry.
void clear()
Clears the project, removing all settings and resetting it back to an empty, default state.
~QgsProject() override
QString error() const
Returns error message from previous read/write.
Q_DECL_DEPRECATED void setUseProjectScales(bool enabled)
Sets whether project mapScales() are enabled.
void readProjectWithContext(const QDomDocument &document, QgsReadWriteContext &context)
Emitted when a project is being read.
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
Reads an integer from the specified scope and key.
Q_DECL_DEPRECATED void setNonIdentifiableLayers(const QList< QgsMapLayer * > &layers)
Set a list of layers which should not be taken into account on map identification.
QList< QgsMapLayer * > addMapLayers(const QList< QgsMapLayer * > &mapLayers, bool addToLegend=true, bool takeOwnership=true)
Add a list of layers to the map of loaded layers.
Qgis::ProjectFlags flags() const
Returns the project's flags, which dictate the behavior of the project.
Definition qgsproject.h:212
Q_DECL_DEPRECATED QFileInfo fileInfo() const
Returns QFileInfo object for the project's associated file.
QString presetHomePath() const
Returns any manual project home path setting, or an empty string if not set.
void setBackgroundColor(const QColor &color)
Sets the default background color used by default map canvases.
void setCrs(const QgsCoordinateReferenceSystem &crs, bool adjustEllipsoid=false)
Sets the project's native coordinate reference system.
QColor selectionColor
Definition qgsproject.h:125
bool commitChanges(QStringList &commitErrors, bool stopEditing=true, QgsVectorLayer *vectorLayer=nullptr)
Attempts to commit to the underlying data provider any buffered changes made since the last to call t...
void mapThemeCollectionChanged()
Emitted when the map theme collection changes.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Qgis::FilePathType filePathStorage() const
Returns the type of paths used when storing file paths in a QGS/QGZ project file.
QString createAttachedFile(const QString &nameTemplate)
Attaches a file to the project.
Q_DECL_DEPRECATED void mapScalesChanged()
Emitted when the list of custom project map scales changes.
void readVersionMismatchOccurred(const QString &fileVersion)
Emitted when a project is read and the version of QGIS used to save the project differs from the curr...
QString ellipsoid
Definition qgsproject.h:117
void fileNameChanged()
Emitted when the file name of the project changes.
void titleChanged()
Emitted when the title of the project changes.
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QString title
Definition qgsproject.h:112
void writeMapLayer(QgsMapLayer *mapLayer, QDomElement &layerElem, QDomDocument &doc)
Emitted when a layer is being saved.
const QgsSensorManager * sensorManager() const
Returns the project's sensor manager, which manages sensors within the project.
void setSnappingConfig(const QgsSnappingConfig &snappingConfig)
The snapping configuration for this project.
void areaUnitsChanged()
Emitted when the default area units changes.
QgsPropertyCollection dataDefinedServerProperties() const
Returns the data defined properties used for overrides in user defined server parameters.
Q_DECL_DEPRECATED void nonIdentifiableLayersChanged(QStringList nonIdentifiableLayers)
Emitted when the list of layer which are excluded from map identification changes.
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the registry.
QString attachmentIdentifier(const QString &attachedFile) const
Returns an identifier for an attachment file path An attachment identifier is a string which does not...
void setScaleMethod(Qgis::ScaleCalculationMethod method)
Sets the method to use for map scale calculations for the project.
QgsVectorLayerEditBufferGroup * editBufferGroup()
Returns the edit buffer group.
void setSelectionColor(const QColor &color)
Sets the color used to highlight selected features.
bool rollBack(QStringList &rollbackErrors, bool stopEditing=true, QgsVectorLayer *vectorLayer=nullptr)
Stops a current editing operation on vectorLayer and discards any uncommitted edits.
void snappingConfigChanged(const QgsSnappingConfig &config)
Emitted whenever the configuration for snapping has changed.
QgsPathResolver pathResolver() const
Returns path resolver object with considering whether the project uses absolute or relative paths and...
void setBadLayerHandler(QgsProjectBadLayerHandler *handler)
Change handler for missing layers.
Q_DECL_DEPRECATED void setEvaluateDefaultValues(bool evaluateDefaultValues)
Defines if default values should be evaluated on provider side when requested and not when committed.
Qgis::AreaUnit areaUnits
Definition qgsproject.h:128
void crsChanged()
Emitted when the crs() of the project has changed.
QString translate(const QString &context, const QString &sourceText, const char *disambiguation=nullptr, int n=-1) const override
Translates a string using the Qt QTranslator mechanism.
const QgsProjectStyleSettings * styleSettings() const
Returns the project's style settings, which contains settings and properties relating to how a QgsPro...
QgsSnappingConfig snappingConfig
Definition qgsproject.h:119
const QgsProjectGpsSettings * gpsSettings() const
Returns the project's GPS settings, which contains settings and properties relating to how a QgsProje...
void setFileName(const QString &name)
Sets the file name associated with the project.
void avoidIntersectionsLayersChanged()
Emitted whenever avoidIntersectionsLayers has changed.
void setDataDefinedServerProperties(const QgsPropertyCollection &properties)
Sets the data defined properties used for overrides in user defined server parameters to properties.
void registerTranslatableObjects(QgsTranslationContext *translationContext)
Registers the objects that require translation into the translationContext.
void distanceUnitsChanged()
Emitted when the default distance units changes.
QgsAnnotationLayer * mainAnnotationLayer()
Returns the main annotation layer associated with the project.
const QgsBookmarkManager * bookmarkManager() const
Returns the project's bookmark manager, which manages bookmarks within the project.
void readMapLayer(QgsMapLayer *mapLayer, const QDomElement &layerNode)
Emitted after the basic initialization of a layer from the project file is done.
std::unique_ptr< QgsLayerTreeGroup > createEmbeddedGroup(const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers, Qgis::ProjectReadFlags flags=Qgis::ProjectReadFlags())
Create layer group instance defined in an arbitrary project file.
Q_DECL_DEPRECATED void setAutoTransaction(bool autoTransaction)
Transactional editing means that on supported datasources (postgres databases) the edit state of all ...
bool startEditing(QgsVectorLayer *vectorLayer=nullptr)
Makes the layer editable.
void aboutToBeCleared()
Emitted when the project is about to be cleared.
Q_DECL_DEPRECATED void setTrustLayerMetadata(bool trust)
Sets the trust option allowing to indicate if the extent has to be read from the XML document when da...
void cleared()
Emitted when the project is cleared (and additionally when an open project is cleared just before a n...
bool setVerticalCrs(const QgsCoordinateReferenceSystem &crs, QString *errorMessage=nullptr)
Sets the project's vertical coordinate reference system.
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets project's global labeling engine settings.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void metadataChanged()
Emitted when the project's metadata is changed.
QString resolveAttachmentIdentifier(const QString &identifier) const
Resolves an attachment identifier to a attachment file path.
const QgsProjectElevationProperties * elevationProperties() const
Returns the project's elevation properties, which contains the project's elevation related settings.
QString absolutePath() const
Returns full absolute path to the project folder if the project is stored in a file system - derived ...
void crs3DChanged()
Emitted when the crs3D() of the project has changed.
void scaleMethodChanged()
Emitted when the project's scale method is changed.
void removeMapLayers(const QStringList &layerIds)
Remove a set of layers from the registry by layer ID.
Q_DECL_DEPRECATED void setRequiredLayers(const QSet< QgsMapLayer * > &layers)
Configures a set of map layers that are required in the project and therefore they should not get rem...
bool createEmbeddedLayer(const QString &layerId, const QString &projectFilePath, QList< QDomNode > &brokenNodes, bool saveFlag=true, Qgis::ProjectReadFlags flags=Qgis::ProjectReadFlags())
Creates a maplayer instance defined in an arbitrary project file.
QList< QgsVectorLayer * > avoidIntersectionsLayers
Definition qgsproject.h:122
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
Reads a string from the specified scope and key.
QgsExpressionContextScope * createExpressionContextScope() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QString baseName() const
Returns the base name of the project file without the path and without extension - derived from fileN...
void ellipsoidChanged(const QString &ellipsoid)
Emitted when the project ellipsoid is changed.
QgsMapThemeCollection * mapThemeCollection
Definition qgsproject.h:118
void generateTsFile(const QString &locale)
Triggers the collection strings of .qgs to be included in ts file and calls writeTsFile().
QStringList entryList(const QString &scope, const QString &key) const
Returns a list of child keys with values which exist within the the specified scope and key.
Qgis::TransactionMode transactionMode
Definition qgsproject.h:130
QgsAnnotationManager * annotationManager()
Returns pointer to the project's annotation manager.
QgsProjectDisplaySettings * displaySettings
Definition qgsproject.h:129
QgsProjectMetadata metadata
Definition qgsproject.h:123
void projectColorsChanged()
Emitted whenever the project's color scheme has been changed.
QString saveUser() const
Returns the user name that did the last save.
QVector< T > layers() const
Returns a list of registered map layers with a specified layer type.
void setProjectColors(const QgsNamedColorList &colors)
Sets the colors for the project's color scheme (see QgsProjectColorScheme).
bool setTransactionMode(Qgis::TransactionMode transactionMode)
Set transaction mode.
QgsCoordinateTransformContext transformContext
Definition qgsproject.h:116
void transactionModeChanged()
Emitted when the transaction mode has changed.
void labelingEngineSettingsChanged()
Emitted when global configuration of the labeling engine changes.
void customVariablesChanged()
Emitted whenever the expression variables stored in the project have been changed.
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
bool readBoolEntry(const QString &scope, const QString &key, bool def=false, bool *ok=nullptr) const
Reads a boolean from the specified scope and key.
QgsMapLayerStore * layerStore()
Returns a pointer to the project's internal layer store.
QString originalPath() const
Returns the original path associated with the project.
void setOriginalPath(const QString &path)
Sets the original path associated with the project.
void dumpProperties() const
Dump out current project properties to stderr.
QgsElevationShadingRenderer elevationShadingRenderer() const
Returns the elevation shading renderer used for map shading.
const QgsMapViewsManager * viewsManager() const
Returns the project's views manager, which manages map views (including 3d maps) in the project.
static void setInstance(QgsProject *project)
Set the current project singleton instance to project.
int validCount() const
Returns the number of registered valid layers.
const QgsLayoutManager * layoutManager() const
Returns the project's layout manager, which manages print layouts, atlases and reports within the pro...
void elevationShadingRendererChanged()
Emitted when the map shading renderer changes.
Q_INVOKABLE QList< QgsMapLayer * > mapLayersByName(const QString &layerName) const
Retrieve a list of matching registered layers by layer name.
QString fileName
Definition qgsproject.h:113
QgsCoordinateReferenceSystem crs3D() const
Returns the CRS to use for the project when transforming 3D data, or when z/elevation value handling ...
Q_DECL_DEPRECATED bool autoTransaction() const
Transactional editing means that on supported datasources (postgres databases) the edit state of all ...
bool accept(QgsStyleEntityVisitorInterface *visitor) const
Accepts the specified style entity visitor, causing it to visit all style entities associated with th...
QStringList attachedFiles() const
Returns a map of all attached files with identifier and real paths.
void setMetadata(const QgsProjectMetadata &metadata)
Sets the project's metadata store.
void missingDatumTransforms(const QStringList &missingTransforms)
Emitted when datum transforms stored in the project are not available locally.
QgsTransactionGroup * transactionGroup(const QString &providerKey, const QString &connString)
Returns the matching transaction group from a provider key and connection string.
QgsCoordinateReferenceSystem crs
Definition qgsproject.h:115
QgsMapLayer * addMapLayer(QgsMapLayer *mapLayer, bool addToLegend=true, bool takeOwnership=true)
Add a layer to the map of loaded layers.
QStringList nonIdentifiableLayers
Definition qgsproject.h:111
void setAvoidIntersectionsMode(const Qgis::AvoidIntersectionsMode mode)
Sets the avoid intersections mode.
void transactionGroupsChanged()
Emitted whenever a new transaction group has been created or a transaction group has been removed.
const QgsAuxiliaryStorage * auxiliaryStorage() const
Returns the current const auxiliary storage.
void reloadAllLayers()
Reload all registered layer's provider data caches, synchronising the layer with any changes in the d...
int count() const
Returns the number of registered layers.
void loadingLayerMessageReceived(const QString &layerName, const QList< QgsReadWriteContext::ReadWriteMessage > &messages)
Emitted when loading layers has produced some messages.
void setAreaUnits(Qgis::AreaUnit unit)
Sets the default area measurement units for the project.
void setTitle(const QString &title)
Sets the project's title.
QMap< QPair< QString, QString >, QgsTransactionGroup * > transactionGroups()
Map of transaction groups.
void setFlag(Qgis::ProjectFlag flag, bool enabled=true)
Sets whether a project flag is enabled.
QDateTime lastModified() const
Returns last modified time of the project file as returned by the file system (or other project stora...
Qgis::ProjectCapabilities capabilities() const
Returns the project's capabilities, which dictate optional functionality which can be selectively ena...
Definition qgsproject.h:202
bool loadFunctionsFromProject(bool force=false)
Loads python expression functions stored in the current project.
bool readLayer(const QDomNode &layerNode)
Reads the layer described in the associated DOM node.
double readDoubleEntry(const QString &scope, const QString &key, double def=0, bool *ok=nullptr) const
Reads a double from the specified scope and key.
bool writeEntry(const QString &scope, const QString &key, bool value)
Write a boolean value to the project file.
QString absoluteFilePath() const
Returns full absolute path to the project file if the project is stored in a file system - derived fr...
const QgsElevationProfileManager * elevationProfileManager() const
Returns the project's elevation profile manager, which manages elevation profiles within the project.
QDateTime lastSaveDateTime() const
Returns the date and time when the project was last saved.
void projectSaved()
Emitted when the project file has been written and closed.
Q_DECL_DEPRECATED bool trustLayerMetadata() const
Returns true if the trust option is activated, false otherwise.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
void setEllipsoid(const QString &ellipsoid)
Sets the project's ellipsoid from a proj string representation, e.g., "WGS84".
void readProject(const QDomDocument &document)
Emitted when a project is being read.
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the project's coordinate transform context, which stores various information regarding which dat...
QColor backgroundColor
Definition qgsproject.h:124
void layerLoaded(int i, int n)
Emitted when a layer from a projects was read.
QStringList subkeyList(const QString &scope, const QString &key) const
Returns a list of child keys which contain other keys that exist within the the specified scope and k...
bool read(const QString &filename, Qgis::ProjectReadFlags flags=Qgis::ProjectReadFlags())
Reads given project file from the given file.
QStringList readListEntry(const QString &scope, const QString &key, const QStringList &def=QStringList(), bool *ok=nullptr) const
Reads a string list from the specified scope and key.
void selectionColorChanged()
Emitted whenever the project's selection color has been changed.
bool topologicalEditing
Definition qgsproject.h:126
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns project's global labeling engine settings.
void removeAllMapLayers()
Removes all registered layers.
Q_DECL_DEPRECATED QVector< double > mapScales() const
Returns the list of custom project map scales.
void setDirty(bool b=true)
Flag the project as dirty (modified).
void backgroundColorChanged()
Emitted whenever the project's canvas background color has been changed.
const QgsProjectViewSettings * viewSettings() const
Returns the project's view settings, which contains settings and properties relating to how a QgsProj...
void cleanFunctionsFromProject()
Unloads python expression functions stored in the current project and reloads local functions from th...
QgsCoordinateReferenceSystem verticalCrs() const
Returns the project's vertical coordinate reference system.
QString readPath(const QString &filename) const
Transforms a filename read from the project file to an absolute path.
void registerTranslatableContainers(QgsTranslationContext *translationContext, QgsAttributeEditorContainer *parent, const QString &layerId)
Registers the containers that require translation into the translationContext.
void setElevationShadingRenderer(const QgsElevationShadingRenderer &elevationShadingRenderer)
Sets the elevation shading renderer used for global map shading.
void setFilePathStorage(Qgis::FilePathType type)
Sets the type of paths used when storing file paths in a QGS/QGZ project file.
Q_DECL_DEPRECATED QSet< QgsMapLayer * > requiredLayers() const
Returns a set of map layers that are required in the project and therefore they should not get remove...
void transformContextChanged()
Emitted when the project transformContext() is changed.
void setTopologicalEditing(bool enabled)
Convenience function to set topological editing.
void legendLayersAdded(const QList< QgsMapLayer * > &layers)
Emitted, when a layer was added to the registry and the legend.
QVariantMap customVariables() const
A map of custom project variables.
void setAvoidIntersectionsLayers(const QList< QgsVectorLayer * > &layers)
Sets the list of layers with which intersections should be avoided.
void homePathChanged()
Emitted when the home path of the project changes.
void dirtySet()
Emitted when setDirty(true) is called.
void setCustomVariables(const QVariantMap &customVariables)
A map of custom project variables.
void writeProject(QDomDocument &document)
Emitted when the project is being written.
QgsCoordinateReferenceSystem defaultCrsForNewLayers() const
Returns the default CRS for new layers based on the settings and the current project CRS.
QString saveUserFullName() const
Returns the full user name that did the last save.
void layersAdded(const QList< QgsMapLayer * > &layers)
Emitted when one or more layers were added to the registry.
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
QString homePath
Definition qgsproject.h:114
bool isDirty() const
Returns true if the project has been modified since the last write().
QgsMapLayer * takeMapLayer(QgsMapLayer *layer)
Takes a layer from the registry.
void isDirtyChanged(bool dirty)
Emitted when the project dirty status changes.
void setDistanceUnits(Qgis::DistanceUnit unit)
Sets the default distance measurement units for the project.
Q_DECL_DEPRECATED bool useProjectScales() const
Returns true if project mapScales() are enabled.
Q_DECL_DEPRECATED void setMapScales(const QVector< double > &scales)
Sets the list of custom project map scales.
void setPresetHomePath(const QString &path)
Sets the project's home path.
void setFlags(Qgis::ProjectFlags flags)
Sets the project's flags, which dictate the behavior of the project.
QList< QgsMapLayer * > mapLayersByShortName(const QString &shortName) const
Retrieves a list of matching registered layers by layer shortName.
QgsProjectStorage * projectStorage() const
Returns pointer to project storage implementation that handles read/write of the project file.
QString layerIsEmbedded(const QString &id) const
Returns the source project file path if the layer with matching id is embedded from other project fil...
const QgsProjectTimeSettings * timeSettings() const
Returns the project's time settings, which contains the project's temporal range and other time based...
void verticalCrsChanged()
Emitted when the verticalCrs() of the project has changed.
void topologicalEditingChanged()
Emitted when the topological editing flag has changed.
bool removeEntry(const QString &scope, const QString &key)
Remove the given key from the specified scope.
QgsProjectVersion lastSaveVersion() const
Returns the QGIS version which the project was last saved using.
void avoidIntersectionsModeChanged()
Emitted whenever the avoid intersections mode has changed.
void loadingLayer(const QString &layerName)
Emitted when a layer is loaded.
A grouped map of multiple QgsProperty objects, each referenced by an integer key value.
void clear() final
Removes all properties from the collection.
@ String
Any string value.
Definition qgsproperty.h:59
virtual QgsProviderMetadata::ProviderCapabilities providerCapabilities() const
Returns the provider's capabilities.
@ ParallelCreateProvider
Indicates that the provider supports parallel creation, that is, can be created on another thread tha...
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
QString relativeToAbsoluteUri(const QString &providerKey, const QString &uri, const QgsReadWriteContext &context) const
Converts relative path(s) to absolute path(s) in the given provider-specific URI.
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
static bool isValid()
Returns true if the runner has an instance (and thus is able to run commands).
A container for the context for various read/write operations on objects.
void setTransformContext(const QgsCoordinateTransformContext &transformContext)
Sets data coordinate transform context to transformContext.
QgsCoordinateTransformContext transformContext() const
Returns data provider coordinate transform context.
QList< QgsReadWriteContext::ReadWriteMessage > takeMessages()
Returns the stored messages and remove them.
void setProjectTranslator(QgsProjectTranslator *projectTranslator)
Sets the project translator.
void setPathResolver(const QgsPathResolver &resolver)
Sets up path resolver for conversion between relative and absolute paths.
Manages a set of relations between layers.
Represents a relationship between two vector layers.
Definition qgsrelation.h:42
void providerCreated(bool isValid, const QString &layerId)
Emitted when a provider is created with isValid set to True when the provider is valid.
QgsDataProvider * dataProvider()
Returns the created data provider.
void clear(const QString &group="startup")
clear Clear all profile data.
Expression function for use within a QgsExpressionContextScope.
Scoped object for logging of the runtime for a single operation or group of operations.
Manages sensors.
static const QgsSettingsEntryInteger * settingsLayerParallelLoadingMaxCount
Settings entry maximum thread count used to load layer in parallel.
static const QgsSettingsEntryBool * settingsLayerParallelLoading
Settings entry whether layer are loading in parallel.
Stores configuration of snapping settings for the project.
An interface for classes which can visit style entity (e.g.
virtual bool visitExit(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor stops visiting a node.
virtual bool visitEnter(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor starts visiting a node.
void triggerIconRebuild()
Triggers emission of the rebuildIconPreviews() signal.
static QgsStyle * defaultStyle(bool initialize=true)
Returns the default application-wide style.
Definition qgsstyle.cpp:147
Represents a transaction group.
bool addLayer(QgsVectorLayer *layer)
Add a layer to this transaction group.
static bool supportsTransaction(const QgsVectorLayer *layer)
Checks if the provider of a given layer supports transactions.
QString connectionString() const
Returns the connection string of the transaction.
Used for the collecting of strings from projects for translation and creation of ts files.
void registerTranslation(const QString &context, const QString &source)
Registers the source to be translated.
void setProject(QgsProject *project)
Sets the project being translated.
static Q_INVOKABLE QString toString(Qgis::DistanceUnit unit)
Returns a translated string representing a distance unit.
static Q_INVOKABLE Qgis::AreaUnit decodeAreaUnit(const QString &string, bool *ok=nullptr)
Decodes an areal unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
static Q_INVOKABLE Qgis::DistanceUnit decodeDistanceUnit(const QString &string, bool *ok=nullptr)
Decodes a distance unit from a string.
The edit buffer group manages a group of edit buffers.
Represents a vector layer which manages a vector based dataset.
Q_INVOKABLE bool startEditing()
Makes the layer editable.
bool loadAuxiliaryLayer(const QgsAuxiliaryStorage &storage, const QString &key=QString())
Loads the auxiliary layer for this vector layer.
QgsAuxiliaryLayer * auxiliaryLayer()
Returns the current auxiliary layer.
QStringList commitErrors() const
Returns a list containing any error messages generated when attempting to commit changes to the layer...
Q_INVOKABLE bool rollBack(bool deleteBuffer=true)
Stops a current editing operation and discards any uncommitted edits.
Q_INVOKABLE bool commitChanges(bool stopEditing=true)
Attempts to commit to the underlying data provider any buffered changes made since the last to call t...
QgsEditFormConfig editFormConfig
static bool isZipFile(const QString &filename)
Returns true if the file name is a zipped file ( i.e with a '.qgz' extension, false otherwise.
QList< QPair< QColor, QString > > QgsNamedColorList
List of colors paired with a friendly display name identifying the color.
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition qgis.h:6817
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7170
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6798
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:6856
T qgsFlagKeysToValue(const QString &keys, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given keys of a flag.
Definition qgis.h:6878
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7169
#define QgsDebugCall
Definition qgslogger.h:53
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61
#define QgsDebugError(str)
Definition qgslogger.h:57
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.
void _getProperties(const QDomDocument &doc, QgsProjectPropertyKey &project_properties)
Restores any optional properties found in "doc" to "properties".
QgsPropertyCollection getDataDefinedServerProperties(const QDomDocument &doc, const QgsPropertiesDefinition &dataDefinedServerPropertyDefinitions)
Returns the data defined server properties collection found in "doc" to "dataDefinedServerProperties"...
void removeKey_(const QString &scope, const QString &key, QgsProjectPropertyKey &rootProperty)
Removes a given key.
QgsProjectVersion getVersion(const QDomDocument &doc)
Returns the version string found in the given DOM document.
void dump_(const QgsProjectPropertyKey &topQgsPropertyKey)
QgsProjectProperty * findKey_(const QString &scope, const QString &key, QgsProjectPropertyKey &rootProperty)
Takes the given scope and key and convert them to a string list of key tokens that will be used to na...
QgsProjectProperty * addKey_(const QString &scope, const QString &key, QgsProjectPropertyKey *rootProperty, const QVariant &value, bool &propertiesModified)
Adds the given key and value.
CORE_EXPORT QgsProjectVersion getVersion(QDomDocument const &doc)
Returns the version string found in the given DOM document.
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.
#define FONTMARKER_CHR_FIX
#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QDomElement layerElement
QString layerId
Qgis::DataProviderReadFlags flags
QgsDataProvider::ProviderOptions options
QString provider
QString dataSource
Setting options for loading annotation layers.
Setting options for creating vector data providers.
Single variable definition for use within a QgsExpressionContextScope.
Contains information relating to a node (i.e.