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