QGIS API Documentation 4.1.0-Master (0cdd3ae6384)
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
1043 mCrs = crs;
1044 writeEntry( u"SpatialRefSys"_s, u"/ProjectionsEnabled"_s, crs.isValid() ? 1 : 0 );
1045 mProjectScope.reset();
1046
1047 // if annotation layer doesn't have a crs (i.e. in a newly created project), it should
1048 // initially inherit the project CRS
1049 if ( !mMainAnnotationLayer->crs().isValid() || mMainAnnotationLayer->isEmpty() )
1050 mMainAnnotationLayer->setCrs( crs );
1051
1052 rebuildCrs3D();
1053
1054 setDirty( true );
1055 emit crsChanged();
1056 // Did vertical crs also change as a result of this? If so, emit signal
1057 if ( oldVerticalCrs != verticalCrs() )
1058 emit verticalCrsChanged();
1059 if ( oldCrs3D != mCrs3D )
1060 emit crs3DChanged();
1061 }
1062
1063 if ( adjustEllipsoid )
1064 setEllipsoid( crs.ellipsoidAcronym() );
1065}
1066
1068{
1069 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
1071
1072 if ( !crs().isValid() )
1073 return Qgis::geoNone();
1074
1075 return readEntry( u"Measure"_s, u"/Ellipsoid"_s, Qgis::geoNone() );
1076}
1077
1079{
1081
1082 if ( ellipsoid == readEntry( u"Measure"_s, u"/Ellipsoid"_s ) )
1083 return;
1084
1085 mProjectScope.reset();
1086 writeEntry( u"Measure"_s, u"/Ellipsoid"_s, ellipsoid );
1087
1088 if ( !mBlockEllipsoidChangedSignal )
1090}
1091
1093{
1094 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
1096
1097 switch ( mCrs.type() )
1098 {
1099 case Qgis::CrsType::Vertical: // would hope this never happens!
1100 QgsDebugError( u"Project has a vertical CRS set as the horizontal CRS!"_s );
1101 return mCrs;
1102
1104 return mCrs.verticalCrs();
1105
1117 break;
1118 }
1119 return mVerticalCrs;
1120}
1121
1123{
1125 bool res = true;
1126 if ( crs.isValid() )
1127 {
1128 // validate that passed crs is a vertical crs
1129 switch ( crs.type() )
1130 {
1132 break;
1133
1146 if ( errorMessage )
1147 *errorMessage = QObject::tr( "Specified CRS is a %1 CRS, not a Vertical CRS" ).arg( qgsEnumValueToKey( crs.type() ) );
1148 return false;
1149 }
1150 }
1151
1152 if ( crs != mVerticalCrs )
1153 {
1154 const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
1155 const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
1156
1157 switch ( mCrs.type() )
1158 {
1160 if ( crs != oldVerticalCrs )
1161 {
1162 if ( errorMessage )
1163 *errorMessage = QObject::tr( "Project CRS is a Compound CRS, specified Vertical CRS will be ignored" );
1164 return false;
1165 }
1166 break;
1167
1169 if ( crs != oldVerticalCrs )
1170 {
1171 if ( errorMessage )
1172 *errorMessage = QObject::tr( "Project CRS is a Geographic 3D CRS, specified Vertical CRS will be ignored" );
1173 return false;
1174 }
1175 break;
1176
1178 if ( crs != oldVerticalCrs )
1179 {
1180 if ( errorMessage )
1181 *errorMessage = QObject::tr( "Project CRS is a Geocentric CRS, specified Vertical CRS will be ignored" );
1182 return false;
1183 }
1184 break;
1185
1187 if ( mCrs.hasVerticalAxis() && crs != oldVerticalCrs )
1188 {
1189 if ( errorMessage )
1190 *errorMessage = QObject::tr( "Project CRS is a Projected 3D CRS, specified Vertical CRS will be ignored" );
1191 return false;
1192 }
1193 break;
1194
1204 break;
1205 }
1206
1207 mVerticalCrs = crs;
1208 res = rebuildCrs3D( errorMessage );
1209 mProjectScope.reset();
1210
1211 setDirty( true );
1212 // only emit signal if vertical crs was actually changed, so eg if mCrs is compound
1213 // then we haven't actually changed the vertical crs by this call!
1214 if ( verticalCrs() != oldVerticalCrs )
1215 emit verticalCrsChanged();
1216 if ( mCrs3D != oldCrs3D )
1217 emit crs3DChanged();
1218 }
1219 return res;
1220}
1221
1223{
1224 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
1226
1227 return mTransformContext;
1228}
1229
1231{
1233
1234 if ( context == mTransformContext )
1235 return;
1236
1237 mTransformContext = context;
1238 mProjectScope.reset();
1239
1240 mMainAnnotationLayer->setTransformContext( context );
1241 for ( auto &layer : mLayerStore.get()->mapLayers() )
1242 {
1243 layer->setTransformContext( context );
1244 }
1246}
1247
1249{
1251
1252 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
1253
1254 emit aboutToBeCleared();
1255
1256 if ( !mIsBeingDeleted )
1257 {
1258 // Unregister expression functions stored in the project.
1259 // If we clean on destruction we may end-up with a non-valid
1260 // mPythonUtils, so be safe and only clean when not destroying.
1261 // This should be called before calling mProperties.clearKeys().
1263 }
1264
1265 mProjectScope.reset();
1266 mFile.setFileName( QString() );
1267 mProperties.clearKeys();
1268 mSaveUser.clear();
1269 mSaveUserFull.clear();
1270 mSaveDateTime = QDateTime();
1271 mSaveVersion = QgsProjectVersion();
1272 mHomePath.clear();
1273 mCachedHomePath.clear();
1274 mTransactionMode = Qgis::TransactionMode::Disabled;
1275 mFlags = Qgis::ProjectFlags();
1276 mDirty = false;
1277 mCustomVariables.clear();
1279 mVerticalCrs = QgsCoordinateReferenceSystem();
1281 mMetadata = QgsProjectMetadata();
1282 mElevationShadingRenderer = QgsElevationShadingRenderer();
1283 if ( !settingsAnonymizeNewProjects->value() )
1284 {
1285 mMetadata.setCreationDateTime( QDateTime::currentDateTime() );
1286 mMetadata.setAuthor( QgsApplication::userFullName() );
1287 }
1288 emit metadataChanged();
1289
1291 context.readSettings();
1292 setTransformContext( context );
1293
1294 //fallback to QGIS default measurement unit
1295 bool ok = false;
1297 setDistanceUnits( ok ? distanceUnit : Qgis::DistanceUnit::Meters );
1298 ok = false;
1301
1303
1304 mEmbeddedLayers.clear();
1305 mRelationManager->clear();
1306 mAnnotationManager->clear();
1307 mLayoutManager->clear();
1308 mElevationProfileManager->clear();
1309 mSelectiveMaskingSourceSetManager->clear();
1310 m3DViewsManager->clear();
1311 mBookmarkManager->clear();
1312 mSensorManager->clear();
1313 mViewSettings->reset();
1314 mTimeSettings->reset();
1315 mElevationProperties->reset();
1316 mDisplaySettings->reset();
1317 mGpsSettings->reset();
1318 mSnappingConfig.reset();
1319 mAvoidIntersectionsMode = Qgis::AvoidIntersectionsMode::AllowIntersections;
1322
1323 mMapThemeCollection = std::make_unique< QgsMapThemeCollection >( this );
1325
1326 mLabelingEngineSettings->clear();
1327
1328 // must happen BEFORE archive reset, because we need to release the hold on any files which
1329 // exists within the archive. Otherwise the archive can't be removed.
1330 releaseHandlesToProjectArchive();
1331
1332 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >();
1333 mArchive = std::make_unique< QgsArchive >();
1334
1335 // must happen AFTER archive reset, as it will populate a new style database within the new archive
1336 mStyleSettings->reset();
1337
1339
1340 if ( !mIsBeingDeleted )
1341 {
1342 // possibly other signals should also not be thrown on destruction -- e.g. labelEngineSettingsChanged, etc.
1343 emit projectColorsChanged();
1344 }
1345
1346 // reset some default project properties
1347 // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
1348 writeEntry( u"PositionPrecision"_s, u"/Automatic"_s, true );
1349 writeEntry( u"PositionPrecision"_s, u"/DecimalPlaces"_s, 2 );
1350
1351 const bool defaultRelativePaths = settingsDefaultProjectPathsRelative->value();
1353
1355
1357
1358 mSnappingConfig.clearIndividualLayerSettings();
1359
1361 mRootGroup->clear();
1362 if ( mMainAnnotationLayer )
1363 mMainAnnotationLayer->reset();
1364
1365 snapSingleBlocker.release();
1366
1367 if ( !mBlockSnappingUpdates )
1368 emit snappingConfigChanged( mSnappingConfig );
1369
1370 setDirty( false );
1371 emit homePathChanged();
1372 emit fileNameChanged();
1373 if ( !mBlockChangeSignalsDuringClear )
1374 {
1375 emit verticalCrsChanged();
1376 emit crs3DChanged();
1377 }
1378 emit cleared();
1379}
1380
1381// basically a debugging tool to dump property list values
1382void dump_( const QgsProjectPropertyKey &topQgsPropertyKey )
1383{
1384 QgsDebugMsgLevel( u"current properties:"_s, 3 );
1385 topQgsPropertyKey.dump();
1386}
1387
1416void _getProperties( const QDomDocument &doc, QgsProjectPropertyKey &project_properties )
1417{
1418 const QDomElement propertiesElem = doc.documentElement().firstChildElement( u"properties"_s );
1419
1420 if ( propertiesElem.isNull() ) // no properties found, so we're done
1421 {
1422 return;
1423 }
1424
1425 const QDomNodeList scopes = propertiesElem.childNodes();
1426
1427 if ( propertiesElem.firstChild().isNull() )
1428 {
1429 QgsDebugError( u"empty ``properties'' XML tag ... bailing"_s );
1430 return;
1431 }
1432
1433 if ( !project_properties.readXml( propertiesElem ) )
1434 {
1435 QgsDebugError( u"Project_properties.readXml() failed"_s );
1436 }
1437}
1438
1445QgsPropertyCollection getDataDefinedServerProperties( const QDomDocument &doc, const QgsPropertiesDefinition &dataDefinedServerPropertyDefinitions )
1446{
1447 QgsPropertyCollection ddServerProperties;
1448 // Read data defined server properties
1449 const QDomElement ddElem = doc.documentElement().firstChildElement( u"dataDefinedServerProperties"_s );
1450 if ( !ddElem.isNull() )
1451 {
1452 if ( !ddServerProperties.readXml( ddElem, dataDefinedServerPropertyDefinitions ) )
1453 {
1454 QgsDebugError( u"dataDefinedServerProperties.readXml() failed"_s );
1455 }
1456 }
1457 return ddServerProperties;
1458}
1459
1464static void _getTitle( const QDomDocument &doc, QString &title )
1465{
1466 const QDomElement titleNode = doc.documentElement().firstChildElement( u"title"_s );
1467
1468 title.clear(); // by default the title will be empty
1469
1470 if ( titleNode.isNull() )
1471 {
1472 QgsDebugMsgLevel( u"unable to find title element"_s, 2 );
1473 return;
1474 }
1475
1476 if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text
1477 {
1478 QgsDebugMsgLevel( u"unable to find title element"_s, 2 );
1479 return;
1480 }
1481
1482 const QDomNode titleTextNode = titleNode.firstChild(); // should only have one child
1483
1484 if ( !titleTextNode.isText() )
1485 {
1486 QgsDebugMsgLevel( u"unable to find title element"_s, 2 );
1487 return;
1488 }
1489
1490 const QDomText titleText = titleTextNode.toText();
1491
1492 title = titleText.data();
1493}
1494
1495static void readProjectFileMetadata( const QDomDocument &doc, QString &lastUser, QString &lastUserFull, QDateTime &lastSaveDateTime )
1496{
1497 const QDomNodeList nl = doc.elementsByTagName( u"qgis"_s );
1498
1499 if ( !nl.count() )
1500 {
1501 QgsDebugError( u"unable to find qgis element"_s );
1502 return;
1503 }
1504
1505 const QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1506
1507 const QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1508 lastUser = qgisElement.attribute( u"saveUser"_s, QString() );
1509 lastUserFull = qgisElement.attribute( u"saveUserFull"_s, QString() );
1510 lastSaveDateTime = QDateTime::fromString( qgisElement.attribute( u"saveDateTime"_s, QString() ), Qt::ISODate );
1511}
1512
1513QgsProjectVersion getVersion( const QDomDocument &doc )
1514{
1515 const QDomNodeList nl = doc.elementsByTagName( u"qgis"_s );
1516
1517 if ( !nl.count() )
1518 {
1519 QgsDebugError( u" unable to find qgis element in project file"_s );
1520 return QgsProjectVersion( 0, 0, 0, QString() );
1521 }
1522
1523 const QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1524
1525 const QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1526 QgsProjectVersion projectVersion( qgisElement.attribute( u"version"_s ) );
1527 return projectVersion;
1528}
1529
1531{
1533
1534 return mSnappingConfig;
1535}
1536
1538{
1540
1541 if ( mSnappingConfig == snappingConfig )
1542 return;
1543
1544 mSnappingConfig = snappingConfig;
1545 setDirty( true );
1546 emit snappingConfigChanged( mSnappingConfig );
1547}
1548
1550{
1552
1553 if ( mAvoidIntersectionsMode == mode )
1554 return;
1555
1556 mAvoidIntersectionsMode = mode;
1558}
1559
1560static QgsMapLayer::ReadFlags projectFlagsToLayerReadFlags( Qgis::ProjectReadFlags projectReadFlags, Qgis::ProjectFlags projectFlags )
1561{
1563 // Propagate don't resolve layers
1564 if ( projectReadFlags & Qgis::ProjectReadFlag::DontResolveLayers )
1566 // Propagate trust layer metadata flag
1567 // Propagate read extent from XML based trust layer metadata flag
1568 if ( ( projectFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics ) || ( projectReadFlags & Qgis::ProjectReadFlag::TrustLayerMetadata ) )
1569 {
1572 }
1573 // Propagate open layers in read-only mode
1574 if ( ( projectReadFlags & Qgis::ProjectReadFlag::ForceReadOnlyLayers ) )
1575 layerFlags |= QgsMapLayer::FlagForceReadOnly;
1576
1577 return layerFlags;
1578}
1579
1589
1590void QgsProject::preloadProviders(
1591 const QVector<QDomNode> &parallelLayerNodes, const QgsReadWriteContext &context, QMap<QString, QgsDataProvider *> &loadedProviders, QgsMapLayer::ReadFlags layerReadFlags, int totalProviderCount
1592)
1593{
1594 int i = 0;
1595 QEventLoop loop;
1596
1597 QMap<QString, LayerToLoad> layersToLoad;
1598
1599 for ( const QDomNode &node : parallelLayerNodes )
1600 {
1601 LayerToLoad layerToLoad;
1602
1603 const QDomElement layerElement = node.toElement();
1604 layerToLoad.layerElement = layerElement;
1605 layerToLoad.layerId = layerElement.namedItem( u"id"_s ).toElement().text();
1606 layerToLoad.provider = layerElement.namedItem( u"provider"_s ).toElement().text();
1607 layerToLoad.dataSource = layerElement.namedItem( u"datasource"_s ).toElement().text();
1608
1609 layerToLoad.dataSource = QgsProviderRegistry::instance()->relativeToAbsoluteUri( layerToLoad.provider, layerToLoad.dataSource, context );
1610
1611 layerToLoad.options = QgsDataProvider::ProviderOptions( { context.transformContext() } );
1612 layerToLoad.flags = QgsMapLayer::providerReadFlags( node, layerReadFlags );
1613
1614 // Requesting credential from worker thread could lead to deadlocks because the main thread is waiting for worker thread to fininsh
1615 layerToLoad.flags.setFlag( Qgis::DataProviderReadFlag::SkipCredentialsRequest, true );
1616 layerToLoad.flags.setFlag( Qgis::DataProviderReadFlag::ParallelThreadLoading, true );
1617
1618 layersToLoad.insert( layerToLoad.layerId, layerToLoad );
1619 }
1620
1621 while ( !layersToLoad.isEmpty() )
1622 {
1623 const QList<LayerToLoad> layersToAttemptInParallel = layersToLoad.values();
1624 QString layerToAttemptInMainThread;
1625
1626 QHash<QString, QgsRunnableProviderCreator *> runnables;
1627 QThreadPool threadPool;
1628 threadPool.setMaxThreadCount( QgsSettingsRegistryCore::settingsLayerParallelLoadingMaxCount->value() );
1629
1630 for ( const LayerToLoad &lay : layersToAttemptInParallel )
1631 {
1632 QgsRunnableProviderCreator *run = new QgsRunnableProviderCreator( lay.layerId, lay.provider, lay.dataSource, lay.options, lay.flags );
1633 runnables.insert( lay.layerId, run );
1634
1635 QObject::connect( run, &QgsRunnableProviderCreator::providerCreated, run, [&]( bool isValid, const QString &layId ) {
1636 if ( isValid )
1637 {
1638 layersToLoad.remove( layId );
1639 i++;
1640 QgsRunnableProviderCreator *finishedRun = runnables.value( layId, nullptr );
1641 Q_ASSERT( finishedRun );
1642
1643 std::unique_ptr<QgsDataProvider> provider( finishedRun->dataProvider() );
1644 Q_ASSERT( provider && provider->isValid() );
1645
1646 provider->moveToThread( QThread::currentThread() );
1647 QgsDebugMsgLevel( u"Retrieved created provider for %1 (belongs to thread %2)"_s.arg( layId, QgsThreadingUtils::threadDescription( provider->thread() ) ), 2 );
1648
1649 loadedProviders.insert( layId, provider.release() );
1650 emit layerLoaded( i, totalProviderCount );
1651 }
1652 else
1653 {
1654 if ( layerToAttemptInMainThread.isEmpty() )
1655 layerToAttemptInMainThread = layId;
1656 threadPool.clear(); //we have to stop all loading provider to try this layer in main thread and maybe have credentials
1657 }
1658
1659 if ( i == parallelLayerNodes.count() || !isValid )
1660 loop.quit();
1661 } );
1662 threadPool.start( run );
1663 }
1664 loop.exec();
1665
1666 threadPool.waitForDone(); // to be sure all threads are finished
1667
1668 qDeleteAll( runnables );
1669
1670 // 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
1671 auto it = layersToLoad.find( layerToAttemptInMainThread );
1672 if ( it != layersToLoad.end() )
1673 {
1674 std::unique_ptr<QgsDataProvider> provider;
1675 QString layerId;
1676 {
1677 const LayerToLoad &lay = it.value();
1678 Qgis::DataProviderReadFlags providerFlags = lay.flags;
1679 providerFlags.setFlag( Qgis::DataProviderReadFlag::SkipCredentialsRequest, false );
1680 providerFlags.setFlag( Qgis::DataProviderReadFlag::ParallelThreadLoading, false );
1681 QgsScopedRuntimeProfile profile( "Create data providers/" + lay.layerId, u"projectload"_s );
1682 provider.reset( QgsProviderRegistry::instance()->createProvider( lay.provider, lay.dataSource, lay.options, providerFlags ) );
1683 i++;
1684 if ( provider && provider->isValid() )
1685 {
1686 emit layerLoaded( i, totalProviderCount );
1687 }
1688 layerId = lay.layerId;
1689 layersToLoad.erase( it );
1690 // can't access "lay" anymore -- it's now been freed
1691 }
1692 loadedProviders.insert( layerId, provider.release() );
1693 }
1694
1695 // if there still are some not loaded providers or some invalid in parallel thread we start again
1696 }
1697}
1698
1699void QgsProject::releaseHandlesToProjectArchive()
1700{
1701 mStyleSettings->removeProjectStyle();
1702}
1703
1704bool QgsProject::rebuildCrs3D( QString *error )
1705{
1706 bool res = true;
1707 if ( !mCrs.isValid() )
1708 {
1709 mCrs3D = QgsCoordinateReferenceSystem();
1710 }
1711 else if ( !mVerticalCrs.isValid() )
1712 {
1713 mCrs3D = mCrs;
1714 }
1715 else
1716 {
1717 switch ( mCrs.type() )
1718 {
1722 mCrs3D = mCrs;
1723 break;
1724
1726 {
1727 QString tempError;
1728 mCrs3D = mCrs.hasVerticalAxis() ? mCrs : QgsCoordinateReferenceSystem::createCompoundCrs( mCrs, mVerticalCrs, error ? *error : tempError );
1729 res = mCrs3D.isValid();
1730 break;
1731 }
1732
1734 // nonsense situation
1735 mCrs3D = QgsCoordinateReferenceSystem();
1736 res = false;
1737 break;
1738
1747 {
1748 QString tempError;
1749 mCrs3D = QgsCoordinateReferenceSystem::createCompoundCrs( mCrs, mVerticalCrs, error ? *error : tempError );
1750 res = mCrs3D.isValid();
1751 break;
1752 }
1753 }
1754 }
1755 return res;
1756}
1757
1758bool QgsProject::_getMapLayers( const QDomDocument &doc, QList<QDomNode> &brokenNodes, Qgis::ProjectReadFlags flags )
1759{
1761
1762 // Layer order is set by the restoring the legend settings from project file.
1763 // This is done on the 'readProject( ... )' signal
1764
1765 QDomElement layerElement = doc.documentElement().firstChildElement( u"projectlayers"_s ).firstChildElement( u"maplayer"_s );
1766
1767 // process the map layer nodes
1768
1769 if ( layerElement.isNull() ) // if we have no layers to process, bail
1770 {
1771 return true; // Decided to return "true" since it's
1772 // possible for there to be a project with no
1773 // layers; but also, more imporantly, this
1774 // would cause the tests/qgsproject to fail
1775 // since the test suite doesn't currently
1776 // support test layers
1777 }
1778
1779 bool returnStatus = true;
1780 int numLayers = 0;
1781
1782 while ( !layerElement.isNull() )
1783 {
1784 numLayers++;
1785 layerElement = layerElement.nextSiblingElement( u"maplayer"_s );
1786 }
1787
1788 // order layers based on their dependencies
1789 QgsScopedRuntimeProfile profile( tr( "Sorting layers" ), u"projectload"_s );
1790 const QgsLayerDefinition::DependencySorter depSorter( doc );
1791 if ( depSorter.hasCycle() )
1792 return false;
1793
1794 // Missing a dependency? We still load all the layers, otherwise the project is completely broken!
1795 if ( depSorter.hasMissingDependency() )
1796 returnStatus = false;
1797
1798 emit layerLoaded( 0, numLayers );
1799
1800 const QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
1801 const int totalLayerCount = sortedLayerNodes.count();
1802
1803 QVector<QDomNode> parallelLoading;
1804 QMap<QString, QgsDataProvider *> loadedProviders;
1805
1807 {
1808 profile.switchTask( tr( "Load providers in parallel" ) );
1809 for ( const QDomNode &node : sortedLayerNodes )
1810 {
1811 const QDomElement element = node.toElement();
1812 if ( element.attribute( u"embedded"_s ) != "1"_L1 )
1813 {
1814 const QString layerId = node.namedItem( u"id"_s ).toElement().text();
1815 if ( !depSorter.isLayerDependent( layerId ) )
1816 {
1817 const QDomNode mnl = element.namedItem( u"provider"_s );
1818 const QDomElement mne = mnl.toElement();
1819 const QString provider = mne.text();
1820 QgsProviderMetadata *meta = QgsProviderRegistry::instance()->providerMetadata( provider );
1821 if ( meta && meta->providerCapabilities().testFlag( QgsProviderMetadata::ParallelCreateProvider ) )
1822 {
1823 parallelLoading.append( node );
1824 continue;
1825 }
1826 }
1827 }
1828 }
1829
1830 QgsReadWriteContext context;
1831 context.setPathResolver( pathResolver() );
1832 if ( !parallelLoading.isEmpty() )
1833 preloadProviders( parallelLoading, context, loadedProviders, projectFlagsToLayerReadFlags( flags, mFlags ), sortedLayerNodes.count() );
1834 }
1835
1836 int i = loadedProviders.count();
1837 for ( const QDomNode &node : std::as_const( sortedLayerNodes ) )
1838 {
1839 const QDomElement element = node.toElement();
1840 const QString name = translate( u"project:layers:%1"_s.arg( node.namedItem( u"id"_s ).toElement().text() ), node.namedItem( u"layername"_s ).toElement().text() );
1841 if ( !name.isNull() )
1842 emit loadingLayer( tr( "Loading layer %1" ).arg( name ) );
1843
1844 profile.switchTask( name );
1845 if ( element.attribute( u"embedded"_s ) == "1"_L1 )
1846 {
1847 createEmbeddedLayer( element.attribute( u"id"_s ), readPath( element.attribute( u"project"_s ) ), brokenNodes, true, flags );
1848 }
1849 else
1850 {
1851 QgsReadWriteContext context;
1852 context.setPathResolver( pathResolver() );
1853 context.setProjectTranslator( this );
1855 QString layerId = element.namedItem( u"id"_s ).toElement().text();
1856 context.setCurrentLayerId( layerId );
1857 if ( !addLayer( element, brokenNodes, context, flags, loadedProviders.take( layerId ) ) )
1858 {
1859 returnStatus = false;
1860 }
1861 const auto messages = context.takeMessages();
1862 if ( !messages.isEmpty() )
1863 {
1864 emit loadingLayerMessageReceived( tr( "Loading layer %1" ).arg( name ), messages );
1865 }
1866 }
1867 emit layerLoaded( i + 1, totalLayerCount );
1868 i++;
1869 }
1870
1871 return returnStatus;
1872}
1873
1874bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &brokenNodes, QgsReadWriteContext &context, Qgis::ProjectReadFlags flags, QgsDataProvider *provider )
1875{
1877
1878 const QString type = layerElem.attribute( u"type"_s );
1879 QgsDebugMsgLevel( "Layer type is " + type, 4 );
1880 std::unique_ptr<QgsMapLayer> mapLayer;
1881
1882 QgsScopedRuntimeProfile profile( tr( "Create layer" ), u"projectload"_s );
1883
1884 bool ok = false;
1885 const Qgis::LayerType layerType( QgsMapLayerFactory::typeFromString( type, ok ) );
1886 if ( !ok )
1887 {
1888 QgsDebugError( u"Unknown layer type \"%1\""_s.arg( type ) );
1889 return false;
1890 }
1891
1892 switch ( layerType )
1893 {
1895 mapLayer = std::make_unique<QgsVectorLayer>();
1896 break;
1897
1899 mapLayer = std::make_unique<QgsRasterLayer>();
1900 break;
1901
1903 mapLayer = std::make_unique<QgsMeshLayer>();
1904 break;
1905
1907 mapLayer = std::make_unique<QgsVectorTileLayer>();
1908 break;
1909
1911 mapLayer = std::make_unique<QgsPointCloudLayer>();
1912 break;
1913
1915 mapLayer = std::make_unique<QgsTiledSceneLayer>();
1916 break;
1917
1919 {
1920 const QString typeName = layerElem.attribute( u"name"_s );
1921 mapLayer.reset( QgsApplication::pluginLayerRegistry()->createLayer( typeName ) );
1922 break;
1923 }
1924
1926 {
1927 const QgsAnnotationLayer::LayerOptions options( mTransformContext );
1928 mapLayer = std::make_unique<QgsAnnotationLayer>( QString(), options );
1929 break;
1930 }
1931
1933 {
1934 const QgsGroupLayer::LayerOptions options( mTransformContext );
1935 mapLayer = std::make_unique<QgsGroupLayer>( QString(), options );
1936 break;
1937 }
1938 }
1939
1940 if ( !mapLayer )
1941 {
1942 QgsDebugError( u"Unable to create layer"_s );
1943 return false;
1944 }
1945
1946 Q_CHECK_PTR( mapLayer ); // NOLINT
1947
1948 // This is tricky: to avoid a leak we need to check if the layer was already in the store
1949 // because if it was, the newly created layer will not be added to the store and it would leak.
1950 const QString layerId { layerElem.namedItem( u"id"_s ).toElement().text() };
1951 Q_ASSERT( !layerId.isEmpty() );
1952 const bool layerWasStored = layerStore()->mapLayer( layerId );
1953
1954 // have the layer restore state that is stored in Dom node
1955 QgsMapLayer::ReadFlags layerFlags = projectFlagsToLayerReadFlags( flags, mFlags );
1956
1957 profile.switchTask( tr( "Load layer source" ) );
1958 const bool layerIsValid = mapLayer->readLayerXml( layerElem, context, layerFlags, provider ) && mapLayer->isValid();
1959
1960 // apply specific settings to vector layer
1961 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1962 {
1963 vl->setReadExtentFromXml( layerFlags & QgsMapLayer::FlagReadExtentFromXml );
1964 if ( vl->dataProvider() )
1965 {
1967 vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, evaluateDefaultValues );
1968 }
1969 }
1970
1971 profile.switchTask( tr( "Add layer to project" ) );
1972 QList<QgsMapLayer *> newLayers;
1973 newLayers << mapLayer.get();
1974 if ( layerIsValid || flags & Qgis::ProjectReadFlag::DontResolveLayers )
1975 {
1976 emit readMapLayer( mapLayer.get(), layerElem );
1977 addMapLayers( newLayers );
1978 // Try to resolve references here (this is necessary to set up joined fields that will be possibly used by
1979 // virtual layers that point to this layer's joined field in their query otherwise they won't be valid ),
1980 // a second attempt to resolve references will be done after all layers are loaded
1981 // see https://github.com/qgis/QGIS/issues/46834
1982 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1983 {
1984 vLayer->joinBuffer()->resolveReferences( this );
1985 }
1986 }
1987 else
1988 {
1989 // It's a bad layer: do not add to legend (the user will decide if she wants to do so)
1990 addMapLayers( newLayers, false );
1991 newLayers.first();
1992 QgsDebugError( "Unable to load " + type + " layer" );
1993 brokenNodes.push_back( layerElem );
1994 }
1995
1996 const bool wasEditable = layerElem.attribute( u"editable"_s, u"0"_s ).toInt();
1997 if ( wasEditable )
1998 {
1999 mapLayer->setCustomProperty( u"_layer_was_editable"_s, true );
2000 }
2001 else
2002 {
2003 mapLayer->removeCustomProperty( u"_layer_was_editable"_s );
2004 }
2005
2006 // It should be safe to delete the layer now if layer was stored, because all the store
2007 // had to to was to reset the data source in case the validity changed.
2008 if ( !layerWasStored )
2009 {
2010 mapLayer.release();
2011 }
2012
2013 return layerIsValid;
2014}
2015
2016bool QgsProject::read( const QString &filename, Qgis::ProjectReadFlags flags )
2017{
2019
2020 mFile.setFileName( filename );
2021 mCachedHomePath.clear();
2022 mProjectScope.reset();
2023
2024 return read( flags );
2025}
2026
2028{
2030
2031 const QString filename = mFile.fileName();
2032 bool returnValue;
2033
2034 if ( QgsProjectStorage *storage = projectStorage() )
2035 {
2036 QTemporaryFile inDevice;
2037 if ( !inDevice.open() )
2038 {
2039 setError( tr( "Unable to open %1" ).arg( inDevice.fileName() ) );
2040 return false;
2041 }
2042
2043 QgsReadWriteContext context;
2044 context.setProjectTranslator( this );
2045 if ( !storage->readProject( filename, &inDevice, context ) )
2046 {
2047 QString err = tr( "Unable to open %1" ).arg( filename );
2048 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
2049 if ( !messages.isEmpty() )
2050 err += u"\n\n"_s + messages.last().message();
2051 setError( err );
2052 return false;
2053 }
2054 returnValue = unzip( inDevice.fileName(), flags ); // calls setError() if returning false
2055 }
2056 else
2057 {
2058 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
2059 {
2060 returnValue = unzip( mFile.fileName(), flags );
2061 }
2062 else
2063 {
2064 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( *this );
2065 const QFileInfo finfo( mFile.fileName() );
2066 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( u"%1_attachments.zip"_s.arg( finfo.completeBaseName() ) );
2067 if ( QFile( attachmentsZip ).exists() )
2068 {
2069 auto archive = std::make_unique<QgsArchive>();
2070 if ( archive->unzip( attachmentsZip ) )
2071 {
2072 releaseHandlesToProjectArchive();
2073 mArchive = std::move( archive );
2074 }
2075 }
2076 returnValue = readProjectFile( mFile.fileName(), flags );
2077 }
2078
2079 //on translation we should not change the filename back
2080 if ( !mTranslator )
2081 {
2082 mFile.setFileName( filename );
2083 mCachedHomePath.clear();
2084 mProjectScope.reset();
2085 }
2086 else
2087 {
2088 //but delete the translator
2089 mTranslator.reset( nullptr );
2090 }
2091 }
2092 emit fileNameChanged();
2093 emit homePathChanged();
2094 return returnValue;
2095}
2096
2097bool QgsProject::readProjectFile( const QString &filename, Qgis::ProjectReadFlags flags )
2098{
2100
2101 // avoid multiple emission of snapping updated signals
2102 ScopedIntIncrementor snapSignalBlock( &mBlockSnappingUpdates );
2103
2104 QFile projectFile( filename );
2105 clearError();
2106
2107 QgsApplication::profiler()->clear( u"projectload"_s );
2108 QgsScopedRuntimeProfile profile( tr( "Setting up translations" ), u"projectload"_s );
2109
2110 const QString locale = QgsApplication::settingsLocaleUserLocale->value();
2111 const QString projectBaseName = QFileInfo( mFile ).baseName();
2112 const QString projectDir = QFileInfo( mFile ).absolutePath();
2113 QString localeFileName = u"%1_%2"_s.arg( projectBaseName, locale );
2114
2115 if ( !QFile( u"%1/%2.qm"_s.arg( projectDir, localeFileName ) ).exists() && locale.contains( '_' ) )
2116 {
2117 // Fallback: try language-only locale (e.g., "fr" from "fr_CH")
2118 localeFileName = u"%1_%2"_s.arg( projectBaseName, locale.left( locale.indexOf( '_' ) ) );
2119 }
2120
2121 if ( QFile( u"%1/%2.qm"_s.arg( projectDir, localeFileName ) ).exists() )
2122 {
2123 mTranslator = std::make_unique< QTranslator >();
2124 ( void ) mTranslator->load( localeFileName, projectDir );
2125 }
2126
2127 profile.switchTask( tr( "Reading project file" ) );
2128 auto doc = std::make_unique<QDomDocument>( u"qgis"_s );
2129
2130 if ( !projectFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
2131 {
2132 projectFile.close();
2133
2134 setError( tr( "Unable to open %1" ).arg( projectFile.fileName() ) );
2135
2136 return false;
2137 }
2138
2139 QTextStream textStream( &projectFile );
2140 QString projectString = textStream.readAll();
2141 projectFile.close();
2142
2143 for ( int i = 0; i < 32; i++ )
2144 {
2145 if ( i == 9 || i == 10 || i == 13 )
2146 {
2147 continue;
2148 }
2149 projectString.replace( QChar( i ), u"%1%2%1"_s.arg( FONTMARKER_CHR_FIX, QString::number( i ) ) );
2150 }
2151
2152 // location of problem associated with errorMsg
2153 int line, column;
2154 QString errorMsg;
2155 if ( !doc->setContent( projectString, &errorMsg, &line, &column ) )
2156 {
2157 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 );
2158 QgsDebugError( errorString );
2159 setError( errorString );
2160
2161 return false;
2162 }
2163
2164 projectFile.close();
2165
2166 QgsDebugMsgLevel( "Opened document " + projectFile.fileName(), 2 );
2167
2168 // get project version string, if any
2169 const QgsProjectVersion fileVersion = getVersion( *doc );
2170 const QgsProjectVersion thisVersion( Qgis::version() );
2171
2172 profile.switchTask( tr( "Updating project file" ) );
2173 if ( thisVersion > fileVersion )
2174 {
2175 const bool isOlderMajorVersion = fileVersion.majorVersion() < thisVersion.majorVersion();
2176
2177 if ( isOlderMajorVersion )
2178 {
2180 "Loading a file that was saved with an older "
2181 "version of qgis (saved in "
2182 + fileVersion.text()
2183 + ", loaded in "
2184 + Qgis::version()
2185 + "). Problems may occur."
2186 );
2187 }
2188
2189 QgsProjectFileTransform projectFile( *doc, fileVersion );
2190
2191 // Shows a warning when an old project file is read.
2193 emit oldProjectVersionWarning( fileVersion.text() );
2195 emit readVersionMismatchOccurred( fileVersion.text() );
2196
2197 projectFile.updateRevision( thisVersion );
2198 }
2199 else if ( fileVersion > thisVersion )
2200 {
2202 "Loading a file that was saved with a newer "
2203 "version of qgis (saved in "
2204 + fileVersion.text()
2205 + ", loaded in "
2206 + Qgis::version()
2207 + "). Problems may occur."
2208 );
2209
2210 emit readVersionMismatchOccurred( fileVersion.text() );
2211 }
2212
2213 // start new project, just keep the file name and auxiliary storage
2214 profile.switchTask( tr( "Creating auxiliary storage" ) );
2215 const QString fileName = mFile.fileName();
2216
2217 const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
2218 const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
2219
2220 // NOTE [ND] -- I suspect this is wrong, as the archive may contain any number of non-auxiliary
2221 // storage related files from the previously loaded project.
2222 std::unique_ptr<QgsAuxiliaryStorage> aStorage = std::move( mAuxiliaryStorage );
2223 std::unique_ptr<QgsArchive> archive = std::move( mArchive );
2224
2225 // don't emit xxxChanged signals during the clear() call, as we'll be emitting
2226 // them again after reading the properties from the project file
2227 mBlockChangeSignalsDuringClear = true;
2228 clear();
2229 mBlockChangeSignalsDuringClear = false;
2230
2231 // this is ugly, but clear() will have created a new archive and started populating it. We
2232 // need to release handles to this archive now as the subsequent call to move will need
2233 // to delete it, and requires free access to do so.
2234 releaseHandlesToProjectArchive();
2235
2236 mAuxiliaryStorage = std::move( aStorage );
2237 mArchive = std::move( archive );
2238
2239 mFile.setFileName( fileName );
2240 mCachedHomePath.clear();
2241 mProjectScope.reset();
2242 mSaveVersion = fileVersion;
2243
2244 // now get any properties
2245 profile.switchTask( tr( "Reading properties" ) );
2246 _getProperties( *doc, mProperties );
2247
2248 // now get the data defined server properties
2249 mDataDefinedServerProperties = getDataDefinedServerProperties( *doc, dataDefinedServerPropertyDefinitions() );
2250
2251 QgsDebugMsgLevel( QString::number( mProperties.count() ) + " properties read", 2 );
2252
2253#if 0
2254 dump_( mProperties );
2255#endif
2256
2257 // get older style project title
2258 QString oldTitle;
2259 _getTitle( *doc, oldTitle );
2260
2261 readProjectFileMetadata( *doc, mSaveUser, mSaveUserFull, mSaveDateTime );
2262
2263 const QDomNodeList homePathNl = doc->elementsByTagName( u"homePath"_s );
2264 if ( homePathNl.count() > 0 )
2265 {
2266 const QDomElement homePathElement = homePathNl.at( 0 ).toElement();
2267 const QString homePath = homePathElement.attribute( u"path"_s );
2268 if ( !homePath.isEmpty() )
2270 }
2271 else
2272 {
2273 emit homePathChanged();
2274 }
2275
2276 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 ) );
2278 const QColor
2279 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 ) );
2281
2282
2283 const QString distanceUnitString = readEntry( u"Measurement"_s, u"/DistanceUnits"_s, QString() );
2284 if ( !distanceUnitString.isEmpty() )
2285 setDistanceUnits( QgsUnitTypes::decodeDistanceUnit( distanceUnitString ) );
2286
2287 const QString areaUnitString = readEntry( u"Measurement"_s, u"/AreaUnits"_s, QString() );
2288 if ( !areaUnitString.isEmpty() )
2289 setAreaUnits( QgsUnitTypes::decodeAreaUnit( areaUnitString ) );
2290
2291 setScaleMethod( qgsEnumKeyToValue( readEntry( u"Measurement"_s, u"/ScaleMethod"_s, QString() ), Qgis::ScaleCalculationMethod::HorizontalMiddle ) );
2292
2293 QgsReadWriteContext context;
2294 context.setPathResolver( pathResolver() );
2295 context.setProjectTranslator( this );
2296
2297 //crs
2298 QgsCoordinateReferenceSystem projectCrs;
2299 if ( readNumEntry( u"SpatialRefSys"_s, u"/ProjectionsEnabled"_s, 0 ) )
2300 {
2301 // first preference - dedicated projectCrs node
2302 const QDomNode srsNode = doc->documentElement().namedItem( u"projectCrs"_s );
2303 if ( !srsNode.isNull() )
2304 {
2305 projectCrs.readXml( srsNode );
2306 }
2307
2308 if ( !projectCrs.isValid() )
2309 {
2310 const QString projCrsString = readEntry( u"SpatialRefSys"_s, u"/ProjectCRSProj4String"_s );
2311 const long currentCRS = readNumEntry( u"SpatialRefSys"_s, u"/ProjectCRSID"_s, -1 );
2312 const QString authid = readEntry( u"SpatialRefSys"_s, u"/ProjectCrs"_s );
2313
2314 // authid should be prioritized over all
2315 const bool isUserAuthId = authid.startsWith( "USER:"_L1, Qt::CaseInsensitive );
2316 if ( !authid.isEmpty() && !isUserAuthId )
2317 projectCrs = QgsCoordinateReferenceSystem( authid );
2318
2319 // try the CRS
2320 if ( !projectCrs.isValid() && currentCRS >= 0 )
2321 {
2322 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2323 }
2324
2325 // if that didn't produce a match, try the proj.4 string
2326 if ( !projCrsString.isEmpty() && ( authid.isEmpty() || isUserAuthId ) && ( !projectCrs.isValid() || projectCrs.toProj() != projCrsString ) )
2327 {
2328 projectCrs = QgsCoordinateReferenceSystem::fromProj( projCrsString );
2329 }
2330
2331 // last just take the given id
2332 if ( !projectCrs.isValid() )
2333 {
2334 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2335 }
2336 }
2337 }
2338 mCrs = projectCrs;
2339
2340 //vertical CRS
2341 {
2342 QgsCoordinateReferenceSystem verticalCrs;
2343 const QDomNode verticalCrsNode = doc->documentElement().namedItem( u"verticalCrs"_s );
2344 if ( !verticalCrsNode.isNull() )
2345 {
2346 verticalCrs.readXml( verticalCrsNode );
2347 }
2348 mVerticalCrs = verticalCrs;
2349 }
2350 rebuildCrs3D();
2351
2352 QStringList datumErrors;
2353 if ( !mTransformContext.readXml( doc->documentElement(), context, datumErrors ) && !datumErrors.empty() )
2354 {
2355 emit missingDatumTransforms( datumErrors );
2356 }
2358
2359 // map shading
2360 const QDomNode elevationShadingNode = doc->documentElement().namedItem( u"elevation-shading-renderer"_s );
2361 if ( !elevationShadingNode.isNull() )
2362 {
2363 mElevationShadingRenderer.readXml( elevationShadingNode.toElement(), context );
2364 }
2366
2367
2368 //add variables defined in project file - do this early in the reading cycle, as other components
2369 //(e.g. layouts) may depend on these variables
2370 const QStringList variableNames = readListEntry( u"Variables"_s, u"/variableNames"_s );
2371 const QStringList variableValues = readListEntry( u"Variables"_s, u"/variableValues"_s );
2372
2373 mCustomVariables.clear();
2374 if ( variableNames.length() == variableValues.length() )
2375 {
2376 for ( int i = 0; i < variableNames.length(); ++i )
2377 {
2378 mCustomVariables.insert( variableNames.at( i ), variableValues.at( i ) );
2379 }
2380 }
2381 else
2382 {
2383 QgsMessageLog::logMessage( tr( "Project Variables Invalid" ), tr( "The project contains invalid variable settings." ) );
2384 }
2385
2386 // Register expression functions stored in the project.
2387 // They might be using project variables and might be
2388 // in turn being used by other components (e.g., layouts).
2390
2391 QDomElement element = doc->documentElement().firstChildElement( u"projectMetadata"_s );
2392
2393 if ( !element.isNull() )
2394 {
2395 mMetadata.readMetadataXml( element, context );
2396 }
2397 else
2398 {
2399 // older project, no metadata => remove auto generated metadata which is populated on QgsProject::clear()
2400 mMetadata = QgsProjectMetadata();
2401 }
2402 if ( mMetadata.title().isEmpty() && !oldTitle.isEmpty() )
2403 {
2404 // upgrade older title storage to storing within project metadata.
2405 mMetadata.setTitle( oldTitle );
2406 }
2407 emit metadataChanged();
2408 emit titleChanged();
2409
2410 // Transaction mode
2411 element = doc->documentElement().firstChildElement( u"transaction"_s );
2412 if ( !element.isNull() )
2413 {
2414 mTransactionMode = qgsEnumKeyToValue( element.attribute( u"mode"_s ), Qgis::TransactionMode::Disabled );
2415 }
2416 else
2417 {
2418 // maybe older project => try read autotransaction
2419 element = doc->documentElement().firstChildElement( u"autotransaction"_s );
2420 if ( !element.isNull() )
2421 {
2422 mTransactionMode = static_cast<Qgis::TransactionMode>( element.attribute( u"active"_s, u"0"_s ).toInt() );
2423 }
2424 }
2425
2426 // read the layer tree from project file
2427 profile.switchTask( tr( "Loading layer tree" ) );
2428 mRootGroup->setCustomProperty( u"loading"_s, 1 );
2429
2430 QDomElement layerTreeElem = doc->documentElement().firstChildElement( u"layer-tree-group"_s );
2431 if ( !layerTreeElem.isNull() )
2432 {
2433 // Use a temporary tree to read the nodes to prevent signals being delivered to the models
2434 QgsLayerTree tempTree;
2435 tempTree.readChildrenFromXml( layerTreeElem, context );
2436 mRootGroup->insertChildNodes( -1, tempTree.abandonChildren() );
2437 }
2438 else
2439 {
2440 QgsLayerTreeUtils::readOldLegend( mRootGroup.get(), doc->documentElement().firstChildElement( u"legend"_s ) );
2441 }
2442
2443 mLayerTreeRegistryBridge->setEnabled( false );
2444
2445 // get the map layers
2446 profile.switchTask( tr( "Reading map layers" ) );
2447
2448 loadProjectFlags( doc.get() );
2449
2450 QList<QDomNode> brokenNodes;
2451 const bool clean = _getMapLayers( *doc, brokenNodes, flags );
2452
2453 // review the integrity of the retrieved map layers
2454 if ( !clean && !( flags & Qgis::ProjectReadFlag::DontResolveLayers ) )
2455 {
2456 QgsDebugError( u"Unable to get map layers from project file."_s );
2457
2458 if ( !brokenNodes.isEmpty() )
2459 {
2460 QgsDebugError( "there are " + QString::number( brokenNodes.size() ) + " broken layers" );
2461 }
2462
2463 // we let a custom handler decide what to do with missing layers
2464 // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
2465 mBadLayerHandler->handleBadLayers( brokenNodes );
2466 }
2467
2468 mMainAnnotationLayer->readLayerXml( doc->documentElement().firstChildElement( u"main-annotation-layer"_s ), context );
2469 mMainAnnotationLayer->setTransformContext( mTransformContext );
2470
2471 // load embedded groups and layers
2472 profile.switchTask( tr( "Loading embedded layers" ) );
2473 loadEmbeddedNodes( mRootGroup.get(), flags );
2474
2475 // Resolve references to other layers
2476 // Needs to be done here once all dependent layers are loaded
2477 profile.switchTask( tr( "Resolving layer references" ) );
2478 QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
2479 for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
2480 {
2481 it.value()->resolveReferences( this );
2482 }
2483 mMainAnnotationLayer->resolveReferences( this );
2484
2485 mLayerTreeRegistryBridge->setEnabled( true );
2486
2487 // now that layers are loaded, we can resolve layer tree's references to the layers
2488 profile.switchTask( tr( "Resolving references" ) );
2489 mRootGroup->resolveReferences( this );
2490
2491 // we need to migrate old fashion designed QgsSymbolLayerReference to new ones
2492 if ( QgsProjectVersion( 3, 28, 0 ) > mSaveVersion )
2493 {
2497 }
2498
2499 if ( !layerTreeElem.isNull() )
2500 {
2501 mRootGroup->readLayerOrderFromXml( layerTreeElem );
2502 }
2503
2504 // Load pre 3.0 configuration
2505 const QDomElement layerTreeCanvasElem = doc->documentElement().firstChildElement( u"layer-tree-canvas"_s );
2506 if ( !layerTreeCanvasElem.isNull() )
2507 {
2508 mRootGroup->readLayerOrderFromXml( layerTreeCanvasElem );
2509 }
2510
2511 // Convert pre 3.4 to create layers flags
2512 if ( QgsProjectVersion( 3, 4, 0 ) > mSaveVersion )
2513 {
2514 const QStringList requiredLayerIds = readListEntry( u"RequiredLayers"_s, u"Layers"_s );
2515 for ( const QString &layerId : requiredLayerIds )
2516 {
2517 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2518 {
2519 layer->setFlags( layer->flags() & ~QgsMapLayer::Removable );
2520 }
2521 }
2522 const QStringList disabledLayerIds = readListEntry( u"Identify"_s, u"/disabledLayers"_s );
2523 for ( const QString &layerId : disabledLayerIds )
2524 {
2525 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2526 {
2527 layer->setFlags( layer->flags() & ~QgsMapLayer::Identifiable );
2528 }
2529 }
2530 }
2531
2532 // Convert pre 3.26 default styles
2533 if ( QgsProjectVersion( 3, 26, 0 ) > mSaveVersion )
2534 {
2535 // Convert default symbols
2536 QString styleName = readEntry( u"DefaultStyles"_s, u"/Marker"_s );
2537 if ( !styleName.isEmpty() )
2538 {
2539 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2541 }
2542 styleName = readEntry( u"DefaultStyles"_s, u"/Line"_s );
2543 if ( !styleName.isEmpty() )
2544 {
2545 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2547 }
2548 styleName = readEntry( u"DefaultStyles"_s, u"/Fill"_s );
2549 if ( !styleName.isEmpty() )
2550 {
2551 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2553 }
2554 styleName = readEntry( u"DefaultStyles"_s, u"/ColorRamp"_s );
2555 if ( !styleName.isEmpty() )
2556 {
2557 std::unique_ptr<QgsColorRamp> colorRamp( QgsStyle::defaultStyle()->colorRamp( styleName ) );
2558 styleSettings()->setDefaultColorRamp( colorRamp.get() );
2559 }
2560
2561 // Convert randomize default symbol fill color
2562 styleSettings()->setRandomizeDefaultSymbolColor( readBoolEntry( u"DefaultStyles"_s, u"/RandomColors"_s, true ) );
2563
2564 // Convert default symbol opacity
2565 double opacity = 1.0;
2566 bool ok = false;
2567 // upgrade old setting
2568 double alpha = readDoubleEntry( u"DefaultStyles"_s, u"/AlphaInt"_s, 255, &ok );
2569 if ( ok )
2570 opacity = alpha / 255.0;
2571 double newOpacity = readDoubleEntry( u"DefaultStyles"_s, u"/Opacity"_s, 1.0, &ok );
2572 if ( ok )
2573 opacity = newOpacity;
2575
2576 // Cleanup
2577 removeEntry( u"DefaultStyles"_s, u"/Marker"_s );
2578 removeEntry( u"DefaultStyles"_s, u"/Line"_s );
2579 removeEntry( u"DefaultStyles"_s, u"/Fill"_s );
2580 removeEntry( u"DefaultStyles"_s, u"/ColorRamp"_s );
2581 removeEntry( u"DefaultStyles"_s, u"/RandomColors"_s );
2582 removeEntry( u"DefaultStyles"_s, u"/AlphaInt"_s );
2583 removeEntry( u"DefaultStyles"_s, u"/Opacity"_s );
2584 }
2585
2586 // After bad layer handling we might still have invalid layers,
2587 // store them in case the user wanted to handle them later
2588 // or wanted to pass them through when saving
2590 {
2591 profile.switchTask( tr( "Storing original layer properties" ) );
2592 QgsLayerTreeUtils::storeOriginalLayersProperties( mRootGroup.get(), doc.get() );
2593 }
2594
2595 mRootGroup->removeCustomProperty( u"loading"_s );
2596
2597 profile.switchTask( tr( "Loading map themes" ) );
2598 mMapThemeCollection = std::make_unique< QgsMapThemeCollection >( this );
2600 mMapThemeCollection->readXml( *doc );
2601
2602 profile.switchTask( tr( "Loading label settings" ) );
2603 mLabelingEngineSettings->readSettingsFromProject( this );
2604 {
2605 const QDomElement labelEngineSettingsElement = doc->documentElement().firstChildElement( u"labelEngineSettings"_s );
2606 mLabelingEngineSettings->readXml( labelEngineSettingsElement, context );
2607 }
2608 mLabelingEngineSettings->resolveReferences( this );
2609
2611
2612 profile.switchTask( tr( "Loading annotations" ) );
2614 {
2615 mAnnotationManager->readXml( doc->documentElement(), context );
2616 }
2617 else
2618 {
2619 mAnnotationManager->readXmlAndUpgradeToAnnotationLayerItems( doc->documentElement(), context, mMainAnnotationLayer, mTransformContext );
2620 }
2622 {
2623 profile.switchTask( tr( "Loading layouts" ) );
2624 mLayoutManager->readXml( doc->documentElement(), *doc );
2625 }
2626
2627 {
2628 profile.switchTask( tr( "Loading elevation profiles" ) );
2629 mElevationProfileManager->readXml( doc->documentElement(), *doc, context );
2630 mElevationProfileManager->resolveReferences( this );
2631 }
2632
2633 {
2634 profile.switchTask( tr( "Loading selective masking source sets" ) );
2635 mSelectiveMaskingSourceSetManager->readXml( doc->documentElement(), *doc, context );
2636 }
2637
2639 {
2640 profile.switchTask( tr( "Loading 3D Views" ) );
2641 m3DViewsManager->readXml( doc->documentElement(), *doc );
2642 }
2643
2644 profile.switchTask( tr( "Loading bookmarks" ) );
2645 mBookmarkManager->readXml( doc->documentElement(), *doc );
2646
2647 profile.switchTask( tr( "Loading sensors" ) );
2648 mSensorManager->readXml( doc->documentElement(), *doc );
2649
2650 // reassign change dependencies now that all layers are loaded
2651 QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2652 for ( QMap<QString, QgsMapLayer *>::iterator it = existingMaps.begin(); it != existingMaps.end(); ++it )
2653 {
2654 it.value()->setDependencies( it.value()->dependencies() );
2655 }
2656
2657 profile.switchTask( tr( "Loading snapping settings" ) );
2658 mSnappingConfig.readProject( *doc );
2659 mAvoidIntersectionsMode = static_cast<Qgis::AvoidIntersectionsMode>(
2660 readNumEntry( u"Digitizing"_s, u"/AvoidIntersectionsMode"_s, static_cast<int>( Qgis::AvoidIntersectionsMode::AvoidIntersectionsLayers ) )
2661 );
2662
2663 profile.switchTask( tr( "Loading view settings" ) );
2664 // restore older project scales settings
2665 mViewSettings->setUseProjectScales( readBoolEntry( u"Scales"_s, u"/useProjectScales"_s ) );
2666 const QStringList scales = readListEntry( u"Scales"_s, u"/ScalesList"_s );
2667 QVector<double> res;
2668 for ( const QString &scale : scales )
2669 {
2670 const QStringList parts = scale.split( ':' );
2671 if ( parts.size() != 2 )
2672 continue;
2673
2674 bool ok = false;
2675 const double denominator = QLocale().toDouble( parts[1], &ok );
2676 if ( ok )
2677 {
2678 res << denominator;
2679 }
2680 }
2681 mViewSettings->setMapScales( res );
2682 const QDomElement viewSettingsElement = doc->documentElement().firstChildElement( u"ProjectViewSettings"_s );
2683 if ( !viewSettingsElement.isNull() )
2684 mViewSettings->readXml( viewSettingsElement, context );
2685
2686 // restore style settings
2687 profile.switchTask( tr( "Loading style properties" ) );
2688 const QDomElement styleSettingsElement = doc->documentElement().firstChildElement( u"ProjectStyleSettings"_s );
2689 if ( !styleSettingsElement.isNull() )
2690 {
2691 mStyleSettings->removeProjectStyle();
2692 mStyleSettings->readXml( styleSettingsElement, context, flags );
2693 }
2694
2695 // restore time settings
2696 profile.switchTask( tr( "Loading temporal settings" ) );
2697 const QDomElement timeSettingsElement = doc->documentElement().firstChildElement( u"ProjectTimeSettings"_s );
2698 if ( !timeSettingsElement.isNull() )
2699 mTimeSettings->readXml( timeSettingsElement, context );
2700
2701
2702 profile.switchTask( tr( "Loading elevation properties" ) );
2703 const QDomElement elevationPropertiesElement = doc->documentElement().firstChildElement( u"ElevationProperties"_s );
2704 if ( !elevationPropertiesElement.isNull() )
2705 mElevationProperties->readXml( elevationPropertiesElement, context );
2706 mElevationProperties->resolveReferences( this );
2707
2708 profile.switchTask( tr( "Loading display settings" ) );
2709 {
2710 const QDomElement displaySettingsElement = doc->documentElement().firstChildElement( u"ProjectDisplaySettings"_s );
2711 if ( !displaySettingsElement.isNull() )
2712 mDisplaySettings->readXml( displaySettingsElement, context );
2713 }
2714
2715 profile.switchTask( tr( "Loading GPS settings" ) );
2716 {
2717 const QDomElement gpsSettingsElement = doc->documentElement().firstChildElement( u"ProjectGpsSettings"_s );
2718 if ( !gpsSettingsElement.isNull() )
2719 mGpsSettings->readXml( gpsSettingsElement, context );
2720 mGpsSettings->resolveReferences( this );
2721 }
2722
2723 profile.switchTask( tr( "Updating variables" ) );
2725 profile.switchTask( tr( "Updating CRS" ) );
2726 emit crsChanged();
2727 if ( verticalCrs() != oldVerticalCrs )
2728 emit verticalCrsChanged();
2729 if ( mCrs3D != oldCrs3D )
2730 emit crs3DChanged();
2731 emit ellipsoidChanged( ellipsoid() );
2732
2733 // read the project: used by map canvas and legend
2734 profile.switchTask( tr( "Reading external settings" ) );
2735 emit readProject( *doc );
2736 emit readProjectWithContext( *doc, context );
2737
2738 profile.switchTask( tr( "Updating interface" ) );
2739
2740 snapSignalBlock.release();
2741 if ( !mBlockSnappingUpdates )
2742 emit snappingConfigChanged( mSnappingConfig );
2743
2746 emit projectColorsChanged();
2747
2748 // if all went well, we're allegedly in pristine state
2749 if ( clean )
2750 setDirty( false );
2751
2752 QgsDebugMsgLevel( u"Project save user: %1"_s.arg( mSaveUser ), 2 );
2753 QgsDebugMsgLevel( u"Project save user: %1"_s.arg( mSaveUserFull ), 2 );
2754
2758
2759 if ( mTranslator )
2760 {
2761 //project possibly translated -> rename it with locale postfix
2762 const QString newFileName( u"%1/%2.qgs"_s.arg( QFileInfo( mFile ).absolutePath(), localeFileName ) );
2763 setFileName( newFileName );
2764
2765 if ( write() )
2766 {
2767 QgsMessageLog::logMessage( tr( "Translated project saved with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Success );
2768 }
2769 else
2770 {
2771 QgsMessageLog::logMessage( tr( "Error saving translated project with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Critical );
2772 }
2773 }
2774
2775 // lastly, make any previously editable layers editable
2776 const QMap<QString, QgsMapLayer *> loadedLayers = mapLayers();
2777 for ( auto it = loadedLayers.constBegin(); it != loadedLayers.constEnd(); ++it )
2778 {
2779 if ( it.value()->isValid() && it.value()->customProperty( u"_layer_was_editable"_s ).toBool() )
2780 {
2781 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( it.value() ) )
2782 vl->startEditing();
2783 it.value()->removeCustomProperty( u"_layer_was_editable"_s );
2784 }
2785 }
2786
2787 return true;
2788}
2789
2790bool QgsProject::loadEmbeddedNodes( QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
2791{
2793
2794 bool valid = true;
2795 const auto constChildren = group->children();
2796 for ( QgsLayerTreeNode *child : constChildren )
2797 {
2798 if ( QgsLayerTree::isGroup( child ) )
2799 {
2800 QgsLayerTreeGroup *childGroup = QgsLayerTree::toGroup( child );
2801 if ( childGroup->customProperty( u"embedded"_s ).toInt() )
2802 {
2803 // make sure to convert the path from relative to absolute
2804 const QString projectPath = readPath( childGroup->customProperty( u"embedded_project"_s ).toString() );
2805 childGroup->setCustomProperty( u"embedded_project"_s, projectPath );
2806 std::unique_ptr< QgsLayerTreeGroup > newGroup = createEmbeddedGroup( childGroup->name(), projectPath, childGroup->customProperty( u"embedded-invisible-layers"_s ).toStringList(), flags );
2807 if ( newGroup )
2808 {
2809 QList<QgsLayerTreeNode *> clonedChildren;
2810 const QList<QgsLayerTreeNode *> constChildren = newGroup->children();
2811 clonedChildren.reserve( constChildren.size() );
2812 for ( QgsLayerTreeNode *newGroupChild : constChildren )
2813 clonedChildren << newGroupChild->clone();
2814
2815 childGroup->insertChildNodes( 0, clonedChildren );
2816 }
2817 }
2818 else
2819 {
2820 loadEmbeddedNodes( childGroup, flags );
2821 }
2822 }
2823 else if ( QgsLayerTree::isLayer( child ) )
2824 {
2825 if ( child->customProperty( u"embedded"_s ).toInt() )
2826 {
2827 QList<QDomNode> brokenNodes;
2828 if ( !createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), readPath( child->customProperty( u"embedded_project"_s ).toString() ), brokenNodes, true, flags ) )
2829 {
2830 valid = valid && false;
2831 }
2832 }
2833 }
2834 }
2835
2836 return valid;
2837}
2838
2840{
2841 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
2843
2844 return mCustomVariables;
2845}
2846
2847void QgsProject::setCustomVariables( const QVariantMap &variables )
2848{
2850
2851 if ( variables == mCustomVariables )
2852 return;
2853
2854 //write variable to project
2855 QStringList variableNames;
2856 QStringList variableValues;
2857
2858 QVariantMap::const_iterator it = variables.constBegin();
2859 for ( ; it != variables.constEnd(); ++it )
2860 {
2861 variableNames << it.key();
2862 variableValues << it.value().toString();
2863 }
2864
2865 writeEntry( u"Variables"_s, u"/variableNames"_s, variableNames );
2866 writeEntry( u"Variables"_s, u"/variableValues"_s, variableValues );
2867
2868 mCustomVariables = variables;
2869 mProjectScope.reset();
2870
2872}
2873
2875{
2877
2878 *mLabelingEngineSettings = settings;
2880}
2881
2883{
2885
2886 return *mLabelingEngineSettings;
2887}
2888
2890{
2892
2893 mProjectScope.reset();
2894 return mLayerStore.get();
2895}
2896
2898{
2900
2901 return mLayerStore.get();
2902}
2903
2904QList<QgsVectorLayer *> QgsProject::avoidIntersectionsLayers() const
2905{
2907
2908 QList<QgsVectorLayer *> layers;
2909 const QStringList layerIds = readListEntry( u"Digitizing"_s, u"/AvoidIntersectionsList"_s, QStringList() );
2910 const auto constLayerIds = layerIds;
2911 for ( const QString &layerId : constLayerIds )
2912 {
2913 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer( layerId ) ) )
2914 layers << vlayer;
2915 }
2916 return layers;
2917}
2918
2919void QgsProject::setAvoidIntersectionsLayers( const QList<QgsVectorLayer *> &layers )
2920{
2922
2923 QStringList list;
2924 list.reserve( layers.size() );
2925
2926 for ( QgsVectorLayer *layer : layers )
2927 {
2928 if ( layer->geometryType() == Qgis::GeometryType::Polygon )
2929 list << layer->id();
2930 }
2931
2932 writeEntry( u"Digitizing"_s, u"/AvoidIntersectionsList"_s, list );
2934}
2935
2946
2948{
2949 // this method is called quite extensively using QgsProject::instance() skip-keyword-check
2951
2952 // MUCH cheaper to clone than build
2953 if ( mProjectScope )
2954 {
2955 auto projectScope = std::make_unique< QgsExpressionContextScope >( *mProjectScope );
2956
2957 // we can't cache these variables
2958 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_distance_units"_s, QgsUnitTypes::toString( distanceUnits() ), true, true ) );
2959 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_area_units"_s, QgsUnitTypes::toString( areaUnits() ), true, true ) );
2960
2961 // neither this function
2962 projectScope->addFunction( u"sensor_data"_s, new GetSensorData( sensorManager()->sensorsData() ) );
2963
2964 return projectScope.release();
2965 }
2966
2967 mProjectScope = std::make_unique< QgsExpressionContextScope >( QObject::tr( "Project" ) );
2968
2969 const QVariantMap vars = customVariables();
2970
2971 QVariantMap::const_iterator it = vars.constBegin();
2972
2973 for ( ; it != vars.constEnd(); ++it )
2974 {
2975 mProjectScope->setVariable( it.key(), it.value(), true );
2976 }
2977
2978 QString projectPath = projectStorage() ? fileName() : absoluteFilePath();
2979 if ( projectPath.isEmpty() )
2980 projectPath = mOriginalPath;
2981 const QString projectFolder = QFileInfo( projectPath ).path();
2982 const QString projectFilename = QFileInfo( projectPath ).fileName();
2983 const QString projectBasename = baseName();
2984
2985 //add other known project variables
2986 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_title"_s, title(), true, true ) );
2987 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_path"_s, QDir::toNativeSeparators( projectPath ), true, true ) );
2988 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_folder"_s, QDir::toNativeSeparators( projectFolder ), true, true ) );
2989 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_filename"_s, projectFilename, true, true ) );
2990 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_basename"_s, projectBasename, true, true ) );
2991 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_home"_s, QDir::toNativeSeparators( homePath() ), true, true ) );
2992 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_last_saved"_s, mSaveDateTime.isNull() ? QVariant() : QVariant( mSaveDateTime ), true, true ) );
2993
2994 const QgsCoordinateReferenceSystem projectCrs = crs();
2995 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs"_s, projectCrs.authid(), true, true ) );
2996 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_definition"_s, projectCrs.toProj(), true, true ) );
2997 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_description"_s, projectCrs.description(), true, true ) );
2998 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_acronym"_s, projectCrs.projectionAcronym(), true ) );
2999 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_ellipsoid"_s, projectCrs.ellipsoidAcronym(), true ) );
3000 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_proj4"_s, projectCrs.toProj(), true ) );
3001 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_wkt"_s, projectCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
3002
3003 const QgsCoordinateReferenceSystem projectVerticalCrs = QgsProject::verticalCrs();
3004 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs"_s, projectVerticalCrs.authid(), true, true ) );
3005 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs_definition"_s, projectVerticalCrs.toProj(), true, true ) );
3006 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs_description"_s, projectVerticalCrs.description(), true, true ) );
3007 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs_wkt"_s, projectVerticalCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
3008
3009 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_ellipsoid"_s, ellipsoid(), true, true ) );
3010 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"_project_transform_context"_s, QVariant::fromValue<QgsCoordinateTransformContext>( transformContext() ), true, true ) );
3011 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_units"_s, QgsUnitTypes::toString( projectCrs.mapUnits() ), true ) );
3012
3013 // metadata
3014 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_author"_s, metadata().author(), true, true ) );
3015 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_abstract"_s, metadata().abstract(), true, true ) );
3016 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_creation_date"_s, metadata().creationDateTime(), true, true ) );
3017 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_identifier"_s, metadata().identifier(), true, true ) );
3018
3019 // keywords
3020 QVariantMap keywords;
3021 const QgsAbstractMetadataBase::KeywordMap metadataKeywords = metadata().keywords();
3022 for ( auto it = metadataKeywords.constBegin(); it != metadataKeywords.constEnd(); ++it )
3023 {
3024 keywords.insert( it.key(), it.value() );
3025 }
3026 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_keywords"_s, keywords, true, true ) );
3027
3028 // layers
3029 QVariantList layersIds;
3030 QVariantList layers;
3031 const QMap<QString, QgsMapLayer *> layersInProject = mLayerStore->mapLayers();
3032 layersIds.reserve( layersInProject.count() );
3033 layers.reserve( layersInProject.count() );
3034 for ( auto it = layersInProject.constBegin(); it != layersInProject.constEnd(); ++it )
3035 {
3036 layersIds << it.value()->id();
3038 }
3039 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"layer_ids"_s, layersIds, true ) );
3040 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"layers"_s, layers, true ) );
3041
3042 mProjectScope->addFunction( u"project_color"_s, new GetNamedProjectColor( this ) );
3043 mProjectScope->addFunction( u"project_color_object"_s, new GetNamedProjectColorObject( this ) );
3044
3046}
3047
3048void QgsProject::onMapLayersAdded( const QList<QgsMapLayer *> &layers )
3049{
3051
3052 const QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
3053
3054 const auto constLayers = layers;
3055 for ( QgsMapLayer *layer : constLayers )
3056 {
3057 if ( !layer->isValid() )
3058 return;
3059
3060 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
3061 {
3062 vlayer->setReadExtentFromXml( mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics );
3063 if ( vlayer->dataProvider() )
3064 vlayer->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, ( bool ) ( mFlags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide ) );
3065 }
3066
3067 connect( layer, &QgsMapLayer::configChanged, this, [this] { setDirty(); } );
3068
3069 // check if we have to update connections for layers with dependencies
3070 for ( QMap<QString, QgsMapLayer *>::const_iterator it = existingMaps.cbegin(); it != existingMaps.cend(); ++it )
3071 {
3072 const QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
3073 if ( deps.contains( layer->id() ) )
3074 {
3075 // reconnect to change signals
3076 it.value()->setDependencies( deps );
3077 }
3078 }
3079 }
3080
3081 updateTransactionGroups();
3082
3083 if ( !mBlockSnappingUpdates && mSnappingConfig.addLayers( layers ) )
3084 emit snappingConfigChanged( mSnappingConfig );
3085}
3086
3087void QgsProject::onMapLayersRemoved( const QList<QgsMapLayer *> &layers )
3088{
3090
3091 if ( !mBlockSnappingUpdates && mSnappingConfig.removeLayers( layers ) )
3092 emit snappingConfigChanged( mSnappingConfig );
3093
3094 for ( QgsMapLayer *layer : layers )
3095 {
3096 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
3097 if ( !vlayer )
3098 continue;
3099
3100 mEditBufferGroup.removeLayer( vlayer );
3101 }
3102}
3103
3104void QgsProject::cleanTransactionGroups( bool force )
3105{
3107
3108 bool changed = false;
3109 for ( QMap< QPair< QString, QString>, QgsTransactionGroup *>::Iterator tg = mTransactionGroups.begin(); tg != mTransactionGroups.end(); )
3110 {
3111 if ( tg.value()->isEmpty() || force )
3112 {
3113 delete tg.value();
3114 tg = mTransactionGroups.erase( tg );
3115 changed = true;
3116 }
3117 else
3118 {
3119 ++tg;
3120 }
3121 }
3122 if ( changed )
3124}
3125
3126void QgsProject::updateTransactionGroups()
3127{
3129
3130 mEditBufferGroup.clear();
3131
3132 switch ( mTransactionMode )
3133 {
3135 {
3136 cleanTransactionGroups( true );
3137 return;
3138 }
3139 break;
3141 cleanTransactionGroups( true );
3142 break;
3144 cleanTransactionGroups( false );
3145 break;
3146 }
3147
3148 bool tgChanged = false;
3149 const auto constLayers = mapLayers().values();
3150 for ( QgsMapLayer *layer : constLayers )
3151 {
3152 if ( !layer->isValid() )
3153 continue;
3154
3155 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
3156 if ( !vlayer )
3157 continue;
3158
3159 switch ( mTransactionMode )
3160 {
3162 Q_ASSERT( false );
3163 break;
3165 {
3167 {
3168 const QString connString = QgsTransaction::connectionString( vlayer->source() );
3169 const QString key = vlayer->providerType();
3170
3171 QgsTransactionGroup *tg = mTransactionGroups.value( qMakePair( key, connString ) );
3172
3173 if ( !tg )
3174 {
3175 tg = new QgsTransactionGroup();
3176 mTransactionGroups.insert( qMakePair( key, connString ), tg );
3177 tgChanged = true;
3178 }
3179 tg->addLayer( vlayer );
3180 }
3181 }
3182 break;
3184 {
3185 if ( vlayer->supportsEditing() )
3186 mEditBufferGroup.addLayer( vlayer );
3187 }
3188 break;
3189 }
3190 }
3191
3192 if ( tgChanged )
3194}
3195
3196bool QgsProject::readLayer( const QDomNode &layerNode )
3197{
3199
3200 QgsReadWriteContext context;
3201 context.setPathResolver( pathResolver() );
3202 context.setProjectTranslator( this );
3203 context.setTransformContext( transformContext() );
3204 context.setCurrentLayerId( layerNode.toElement().firstChildElement( u"id"_s ).text() );
3205 QList<QDomNode> brokenNodes;
3206 if ( addLayer( layerNode.toElement(), brokenNodes, context ) )
3207 {
3208 // have to try to update joins for all layers now - a previously added layer may be dependent on this newly
3209 // added layer for joins
3210 const QVector<QgsVectorLayer *> vectorLayers = layers<QgsVectorLayer *>();
3211 for ( QgsVectorLayer *layer : vectorLayers )
3212 {
3213 // TODO: should be only done later - and with all layers (other layers may have referenced this layer)
3214 layer->resolveReferences( this );
3215
3216 if ( layer->isValid() && layer->customProperty( u"_layer_was_editable"_s ).toBool() )
3217 {
3218 layer->startEditing();
3219 layer->removeCustomProperty( u"_layer_was_editable"_s );
3220 }
3221 }
3222 return true;
3223 }
3224 return false;
3225}
3226
3227bool QgsProject::write( const QString &filename )
3228{
3230
3231 mFile.setFileName( filename );
3232 emit fileNameChanged();
3233 mCachedHomePath.clear();
3234 return write();
3235}
3236
3238{
3240
3241 mProjectScope.reset();
3242 if ( QgsProjectStorage *storage = projectStorage() )
3243 {
3244 QgsReadWriteContext context;
3245 // for projects stored in a custom storage, we have to check for the support
3246 // of relative paths since the storage most likely will not be in a file system
3247 const QString storageFilePath { storage->filePath( mFile.fileName() ) };
3248 if ( storageFilePath.isEmpty() )
3249 {
3251 }
3252 context.setPathResolver( pathResolver() );
3253
3254 const QString tempPath = QStandardPaths::standardLocations( QStandardPaths::TempLocation ).at( 0 );
3255 const QString tmpZipFilename( tempPath + QDir::separator() + QUuid::createUuid().toString() );
3256
3257 if ( !zip( tmpZipFilename ) )
3258 return false; // zip() already calls setError() when returning false
3259
3260 QFile tmpZipFile( tmpZipFilename );
3261 if ( !tmpZipFile.open( QIODevice::ReadOnly ) )
3262 {
3263 setError( tr( "Unable to read file %1" ).arg( tmpZipFilename ) );
3264 return false;
3265 }
3266
3268 if ( !storage->writeProject( mFile.fileName(), &tmpZipFile, context ) )
3269 {
3270 QString err = tr( "Unable to save project to storage %1" ).arg( mFile.fileName() );
3271 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
3272 if ( !messages.isEmpty() )
3273 err += u"\n\n"_s + messages.last().message();
3274 setError( err );
3275 return false;
3276 }
3277
3278 tmpZipFile.close();
3279 QFile::remove( tmpZipFilename );
3280
3281 return true;
3282 }
3283
3284 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
3285 {
3286 return zip( mFile.fileName() );
3287 }
3288 else
3289 {
3290 // write project file even if the auxiliary storage is not correctly
3291 // saved
3292 const bool asOk = saveAuxiliaryStorage();
3293 const bool writeOk = writeProjectFile( mFile.fileName() );
3294 bool attachmentsOk = true;
3295 if ( !mArchive->files().isEmpty() )
3296 {
3297 const QFileInfo finfo( mFile.fileName() );
3298 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( u"%1_attachments.zip"_s.arg( finfo.completeBaseName() ) );
3299 attachmentsOk = mArchive->zip( attachmentsZip );
3300 }
3301
3302 // errors raised during writing project file are more important
3303 if ( ( !asOk || !attachmentsOk ) && writeOk )
3304 {
3305 QStringList errorMessage;
3306 if ( !asOk )
3307 {
3308 const QString err = mAuxiliaryStorage->errorString();
3309 errorMessage.append( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
3310 }
3311 if ( !attachmentsOk )
3312 {
3313 errorMessage.append( tr( "Unable to save attachments archive" ) );
3314 }
3315 setError( errorMessage.join( '\n' ) );
3316 }
3317
3318 return asOk && writeOk && attachmentsOk;
3319 }
3320}
3321
3322bool QgsProject::writeProjectFile( const QString &filename )
3323{
3325
3326 QFile projectFile( filename );
3327 clearError();
3328
3329 // if we have problems creating or otherwise writing to the project file,
3330 // let's find out up front before we go through all the hand-waving
3331 // necessary to create all the Dom objects
3332 const QFileInfo myFileInfo( projectFile );
3333 if ( myFileInfo.exists() && !myFileInfo.isWritable() )
3334 {
3335 setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." ).arg( projectFile.fileName() ) );
3336 return false;
3337 }
3338
3339 QgsReadWriteContext context;
3340 context.setPathResolver( pathResolver() );
3342
3343 QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
3344
3345 const QDomDocumentType documentType = QDomImplementation().createDocumentType( u"qgis"_s, u"http://mrcc.com/qgis.dtd"_s, u"SYSTEM"_s );
3346 auto doc = std::make_unique<QDomDocument>( documentType );
3347
3348 QDomElement qgisNode = doc->createElement( u"qgis"_s );
3349 qgisNode.setAttribute( u"projectname"_s, title() );
3350 qgisNode.setAttribute( u"version"_s, Qgis::version() );
3351
3352 if ( !settingsAnonymizeSavedProjects->value() )
3353 {
3354 const QString newSaveUser = QgsApplication::userLoginName();
3355 const QString newSaveUserFull = QgsApplication::userFullName();
3356 qgisNode.setAttribute( u"saveUser"_s, newSaveUser );
3357 qgisNode.setAttribute( u"saveUserFull"_s, newSaveUserFull );
3358 mSaveUser = newSaveUser;
3359 mSaveUserFull = newSaveUserFull;
3360 if ( mMetadata.author().isEmpty() )
3361 {
3362 mMetadata.setAuthor( QgsApplication::userFullName() );
3363 }
3364 if ( !mMetadata.creationDateTime().isValid() )
3365 {
3366 mMetadata.setCreationDateTime( QDateTime( QDateTime::currentDateTime() ) );
3367 }
3368 mSaveDateTime = QDateTime::currentDateTime();
3369 qgisNode.setAttribute( u"saveDateTime"_s, mSaveDateTime.toString( Qt::ISODate ) );
3370 }
3371 else
3372 {
3373 mSaveUser.clear();
3374 mSaveUserFull.clear();
3375 mMetadata.setAuthor( QString() );
3376 mMetadata.setCreationDateTime( QDateTime() );
3377 mSaveDateTime = QDateTime();
3378 }
3379 doc->appendChild( qgisNode );
3380 mSaveVersion = QgsProjectVersion( Qgis::version() );
3381
3382 QDomElement homePathNode = doc->createElement( u"homePath"_s );
3383 homePathNode.setAttribute( u"path"_s, mHomePath );
3384 qgisNode.appendChild( homePathNode );
3385
3386 // title
3387 QDomElement titleNode = doc->createElement( u"title"_s );
3388 qgisNode.appendChild( titleNode );
3389
3390 QDomElement transactionNode = doc->createElement( u"transaction"_s );
3391 transactionNode.setAttribute( u"mode"_s, qgsEnumValueToKey( mTransactionMode ) );
3392 qgisNode.appendChild( transactionNode );
3393
3394 QDomElement flagsNode = doc->createElement( u"projectFlags"_s );
3395 flagsNode.setAttribute( u"set"_s, qgsFlagValueToKeys( mFlags ) );
3396 qgisNode.appendChild( flagsNode );
3397
3398 const QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
3399 titleNode.appendChild( titleText );
3400
3401 // write project CRS
3402 {
3403 QDomElement srsNode = doc->createElement( u"projectCrs"_s );
3404 mCrs.writeXml( srsNode, *doc );
3405 qgisNode.appendChild( srsNode );
3406 }
3407 {
3408 QDomElement verticalSrsNode = doc->createElement( u"verticalCrs"_s );
3409 mVerticalCrs.writeXml( verticalSrsNode, *doc );
3410 qgisNode.appendChild( verticalSrsNode );
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}
@ Macro
Project macros.
Definition qgis.h:464
@ ExpressionFunction
Expression functions.
Definition qgis.h:465
@ DontLoad3DViews
Skip loading 3D views.
Definition qgis.h:4677
@ DontStoreOriginalStyles
Skip the initial XML style storage for layers. Useful for minimising project load times in non-intera...
Definition qgis.h:4676
@ ForceReadOnlyLayers
Open layers in a read-only mode.
Definition qgis.h:4679
@ TrustLayerMetadata
Trust layer metadata. Improves project read time. Do not use it if layers' extent is not fixed during...
Definition qgis.h:4674
@ DontUpgradeAnnotations
Don't upgrade old annotation items to QgsAnnotationItem.
Definition qgis.h:4680
@ DontLoadLayouts
Don't load print layouts. Improves project read time if layouts are not required, and allows projects...
Definition qgis.h:4672
@ DontResolveLayers
Don't resolve layer paths (i.e. don't load any layer content). Dramatically improves project read tim...
Definition qgis.h:4670
static QString version()
Version string.
Definition qgis.cpp:682
@ Trusted
The project has been determined by the user as trusted.
Definition qgis.h:478
QFlags< ProjectCapability > ProjectCapabilities
Flags which control project capabilities.
Definition qgis.h:4713
QFlags< ProjectReadFlag > ProjectReadFlags
Project load flags.
Definition qgis.h:4691
DistanceUnit
Units of distance.
Definition qgis.h:5437
@ Meters
Meters.
Definition qgis.h:5438
FilePathType
File path types.
Definition qgis.h:1797
@ Relative
Relative path.
Definition qgis.h:1799
@ Absolute
Absolute path.
Definition qgis.h:1798
TransactionMode
Transaction mode.
Definition qgis.h:4154
@ AutomaticGroups
Automatic transactional editing means that on supported datasources (postgres and geopackage database...
Definition qgis.h:4156
@ BufferedGroups
Buffered transactional editing means that all editable layers in the buffered transaction group are t...
Definition qgis.h:4157
@ Disabled
Edits are buffered locally and sent to the provider when toggling layer editing mode.
Definition qgis.h:4155
AreaUnit
Units of area.
Definition qgis.h:5514
@ SquareMeters
Square meters.
Definition qgis.h:5515
@ 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:2469
@ Temporal
Temporal CRS.
Definition qgis.h:2472
@ Compound
Compound (horizontal + vertical) CRS.
Definition qgis.h:2471
@ Projected
Projected CRS.
Definition qgis.h:2470
@ Other
Other type.
Definition qgis.h:2475
@ Bound
Bound CRS.
Definition qgis.h:2474
@ DerivedProjected
Derived projected CRS.
Definition qgis.h:2476
@ Unknown
Unknown type.
Definition qgis.h:2464
@ Engineering
Engineering CRS.
Definition qgis.h:2473
@ Geographic3d
3D geopraphic CRS
Definition qgis.h:2468
@ Geodetic
Geodetic CRS.
Definition qgis.h:2465
@ Geographic2d
2D geographic CRS
Definition qgis.h:2467
@ Geocentric
Geocentric CRS.
Definition qgis.h:2466
AvoidIntersectionsMode
Flags which control how intersections of pre-existing feature are handled when digitizing new feature...
Definition qgis.h:4640
@ AvoidIntersectionsLayers
Overlap with features from a specified list of layers when digitizing new features not allowed.
Definition qgis.h:4643
@ AllowIntersections
Overlap with any feature allowed when digitizing new features.
Definition qgis.h:4641
ProjectFlag
Flags which control the behavior of QgsProjects.
Definition qgis.h:4288
@ RememberLayerEditStatusBetweenSessions
If set, then any layers set to be editable will be stored in the project and immediately made editabl...
Definition qgis.h:4292
@ EvaluateDefaultValuesOnProviderSide
If set, default values for fields will be evaluated on the provider side when features from the proje...
Definition qgis.h:4289
@ TrustStoredLayerStatistics
If set, then layer statistics (such as the layer extent) will be read from values stored in the proje...
Definition qgis.h:4290
@ 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:5714
@ HorizontalMiddle
Calculate horizontally, across midle of map.
Definition qgis.h:5716
@ 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:2597
QFlags< ProjectFlag > ProjectFlags
Definition qgis.h:4296
@ Container
A container.
Definition qgis.h:6075
@ Marker
Marker symbol.
Definition qgis.h:639
@ Line
Line symbol.
Definition qgis.h:640
@ Fill
Fill symbol.
Definition qgis.h:641
static QString geoNone()
Constant that holds the string representation for "No ellipse/No CRS".
Definition qgis.h:7054
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
Definition qgis.h:2581
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:7595
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7938
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7576
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:7634
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:7663
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7937
#define QgsDebugCall
Definition qgslogger.h:66
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
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.