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