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