QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsproject.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsproject.cpp - description
3 -------------------
4 begin : July 23, 2004
5 copyright : (C) 2004 by Mark Coletti
6 email : mcoletti at gmail.com
7***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgsproject.h"
19
20#include "qgsdatasourceuri.h"
22#include "qgslayertree.h"
23#include "qgslayertreeutils.h"
25#include "qgslogger.h"
26#include "qgsmessagelog.h"
27#include "qgsmaplayerfactory.h"
28#include "qgspluginlayer.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
78#include <algorithm>
79#include <QApplication>
80#include <QFileInfo>
81#include <QDomNode>
82#include <QObject>
83#include <QTextStream>
84#include <QTemporaryFile>
85#include <QDir>
86#include <QUrl>
87#include <QStandardPaths>
88#include <QUuid>
89#include <QRegularExpression>
90#include <QThreadPool>
91
92#ifdef _MSC_VER
93#include <sys/utime.h>
94#else
95#include <utime.h>
96#endif
97
98// canonical project instance
99QgsProject *QgsProject::sProject = nullptr;
100
109QStringList makeKeyTokens_( const QString &scope, const QString &key )
110{
111 QStringList keyTokens = QStringList( scope );
112 keyTokens += key.split( '/', Qt::SkipEmptyParts );
113
114 // be sure to include the canonical root node
115 keyTokens.push_front( QStringLiteral( "properties" ) );
116
117 //check validy of keys since an invalid xml name will will be dropped upon saving the xml file. If not valid, we print a message to the console.
118 for ( int i = 0; i < keyTokens.size(); ++i )
119 {
120 const QString keyToken = keyTokens.at( i );
121
122 //invalid chars in XML are found at http://www.w3.org/TR/REC-xml/#NT-NameChar
123 //note : it seems \x10000-\xEFFFF is valid, but it when added to the regexp, a lot of unwanted characters remain
124 const thread_local QRegularExpression sInvalidRegexp = QRegularExpression( QStringLiteral( "([^:A-Z_a-z\\x{C0}-\\x{D6}\\x{D8}-\\x{F6}\\x{F8}-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}\\-\\.0-9\\x{B7}\\x{0300}-\\x{036F}\\x{203F}-\\x{2040}]|^[^:A-Z_a-z\\x{C0}-\\x{D6}\\x{D8}-\\x{F6}\\x{F8}-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}])" ) );
125 if ( keyToken.contains( sInvalidRegexp ) )
126 {
127 const QString errorString = QObject::tr( "Entry token invalid : '%1'. The token will not be saved to file." ).arg( keyToken );
128 QgsMessageLog::logMessage( errorString, QString(), Qgis::MessageLevel::Critical );
129 }
130 }
131
132 return keyTokens;
133}
134
135
136
146QgsProjectProperty *findKey_( const QString &scope,
147 const QString &key,
148 QgsProjectPropertyKey &rootProperty )
149{
150 QgsProjectPropertyKey *currentProperty = &rootProperty;
151 QgsProjectProperty *nextProperty; // link to next property down hierarchy
152
153 QStringList keySequence = makeKeyTokens_( scope, key );
154
155 while ( !keySequence.isEmpty() )
156 {
157 // if the current head of the sequence list matches the property name,
158 // then traverse down the property hierarchy
159 if ( keySequence.first() == currentProperty->name() )
160 {
161 // remove front key since we're traversing down a level
162 keySequence.pop_front();
163
164 if ( 1 == keySequence.count() )
165 {
166 // if we have only one key name left, then return the key found
167 return currentProperty->find( keySequence.front() );
168 }
169 else if ( keySequence.isEmpty() )
170 {
171 // if we're out of keys then the current property is the one we
172 // want; i.e., we're in the rate case of being at the top-most
173 // property node
174 return currentProperty;
175 }
176 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
177 {
178 if ( nextProperty->isKey() )
179 {
180 currentProperty = static_cast<QgsProjectPropertyKey *>( nextProperty );
181 }
182 else if ( nextProperty->isValue() && 1 == keySequence.count() )
183 {
184 // it may be that this may be one of several property value
185 // nodes keyed by QDict string; if this is the last remaining
186 // key token and the next property is a value node, then
187 // that's the situation, so return the currentProperty
188 return currentProperty;
189 }
190 else
191 {
192 // QgsProjectPropertyValue not Key, so return null
193 return nullptr;
194 }
195 }
196 else
197 {
198 // if the next key down isn't found
199 // then the overall key sequence doesn't exist
200 return nullptr;
201 }
202 }
203 else
204 {
205 return nullptr;
206 }
207 }
208
209 return nullptr;
210}
211
212
213
223QgsProjectProperty *addKey_( const QString &scope,
224 const QString &key,
225 QgsProjectPropertyKey *rootProperty,
226 const QVariant &value,
227 bool &propertiesModified )
228{
229 QStringList keySequence = makeKeyTokens_( scope, key );
230
231 // cursor through property key/value hierarchy
232 QgsProjectPropertyKey *currentProperty = rootProperty;
233 QgsProjectProperty *nextProperty; // link to next property down hierarchy
234 QgsProjectPropertyKey *newPropertyKey = nullptr;
235
236 propertiesModified = false;
237 while ( ! keySequence.isEmpty() )
238 {
239 // if the current head of the sequence list matches the property name,
240 // then traverse down the property hierarchy
241 if ( keySequence.first() == currentProperty->name() )
242 {
243 // remove front key since we're traversing down a level
244 keySequence.pop_front();
245
246 // if key sequence has one last element, then we use that as the
247 // name to store the value
248 if ( 1 == keySequence.count() )
249 {
250 QgsProjectProperty *property = currentProperty->find( keySequence.front() );
251 if ( !property || property->value() != value )
252 {
253 currentProperty->setValue( keySequence.front(), value );
254 propertiesModified = true;
255 }
256
257 return currentProperty;
258 }
259 // we're at the top element if popping the keySequence element
260 // will leave it empty; in that case, just add the key
261 else if ( keySequence.isEmpty() )
262 {
263 if ( currentProperty->value() != value )
264 {
265 currentProperty->setValue( value );
266 propertiesModified = true;
267 }
268
269 return currentProperty;
270 }
271 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
272 {
273 currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
274
275 if ( currentProperty )
276 {
277 continue;
278 }
279 else // QgsProjectPropertyValue not Key, so return null
280 {
281 return nullptr;
282 }
283 }
284 else // the next subkey doesn't exist, so add it
285 {
286 if ( ( newPropertyKey = currentProperty->addKey( keySequence.first() ) ) )
287 {
288 currentProperty = newPropertyKey;
289 }
290 continue;
291 }
292 }
293 else
294 {
295 return nullptr;
296 }
297 }
298
299 return nullptr;
300}
301
309void removeKey_( const QString &scope,
310 const QString &key,
311 QgsProjectPropertyKey &rootProperty )
312{
313 QgsProjectPropertyKey *currentProperty = &rootProperty;
314
315 QgsProjectProperty *nextProperty = nullptr; // link to next property down hierarchy
316 QgsProjectPropertyKey *previousQgsPropertyKey = nullptr; // link to previous property up hierarchy
317
318 QStringList keySequence = makeKeyTokens_( scope, key );
319
320 while ( ! keySequence.isEmpty() )
321 {
322 // if the current head of the sequence list matches the property name,
323 // then traverse down the property hierarchy
324 if ( keySequence.first() == currentProperty->name() )
325 {
326 // remove front key since we're traversing down a level
327 keySequence.pop_front();
328
329 // if we have only one key name left, then try to remove the key
330 // with that name
331 if ( 1 == keySequence.count() )
332 {
333 currentProperty->removeKey( keySequence.front() );
334 }
335 // if we're out of keys then the current property is the one we
336 // want to remove, but we can't delete it directly; we need to
337 // delete it from the parent property key container
338 else if ( keySequence.isEmpty() )
339 {
340 previousQgsPropertyKey->removeKey( currentProperty->name() );
341 }
342 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
343 {
344 previousQgsPropertyKey = currentProperty;
345 currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
346
347 if ( currentProperty )
348 {
349 continue;
350 }
351 else // QgsProjectPropertyValue not Key, so return null
352 {
353 return;
354 }
355 }
356 else // if the next key down isn't found
357 {
358 // then the overall key sequence doesn't exist
359 return;
360 }
361 }
362 else
363 {
364 return;
365 }
366 }
367}
368
369QgsProject::QgsProject( QObject *parent, Qgis::ProjectCapabilities capabilities )
370 : QObject( parent )
371 , mCapabilities( capabilities )
372 , mLayerStore( new QgsMapLayerStore( this ) )
373 , mBadLayerHandler( new QgsProjectBadLayerHandler() )
374 , mSnappingConfig( this )
375 , mRelationManager( new QgsRelationManager( this ) )
376 , mAnnotationManager( new QgsAnnotationManager( this ) )
377 , mLayoutManager( new QgsLayoutManager( this ) )
378 , m3DViewsManager( new QgsMapViewsManager( this ) )
379 , mBookmarkManager( QgsBookmarkManager::createProjectBasedManager( this ) )
380 , mSensorManager( new QgsSensorManager( this ) )
381 , mViewSettings( new QgsProjectViewSettings( this ) )
382 , mStyleSettings( new QgsProjectStyleSettings( this ) )
383 , mTimeSettings( new QgsProjectTimeSettings( this ) )
384 , mElevationProperties( new QgsProjectElevationProperties( this ) )
385 , mDisplaySettings( new QgsProjectDisplaySettings( this ) )
386 , mGpsSettings( new QgsProjectGpsSettings( this ) )
387 , mRootGroup( new QgsLayerTree )
388 , mLabelingEngineSettings( new QgsLabelingEngineSettings )
389 , mArchive( new QgsArchive() )
390 , mAuxiliaryStorage( new QgsAuxiliaryStorage() )
391{
392 mProperties.setName( QStringLiteral( "properties" ) );
393
394 mMainAnnotationLayer = new QgsAnnotationLayer( QObject::tr( "Annotations" ), QgsAnnotationLayer::LayerOptions( mTransformContext ) );
395 mMainAnnotationLayer->setParent( this );
396
397 clear();
398
399 // bind the layer tree to the map layer registry.
400 // whenever layers are added to or removed from the registry,
401 // layer tree will be updated
402 mLayerTreeRegistryBridge = new QgsLayerTreeRegistryBridge( mRootGroup, this, this );
403 connect( this, &QgsProject::layersAdded, this, &QgsProject::onMapLayersAdded );
404 connect( this, &QgsProject::layersRemoved, this, [this] { cleanTransactionGroups(); } );
405 connect( this, qOverload< const QList<QgsMapLayer *> & >( &QgsProject::layersWillBeRemoved ), this, &QgsProject::onMapLayersRemoved );
406
407 // proxy map layer store signals to this
408 connect( mLayerStore.get(), qOverload<const QStringList &>( &QgsMapLayerStore::layersWillBeRemoved ),
409 this, [this]( const QStringList & layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
410 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersWillBeRemoved ),
411 this, [this]( const QList<QgsMapLayer *> &layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
412 connect( mLayerStore.get(), qOverload< const QString & >( &QgsMapLayerStore::layerWillBeRemoved ),
413 this, [this]( const QString & layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
414 connect( mLayerStore.get(), qOverload< QgsMapLayer * >( &QgsMapLayerStore::layerWillBeRemoved ),
415 this, [this]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
416 connect( mLayerStore.get(), qOverload<const QStringList & >( &QgsMapLayerStore::layersRemoved ), this,
417 [this]( const QStringList & layers ) { mProjectScope.reset(); emit layersRemoved( layers ); } );
418 connect( mLayerStore.get(), &QgsMapLayerStore::layerRemoved, this,
419 [this]( const QString & layer ) { mProjectScope.reset(); emit layerRemoved( layer ); } );
420 connect( mLayerStore.get(), &QgsMapLayerStore::allLayersRemoved, this,
421 [this]() { mProjectScope.reset(); emit removeAll(); } );
422 connect( mLayerStore.get(), &QgsMapLayerStore::layersAdded, this,
423 [this]( const QList< QgsMapLayer * > &layers ) { mProjectScope.reset(); emit layersAdded( layers ); } );
424 connect( mLayerStore.get(), &QgsMapLayerStore::layerWasAdded, this,
425 [this]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWasAdded( layer ); } );
426
428 {
430 }
431
432 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersWillBeRemoved ), this,
433 [this]( const QList<QgsMapLayer *> &layers )
434 {
435 for ( const auto &layer : layers )
436 {
437 disconnect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager, &QgsRelationManager::updateRelationsStatus );
438 }
439 }
440 );
441 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersAdded ), this,
442 [this]( const QList<QgsMapLayer *> &layers )
443 {
444 for ( const auto &layer : layers )
445 {
446 connect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager, &QgsRelationManager::updateRelationsStatus );
447 }
448 }
449 );
450
454
455 mStyleSettings->combinedStyleModel()->addDefaultStyle();
456}
457
458
460{
461 mIsBeingDeleted = true;
462
463 clear();
464 releaseHandlesToProjectArchive();
465 delete mBadLayerHandler;
466 delete mRelationManager;
467 delete mLayerTreeRegistryBridge;
468 delete mRootGroup;
469 if ( this == sProject )
470 {
471 sProject = nullptr;
472 }
473}
474
476{
477 sProject = project;
478}
479
480
482{
483 if ( !sProject )
484 {
485 sProject = new QgsProject;
486
488 }
489 return sProject;
490}
491
492void QgsProject::setTitle( const QString &title )
493{
495
496 if ( title == mMetadata.title() )
497 return;
498
499 mMetadata.setTitle( title );
500 mProjectScope.reset();
501 emit metadataChanged();
502
503 setDirty( true );
504}
505
506QString QgsProject::title() const
507{
508 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
510
511 return mMetadata.title();
512}
513
515{
517
518 const bool oldEvaluateDefaultValues = mFlags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide;
519 const bool newEvaluateDefaultValues = flags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide;
520 if ( oldEvaluateDefaultValues != newEvaluateDefaultValues )
521 {
522 const QMap<QString, QgsMapLayer *> layers = mapLayers();
523 for ( auto layerIt = layers.constBegin(); layerIt != layers.constEnd(); ++layerIt )
524 {
525 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() ) )
526 if ( vl->dataProvider() )
527 vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, newEvaluateDefaultValues );
528 }
529 }
530
531 const bool oldTrustLayerMetadata = mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics;
532 const bool newTrustLayerMetadata = flags & Qgis::ProjectFlag::TrustStoredLayerStatistics;
533 if ( oldTrustLayerMetadata != newTrustLayerMetadata )
534 {
535 const QMap<QString, QgsMapLayer *> layers = mapLayers();
536 for ( auto layerIt = layers.constBegin(); layerIt != layers.constEnd(); ++layerIt )
537 {
538 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() ) )
539 {
540 vl->setReadExtentFromXml( newTrustLayerMetadata );
541 }
542 }
543 }
544
545 if ( mFlags != flags )
546 {
547 mFlags = flags;
548 setDirty( true );
549 }
550}
551
552void QgsProject::setFlag( Qgis::ProjectFlag flag, bool enabled )
553{
555
556 Qgis::ProjectFlags newFlags = mFlags;
557 if ( enabled )
558 newFlags |= flag;
559 else
560 newFlags &= ~( static_cast< int >( flag ) );
561 setFlags( newFlags );
562}
563
564QString QgsProject::saveUser() const
565{
567
568 return mSaveUser;
569}
570
572{
574
575 return mSaveUserFull;
576}
577
579{
581
582 return mSaveDateTime;
583}
584
586{
588
589 return mSaveVersion;
590}
591
593{
595
596 return mDirty;
597}
598
599void QgsProject::setDirty( const bool dirty )
600{
602
603 if ( dirty && mDirtyBlockCount > 0 )
604 return;
605
606 if ( dirty )
607 emit dirtySet();
608
609 if ( mDirty == dirty )
610 return;
611
612 mDirty = dirty;
613 emit isDirtyChanged( mDirty );
614}
615
616void QgsProject::setPresetHomePath( const QString &path )
617{
619
620 if ( path == mHomePath )
621 return;
622
623 mHomePath = path;
624 mCachedHomePath.clear();
625 mProjectScope.reset();
626
627 emit homePathChanged();
628
629 setDirty( true );
630}
631
632void QgsProject::registerTranslatableContainers( QgsTranslationContext *translationContext, QgsAttributeEditorContainer *parent, const QString &layerId )
633{
635
636 const QList<QgsAttributeEditorElement *> elements = parent->children();
637
638 for ( QgsAttributeEditorElement *element : elements )
639 {
640 if ( element->type() == Qgis::AttributeEditorType::Container )
641 {
642 QgsAttributeEditorContainer *container = dynamic_cast<QgsAttributeEditorContainer *>( element );
643
644 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:formcontainers" ).arg( layerId ), container->name() );
645
646 if ( !container->children().empty() )
647 registerTranslatableContainers( translationContext, container, layerId );
648 }
649 }
650}
651
653{
655
656 //register layers
657 const QList<QgsLayerTreeLayer *> layers = mRootGroup->findLayers();
658
659 for ( const QgsLayerTreeLayer *layer : layers )
660 {
661 translationContext->registerTranslation( QStringLiteral( "project:layers:%1" ).arg( layer->layerId() ), layer->name() );
662
663 QgsMapLayer *mapLayer = layer->layer();
665 {
666 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer );
667
668 //register aliases and fields
669 const QgsFields fields = vlayer->fields();
670 for ( const QgsField &field : fields )
671 {
672 QString fieldName;
673 if ( field.alias().isEmpty() )
674 fieldName = field.name();
675 else
676 fieldName = field.alias();
677
678 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:fieldaliases" ).arg( vlayer->id() ), fieldName );
679
680 if ( field.editorWidgetSetup().type() == QLatin1String( "ValueRelation" ) )
681 {
682 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:fields:%2:valuerelationvalue" ).arg( vlayer->id(), field.name() ), field.editorWidgetSetup().config().value( QStringLiteral( "Value" ) ).toString() );
683 }
684 }
685
686 //register formcontainers
687 registerTranslatableContainers( translationContext, vlayer->editFormConfig().invisibleRootContainer(), vlayer->id() );
688
689 }
690 }
691
692 //register layergroups
693 const QList<QgsLayerTreeGroup *> groupLayers = mRootGroup->findGroups();
694 for ( const QgsLayerTreeGroup *groupLayer : groupLayers )
695 {
696 translationContext->registerTranslation( QStringLiteral( "project:layergroups" ), groupLayer->name() );
697 }
698
699 //register relations
700 const QList<QgsRelation> &relations = mRelationManager->relations().values();
701 for ( const QgsRelation &relation : relations )
702 {
703 translationContext->registerTranslation( QStringLiteral( "project:relations" ), relation.name() );
704 }
705}
706
708{
710
711 mDataDefinedServerProperties = properties;
712}
713
715{
717
718 return mDataDefinedServerProperties;
719}
720
722{
724
725 switch ( mTransactionMode )
726 {
729 {
730 if ( ! vectorLayer )
731 return false;
732 return vectorLayer->startEditing();
733 }
734
736 return mEditBufferGroup.startEditing();
737 }
738
739 return false;
740}
741
742bool QgsProject::commitChanges( QStringList &commitErrors, bool stopEditing, QgsVectorLayer *vectorLayer )
743{
745
746 switch ( mTransactionMode )
747 {
750 {
751 if ( ! vectorLayer )
752 {
753 commitErrors.append( tr( "Trying to commit changes without a layer specified. This only works if the transaction mode is buffered" ) );
754 return false;
755 }
756 bool success = vectorLayer->commitChanges( stopEditing );
757 commitErrors = vectorLayer->commitErrors();
758 return success;
759 }
760
762 return mEditBufferGroup.commitChanges( commitErrors, stopEditing );
763 }
764
765 return false;
766}
767
768bool QgsProject::rollBack( QStringList &rollbackErrors, bool stopEditing, QgsVectorLayer *vectorLayer )
769{
771
772 switch ( mTransactionMode )
773 {
776 {
777 if ( ! vectorLayer )
778 {
779 rollbackErrors.append( tr( "Trying to roll back changes without a layer specified. This only works if the transaction mode is buffered" ) );
780 return false;
781 }
782 bool success = vectorLayer->rollBack( stopEditing );
783 rollbackErrors = vectorLayer->commitErrors();
784 return success;
785 }
786
788 return mEditBufferGroup.rollBack( rollbackErrors, stopEditing );
789 }
790
791 return false;
792}
793
794void QgsProject::setFileName( const QString &name )
795{
797
798 if ( name == mFile.fileName() )
799 return;
800
801 const QString oldHomePath = homePath();
802
803 mFile.setFileName( name );
804 mCachedHomePath.clear();
805 mProjectScope.reset();
806
807 emit fileNameChanged();
808
809 const QString newHomePath = homePath();
810 if ( newHomePath != oldHomePath )
811 emit homePathChanged();
812
813 setDirty( true );
814}
815
816QString QgsProject::fileName() const
817{
818 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
820
821 return mFile.fileName();
822}
823
824void QgsProject::setOriginalPath( const QString &path )
825{
827
828 mOriginalPath = path;
829}
830
832{
834
835 return mOriginalPath;
836}
837
838QFileInfo QgsProject::fileInfo() const
839{
841
842 return QFileInfo( mFile );
843}
844
846{
847 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
849
851}
852
854{
856
857 if ( QgsProjectStorage *storage = projectStorage() )
858 {
860 storage->readProjectStorageMetadata( mFile.fileName(), metadata );
861 return metadata.lastModified;
862 }
863 else
864 {
865 return QFileInfo( mFile.fileName() ).lastModified();
866 }
867}
868
870{
872
873 if ( projectStorage() )
874 return QString();
875
876 if ( mFile.fileName().isEmpty() )
877 return QString(); // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
878
879 return QFileInfo( mFile.fileName() ).absolutePath();
880}
881
883{
884 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
886
887 if ( projectStorage() )
888 return QString();
889
890 if ( mFile.fileName().isEmpty() )
891 return QString(); // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
892
893 return QFileInfo( mFile.fileName() ).absoluteFilePath();
894}
895
896QString QgsProject::baseName() const
897{
898 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
900
901 if ( QgsProjectStorage *storage = projectStorage() )
902 {
904 storage->readProjectStorageMetadata( mFile.fileName(), metadata );
905 return metadata.name;
906 }
907 else
908 {
909 return QFileInfo( mFile.fileName() ).completeBaseName();
910 }
911}
912
914{
916
917 const bool absolutePaths = readBoolEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), false );
919}
920
922{
924
925 switch ( type )
926 {
928 writeEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), true );
929 break;
931 writeEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), false );
932 break;
933 }
934}
935
937{
938 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
940
941 return mCrs;
942}
943
944void QgsProject::setCrs( const QgsCoordinateReferenceSystem &crs, bool adjustEllipsoid )
945{
947
948 if ( crs != mCrs )
949 {
950 mCrs = crs;
951 writeEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), crs.isValid() ? 1 : 0 );
952 mProjectScope.reset();
953
954 // if annotation layer doesn't have a crs (i.e. in a newly created project), it should
955 // initially inherit the project CRS
956 if ( !mMainAnnotationLayer->crs().isValid() || mMainAnnotationLayer->isEmpty() )
957 mMainAnnotationLayer->setCrs( crs );
958
959 setDirty( true );
960 emit crsChanged();
961 }
962
963 if ( adjustEllipsoid )
965}
966
968{
969 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
971
972 if ( !crs().isValid() )
973 return geoNone();
974
975 return readEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ), geoNone() );
976}
977
978void QgsProject::setEllipsoid( const QString &ellipsoid )
979{
981
982 if ( ellipsoid == readEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ) ) )
983 return;
984
985 mProjectScope.reset();
986 writeEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ), ellipsoid );
988}
989
991{
992 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
994
995 return mTransformContext;
996}
997
999{
1001
1002 if ( context == mTransformContext )
1003 return;
1004
1005 mTransformContext = context;
1006 mProjectScope.reset();
1007
1008 mMainAnnotationLayer->setTransformContext( context );
1009 for ( auto &layer : mLayerStore.get()->mapLayers() )
1010 {
1011 layer->setTransformContext( context );
1012 }
1014}
1015
1017{
1019
1020 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
1021
1022 emit aboutToBeCleared();
1023
1024 mProjectScope.reset();
1025 mFile.setFileName( QString() );
1026 mProperties.clearKeys();
1027 mSaveUser.clear();
1028 mSaveUserFull.clear();
1029 mSaveDateTime = QDateTime();
1030 mSaveVersion = QgsProjectVersion();
1031 mHomePath.clear();
1032 mCachedHomePath.clear();
1033 mTransactionMode = Qgis::TransactionMode::Disabled;
1034 mFlags = Qgis::ProjectFlags();
1035 mDirty = false;
1036 mCustomVariables.clear();
1038 mMetadata = QgsProjectMetadata();
1039 mElevationShadingRenderer = QgsElevationShadingRenderer();
1040 if ( !mSettings.value( QStringLiteral( "projects/anonymize_new_projects" ), false, QgsSettings::Core ).toBool() )
1041 {
1042 mMetadata.setCreationDateTime( QDateTime::currentDateTime() );
1044 }
1045 emit metadataChanged();
1046
1048 context.readSettings();
1049 setTransformContext( context );
1050
1051 //fallback to QGIS default measurement unit
1052 bool ok = false;
1053 const Qgis::DistanceUnit distanceUnit = QgsUnitTypes::decodeDistanceUnit( mSettings.value( QStringLiteral( "/qgis/measure/displayunits" ) ).toString(), &ok );
1054 setDistanceUnits( ok ? distanceUnit : Qgis::DistanceUnit::Meters );
1055 ok = false;
1056 const Qgis::AreaUnit areaUnits = QgsUnitTypes::decodeAreaUnit( mSettings.value( QStringLiteral( "/qgis/measure/areaunits" ) ).toString(), &ok );
1058
1059 mEmbeddedLayers.clear();
1060 mRelationManager->clear();
1061 mAnnotationManager->clear();
1062 mLayoutManager->clear();
1063 m3DViewsManager->clear();
1064 mBookmarkManager->clear();
1065 mSensorManager->clear();
1066 mViewSettings->reset();
1067 mTimeSettings->reset();
1068 mElevationProperties->reset();
1069 mDisplaySettings->reset();
1070 mGpsSettings->reset();
1071 mSnappingConfig.reset();
1072 mAvoidIntersectionsMode = Qgis::AvoidIntersectionsMode::AllowIntersections;
1075
1076 mMapThemeCollection.reset( new QgsMapThemeCollection( this ) );
1078
1079 mLabelingEngineSettings->clear();
1080
1081 // must happen BEFORE archive reset, because we need to release the hold on any files which
1082 // exists within the archive. Otherwise the archive can't be removed.
1083 releaseHandlesToProjectArchive();
1084
1085 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage() );
1086 mArchive.reset( new QgsArchive() );
1087
1088 // must happen AFTER archive reset, as it will populate a new style database within the new archive
1089 mStyleSettings->reset();
1090
1092
1093 if ( !mIsBeingDeleted )
1094 {
1095 // possibly other signals should also not be thrown on destruction -- e.g. labelEngineSettingsChanged, etc.
1096 emit projectColorsChanged();
1097 }
1098
1099 // reset some default project properties
1100 // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
1101 writeEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/Automatic" ), true );
1102 writeEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DecimalPlaces" ), 2 );
1103
1104 const bool defaultRelativePaths = mSettings.value( QStringLiteral( "/qgis/defaultProjectPathsRelative" ), true ).toBool();
1106
1107 int red = mSettings.value( QStringLiteral( "qgis/default_canvas_color_red" ), 255 ).toInt();
1108 int green = mSettings.value( QStringLiteral( "qgis/default_canvas_color_green" ), 255 ).toInt();
1109 int blue = mSettings.value( QStringLiteral( "qgis/default_canvas_color_blue" ), 255 ).toInt();
1110 setBackgroundColor( QColor( red, green, blue ) );
1111
1112 red = mSettings.value( QStringLiteral( "qgis/default_selection_color_red" ), 255 ).toInt();
1113 green = mSettings.value( QStringLiteral( "qgis/default_selection_color_green" ), 255 ).toInt();
1114 blue = mSettings.value( QStringLiteral( "qgis/default_selection_color_blue" ), 0 ).toInt();
1115 const int alpha = mSettings.value( QStringLiteral( "qgis/default_selection_color_alpha" ), 255 ).toInt();
1116 setSelectionColor( QColor( red, green, blue, alpha ) );
1117
1118 mSnappingConfig.clearIndividualLayerSettings();
1119
1121 mRootGroup->clear();
1122 if ( mMainAnnotationLayer )
1123 mMainAnnotationLayer->reset();
1124
1125 snapSingleBlocker.release();
1126
1127 if ( !mBlockSnappingUpdates )
1128 emit snappingConfigChanged( mSnappingConfig );
1129
1130 setDirty( false );
1131 emit homePathChanged();
1132 emit cleared();
1133}
1134
1135// basically a debugging tool to dump property list values
1136void dump_( const QgsProjectPropertyKey &topQgsPropertyKey )
1137{
1138 QgsDebugMsgLevel( QStringLiteral( "current properties:" ), 3 );
1139 topQgsPropertyKey.dump();
1140}
1141
1170void _getProperties( const QDomDocument &doc, QgsProjectPropertyKey &project_properties )
1171{
1172 const QDomElement propertiesElem = doc.documentElement().firstChildElement( QStringLiteral( "properties" ) );
1173
1174 if ( propertiesElem.isNull() ) // no properties found, so we're done
1175 {
1176 return;
1177 }
1178
1179 const QDomNodeList scopes = propertiesElem.childNodes();
1180
1181 if ( propertiesElem.firstChild().isNull() )
1182 {
1183 QgsDebugError( QStringLiteral( "empty ``properties'' XML tag ... bailing" ) );
1184 return;
1185 }
1186
1187 if ( ! project_properties.readXml( propertiesElem ) )
1188 {
1189 QgsDebugError( QStringLiteral( "Project_properties.readXml() failed" ) );
1190 }
1191}
1192
1199QgsPropertyCollection getDataDefinedServerProperties( const QDomDocument &doc, const QgsPropertiesDefinition &dataDefinedServerPropertyDefinitions )
1200{
1201 QgsPropertyCollection ddServerProperties;
1202 // Read data defined server properties
1203 const QDomElement ddElem = doc.documentElement().firstChildElement( QStringLiteral( "dataDefinedServerProperties" ) );
1204 if ( !ddElem.isNull() )
1205 {
1206 if ( !ddServerProperties.readXml( ddElem, dataDefinedServerPropertyDefinitions ) )
1207 {
1208 QgsDebugError( QStringLiteral( "dataDefinedServerProperties.readXml() failed" ) );
1209 }
1210 }
1211 return ddServerProperties;
1212}
1213
1218static void _getTitle( const QDomDocument &doc, QString &title )
1219{
1220 const QDomElement titleNode = doc.documentElement().firstChildElement( QStringLiteral( "title" ) );
1221
1222 title.clear(); // by default the title will be empty
1223
1224 if ( titleNode.isNull() )
1225 {
1226 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1227 return;
1228 }
1229
1230 if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text
1231 {
1232 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1233 return;
1234 }
1235
1236 const QDomNode titleTextNode = titleNode.firstChild(); // should only have one child
1237
1238 if ( !titleTextNode.isText() )
1239 {
1240 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1241 return;
1242 }
1243
1244 const QDomText titleText = titleTextNode.toText();
1245
1246 title = titleText.data();
1247
1248}
1249
1250static void readProjectFileMetadata( const QDomDocument &doc, QString &lastUser, QString &lastUserFull, QDateTime &lastSaveDateTime )
1251{
1252 const QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
1253
1254 if ( !nl.count() )
1255 {
1256 QgsDebugError( QStringLiteral( "unable to find qgis element" ) );
1257 return;
1258 }
1259
1260 const QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1261
1262 const QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1263 lastUser = qgisElement.attribute( QStringLiteral( "saveUser" ), QString() );
1264 lastUserFull = qgisElement.attribute( QStringLiteral( "saveUserFull" ), QString() );
1265 lastSaveDateTime = QDateTime::fromString( qgisElement.attribute( QStringLiteral( "saveDateTime" ), QString() ), Qt::ISODate );
1266}
1267
1268QgsProjectVersion getVersion( const QDomDocument &doc )
1269{
1270 const QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
1271
1272 if ( !nl.count() )
1273 {
1274 QgsDebugError( QStringLiteral( " unable to find qgis element in project file" ) );
1275 return QgsProjectVersion( 0, 0, 0, QString() );
1276 }
1277
1278 const QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1279
1280 const QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1281 QgsProjectVersion projectVersion( qgisElement.attribute( QStringLiteral( "version" ) ) );
1282 return projectVersion;
1283}
1284
1286{
1288
1289 return mSnappingConfig;
1290}
1291
1293{
1295
1296 if ( mSnappingConfig == snappingConfig )
1297 return;
1298
1299 mSnappingConfig = snappingConfig;
1300 setDirty( true );
1301 emit snappingConfigChanged( mSnappingConfig );
1302}
1303
1305{
1307
1308 if ( mAvoidIntersectionsMode == mode )
1309 return;
1310
1311 mAvoidIntersectionsMode = mode;
1313}
1314
1315static QgsMapLayer::ReadFlags projectFlagsToLayerReadFlags( Qgis::ProjectReadFlags projectReadFlags, Qgis::ProjectFlags projectFlags )
1316{
1318 if ( projectReadFlags & Qgis::ProjectReadFlag::DontResolveLayers )
1320 // Propagate trust layer metadata flag
1321 if ( ( projectFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics ) || ( projectReadFlags & Qgis::ProjectReadFlag::TrustLayerMetadata ) )
1323 // Propagate open layers in read-only mode
1324 if ( ( projectReadFlags & Qgis::ProjectReadFlag::ForceReadOnlyLayers ) )
1325 layerFlags |= QgsMapLayer::FlagForceReadOnly;
1326
1327 return layerFlags;
1328}
1329
1331{
1332 QString layerId;
1333 QString provider;
1334 QString dataSource;
1337 QDomElement layerElement;
1338};
1339
1340void QgsProject::preloadProviders( const QVector<QDomNode> &parallelLayerNodes,
1341 const QgsReadWriteContext &context,
1342 QMap<QString, QgsDataProvider *> &loadedProviders,
1343 QgsMapLayer::ReadFlags layerReadFlags,
1344 int totalProviderCount )
1345{
1346 int i = 0;
1347 QEventLoop loop;
1348
1349 QMap<QString, LayerToLoad> layersToLoad;
1350
1351 for ( const QDomNode &node : parallelLayerNodes )
1352 {
1353 LayerToLoad layerToLoad;
1354
1355 const QDomElement layerElement = node.toElement();
1356 layerToLoad.layerElement = layerElement;
1357 layerToLoad.layerId = layerElement.namedItem( QStringLiteral( "id" ) ).toElement().text();
1358 layerToLoad.provider = layerElement.namedItem( QStringLiteral( "provider" ) ).toElement().text();
1359 layerToLoad.dataSource = layerElement.namedItem( QStringLiteral( "datasource" ) ).toElement().text();
1360
1361 layerToLoad.dataSource = QgsProviderRegistry::instance()->relativeToAbsoluteUri( layerToLoad.provider, layerToLoad.dataSource, context );
1362
1363 layerToLoad.options = QgsDataProvider::ProviderOptions( {context.transformContext()} );
1364 layerToLoad.flags = QgsMapLayer::providerReadFlags( node, layerReadFlags );
1365
1366 // Requesting credential from worker thread could lead to deadlocks because the main thread is waiting for worker thread to fininsh
1367 layerToLoad.flags.setFlag( QgsDataProvider::SkipCredentialsRequest, true );
1368 layerToLoad.flags.setFlag( QgsDataProvider::ParallelThreadLoading, true );
1369
1370 layersToLoad.insert( layerToLoad.layerId, layerToLoad );
1371 }
1372
1373 while ( !layersToLoad.isEmpty() )
1374 {
1375 const QList<LayerToLoad> layersToAttemptInParallel = layersToLoad.values();
1376 QString layerToAttemptInMainThread;
1377
1378 QHash<QString, QgsRunnableProviderCreator *> runnables;
1379 QThreadPool threadPool;
1380 threadPool.setMaxThreadCount( QgsSettingsRegistryCore::settingsLayerParallelLoadingMaxCount->value() );
1381
1382 for ( const LayerToLoad &lay : layersToAttemptInParallel )
1383 {
1384 QgsRunnableProviderCreator *run = new QgsRunnableProviderCreator( lay.layerId, lay.provider, lay.dataSource, lay.options, lay.flags );
1385 runnables.insert( lay.layerId, run );
1386
1387 QObject::connect( run, &QgsRunnableProviderCreator::providerCreated, run, [&]( bool isValid, const QString & layId )
1388 {
1389 if ( isValid )
1390 {
1391 layersToLoad.remove( layId );
1392 i++;
1393 QgsRunnableProviderCreator *finishedRun = runnables.value( layId, nullptr );
1394 Q_ASSERT( finishedRun );
1395
1396 std::unique_ptr<QgsDataProvider> provider( finishedRun->dataProvider() );
1397 Q_ASSERT( provider && provider->isValid() );
1398
1399 loadedProviders.insert( layId, provider.release() );
1400 emit layerLoaded( i, totalProviderCount );
1401 }
1402 else
1403 {
1404 if ( layerToAttemptInMainThread.isEmpty() )
1405 layerToAttemptInMainThread = layId;
1406 threadPool.clear(); //we have to stop all loading provider to try this layer in main thread and maybe have credentials
1407 }
1408
1409 if ( i == parallelLayerNodes.count() || !isValid )
1410 loop.quit();
1411 } );
1412 threadPool.start( run );
1413 }
1414 loop.exec();
1415
1416 threadPool.waitForDone(); // to be sure all threads are finished
1417
1418 qDeleteAll( runnables );
1419
1420 // 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
1421 auto it = layersToLoad.find( layerToAttemptInMainThread );
1422 if ( it != layersToLoad.end() )
1423 {
1424 std::unique_ptr<QgsDataProvider> provider;
1425 QString layerId;
1426 {
1427 const LayerToLoad &lay = it.value();
1428 QgsDataProvider::ReadFlags providerFlags = lay.flags;
1429 providerFlags.setFlag( QgsDataProvider::SkipCredentialsRequest, false );
1430 providerFlags.setFlag( QgsDataProvider::ParallelThreadLoading, false );
1431 QgsScopedRuntimeProfile profile( "Create data providers/" + lay.layerId, QStringLiteral( "projectload" ) );
1432 provider.reset( QgsProviderRegistry::instance()->createProvider( lay.provider, lay.dataSource, lay.options, providerFlags ) );
1433 i++;
1434 if ( provider && provider->isValid() )
1435 {
1436 emit layerLoaded( i, totalProviderCount );
1437 }
1438 layerId = lay.layerId;
1439 layersToLoad.erase( it );
1440 // can't access "lay" anymore -- it's now been freed
1441 }
1442 loadedProviders.insert( layerId, provider.release() );
1443 }
1444
1445 // if there still are some not loaded providers or some invalid in parallel thread we start again
1446 }
1447
1448}
1449
1450void QgsProject::releaseHandlesToProjectArchive()
1451{
1452 mStyleSettings->removeProjectStyle();
1453}
1454
1455bool QgsProject::_getMapLayers( const QDomDocument &doc, QList<QDomNode> &brokenNodes, Qgis::ProjectReadFlags flags )
1456{
1458
1459 // Layer order is set by the restoring the legend settings from project file.
1460 // This is done on the 'readProject( ... )' signal
1461
1462 QDomElement layerElement = doc.documentElement().firstChildElement( QStringLiteral( "projectlayers" ) ).firstChildElement( QStringLiteral( "maplayer" ) );
1463
1464 // process the map layer nodes
1465
1466 if ( layerElement.isNull() ) // if we have no layers to process, bail
1467 {
1468 return true; // Decided to return "true" since it's
1469 // possible for there to be a project with no
1470 // layers; but also, more imporantly, this
1471 // would cause the tests/qgsproject to fail
1472 // since the test suite doesn't currently
1473 // support test layers
1474 }
1475
1476 bool returnStatus = true;
1477 int numLayers = 0;
1478
1479 while ( ! layerElement.isNull() )
1480 {
1481 numLayers++;
1482 layerElement = layerElement.nextSiblingElement( QStringLiteral( "maplayer" ) );
1483 }
1484
1485 // order layers based on their dependencies
1486 QgsScopedRuntimeProfile profile( tr( "Sorting layers" ), QStringLiteral( "projectload" ) );
1487 const QgsLayerDefinition::DependencySorter depSorter( doc );
1488 if ( depSorter.hasCycle() )
1489 return false;
1490
1491 // Missing a dependency? We still load all the layers, otherwise the project is completely broken!
1492 if ( depSorter.hasMissingDependency() )
1493 returnStatus = false;
1494
1495 emit layerLoaded( 0, numLayers );
1496
1497 const QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
1498 const int totalLayerCount = sortedLayerNodes.count();
1499
1500 QVector<QDomNode> parallelLoading;
1501 QMap<QString, QgsDataProvider *> loadedProviders;
1502
1504 {
1505 profile.switchTask( tr( "Load providers in parallel" ) );
1506 for ( const QDomNode &node : sortedLayerNodes )
1507 {
1508 const QDomElement element = node.toElement();
1509 if ( element.attribute( QStringLiteral( "embedded" ) ) != QLatin1String( "1" ) )
1510 {
1511 const QString layerId = node.namedItem( QStringLiteral( "id" ) ).toElement().text();
1512 if ( !depSorter.isLayerDependent( layerId ) )
1513 {
1514 const QDomNode mnl = element.namedItem( QStringLiteral( "provider" ) );
1515 const QDomElement mne = mnl.toElement();
1516 const QString provider = mne.text();
1518 if ( meta && meta->providerCapabilities().testFlag( QgsProviderMetadata::ParallelCreateProvider ) )
1519 {
1520 parallelLoading.append( node );
1521 continue;
1522 }
1523 }
1524 }
1525 }
1526
1527 QgsReadWriteContext context;
1528 context.setPathResolver( pathResolver() );
1529 if ( !parallelLoading.isEmpty() )
1530 preloadProviders( parallelLoading, context, loadedProviders, projectFlagsToLayerReadFlags( flags, mFlags ), sortedLayerNodes.count() );
1531 }
1532
1533 int i = loadedProviders.count();
1534 for ( const QDomNode &node : std::as_const( sortedLayerNodes ) )
1535 {
1536 const QDomElement element = node.toElement();
1537 const QString name = translate( QStringLiteral( "project:layers:%1" ).arg( node.namedItem( QStringLiteral( "id" ) ).toElement().text() ), node.namedItem( QStringLiteral( "layername" ) ).toElement().text() );
1538 if ( !name.isNull() )
1539 emit loadingLayer( tr( "Loading layer %1" ).arg( name ) );
1540
1541 profile.switchTask( name );
1542 if ( element.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
1543 {
1544 createEmbeddedLayer( element.attribute( QStringLiteral( "id" ) ), readPath( element.attribute( QStringLiteral( "project" ) ) ), brokenNodes, true, flags );
1545 }
1546 else
1547 {
1548 QgsReadWriteContext context;
1549 context.setPathResolver( pathResolver() );
1550 context.setProjectTranslator( this );
1552 QString layerId = element.namedItem( QStringLiteral( "id" ) ).toElement().text();
1553
1554 if ( !addLayer( element, brokenNodes, context, flags, loadedProviders.take( layerId ) ) )
1555 {
1556 returnStatus = false;
1557 }
1558 const auto messages = context.takeMessages();
1559 if ( !messages.isEmpty() )
1560 {
1561 emit loadingLayerMessageReceived( tr( "Loading layer %1" ).arg( name ), messages );
1562 }
1563 }
1564 emit layerLoaded( i + 1, totalLayerCount );
1565 i++;
1566 }
1567
1568 return returnStatus;
1569}
1570
1571bool QgsProject::addLayer( const QDomElement &layerElem,
1572 QList<QDomNode> &brokenNodes,
1573 QgsReadWriteContext &context,
1575 QgsDataProvider *provider )
1576{
1578
1579 const QString type = layerElem.attribute( QStringLiteral( "type" ) );
1580 QgsDebugMsgLevel( "Layer type is " + type, 4 );
1581 std::unique_ptr<QgsMapLayer> mapLayer;
1582
1583 QgsScopedRuntimeProfile profile( tr( "Create layer" ), QStringLiteral( "projectload" ) );
1584
1585 bool ok = false;
1586 const Qgis::LayerType layerType( QgsMapLayerFactory::typeFromString( type, ok ) );
1587 if ( !ok )
1588 {
1589 QgsDebugError( QStringLiteral( "Unknown layer type \"%1\"" ).arg( type ) );
1590 return false;
1591 }
1592
1593 switch ( layerType )
1594 {
1596 mapLayer = std::make_unique<QgsVectorLayer>();
1597 break;
1598
1600 mapLayer = std::make_unique<QgsRasterLayer>();
1601 break;
1602
1604 mapLayer = std::make_unique<QgsMeshLayer>();
1605 break;
1606
1608 mapLayer = std::make_unique<QgsVectorTileLayer>();
1609 break;
1610
1612 mapLayer = std::make_unique<QgsPointCloudLayer>();
1613 break;
1614
1616 mapLayer = std::make_unique<QgsTiledSceneLayer>();
1617 break;
1618
1620 {
1621 const QString typeName = layerElem.attribute( QStringLiteral( "name" ) );
1622 mapLayer.reset( QgsApplication::pluginLayerRegistry()->createLayer( typeName ) );
1623 break;
1624 }
1625
1627 {
1628 const QgsAnnotationLayer::LayerOptions options( mTransformContext );
1629 mapLayer = std::make_unique<QgsAnnotationLayer>( QString(), options );
1630 break;
1631 }
1632
1634 {
1635 const QgsGroupLayer::LayerOptions options( mTransformContext );
1636 mapLayer = std::make_unique<QgsGroupLayer>( QString(), options );
1637 break;
1638 }
1639 }
1640
1641 if ( !mapLayer )
1642 {
1643 QgsDebugError( QStringLiteral( "Unable to create layer" ) );
1644 return false;
1645 }
1646
1647 Q_CHECK_PTR( mapLayer ); // NOLINT
1648
1649 // This is tricky: to avoid a leak we need to check if the layer was already in the store
1650 // because if it was, the newly created layer will not be added to the store and it would leak.
1651 const QString layerId { layerElem.namedItem( QStringLiteral( "id" ) ).toElement().text() };
1652 Q_ASSERT( ! layerId.isEmpty() );
1653 const bool layerWasStored { layerStore()->mapLayer( layerId ) != nullptr };
1654
1655 // have the layer restore state that is stored in Dom node
1656 QgsMapLayer::ReadFlags layerFlags = projectFlagsToLayerReadFlags( flags, mFlags );
1657
1658 profile.switchTask( tr( "Load layer source" ) );
1659 const bool layerIsValid = mapLayer->readLayerXml( layerElem, context, layerFlags, provider ) && mapLayer->isValid();
1660
1661 // apply specific settings to vector layer
1662 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1663 {
1665 if ( vl->dataProvider() )
1666 {
1668 vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, evaluateDefaultValues );
1669 }
1670 }
1671
1672 profile.switchTask( tr( "Add layer to project" ) );
1673 QList<QgsMapLayer *> newLayers;
1674 newLayers << mapLayer.get();
1675 if ( layerIsValid || flags & Qgis::ProjectReadFlag::DontResolveLayers )
1676 {
1677 emit readMapLayer( mapLayer.get(), layerElem );
1678 addMapLayers( newLayers );
1679 // Try to resolve references here (this is necessary to set up joined fields that will be possibly used by
1680 // virtual layers that point to this layer's joined field in their query otherwise they won't be valid ),
1681 // a second attempt to resolve references will be done after all layers are loaded
1682 // see https://github.com/qgis/QGIS/issues/46834
1683 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1684 {
1685 vLayer->joinBuffer()->resolveReferences( this );
1686 }
1687 }
1688 else
1689 {
1690 // It's a bad layer: do not add to legend (the user will decide if she wants to do so)
1691 addMapLayers( newLayers, false );
1692 newLayers.first();
1693 QgsDebugError( "Unable to load " + type + " layer" );
1694 brokenNodes.push_back( layerElem );
1695 }
1696
1697 const bool wasEditable = layerElem.attribute( QStringLiteral( "editable" ), QStringLiteral( "0" ) ).toInt();
1698 if ( wasEditable )
1699 {
1700 mapLayer->setCustomProperty( QStringLiteral( "_layer_was_editable" ), true );
1701 }
1702 else
1703 {
1704 mapLayer->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
1705 }
1706
1707 // It should be safe to delete the layer now if layer was stored, because all the store
1708 // had to to was to reset the data source in case the validity changed.
1709 if ( ! layerWasStored )
1710 {
1711 mapLayer.release();
1712 }
1713
1714 return layerIsValid;
1715}
1716
1717bool QgsProject::read( const QString &filename, Qgis::ProjectReadFlags flags )
1718{
1720
1721 mFile.setFileName( filename );
1722 mCachedHomePath.clear();
1723 mProjectScope.reset();
1724
1725 return read( flags );
1726}
1727
1729{
1731
1732 const QString filename = mFile.fileName();
1733 bool returnValue;
1734
1735 if ( QgsProjectStorage *storage = projectStorage() )
1736 {
1737 QTemporaryFile inDevice;
1738 if ( !inDevice.open() )
1739 {
1740 setError( tr( "Unable to open %1" ).arg( inDevice.fileName() ) );
1741 return false;
1742 }
1743
1744 QgsReadWriteContext context;
1745 context.setProjectTranslator( this );
1746 if ( !storage->readProject( filename, &inDevice, context ) )
1747 {
1748 QString err = tr( "Unable to open %1" ).arg( filename );
1749 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
1750 if ( !messages.isEmpty() )
1751 err += QStringLiteral( "\n\n" ) + messages.last().message();
1752 setError( err );
1753 return false;
1754 }
1755 returnValue = unzip( inDevice.fileName(), flags ); // calls setError() if returning false
1756 }
1757 else
1758 {
1759 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
1760 {
1761 returnValue = unzip( mFile.fileName(), flags );
1762 }
1763 else
1764 {
1765 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
1766 const QFileInfo finfo( mFile.fileName() );
1767 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
1768 if ( QFile( attachmentsZip ).exists() )
1769 {
1770 std::unique_ptr<QgsArchive> archive( new QgsArchive() );
1771 if ( archive->unzip( attachmentsZip ) )
1772 {
1773 releaseHandlesToProjectArchive();
1774 mArchive = std::move( archive );
1775 }
1776 }
1777 returnValue = readProjectFile( mFile.fileName(), flags );
1778 }
1779
1780 //on translation we should not change the filename back
1781 if ( !mTranslator )
1782 {
1783 mFile.setFileName( filename );
1784 mCachedHomePath.clear();
1785 mProjectScope.reset();
1786 }
1787 else
1788 {
1789 //but delete the translator
1790 mTranslator.reset( nullptr );
1791 }
1792 }
1793 emit homePathChanged();
1794 return returnValue;
1795}
1796
1797bool QgsProject::readProjectFile( const QString &filename, Qgis::ProjectReadFlags flags )
1798{
1800
1801 // avoid multiple emission of snapping updated signals
1802 ScopedIntIncrementor snapSignalBlock( &mBlockSnappingUpdates );
1803
1804 QFile projectFile( filename );
1805 clearError();
1806
1807 QgsApplication::profiler()->clear( QStringLiteral( "projectload" ) );
1808 QgsScopedRuntimeProfile profile( tr( "Setting up translations" ), QStringLiteral( "projectload" ) );
1809
1810 const QString localeFileName = QStringLiteral( "%1_%2" ).arg( QFileInfo( projectFile.fileName() ).baseName(), QgsApplication::settingsLocaleUserLocale->value() );
1811
1812 if ( QFile( QStringLiteral( "%1/%2.qm" ).arg( QFileInfo( projectFile.fileName() ).absolutePath(), localeFileName ) ).exists() )
1813 {
1814 mTranslator.reset( new QTranslator() );
1815 ( void )mTranslator->load( localeFileName, QFileInfo( projectFile.fileName() ).absolutePath() );
1816 }
1817
1818 profile.switchTask( tr( "Reading project file" ) );
1819 std::unique_ptr<QDomDocument> doc( new QDomDocument( QStringLiteral( "qgis" ) ) );
1820
1821 if ( !projectFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
1822 {
1823 projectFile.close();
1824
1825 setError( tr( "Unable to open %1" ).arg( projectFile.fileName() ) );
1826
1827 return false;
1828 }
1829
1830 QTextStream textStream( &projectFile );
1831#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1832 textStream.setCodec( "UTF-8" );
1833#endif
1834 QString projectString = textStream.readAll();
1835 projectFile.close();
1836
1837 for ( int i = 0; i < 32; i++ )
1838 {
1839 if ( i == 9 || i == 10 || i == 13 )
1840 {
1841 continue;
1842 }
1843 projectString.replace( QChar( i ), QStringLiteral( "%1%2%1" ).arg( FONTMARKER_CHR_FIX, QString::number( i ) ) );
1844 }
1845
1846 // location of problem associated with errorMsg
1847 int line, column;
1848 QString errorMsg;
1849 if ( !doc->setContent( projectString, &errorMsg, &line, &column ) )
1850 {
1851 const QString errorString = tr( "Project file read error in file %1: %2 at line %3 column %4" )
1852 .arg( projectFile.fileName(), errorMsg ).arg( line ).arg( column );
1853 QgsDebugError( errorString );
1854 setError( errorString );
1855
1856 return false;
1857 }
1858
1859 projectFile.close();
1860
1861 QgsDebugMsgLevel( "Opened document " + projectFile.fileName(), 2 );
1862
1863 // get project version string, if any
1864 const QgsProjectVersion fileVersion = getVersion( *doc );
1865 const QgsProjectVersion thisVersion( Qgis::version() );
1866
1867 profile.switchTask( tr( "Updating project file" ) );
1868 if ( thisVersion > fileVersion )
1869 {
1870 const bool isOlderMajorVersion = fileVersion.majorVersion() < thisVersion.majorVersion();
1871
1872 if ( isOlderMajorVersion )
1873 {
1874 QgsLogger::warning( "Loading a file that was saved with an older "
1875 "version of qgis (saved in " + fileVersion.text() +
1876 ", loaded in " + Qgis::version() +
1877 "). Problems may occur." );
1878 }
1879
1880 QgsProjectFileTransform projectFile( *doc, fileVersion );
1881
1882 // Shows a warning when an old project file is read.
1884 emit oldProjectVersionWarning( fileVersion.text() );
1886 emit readVersionMismatchOccurred( fileVersion.text() );
1887
1888 projectFile.updateRevision( thisVersion );
1889 }
1890 else if ( fileVersion > thisVersion )
1891 {
1892 QgsLogger::warning( "Loading a file that was saved with a newer "
1893 "version of qgis (saved in " + fileVersion.text() +
1894 ", loaded in " + Qgis::version() +
1895 "). Problems may occur." );
1896
1897 emit readVersionMismatchOccurred( fileVersion.text() );
1898 }
1899
1900 // start new project, just keep the file name and auxiliary storage
1901 profile.switchTask( tr( "Creating auxiliary storage" ) );
1902 const QString fileName = mFile.fileName();
1903
1904
1905 // NOTE [ND] -- I suspect this is wrong, as the archive may contain any number of non-auxiliary
1906 // storage related files from the previously loaded project.
1907 std::unique_ptr<QgsAuxiliaryStorage> aStorage = std::move( mAuxiliaryStorage );
1908 std::unique_ptr<QgsArchive> archive = std::move( mArchive );
1909
1910 clear();
1911 // this is ugly, but clear() will have created a new archive and started populating it. We
1912 // need to release handles to this archive now as the subsequent call to move will need
1913 // to delete it, and requires free access to do so.
1914 releaseHandlesToProjectArchive();
1915
1916 mAuxiliaryStorage = std::move( aStorage );
1917 mArchive = std::move( archive );
1918
1919
1920
1921 mFile.setFileName( fileName );
1922 mCachedHomePath.clear();
1923 mProjectScope.reset();
1924 mSaveVersion = fileVersion;
1925
1926 // now get any properties
1927 profile.switchTask( tr( "Reading properties" ) );
1928 _getProperties( *doc, mProperties );
1929
1930 // now get the data defined server properties
1931 mDataDefinedServerProperties = getDataDefinedServerProperties( *doc, dataDefinedServerPropertyDefinitions() );
1932
1933 QgsDebugMsgLevel( QString::number( mProperties.count() ) + " properties read", 2 );
1934
1935#if 0
1936 dump_( mProperties );
1937#endif
1938
1939 // get older style project title
1940 QString oldTitle;
1941 _getTitle( *doc, oldTitle );
1942
1943 readProjectFileMetadata( *doc, mSaveUser, mSaveUserFull, mSaveDateTime );
1944
1945 const QDomNodeList homePathNl = doc->elementsByTagName( QStringLiteral( "homePath" ) );
1946 if ( homePathNl.count() > 0 )
1947 {
1948 const QDomElement homePathElement = homePathNl.at( 0 ).toElement();
1949 const QString homePath = homePathElement.attribute( QStringLiteral( "path" ) );
1950 if ( !homePath.isEmpty() )
1952 }
1953 else
1954 {
1955 emit homePathChanged();
1956 }
1957
1958 const QColor backgroundColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), 255 ),
1959 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), 255 ),
1960 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), 255 ) );
1962 const QColor selectionColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), 255 ),
1963 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), 255 ),
1964 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), 255 ),
1965 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), 255 ) );
1967
1968
1969 const QString distanceUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QString() );
1970 if ( !distanceUnitString.isEmpty() )
1971 setDistanceUnits( QgsUnitTypes::decodeDistanceUnit( distanceUnitString ) );
1972
1973 const QString areaUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QString() );
1974 if ( !areaUnitString.isEmpty() )
1975 setAreaUnits( QgsUnitTypes::decodeAreaUnit( areaUnitString ) );
1976
1977 QgsReadWriteContext context;
1978 context.setPathResolver( pathResolver() );
1979 context.setProjectTranslator( this );
1980
1981 //crs
1983 if ( readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), 0 ) )
1984 {
1985 // first preference - dedicated projectCrs node
1986 const QDomNode srsNode = doc->documentElement().namedItem( QStringLiteral( "projectCrs" ) );
1987 if ( !srsNode.isNull() )
1988 {
1989 projectCrs.readXml( srsNode );
1990 }
1991
1992 if ( !projectCrs.isValid() )
1993 {
1994 const QString projCrsString = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSProj4String" ) );
1995 const long currentCRS = readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSID" ), -1 );
1996 const QString authid = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCrs" ) );
1997
1998 // authid should be prioritized over all
1999 const bool isUserAuthId = authid.startsWith( QLatin1String( "USER:" ), Qt::CaseInsensitive );
2000 if ( !authid.isEmpty() && !isUserAuthId )
2001 projectCrs = QgsCoordinateReferenceSystem( authid );
2002
2003 // try the CRS
2004 if ( !projectCrs.isValid() && currentCRS >= 0 )
2005 {
2006 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2007 }
2008
2009 // if that didn't produce a match, try the proj.4 string
2010 if ( !projCrsString.isEmpty() && ( authid.isEmpty() || isUserAuthId ) && ( !projectCrs.isValid() || projectCrs.toProj() != projCrsString ) )
2011 {
2012 projectCrs = QgsCoordinateReferenceSystem::fromProj( projCrsString );
2013 }
2014
2015 // last just take the given id
2016 if ( !projectCrs.isValid() )
2017 {
2018 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2019 }
2020 }
2021 }
2022 mCrs = projectCrs;
2023
2024 QStringList datumErrors;
2025 if ( !mTransformContext.readXml( doc->documentElement(), context, datumErrors ) && !datumErrors.empty() )
2026 {
2027 emit missingDatumTransforms( datumErrors );
2028 }
2030
2031 // map shading
2032 const QDomNode elevationShadingNode = doc->documentElement().namedItem( QStringLiteral( "elevation-shading-renderer" ) );
2033 if ( !elevationShadingNode.isNull() )
2034 {
2035 mElevationShadingRenderer.readXml( elevationShadingNode.toElement(), context );
2036 }
2038
2039
2040 //add variables defined in project file - do this early in the reading cycle, as other components
2041 //(e.g. layouts) may depend on these variables
2042 const QStringList variableNames = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ) );
2043 const QStringList variableValues = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ) );
2044
2045 mCustomVariables.clear();
2046 if ( variableNames.length() == variableValues.length() )
2047 {
2048 for ( int i = 0; i < variableNames.length(); ++i )
2049 {
2050 mCustomVariables.insert( variableNames.at( i ), variableValues.at( i ) );
2051 }
2052 }
2053 else
2054 {
2055 QgsMessageLog::logMessage( tr( "Project Variables Invalid" ), tr( "The project contains invalid variable settings." ) );
2056 }
2057
2058 QDomElement element = doc->documentElement().firstChildElement( QStringLiteral( "projectMetadata" ) );
2059
2060 if ( !element.isNull() )
2061 {
2062 mMetadata.readMetadataXml( element );
2063 }
2064 else
2065 {
2066 // older project, no metadata => remove auto generated metadata which is populated on QgsProject::clear()
2067 mMetadata = QgsProjectMetadata();
2068 }
2069 if ( mMetadata.title().isEmpty() && !oldTitle.isEmpty() )
2070 {
2071 // upgrade older title storage to storing within project metadata.
2072 mMetadata.setTitle( oldTitle );
2073 }
2074 emit metadataChanged();
2075
2076 // Transaction mode
2077 element = doc->documentElement().firstChildElement( QStringLiteral( "transaction" ) );
2078 if ( !element.isNull() )
2079 {
2080 mTransactionMode = qgsEnumKeyToValue( element.attribute( QStringLiteral( "mode" ) ), Qgis::TransactionMode::Disabled );
2081 }
2082 else
2083 {
2084 // maybe older project => try read autotransaction
2085 element = doc->documentElement().firstChildElement( QStringLiteral( "autotransaction" ) );
2086 if ( ! element.isNull() )
2087 {
2088 mTransactionMode = static_cast<Qgis::TransactionMode>( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() );
2089 }
2090 }
2091
2092 // read the layer tree from project file
2093 profile.switchTask( tr( "Loading layer tree" ) );
2094 mRootGroup->setCustomProperty( QStringLiteral( "loading" ), 1 );
2095
2096 QDomElement layerTreeElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
2097 if ( !layerTreeElem.isNull() )
2098 {
2099 // Use a temporary tree to read the nodes to prevent signals being delivered to the models
2100 QgsLayerTree tempTree;
2101 tempTree.readChildrenFromXml( layerTreeElem, context );
2102 mRootGroup->insertChildNodes( -1, tempTree.abandonChildren() );
2103 }
2104 else
2105 {
2106 QgsLayerTreeUtils::readOldLegend( mRootGroup, doc->documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
2107 }
2108
2109 mLayerTreeRegistryBridge->setEnabled( false );
2110
2111 // get the map layers
2112 profile.switchTask( tr( "Reading map layers" ) );
2113
2114 loadProjectFlags( doc.get() );
2115
2116 QList<QDomNode> brokenNodes;
2117 const bool clean = _getMapLayers( *doc, brokenNodes, flags );
2118
2119 // review the integrity of the retrieved map layers
2120 if ( !clean && !( flags & Qgis::ProjectReadFlag::DontResolveLayers ) )
2121 {
2122 QgsDebugError( QStringLiteral( "Unable to get map layers from project file." ) );
2123
2124 if ( !brokenNodes.isEmpty() )
2125 {
2126 QgsDebugError( "there are " + QString::number( brokenNodes.size() ) + " broken layers" );
2127 }
2128
2129 // we let a custom handler decide what to do with missing layers
2130 // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
2131 mBadLayerHandler->handleBadLayers( brokenNodes );
2132 }
2133
2134 mMainAnnotationLayer->readLayerXml( doc->documentElement().firstChildElement( QStringLiteral( "main-annotation-layer" ) ), context );
2135 mMainAnnotationLayer->setTransformContext( mTransformContext );
2136
2137 // load embedded groups and layers
2138 profile.switchTask( tr( "Loading embedded layers" ) );
2139 loadEmbeddedNodes( mRootGroup, flags );
2140
2141 // Resolve references to other layers
2142 // Needs to be done here once all dependent layers are loaded
2143 profile.switchTask( tr( "Resolving layer references" ) );
2144 QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
2145 for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
2146 {
2147 it.value()->resolveReferences( this );
2148 }
2149
2150 mLayerTreeRegistryBridge->setEnabled( true );
2151
2152 // now that layers are loaded, we can resolve layer tree's references to the layers
2153 profile.switchTask( tr( "Resolving references" ) );
2154 mRootGroup->resolveReferences( this );
2155
2156 // we need to migrate old fashion designed QgsSymbolLayerReference to new ones
2157 if ( QgsProjectVersion( 3, 28, 0 ) > mSaveVersion )
2158 {
2162 }
2163
2164 if ( !layerTreeElem.isNull() )
2165 {
2166 mRootGroup->readLayerOrderFromXml( layerTreeElem );
2167 }
2168
2169 // Load pre 3.0 configuration
2170 const QDomElement layerTreeCanvasElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-canvas" ) );
2171 if ( !layerTreeCanvasElem.isNull( ) )
2172 {
2173 mRootGroup->readLayerOrderFromXml( layerTreeCanvasElem );
2174 }
2175
2176 // Convert pre 3.4 to create layers flags
2177 if ( QgsProjectVersion( 3, 4, 0 ) > mSaveVersion )
2178 {
2179 const QStringList requiredLayerIds = readListEntry( QStringLiteral( "RequiredLayers" ), QStringLiteral( "Layers" ) );
2180 for ( const QString &layerId : requiredLayerIds )
2181 {
2182 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2183 {
2184 layer->setFlags( layer->flags() & ~QgsMapLayer::Removable );
2185 }
2186 }
2187 const QStringList disabledLayerIds = readListEntry( QStringLiteral( "Identify" ), QStringLiteral( "/disabledLayers" ) );
2188 for ( const QString &layerId : disabledLayerIds )
2189 {
2190 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2191 {
2192 layer->setFlags( layer->flags() & ~QgsMapLayer::Identifiable );
2193 }
2194 }
2195 }
2196
2197 // Convert pre 3.26 default styles
2198 if ( QgsProjectVersion( 3, 26, 0 ) > mSaveVersion )
2199 {
2200 // Convert default symbols
2201 QString styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Marker" ) );
2202 if ( !styleName.isEmpty() )
2203 {
2204 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2206 }
2207 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Line" ) );
2208 if ( !styleName.isEmpty() )
2209 {
2210 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2212 }
2213 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Fill" ) );
2214 if ( !styleName.isEmpty() )
2215 {
2216 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2218 }
2219 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ) );
2220 if ( !styleName.isEmpty() )
2221 {
2222 std::unique_ptr<QgsColorRamp> colorRamp( QgsStyle::defaultStyle()->colorRamp( styleName ) );
2223 styleSettings()->setDefaultColorRamp( colorRamp.get() );
2224 }
2225
2226 // Convert randomize default symbol fill color
2227 styleSettings()->setRandomizeDefaultSymbolColor( readBoolEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ), true ) );
2228
2229 // Convert default symbol opacity
2230 double opacity = 1.0;
2231 bool ok = false;
2232 // upgrade old setting
2233 double alpha = readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/AlphaInt" ), 255, &ok );
2234 if ( ok )
2235 opacity = alpha / 255.0;
2236 double newOpacity = readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Opacity" ), 1.0, &ok );
2237 if ( ok )
2238 opacity = newOpacity;
2240
2241 // Cleanup
2242 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Marker" ) );
2243 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Line" ) );
2244 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Fill" ) );
2245 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ) );
2246 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ) );
2247 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/AlphaInt" ) );
2248 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Opacity" ) );
2249 }
2250
2251 // After bad layer handling we might still have invalid layers,
2252 // store them in case the user wanted to handle them later
2253 // or wanted to pass them through when saving
2255 {
2256 profile.switchTask( tr( "Storing original layer properties" ) );
2257 QgsLayerTreeUtils::storeOriginalLayersProperties( mRootGroup, doc.get() );
2258 }
2259
2260 mRootGroup->removeCustomProperty( QStringLiteral( "loading" ) );
2261
2262 profile.switchTask( tr( "Loading map themes" ) );
2263 mMapThemeCollection.reset( new QgsMapThemeCollection( this ) );
2265 mMapThemeCollection->readXml( *doc );
2266
2267 profile.switchTask( tr( "Loading label settings" ) );
2268 mLabelingEngineSettings->readSettingsFromProject( this );
2270
2271 profile.switchTask( tr( "Loading annotations" ) );
2272 mAnnotationManager->readXml( doc->documentElement(), context );
2274 {
2275 profile.switchTask( tr( "Loading layouts" ) );
2276 mLayoutManager->readXml( doc->documentElement(), *doc );
2277 }
2278
2280 {
2281 profile.switchTask( tr( "Loading 3D Views" ) );
2282 m3DViewsManager->readXml( doc->documentElement(), *doc );
2283 }
2284
2285 profile.switchTask( tr( "Loading bookmarks" ) );
2286 mBookmarkManager->readXml( doc->documentElement(), *doc );
2287
2288 profile.switchTask( tr( "Loading sensors" ) );
2289 mSensorManager->readXml( doc->documentElement(), *doc );
2290
2291 // reassign change dependencies now that all layers are loaded
2292 QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2293 for ( QMap<QString, QgsMapLayer *>::iterator it = existingMaps.begin(); it != existingMaps.end(); ++it )
2294 {
2295 it.value()->setDependencies( it.value()->dependencies() );
2296 }
2297
2298 profile.switchTask( tr( "Loading snapping settings" ) );
2299 mSnappingConfig.readProject( *doc );
2300 mAvoidIntersectionsMode = static_cast<Qgis::AvoidIntersectionsMode>( readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( Qgis::AvoidIntersectionsMode::AvoidIntersectionsLayers ) ) );
2301
2302 profile.switchTask( tr( "Loading view settings" ) );
2303 // restore older project scales settings
2304 mViewSettings->setUseProjectScales( readBoolEntry( QStringLiteral( "Scales" ), QStringLiteral( "/useProjectScales" ) ) );
2305 const QStringList scales = readListEntry( QStringLiteral( "Scales" ), QStringLiteral( "/ScalesList" ) );
2306 QVector<double> res;
2307 for ( const QString &scale : scales )
2308 {
2309 const QStringList parts = scale.split( ':' );
2310 if ( parts.size() != 2 )
2311 continue;
2312
2313 bool ok = false;
2314 const double denominator = QLocale().toDouble( parts[1], &ok );
2315 if ( ok )
2316 {
2317 res << denominator;
2318 }
2319 }
2320 mViewSettings->setMapScales( res );
2321 const QDomElement viewSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectViewSettings" ) );
2322 if ( !viewSettingsElement.isNull() )
2323 mViewSettings->readXml( viewSettingsElement, context );
2324
2325 // restore style settings
2326 profile.switchTask( tr( "Loading style properties" ) );
2327 const QDomElement styleSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectStyleSettings" ) );
2328 if ( !styleSettingsElement.isNull() )
2329 {
2330 mStyleSettings->removeProjectStyle();
2331 mStyleSettings->readXml( styleSettingsElement, context, flags );
2332 }
2333
2334 // restore time settings
2335 profile.switchTask( tr( "Loading temporal settings" ) );
2336 const QDomElement timeSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectTimeSettings" ) );
2337 if ( !timeSettingsElement.isNull() )
2338 mTimeSettings->readXml( timeSettingsElement, context );
2339
2340
2341 profile.switchTask( tr( "Loading elevation properties" ) );
2342 const QDomElement elevationPropertiesElement = doc->documentElement().firstChildElement( QStringLiteral( "ElevationProperties" ) );
2343 if ( !elevationPropertiesElement.isNull() )
2344 mElevationProperties->readXml( elevationPropertiesElement, context );
2345 mElevationProperties->resolveReferences( this );
2346
2347 profile.switchTask( tr( "Loading display settings" ) );
2348 {
2349 const QDomElement displaySettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectDisplaySettings" ) );
2350 if ( !displaySettingsElement.isNull() )
2351 mDisplaySettings->readXml( displaySettingsElement, context );
2352 }
2353
2354 profile.switchTask( tr( "Loading GPS settings" ) );
2355 {
2356 const QDomElement gpsSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectGpsSettings" ) );
2357 if ( !gpsSettingsElement.isNull() )
2358 mGpsSettings->readXml( gpsSettingsElement, context );
2359 mGpsSettings->resolveReferences( this );
2360 }
2361
2362 profile.switchTask( tr( "Updating variables" ) );
2364 profile.switchTask( tr( "Updating CRS" ) );
2365 emit crsChanged();
2366 emit ellipsoidChanged( ellipsoid() );
2367
2368 // read the project: used by map canvas and legend
2369 profile.switchTask( tr( "Reading external settings" ) );
2370 emit readProject( *doc );
2371 emit readProjectWithContext( *doc, context );
2372
2373 profile.switchTask( tr( "Updating interface" ) );
2374
2375 snapSignalBlock.release();
2376 if ( !mBlockSnappingUpdates )
2377 emit snappingConfigChanged( mSnappingConfig );
2378
2381 emit projectColorsChanged();
2382
2383 // if all went well, we're allegedly in pristine state
2384 if ( clean )
2385 setDirty( false );
2386
2387 QgsDebugMsgLevel( QStringLiteral( "Project save user: %1" ).arg( mSaveUser ), 2 );
2388 QgsDebugMsgLevel( QStringLiteral( "Project save user: %1" ).arg( mSaveUserFull ), 2 );
2389
2393
2394 if ( mTranslator )
2395 {
2396 //project possibly translated -> rename it with locale postfix
2397 const QString newFileName( QStringLiteral( "%1/%2.qgs" ).arg( QFileInfo( projectFile.fileName() ).absolutePath(), localeFileName ) );
2398 setFileName( newFileName );
2399
2400 if ( write() )
2401 {
2402 setTitle( localeFileName );
2403 QgsMessageLog::logMessage( tr( "Translated project saved with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Success );
2404 }
2405 else
2406 {
2407 QgsMessageLog::logMessage( tr( "Error saving translated project with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Critical );
2408 }
2409 }
2410
2411 // lastly, make any previously editable layers editable
2412 const QMap<QString, QgsMapLayer *> loadedLayers = mapLayers();
2413 for ( auto it = loadedLayers.constBegin(); it != loadedLayers.constEnd(); ++it )
2414 {
2415 if ( it.value()->isValid() && it.value()->customProperty( QStringLiteral( "_layer_was_editable" ) ).toBool() )
2416 {
2417 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( it.value() ) )
2418 vl->startEditing();
2419 it.value()->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
2420 }
2421 }
2422
2423 return true;
2424}
2425
2426bool QgsProject::loadEmbeddedNodes( QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
2427{
2429
2430 bool valid = true;
2431 const auto constChildren = group->children();
2432 for ( QgsLayerTreeNode *child : constChildren )
2433 {
2434 if ( QgsLayerTree::isGroup( child ) )
2435 {
2436 QgsLayerTreeGroup *childGroup = QgsLayerTree::toGroup( child );
2437 if ( childGroup->customProperty( QStringLiteral( "embedded" ) ).toInt() )
2438 {
2439 // make sure to convert the path from relative to absolute
2440 const QString projectPath = readPath( childGroup->customProperty( QStringLiteral( "embedded_project" ) ).toString() );
2441 childGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectPath );
2442 QgsLayerTreeGroup *newGroup = createEmbeddedGroup( childGroup->name(), projectPath, childGroup->customProperty( QStringLiteral( "embedded-invisible-layers" ) ).toStringList(), flags );
2443 if ( newGroup )
2444 {
2445 QList<QgsLayerTreeNode *> clonedChildren;
2446 const QList<QgsLayerTreeNode *> constChildren = newGroup->children();
2447 clonedChildren.reserve( constChildren.size() );
2448 for ( QgsLayerTreeNode *newGroupChild : constChildren )
2449 clonedChildren << newGroupChild->clone();
2450 delete newGroup;
2451
2452 childGroup->insertChildNodes( 0, clonedChildren );
2453 }
2454 }
2455 else
2456 {
2457 loadEmbeddedNodes( childGroup, flags );
2458 }
2459 }
2460 else if ( QgsLayerTree::isLayer( child ) )
2461 {
2462 if ( child->customProperty( QStringLiteral( "embedded" ) ).toInt() )
2463 {
2464 QList<QDomNode> brokenNodes;
2465 if ( ! createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), readPath( child->customProperty( QStringLiteral( "embedded_project" ) ).toString() ), brokenNodes, true, flags ) )
2466 {
2467 valid = valid && false;
2468 }
2469 }
2470 }
2471
2472 }
2473
2474 return valid;
2475}
2476
2478{
2479 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
2481
2482 return mCustomVariables;
2483}
2484
2485void QgsProject::setCustomVariables( const QVariantMap &variables )
2486{
2488
2489 if ( variables == mCustomVariables )
2490 return;
2491
2492 //write variable to project
2493 QStringList variableNames;
2494 QStringList variableValues;
2495
2496 QVariantMap::const_iterator it = variables.constBegin();
2497 for ( ; it != variables.constEnd(); ++it )
2498 {
2499 variableNames << it.key();
2500 variableValues << it.value().toString();
2501 }
2502
2503 writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ), variableNames );
2504 writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ), variableValues );
2505
2506 mCustomVariables = variables;
2507 mProjectScope.reset();
2508
2510}
2511
2513{
2515
2516 *mLabelingEngineSettings = settings;
2518}
2519
2521{
2523
2524 return *mLabelingEngineSettings;
2525}
2526
2528{
2530
2531 mProjectScope.reset();
2532 return mLayerStore.get();
2533}
2534
2536{
2538
2539 return mLayerStore.get();
2540}
2541
2542QList<QgsVectorLayer *> QgsProject::avoidIntersectionsLayers() const
2543{
2545
2546 QList<QgsVectorLayer *> layers;
2547 const QStringList layerIds = readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), QStringList() );
2548 const auto constLayerIds = layerIds;
2549 for ( const QString &layerId : constLayerIds )
2550 {
2551 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer( layerId ) ) )
2552 layers << vlayer;
2553 }
2554 return layers;
2555}
2556
2557void QgsProject::setAvoidIntersectionsLayers( const QList<QgsVectorLayer *> &layers )
2558{
2560
2561 QStringList list;
2562 list.reserve( layers.size() );
2563 for ( QgsVectorLayer *layer : layers )
2564 list << layer->id();
2565 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), list );
2567}
2568
2570{
2572
2573 QgsExpressionContext context;
2574
2577
2578 return context;
2579}
2580
2582{
2583 // this method is called quite extensively using QgsProject::instance()
2585
2586 // MUCH cheaper to clone than build
2587 if ( mProjectScope )
2588 {
2589 std::unique_ptr< QgsExpressionContextScope > projectScope = std::make_unique< QgsExpressionContextScope >( *mProjectScope );
2590
2591 // we can't cache these variables
2592 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_distance_units" ), QgsUnitTypes::toString( distanceUnits() ), true, true ) );
2593 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_area_units" ), QgsUnitTypes::toString( areaUnits() ), true, true ) );
2594
2595 // neither this function
2596 projectScope->addFunction( QStringLiteral( "sensor_data" ), new GetSensorData( sensorManager()->sensorsData() ) );
2597
2598 return projectScope.release();
2599 }
2600
2601 mProjectScope = std::make_unique< QgsExpressionContextScope >( QObject::tr( "Project" ) );
2602
2603 const QVariantMap vars = customVariables();
2604
2605 QVariantMap::const_iterator it = vars.constBegin();
2606
2607 for ( ; it != vars.constEnd(); ++it )
2608 {
2609 mProjectScope->setVariable( it.key(), it.value(), true );
2610 }
2611
2612 QString projectPath = projectStorage() ? fileName() : absoluteFilePath();
2613 if ( projectPath.isEmpty() )
2614 projectPath = mOriginalPath;
2615 const QString projectFolder = QFileInfo( projectPath ).path();
2616 const QString projectFilename = QFileInfo( projectPath ).fileName();
2617 const QString projectBasename = baseName();
2618
2619 //add other known project variables
2620 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_title" ), title(), true, true ) );
2621 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_path" ), QDir::toNativeSeparators( projectPath ), true, true ) );
2622 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_folder" ), QDir::toNativeSeparators( projectFolder ), true, true ) );
2623 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_filename" ), projectFilename, true, true ) );
2624 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_basename" ), projectBasename, true, true ) );
2625 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_home" ), QDir::toNativeSeparators( homePath() ), true, true ) );
2626 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_last_saved" ), mSaveDateTime.isNull() ? QVariant() : QVariant( mSaveDateTime ), true, true ) );
2627 const QgsCoordinateReferenceSystem projectCrs = crs();
2628 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs" ), projectCrs.authid(), true, true ) );
2629 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_definition" ), projectCrs.toProj(), true, true ) );
2630 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_description" ), projectCrs.description(), true, true ) );
2631 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_ellipsoid" ), ellipsoid(), true, true ) );
2632 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "_project_transform_context" ), QVariant::fromValue<QgsCoordinateTransformContext>( transformContext() ), true, true ) );
2633 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_units" ), QgsUnitTypes::toString( projectCrs.mapUnits() ), true ) );
2634 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_acronym" ), projectCrs.projectionAcronym(), true ) );
2635 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_ellipsoid" ), projectCrs.ellipsoidAcronym(), true ) );
2636 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_proj4" ), projectCrs.toProj(), true ) );
2637 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_wkt" ), projectCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
2638
2639 // metadata
2640 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_author" ), metadata().author(), true, true ) );
2641 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_abstract" ), metadata().abstract(), true, true ) );
2642 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_creation_date" ), metadata().creationDateTime(), true, true ) );
2643 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_identifier" ), metadata().identifier(), true, true ) );
2644
2645 // keywords
2646 QVariantMap keywords;
2647 const QgsAbstractMetadataBase::KeywordMap metadataKeywords = metadata().keywords();
2648 for ( auto it = metadataKeywords.constBegin(); it != metadataKeywords.constEnd(); ++it )
2649 {
2650 keywords.insert( it.key(), it.value() );
2651 }
2652 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_keywords" ), keywords, true, true ) );
2653
2654 // layers
2655 QVariantList layersIds;
2656 QVariantList layers;
2657 const QMap<QString, QgsMapLayer *> layersInProject = mLayerStore->mapLayers();
2658 layersIds.reserve( layersInProject.count() );
2659 layers.reserve( layersInProject.count() );
2660 for ( auto it = layersInProject.constBegin(); it != layersInProject.constEnd(); ++it )
2661 {
2662 layersIds << it.value()->id();
2663 layers << QVariant::fromValue<QgsWeakMapLayerPointer>( QgsWeakMapLayerPointer( it.value() ) );
2664 }
2665 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer_ids" ), layersIds, true ) );
2666 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layers" ), layers, true ) );
2667
2668 mProjectScope->addFunction( QStringLiteral( "project_color" ), new GetNamedProjectColor( this ) );
2669
2671}
2672
2673void QgsProject::onMapLayersAdded( const QList<QgsMapLayer *> &layers )
2674{
2676
2677 const QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2678
2679 const auto constLayers = layers;
2680 for ( QgsMapLayer *layer : constLayers )
2681 {
2682 if ( ! layer->isValid() )
2683 return;
2684
2685 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
2686 {
2687 vlayer->setReadExtentFromXml( mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics );
2688 if ( vlayer->dataProvider() )
2689 vlayer->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues,
2691 }
2692
2693 connect( layer, &QgsMapLayer::configChanged, this, [this] { setDirty(); } );
2694
2695 // check if we have to update connections for layers with dependencies
2696 for ( QMap<QString, QgsMapLayer *>::const_iterator it = existingMaps.cbegin(); it != existingMaps.cend(); ++it )
2697 {
2698 const QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
2699 if ( deps.contains( layer->id() ) )
2700 {
2701 // reconnect to change signals
2702 it.value()->setDependencies( deps );
2703 }
2704 }
2705 }
2706
2707 updateTransactionGroups();
2708
2709 if ( !mBlockSnappingUpdates && mSnappingConfig.addLayers( layers ) )
2710 emit snappingConfigChanged( mSnappingConfig );
2711}
2712
2713void QgsProject::onMapLayersRemoved( const QList<QgsMapLayer *> &layers )
2714{
2716
2717 if ( !mBlockSnappingUpdates && mSnappingConfig.removeLayers( layers ) )
2718 emit snappingConfigChanged( mSnappingConfig );
2719}
2720
2721void QgsProject::cleanTransactionGroups( bool force )
2722{
2724
2725 bool changed = false;
2726 for ( QMap< QPair< QString, QString>, QgsTransactionGroup *>::Iterator tg = mTransactionGroups.begin(); tg != mTransactionGroups.end(); )
2727 {
2728 if ( tg.value()->isEmpty() || force )
2729 {
2730 delete tg.value();
2731 tg = mTransactionGroups.erase( tg );
2732 changed = true;
2733 }
2734 else
2735 {
2736 ++tg;
2737 }
2738 }
2739 if ( changed )
2741}
2742
2743void QgsProject::updateTransactionGroups()
2744{
2746
2747 mEditBufferGroup.clear();
2748
2749 switch ( mTransactionMode )
2750 {
2752 {
2753 cleanTransactionGroups( true );
2754 return;
2755 }
2756 break;
2758 cleanTransactionGroups( true );
2759 break;
2761 cleanTransactionGroups( false );
2762 break;
2763 }
2764
2765 bool tgChanged = false;
2766 const auto constLayers = mapLayers().values();
2767 for ( QgsMapLayer *layer : constLayers )
2768 {
2769 if ( ! layer->isValid() )
2770 continue;
2771
2772 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
2773 if ( ! vlayer )
2774 continue;
2775
2776 switch ( mTransactionMode )
2777 {
2779 Q_ASSERT( false );
2780 break;
2782 {
2784 {
2785 const QString connString = QgsTransaction::connectionString( vlayer->source() );
2786 const QString key = vlayer->providerType();
2787
2788 QgsTransactionGroup *tg = mTransactionGroups.value( qMakePair( key, connString ) );
2789
2790 if ( !tg )
2791 {
2792 tg = new QgsTransactionGroup();
2793 mTransactionGroups.insert( qMakePair( key, connString ), tg );
2794 tgChanged = true;
2795 }
2796 tg->addLayer( vlayer );
2797 }
2798 }
2799 break;
2801 {
2802 if ( vlayer->supportsEditing() )
2803 mEditBufferGroup.addLayer( vlayer );
2804 }
2805 break;
2806 }
2807 }
2808
2809 if ( tgChanged )
2811}
2812
2813bool QgsProject::readLayer( const QDomNode &layerNode )
2814{
2816
2817 QgsReadWriteContext context;
2818 context.setPathResolver( pathResolver() );
2819 context.setProjectTranslator( this );
2820 context.setTransformContext( transformContext() );
2821 QList<QDomNode> brokenNodes;
2822 if ( addLayer( layerNode.toElement(), brokenNodes, context ) )
2823 {
2824 // have to try to update joins for all layers now - a previously added layer may be dependent on this newly
2825 // added layer for joins
2826 const QVector<QgsVectorLayer *> vectorLayers = layers<QgsVectorLayer *>();
2827 for ( QgsVectorLayer *layer : vectorLayers )
2828 {
2829 // TODO: should be only done later - and with all layers (other layers may have referenced this layer)
2830 layer->resolveReferences( this );
2831
2832 if ( layer->isValid() && layer->customProperty( QStringLiteral( "_layer_was_editable" ) ).toBool() )
2833 {
2834 layer->startEditing();
2835 layer->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
2836 }
2837 }
2838 return true;
2839 }
2840 return false;
2841}
2842
2843bool QgsProject::write( const QString &filename )
2844{
2846
2847 mFile.setFileName( filename );
2848 mCachedHomePath.clear();
2849 return write();
2850}
2851
2853{
2855
2856 mProjectScope.reset();
2857 if ( QgsProjectStorage *storage = projectStorage() )
2858 {
2859 QgsReadWriteContext context;
2860 // for projects stored in a custom storage, we have to check for the support
2861 // of relative paths since the storage most likely will not be in a file system
2862 const QString storageFilePath { storage->filePath( mFile.fileName() ) };
2863 if ( storageFilePath.isEmpty() )
2864 {
2866 }
2867 context.setPathResolver( pathResolver() );
2868
2869 const QString tempPath = QStandardPaths::standardLocations( QStandardPaths::TempLocation ).at( 0 );
2870 const QString tmpZipFilename( tempPath + QDir::separator() + QUuid::createUuid().toString() );
2871
2872 if ( !zip( tmpZipFilename ) )
2873 return false; // zip() already calls setError() when returning false
2874
2875 QFile tmpZipFile( tmpZipFilename );
2876 if ( !tmpZipFile.open( QIODevice::ReadOnly ) )
2877 {
2878 setError( tr( "Unable to read file %1" ).arg( tmpZipFilename ) );
2879 return false;
2880 }
2881
2883 if ( !storage->writeProject( mFile.fileName(), &tmpZipFile, context ) )
2884 {
2885 QString err = tr( "Unable to save project to storage %1" ).arg( mFile.fileName() );
2886 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
2887 if ( !messages.isEmpty() )
2888 err += QStringLiteral( "\n\n" ) + messages.last().message();
2889 setError( err );
2890 return false;
2891 }
2892
2893 tmpZipFile.close();
2894 QFile::remove( tmpZipFilename );
2895
2896 return true;
2897 }
2898
2899 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
2900 {
2901 return zip( mFile.fileName() );
2902 }
2903 else
2904 {
2905 // write project file even if the auxiliary storage is not correctly
2906 // saved
2907 const bool asOk = saveAuxiliaryStorage();
2908 const bool writeOk = writeProjectFile( mFile.fileName() );
2909 bool attachmentsOk = true;
2910 if ( !mArchive->files().isEmpty() )
2911 {
2912 const QFileInfo finfo( mFile.fileName() );
2913 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
2914 attachmentsOk = mArchive->zip( attachmentsZip );
2915 }
2916
2917 // errors raised during writing project file are more important
2918 if ( ( !asOk || !attachmentsOk ) && writeOk )
2919 {
2920 QStringList errorMessage;
2921 if ( !asOk )
2922 {
2923 const QString err = mAuxiliaryStorage->errorString();
2924 errorMessage.append( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
2925 }
2926 if ( !attachmentsOk )
2927 {
2928 errorMessage.append( tr( "Unable to save attachments archive" ) );
2929 }
2930 setError( errorMessage.join( '\n' ) );
2931 }
2932
2933 return asOk && writeOk && attachmentsOk;
2934 }
2935}
2936
2937bool QgsProject::writeProjectFile( const QString &filename )
2938{
2940
2941 QFile projectFile( filename );
2942 clearError();
2943
2944 // if we have problems creating or otherwise writing to the project file,
2945 // let's find out up front before we go through all the hand-waving
2946 // necessary to create all the Dom objects
2947 const QFileInfo myFileInfo( projectFile );
2948 if ( myFileInfo.exists() && !myFileInfo.isWritable() )
2949 {
2950 setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." )
2951 .arg( projectFile.fileName() ) );
2952 return false;
2953 }
2954
2955 QgsReadWriteContext context;
2956 context.setPathResolver( pathResolver() );
2958
2959 QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
2960
2961 const QDomDocumentType documentType =
2962 QDomImplementation().createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ),
2963 QStringLiteral( "SYSTEM" ) );
2964 std::unique_ptr<QDomDocument> doc( new QDomDocument( documentType ) );
2965
2966 QDomElement qgisNode = doc->createElement( QStringLiteral( "qgis" ) );
2967 qgisNode.setAttribute( QStringLiteral( "projectname" ), title() );
2968 qgisNode.setAttribute( QStringLiteral( "version" ), Qgis::version() );
2969
2970 if ( !mSettings.value( QStringLiteral( "projects/anonymize_saved_projects" ), false, QgsSettings::Core ).toBool() )
2971 {
2972 const QString newSaveUser = QgsApplication::userLoginName();
2973 const QString newSaveUserFull = QgsApplication::userFullName();
2974 qgisNode.setAttribute( QStringLiteral( "saveUser" ), newSaveUser );
2975 qgisNode.setAttribute( QStringLiteral( "saveUserFull" ), newSaveUserFull );
2976 mSaveUser = newSaveUser;
2977 mSaveUserFull = newSaveUserFull;
2978 mSaveDateTime = QDateTime::currentDateTime();
2979 qgisNode.setAttribute( QStringLiteral( "saveDateTime" ), mSaveDateTime.toString( Qt::ISODate ) );
2980 }
2981 else
2982 {
2983 mSaveUser.clear();
2984 mSaveUserFull.clear();
2985 mSaveDateTime = QDateTime();
2986 }
2987 doc->appendChild( qgisNode );
2988 mSaveVersion = QgsProjectVersion( Qgis::version() );
2989
2990 QDomElement homePathNode = doc->createElement( QStringLiteral( "homePath" ) );
2991 homePathNode.setAttribute( QStringLiteral( "path" ), mHomePath );
2992 qgisNode.appendChild( homePathNode );
2993
2994 // title
2995 QDomElement titleNode = doc->createElement( QStringLiteral( "title" ) );
2996 qgisNode.appendChild( titleNode );
2997
2998 QDomElement transactionNode = doc->createElement( QStringLiteral( "transaction" ) );
2999 transactionNode.setAttribute( QStringLiteral( "mode" ), qgsEnumValueToKey( mTransactionMode ) );
3000 qgisNode.appendChild( transactionNode );
3001
3002 QDomElement flagsNode = doc->createElement( QStringLiteral( "projectFlags" ) );
3003 flagsNode.setAttribute( QStringLiteral( "set" ), qgsFlagValueToKeys( mFlags ) );
3004 qgisNode.appendChild( flagsNode );
3005
3006 const QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
3007 titleNode.appendChild( titleText );
3008
3009 // write project CRS
3010 QDomElement srsNode = doc->createElement( QStringLiteral( "projectCrs" ) );
3011 mCrs.writeXml( srsNode, *doc );
3012 qgisNode.appendChild( srsNode );
3013
3014 QDomElement elevationShadingNode = doc->createElement( QStringLiteral( "elevation-shading-renderer" ) );
3015 mElevationShadingRenderer.writeXml( elevationShadingNode, context );
3016 qgisNode.appendChild( elevationShadingNode );
3017
3018 // write layer tree - make sure it is without embedded subgroups
3019 QgsLayerTreeNode *clonedRoot = mRootGroup->clone();
3021 QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ), this ); // convert absolute paths to relative paths if required
3022
3023 clonedRoot->writeXml( qgisNode, context );
3024 delete clonedRoot;
3025
3026 mSnappingConfig.writeProject( *doc );
3027 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( mAvoidIntersectionsMode ) );
3028
3029 // let map canvas and legend write their information
3030 emit writeProject( *doc );
3031
3032 // within top level node save list of layers
3033 const QMap<QString, QgsMapLayer *> layers = mapLayers();
3034
3035 QDomElement annotationLayerNode = doc->createElement( QStringLiteral( "main-annotation-layer" ) );
3036 mMainAnnotationLayer->writeLayerXml( annotationLayerNode, *doc, context );
3037 qgisNode.appendChild( annotationLayerNode );
3038
3039 // Iterate over layers in zOrder
3040 // Call writeXml() on each
3041 QDomElement projectLayersNode = doc->createElement( QStringLiteral( "projectlayers" ) );
3042
3043 QMap<QString, QgsMapLayer *>::ConstIterator li = layers.constBegin();
3044 while ( li != layers.end() )
3045 {
3046 QgsMapLayer *ml = li.value();
3047
3048 if ( ml )
3049 {
3050 const QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.constFind( ml->id() );
3051 if ( emIt == mEmbeddedLayers.constEnd() )
3052 {
3053 QDomElement maplayerElem;
3054 // If layer is not valid, prefer to restore saved properties from invalidLayerProperties. But if that's
3055 // not available, just write what we DO have
3056 if ( ml->isValid() || ml->originalXmlProperties().isEmpty() )
3057 {
3058 // general layer metadata
3059 maplayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
3060 ml->writeLayerXml( maplayerElem, *doc, context );
3061
3063 maplayerElem.setAttribute( QStringLiteral( "editable" ), QStringLiteral( "1" ) );
3064 }
3065 else if ( ! ml->originalXmlProperties().isEmpty() )
3066 {
3067 QDomDocument document;
3068 if ( document.setContent( ml->originalXmlProperties() ) )
3069 {
3070 maplayerElem = document.firstChildElement();
3071 }
3072 else
3073 {
3074 QgsDebugError( QStringLiteral( "Could not restore layer properties for layer %1" ).arg( ml->id() ) );
3075 }
3076 }
3077
3078 emit writeMapLayer( ml, maplayerElem, *doc );
3079
3080 projectLayersNode.appendChild( maplayerElem );
3081 }
3082 else
3083 {
3084 // layer defined in an external project file
3085 // only save embedded layer if not managed by a legend group
3086 if ( emIt.value().second )
3087 {
3088 QDomElement mapLayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
3089 mapLayerElem.setAttribute( QStringLiteral( "embedded" ), 1 );
3090 mapLayerElem.setAttribute( QStringLiteral( "project" ), writePath( emIt.value().first ) );
3091 mapLayerElem.setAttribute( QStringLiteral( "id" ), ml->id() );
3092 projectLayersNode.appendChild( mapLayerElem );
3093 }
3094 }
3095 }
3096 li++;
3097 }
3098
3099 qgisNode.appendChild( projectLayersNode );
3100
3101 QDomElement layerOrderNode = doc->createElement( QStringLiteral( "layerorder" ) );
3102 const auto constCustomLayerOrder = mRootGroup->customLayerOrder();
3103 for ( QgsMapLayer *layer : constCustomLayerOrder )
3104 {
3105 QDomElement mapLayerElem = doc->createElement( QStringLiteral( "layer" ) );
3106 mapLayerElem.setAttribute( QStringLiteral( "id" ), layer->id() );
3107 layerOrderNode.appendChild( mapLayerElem );
3108 }
3109 qgisNode.appendChild( layerOrderNode );
3110
3111 mLabelingEngineSettings->writeSettingsToProject( this );
3112
3113 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), mBackgroundColor.red() );
3114 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), mBackgroundColor.green() );
3115 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), mBackgroundColor.blue() );
3116
3117 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), mSelectionColor.red() );
3118 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), mSelectionColor.green() );
3119 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), mSelectionColor.blue() );
3120 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), mSelectionColor.alpha() );
3121
3122 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QgsUnitTypes::encodeUnit( mDistanceUnits ) );
3123 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QgsUnitTypes::encodeUnit( mAreaUnits ) );
3124
3125 // now add the optional extra properties
3126#if 0
3127 dump_( mProperties );
3128#endif
3129
3130 QgsDebugMsgLevel( QStringLiteral( "there are %1 property scopes" ).arg( static_cast<int>( mProperties.count() ) ), 2 );
3131
3132 if ( !mProperties.isEmpty() ) // only worry about properties if we
3133 // actually have any properties
3134 {
3135 mProperties.writeXml( QStringLiteral( "properties" ), qgisNode, *doc );
3136 }
3137
3138 QDomElement ddElem = doc->createElement( QStringLiteral( "dataDefinedServerProperties" ) );
3139 mDataDefinedServerProperties.writeXml( ddElem, dataDefinedServerPropertyDefinitions() );
3140 qgisNode.appendChild( ddElem );
3141
3142 mMapThemeCollection->writeXml( *doc );
3143
3144 mTransformContext.writeXml( qgisNode, context );
3145
3146 QDomElement metadataElem = doc->createElement( QStringLiteral( "projectMetadata" ) );
3147 mMetadata.writeMetadataXml( metadataElem, *doc );
3148 qgisNode.appendChild( metadataElem );
3149
3150 {
3151 const QDomElement annotationsElem = mAnnotationManager->writeXml( *doc, context );
3152 qgisNode.appendChild( annotationsElem );
3153 }
3154
3155 {
3156 const QDomElement layoutElem = mLayoutManager->writeXml( *doc );
3157 qgisNode.appendChild( layoutElem );
3158 }
3159
3160 {
3161 const QDomElement views3DElem = m3DViewsManager->writeXml( *doc );
3162 qgisNode.appendChild( views3DElem );
3163 }
3164
3165 {
3166 const QDomElement bookmarkElem = mBookmarkManager->writeXml( *doc );
3167 qgisNode.appendChild( bookmarkElem );
3168 }
3169
3170 {
3171 const QDomElement sensorElem = mSensorManager->writeXml( *doc );
3172 qgisNode.appendChild( sensorElem );
3173 }
3174
3175 {
3176 const QDomElement viewSettingsElem = mViewSettings->writeXml( *doc, context );
3177 qgisNode.appendChild( viewSettingsElem );
3178 }
3179
3180 {
3181 const QDomElement styleSettingsElem = mStyleSettings->writeXml( *doc, context );
3182 qgisNode.appendChild( styleSettingsElem );
3183 }
3184
3185 {
3186 const QDomElement timeSettingsElement = mTimeSettings->writeXml( *doc, context );
3187 qgisNode.appendChild( timeSettingsElement );
3188 }
3189
3190 {
3191 const QDomElement elevationPropertiesElement = mElevationProperties->writeXml( *doc, context );
3192 qgisNode.appendChild( elevationPropertiesElement );
3193 }
3194
3195 {
3196 const QDomElement displaySettingsElem = mDisplaySettings->writeXml( *doc, context );
3197 qgisNode.appendChild( displaySettingsElem );
3198 }
3199
3200 {
3201 const QDomElement gpsSettingsElem = mGpsSettings->writeXml( *doc, context );
3202 qgisNode.appendChild( gpsSettingsElem );
3203 }
3204
3205 // now wrap it up and ship it to the project file
3206 doc->normalize(); // XXX I'm not entirely sure what this does
3207
3208 // Create backup file
3209 if ( QFile::exists( fileName() ) )
3210 {
3211 QFile backupFile( QStringLiteral( "%1~" ).arg( filename ) );
3212 bool ok = true;
3213 ok &= backupFile.open( QIODevice::WriteOnly | QIODevice::Truncate );
3214 ok &= projectFile.open( QIODevice::ReadOnly );
3215
3216 QByteArray ba;
3217 while ( ok && !projectFile.atEnd() )
3218 {
3219 ba = projectFile.read( 10240 );
3220 ok &= backupFile.write( ba ) == ba.size();
3221 }
3222
3223 projectFile.close();
3224 backupFile.close();
3225
3226 if ( !ok )
3227 {
3228 setError( tr( "Unable to create backup file %1" ).arg( backupFile.fileName() ) );
3229 return false;
3230 }
3231
3232 const QFileInfo fi( fileName() );
3233 struct utimbuf tb = { static_cast<time_t>( fi.lastRead().toSecsSinceEpoch() ), static_cast<time_t>( fi.lastModified().toSecsSinceEpoch() ) };
3234 utime( backupFile.fileName().toUtf8().constData(), &tb );
3235 }
3236
3237 if ( !projectFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
3238 {
3239 projectFile.close(); // even though we got an error, let's make
3240 // sure it's closed anyway
3241
3242 setError( tr( "Unable to save to file %1" ).arg( projectFile.fileName() ) );
3243 return false;
3244 }
3245
3246 QTemporaryFile tempFile;
3247 bool ok = tempFile.open();
3248 if ( ok )
3249 {
3250 QTextStream projectFileStream( &tempFile );
3251 doc->save( projectFileStream, 2 ); // save as utf-8
3252 ok &= projectFileStream.pos() > -1;
3253
3254 ok &= tempFile.seek( 0 );
3255
3256 QByteArray ba;
3257 while ( ok && !tempFile.atEnd() )
3258 {
3259 ba = tempFile.read( 10240 );
3260 ok &= projectFile.write( ba ) == ba.size();
3261 }
3262
3263 ok &= projectFile.error() == QFile::NoError;
3264
3265 projectFile.close();
3266 }
3267
3268 tempFile.close();
3269
3270 if ( !ok )
3271 {
3272 setError( tr( "Unable to save to file %1. Your project "
3273 "may be corrupted on disk. Try clearing some space on the volume and "
3274 "check file permissions before pressing save again." )
3275 .arg( projectFile.fileName() ) );
3276 return false;
3277 }
3278
3279 setDirty( false ); // reset to pristine state
3280
3281 emit projectSaved();
3282 return true;
3283}
3284
3285bool QgsProject::writeEntry( const QString &scope, QString const &key, bool value )
3286{
3288
3289 bool propertiesModified;
3290 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3291
3292 if ( propertiesModified )
3293 setDirty( true );
3294
3295 return success;
3296}
3297
3298bool QgsProject::writeEntry( const QString &scope, const QString &key, double value )
3299{
3301
3302 bool propertiesModified;
3303 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3304
3305 if ( propertiesModified )
3306 setDirty( true );
3307
3308 return success;
3309}
3310
3311bool QgsProject::writeEntry( const QString &scope, QString const &key, int value )
3312{
3314
3315 bool propertiesModified;
3316 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3317
3318 if ( propertiesModified )
3319 setDirty( true );
3320
3321 return success;
3322}
3323
3324bool QgsProject::writeEntry( const QString &scope, const QString &key, const QString &value )
3325{
3327
3328 bool propertiesModified;
3329 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3330
3331 if ( propertiesModified )
3332 setDirty( true );
3333
3334 return success;
3335}
3336
3337bool QgsProject::writeEntry( const QString &scope, const QString &key, const QStringList &value )
3338{
3340
3341 bool propertiesModified;
3342 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3343
3344 if ( propertiesModified )
3345 setDirty( true );
3346
3347 return success;
3348}
3349
3350QStringList QgsProject::readListEntry( const QString &scope,
3351 const QString &key,
3352 const QStringList &def,
3353 bool *ok ) const
3354{
3355 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
3357
3358 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3359
3360 QVariant value;
3361
3362 if ( property )
3363 {
3364 value = property->value();
3365
3366 const bool valid = QVariant::StringList == value.type();
3367 if ( ok )
3368 *ok = valid;
3369
3370 if ( valid )
3371 {
3372 return value.toStringList();
3373 }
3374 }
3375 else if ( ok )
3376 *ok = false;
3377
3378
3379 return def;
3380}
3381
3382QString QgsProject::readEntry( const QString &scope,
3383 const QString &key,
3384 const QString &def,
3385 bool *ok ) const
3386{
3388
3389 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3390
3391 QVariant value;
3392
3393 if ( property )
3394 {
3395 value = property->value();
3396
3397 const bool valid = value.canConvert( QVariant::String );
3398 if ( ok )
3399 *ok = valid;
3400
3401 if ( valid )
3402 return value.toString();
3403 }
3404 else if ( ok )
3405 *ok = false;
3406
3407 return def;
3408}
3409
3410int QgsProject::readNumEntry( const QString &scope, const QString &key, int def,
3411 bool *ok ) const
3412{
3414
3415 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3416
3417 QVariant value;
3418
3419 if ( property )
3420 {
3421 value = property->value();
3422 }
3423
3424 const bool valid = value.canConvert( QVariant::Int );
3425
3426 if ( ok )
3427 {
3428 *ok = valid;
3429 }
3430
3431 if ( valid )
3432 {
3433 return value.toInt();
3434 }
3435
3436 return def;
3437}
3438
3439double QgsProject::readDoubleEntry( const QString &scope, const QString &key,
3440 double def,
3441 bool *ok ) const
3442{
3444
3445 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3446 if ( property )
3447 {
3448 const QVariant value = property->value();
3449
3450 const bool valid = value.canConvert( QVariant::Double );
3451 if ( ok )
3452 *ok = valid;
3453
3454 if ( valid )
3455 return value.toDouble();
3456 }
3457 else if ( ok )
3458 *ok = false;
3459
3460 return def;
3461}
3462
3463bool QgsProject::readBoolEntry( const QString &scope, const QString &key, bool def,
3464 bool *ok ) const
3465{
3467
3468 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3469
3470 if ( property )
3471 {
3472 const QVariant value = property->value();
3473
3474 const bool valid = value.canConvert( QVariant::Bool );
3475 if ( ok )
3476 *ok = valid;
3477
3478 if ( valid )
3479 return value.toBool();
3480 }
3481 else if ( ok )
3482 *ok = false;
3483
3484 return def;
3485}
3486
3487bool QgsProject::removeEntry( const QString &scope, const QString &key )
3488{
3490
3491 if ( findKey_( scope, key, mProperties ) )
3492 {
3493 removeKey_( scope, key, mProperties );
3494 setDirty( true );
3495 }
3496
3497 return !findKey_( scope, key, mProperties );
3498}
3499
3500QStringList QgsProject::entryList( const QString &scope, const QString &key ) const
3501{
3503
3504 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3505
3506 QStringList entries;
3507
3508 if ( foundProperty )
3509 {
3510 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3511
3512 if ( propertyKey )
3513 { propertyKey->entryList( entries ); }
3514 }
3515
3516 return entries;
3517}
3518
3519QStringList QgsProject::subkeyList( const QString &scope, const QString &key ) const
3520{
3522
3523 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3524
3525 QStringList entries;
3526
3527 if ( foundProperty )
3528 {
3529 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3530
3531 if ( propertyKey )
3532 { propertyKey->subkeyList( entries ); }
3533 }
3534
3535 return entries;
3536}
3537
3539{
3541
3542 dump_( mProperties );
3543}
3544
3546{
3548
3549 QString filePath;
3550 switch ( filePathStorage() )
3551 {
3553 break;
3554
3556 {
3557 // for projects stored in a custom storage, we need to ask to the
3558 // storage for the path, if the storage returns an empty path
3559 // relative paths are not supported
3560 if ( QgsProjectStorage *storage = projectStorage() )
3561 {
3562 filePath = storage->filePath( mFile.fileName() );
3563 }
3564 else
3565 {
3566 filePath = fileName();
3567 }
3568 break;
3569 }
3570 }
3571
3572 return QgsPathResolver( filePath, mArchive->dir() );
3573}
3574
3575QString QgsProject::readPath( const QString &src ) const
3576{
3578
3579 return pathResolver().readPath( src );
3580}
3581
3582QString QgsProject::writePath( const QString &src ) const
3583{
3585
3586 return pathResolver().writePath( src );
3587}
3588
3589void QgsProject::setError( const QString &errorMessage )
3590{
3592
3593 mErrorMessage = errorMessage;
3594}
3595
3596QString QgsProject::error() const
3597{
3599
3600 return mErrorMessage;
3601}
3602
3603void QgsProject::clearError()
3604{
3606
3607 setError( QString() );
3608}
3609
3611{
3613
3614 delete mBadLayerHandler;
3615 mBadLayerHandler = handler;
3616}
3617
3618QString QgsProject::layerIsEmbedded( const QString &id ) const
3619{
3621
3622 const QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
3623 if ( it == mEmbeddedLayers.constEnd() )
3624 {
3625 return QString();
3626 }
3627 return it.value().first;
3628}
3629
3630bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &projectFilePath, QList<QDomNode> &brokenNodes,
3631 bool saveFlag, Qgis::ProjectReadFlags flags )
3632{
3634
3636
3637 static QString sPrevProjectFilePath;
3638 static QDateTime sPrevProjectFileTimestamp;
3639 static QDomDocument sProjectDocument;
3640
3641 QString qgsProjectFile = projectFilePath;
3642 QgsProjectArchive archive;
3643 if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
3644 {
3645 archive.unzip( projectFilePath );
3646 qgsProjectFile = archive.projectFile();
3647 }
3648
3649 const QDateTime projectFileTimestamp = QFileInfo( projectFilePath ).lastModified();
3650
3651 if ( projectFilePath != sPrevProjectFilePath || projectFileTimestamp != sPrevProjectFileTimestamp )
3652 {
3653 sPrevProjectFilePath.clear();
3654
3655 QFile projectFile( qgsProjectFile );
3656 if ( !projectFile.open( QIODevice::ReadOnly ) )
3657 {
3658 return false;
3659 }
3660
3661 if ( !sProjectDocument.setContent( &projectFile ) )
3662 {
3663 return false;
3664 }
3665
3666 sPrevProjectFilePath = projectFilePath;
3667 sPrevProjectFileTimestamp = projectFileTimestamp;
3668 }
3669
3670 // does project store paths absolute or relative?
3671 bool useAbsolutePaths = true;
3672
3673 const QDomElement propertiesElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "properties" ) );
3674 if ( !propertiesElem.isNull() )
3675 {
3676 const QDomElement absElem = propertiesElem.firstChildElement( QStringLiteral( "Paths" ) ).firstChildElement( QStringLiteral( "Absolute" ) );
3677 if ( !absElem.isNull() )
3678 {
3679 useAbsolutePaths = absElem.text().compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
3680 }
3681 }
3682
3683 QgsReadWriteContext embeddedContext;
3684 if ( !useAbsolutePaths )
3685 embeddedContext.setPathResolver( QgsPathResolver( projectFilePath ) );
3686 embeddedContext.setProjectTranslator( this );
3687 embeddedContext.setTransformContext( transformContext() );
3688
3689 const QDomElement projectLayersElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "projectlayers" ) );
3690 if ( projectLayersElem.isNull() )
3691 {
3692 return false;
3693 }
3694
3695 QDomElement mapLayerElem = projectLayersElem.firstChildElement( QStringLiteral( "maplayer" ) );
3696 while ( ! mapLayerElem.isNull() )
3697 {
3698 // get layer id
3699 const QString id = mapLayerElem.firstChildElement( QStringLiteral( "id" ) ).text();
3700 if ( id == layerId )
3701 {
3702 // layer can be embedded only once
3703 if ( mapLayerElem.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
3704 {
3705 return false;
3706 }
3707
3708 mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
3709
3710 if ( addLayer( mapLayerElem, brokenNodes, embeddedContext, flags ) )
3711 {
3712 return true;
3713 }
3714 else
3715 {
3716 mEmbeddedLayers.remove( layerId );
3717 return false;
3718 }
3719 }
3720 mapLayerElem = mapLayerElem.nextSiblingElement( QStringLiteral( "maplayer" ) );
3721 }
3722
3723 return false;
3724}
3725
3726QgsLayerTreeGroup *QgsProject::createEmbeddedGroup( const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers, Qgis::ProjectReadFlags flags )
3727{
3729
3730 QString qgsProjectFile = projectFilePath;
3731 QgsProjectArchive archive;
3732 if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
3733 {
3734 archive.unzip( projectFilePath );
3735 qgsProjectFile = archive.projectFile();
3736 }
3737
3738 // open project file, get layer ids in group, add the layers
3739 QFile projectFile( qgsProjectFile );
3740 if ( !projectFile.open( QIODevice::ReadOnly ) )
3741 {
3742 return nullptr;
3743 }
3744
3745 QDomDocument projectDocument;
3746 if ( !projectDocument.setContent( &projectFile ) )
3747 {
3748 return nullptr;
3749 }
3750
3751 QgsReadWriteContext context;
3752 context.setPathResolver( pathResolver() );
3753 context.setProjectTranslator( this );
3755
3757
3758 QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
3759 if ( !layerTreeElem.isNull() )
3760 {
3761 root->readChildrenFromXml( layerTreeElem, context );
3762 }
3763 else
3764 {
3765 QgsLayerTreeUtils::readOldLegend( root, projectDocument.documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
3766 }
3767
3768 QgsLayerTreeGroup *group = root->findGroup( groupName );
3769 if ( !group || group->customProperty( QStringLiteral( "embedded" ) ).toBool() )
3770 {
3771 // embedded groups cannot be embedded again
3772 delete root;
3773 return nullptr;
3774 }
3775
3776 // clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
3777 QgsLayerTreeGroup *newGroup = QgsLayerTree::toGroup( group->clone() );
3778 delete root;
3779 root = nullptr;
3780
3781 newGroup->setCustomProperty( QStringLiteral( "embedded" ), 1 );
3782 newGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectFilePath );
3783
3784 // set "embedded" to all children + load embedded layers
3785 mLayerTreeRegistryBridge->setEnabled( false );
3786 initializeEmbeddedSubtree( projectFilePath, newGroup, flags );
3787 mLayerTreeRegistryBridge->setEnabled( true );
3788
3789 // consider the layers might be identify disabled in its project
3790 const auto constFindLayerIds = newGroup->findLayerIds();
3791 for ( const QString &layerId : constFindLayerIds )
3792 {
3793 QgsLayerTreeLayer *layer = newGroup->findLayer( layerId );
3794 if ( layer )
3795 {
3796 layer->resolveReferences( this );
3797 layer->setItemVisibilityChecked( !invisibleLayers.contains( layerId ) );
3798 }
3799 }
3800
3801 return newGroup;
3802}
3803
3804void QgsProject::initializeEmbeddedSubtree( const QString &projectFilePath, QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
3805{
3807
3808 const auto constChildren = group->children();
3809 for ( QgsLayerTreeNode *child : constChildren )
3810 {
3811 // all nodes in the subtree will have "embedded" custom property set
3812 child->setCustomProperty( QStringLiteral( "embedded" ), 1 );
3813
3814 if ( QgsLayerTree::isGroup( child ) )
3815 {
3816 initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ), flags );
3817 }
3818 else if ( QgsLayerTree::isLayer( child ) )
3819 {
3820 // load the layer into our project
3821 QList<QDomNode> brokenNodes;
3822 createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, false, flags );
3823 }
3824 }
3825}
3826
3828{
3830
3832}
3833
3834void QgsProject::setEvaluateDefaultValues( bool evaluateDefaultValues )
3835{
3837
3839}
3840
3842{
3844
3845 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), ( enabled ? 1 : 0 ) );
3847}
3848
3850{
3852
3853 return readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), 0 );
3854}
3855
3857{
3859
3860 if ( mDistanceUnits == unit )
3861 return;
3862
3863 mDistanceUnits = unit;
3864
3865 emit distanceUnitsChanged();
3866}
3867
3869{
3871
3872 if ( mAreaUnits == unit )
3873 return;
3874
3875 mAreaUnits = unit;
3876
3877 emit areaUnitsChanged();
3878}
3879
3881{
3882 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
3884
3885 if ( !mCachedHomePath.isEmpty() )
3886 return mCachedHomePath;
3887
3888 const QFileInfo pfi( fileName() );
3889
3890 if ( !mHomePath.isEmpty() )
3891 {
3892 const QFileInfo homeInfo( mHomePath );
3893 if ( !homeInfo.isRelative() )
3894 {
3895 mCachedHomePath = mHomePath;
3896 return mHomePath;
3897 }
3898 }
3899 else if ( !fileName().isEmpty() )
3900 {
3901
3902 // If it's not stored in the file system, try to get the path from the storage
3903 if ( QgsProjectStorage *storage = projectStorage() )
3904 {
3905 const QString storagePath { storage->filePath( fileName() ) };
3906 if ( ! storagePath.isEmpty() && QFileInfo::exists( storagePath ) )
3907 {
3908 mCachedHomePath = QFileInfo( storagePath ).path();
3909 return mCachedHomePath;
3910 }
3911 }
3912
3913 mCachedHomePath = pfi.path();
3914 return mCachedHomePath;
3915 }
3916
3917 if ( !pfi.exists() )
3918 {
3919 mCachedHomePath = mHomePath;
3920 return mHomePath;
3921 }
3922
3923 if ( !mHomePath.isEmpty() )
3924 {
3925 // path is relative to project file
3926 mCachedHomePath = QDir::cleanPath( pfi.path() + '/' + mHomePath );
3927 }
3928 else
3929 {
3930 mCachedHomePath = pfi.canonicalPath();
3931 }
3932 return mCachedHomePath;
3933}
3934
3936{
3938
3939 return mHomePath;
3940}
3941
3943{
3944 // because relation aggregate functions are not thread safe
3946
3947 return mRelationManager;
3948}
3949
3951{
3953
3954 return mLayoutManager.get();
3955}
3956
3958{
3960
3961 return mLayoutManager.get();
3962}
3963
3965{
3967
3968 return m3DViewsManager.get();
3969}
3970
3972{
3974
3975 return m3DViewsManager.get();
3976}
3977
3979{
3981
3982 return mBookmarkManager;
3983}
3984
3986{
3988
3989 return mBookmarkManager;
3990}
3991
3993{
3995
3996 return mSensorManager;
3997}
3998
4000{
4002
4003 return mSensorManager;
4004}
4005
4007{
4009
4010 return mViewSettings;
4011}
4012
4014{
4016
4017 return mViewSettings;
4018}
4019
4021{
4023
4024 return mStyleSettings;
4025}
4026
4028{
4029 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4031
4032 return mStyleSettings;
4033}
4034
4036{
4038
4039 return mTimeSettings;
4040}
4041
4043{
4045
4046 return mTimeSettings;
4047}
4048
4050{
4052
4053 return mElevationProperties;
4054}
4055
4057{
4059
4060 return mElevationProperties;
4061}
4062
4064{
4066
4067 return mDisplaySettings;
4068}
4069
4071{
4073
4074 return mDisplaySettings;
4075}
4076
4078{
4080
4081 return mGpsSettings;
4082}
4083
4085{
4087
4088 return mGpsSettings;
4089}
4090
4092{
4094
4095 return mRootGroup;
4096}
4097
4099{
4101
4102 return mMapThemeCollection.get();
4103}
4104
4106{
4108
4109 return mAnnotationManager.get();
4110}
4111
4113{
4115
4116 return mAnnotationManager.get();
4117}
4118
4119void QgsProject::setNonIdentifiableLayers( const QList<QgsMapLayer *> &layers )
4120{
4122
4123 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
4124 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
4125 {
4126 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4127 continue;
4128
4129 if ( layers.contains( it.value() ) )
4130 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Identifiable );
4131 else
4132 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Identifiable );
4133 }
4134
4138}
4139
4140void QgsProject::setNonIdentifiableLayers( const QStringList &layerIds )
4141{
4143
4144 QList<QgsMapLayer *> nonIdentifiableLayers;
4145 nonIdentifiableLayers.reserve( layerIds.count() );
4146 for ( const QString &layerId : layerIds )
4147 {
4148 QgsMapLayer *layer = mapLayer( layerId );
4149 if ( layer )
4150 nonIdentifiableLayers << layer;
4151 }
4155}
4156
4158{
4160
4161 QStringList nonIdentifiableLayers;
4162
4163 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
4164 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
4165 {
4166 if ( !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4167 {
4168 nonIdentifiableLayers.append( it.value()->id() );
4169 }
4170 }
4171 return nonIdentifiableLayers;
4172}
4173
4175{
4177
4178 return mTransactionMode == Qgis::TransactionMode::AutomaticGroups;
4179}
4180
4181void QgsProject::setAutoTransaction( bool autoTransaction )
4182{
4184
4185 if ( autoTransaction
4186 && mTransactionMode == Qgis::TransactionMode::AutomaticGroups )
4187 return;
4188
4189 if ( ! autoTransaction
4190 && mTransactionMode == Qgis::TransactionMode::Disabled )
4191 return;
4192
4193 if ( autoTransaction )
4194 mTransactionMode = Qgis::TransactionMode::AutomaticGroups;
4195 else
4196 mTransactionMode = Qgis::TransactionMode::Disabled;
4197
4198 updateTransactionGroups();
4199}
4200
4202{
4204
4205 return mTransactionMode;
4206}
4207
4209{
4211
4212 if ( transactionMode == mTransactionMode )
4213 return true;
4214
4215 // Check that all layer are not in edit mode
4216 const auto constLayers = mapLayers().values();
4217 for ( QgsMapLayer *layer : constLayers )
4218 {
4219 if ( layer->isEditable() )
4220 {
4221 QgsLogger::warning( tr( "Transaction mode can be changed only if all layers are not editable." ) );
4222 return false;
4223 }
4224 }
4225
4226 mTransactionMode = transactionMode;
4227 updateTransactionGroups();
4228 return true;
4229}
4230
4231QMap<QPair<QString, QString>, QgsTransactionGroup *> QgsProject::transactionGroups()
4232{
4234
4235 return mTransactionGroups;
4236}
4237
4238
4239//
4240// QgsMapLayerStore methods
4241//
4242
4243
4245{
4247
4248 return mLayerStore->count();
4249}
4250
4252{
4254
4255 return mLayerStore->validCount();
4256}
4257
4258QgsMapLayer *QgsProject::mapLayer( const QString &layerId ) const
4259{
4260 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
4262
4263 return mLayerStore->mapLayer( layerId );
4264}
4265
4266QList<QgsMapLayer *> QgsProject::mapLayersByName( const QString &layerName ) const
4267{
4269
4270 return mLayerStore->mapLayersByName( layerName );
4271}
4272
4273QList<QgsMapLayer *> QgsProject::mapLayersByShortName( const QString &shortName ) const
4274{
4276
4277 QList<QgsMapLayer *> layers;
4278 const auto constMapLayers { mLayerStore->mapLayers() };
4279 for ( const auto &l : constMapLayers )
4280 {
4281 if ( ! l->shortName().isEmpty() )
4282 {
4283 if ( l->shortName() == shortName )
4284 layers << l;
4285 }
4286 else if ( l->name() == shortName )
4287 {
4288 layers << l;
4289 }
4290 }
4291 return layers;
4292}
4293
4294bool QgsProject::unzip( const QString &filename, Qgis::ProjectReadFlags flags )
4295{
4297
4298 clearError();
4299 std::unique_ptr<QgsProjectArchive> archive( new QgsProjectArchive() );
4300
4301 // unzip the archive
4302 if ( !archive->unzip( filename ) )
4303 {
4304 setError( tr( "Unable to unzip file '%1'" ).arg( filename ) );
4305 return false;
4306 }
4307
4308 // test if zip provides a .qgs file
4309 if ( archive->projectFile().isEmpty() )
4310 {
4311 setError( tr( "Zip archive does not provide a project file" ) );
4312 return false;
4313 }
4314
4315 // Keep the archive
4316 releaseHandlesToProjectArchive();
4317 mArchive = std::move( archive );
4318
4319 // load auxiliary storage
4320 if ( !static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile().isEmpty() )
4321 {
4322 // database file is already a copy as it's been unzipped. So we don't open
4323 // auxiliary storage in copy mode in this case
4324 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile(), false ) );
4325 }
4326 else
4327 {
4328 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
4329 }
4330
4331 // read the project file
4332 if ( ! readProjectFile( static_cast<QgsProjectArchive *>( mArchive.get() )->projectFile(), flags ) )
4333 {
4334 setError( tr( "Cannot read unzipped qgs project file" ) + QStringLiteral( ": " ) + error() );
4335 return false;
4336 }
4337
4338 // Remove the temporary .qgs file
4339 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4340
4341 return true;
4342}
4343
4344bool QgsProject::zip( const QString &filename )
4345{
4347
4348 clearError();
4349
4350 // save the current project in a temporary .qgs file
4351 std::unique_ptr<QgsProjectArchive> archive( new QgsProjectArchive() );
4352 const QString baseName = QFileInfo( filename ).baseName();
4353 const QString qgsFileName = QStringLiteral( "%1.qgs" ).arg( baseName );
4354 QFile qgsFile( QDir( archive->dir() ).filePath( qgsFileName ) );
4355
4356 bool writeOk = false;
4357 if ( qgsFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
4358 {
4359 writeOk = writeProjectFile( qgsFile.fileName() );
4360 qgsFile.close();
4361 }
4362
4363 // stop here with an error message
4364 if ( ! writeOk )
4365 {
4366 setError( tr( "Unable to write temporary qgs file" ) );
4367 return false;
4368 }
4369
4370 // save auxiliary storage
4371 const QFileInfo info( qgsFile );
4372 const QString asExt = QStringLiteral( ".%1" ).arg( QgsAuxiliaryStorage::extension() );
4373 const QString asFileName = info.path() + QDir::separator() + info.completeBaseName() + asExt;
4374
4375 bool auxiliaryStorageSavedOk = true;
4376 if ( ! saveAuxiliaryStorage( asFileName ) )
4377 {
4378 const QString err = mAuxiliaryStorage->errorString();
4379 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 ) );
4380 auxiliaryStorageSavedOk = false;
4381
4382 // fixes the current archive and keep the previous version of qgd
4383 if ( !mArchive->exists() )
4384 {
4385 releaseHandlesToProjectArchive();
4386 mArchive.reset( new QgsProjectArchive() );
4387 mArchive->unzip( mFile.fileName() );
4388 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4389
4390 const QString auxiliaryStorageFile = static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile();
4391 if ( ! auxiliaryStorageFile.isEmpty() )
4392 {
4393 archive->addFile( auxiliaryStorageFile );
4394 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( auxiliaryStorageFile, false ) );
4395 }
4396 }
4397 }
4398 else
4399 {
4400 // in this case, an empty filename means that the auxiliary database is
4401 // empty, so we don't want to save it
4402 if ( QFile::exists( asFileName ) )
4403 {
4404 archive->addFile( asFileName );
4405 }
4406 }
4407
4408 // create the archive
4409 archive->addFile( qgsFile.fileName() );
4410
4411 // Add all other files
4412 const QStringList &files = mArchive->files();
4413 for ( const QString &file : files )
4414 {
4415 if ( !file.endsWith( QLatin1String( ".qgs" ), Qt::CaseInsensitive ) && !file.endsWith( asExt, Qt::CaseInsensitive ) )
4416 {
4417 archive->addFile( file );
4418 }
4419 }
4420
4421 // zip
4422 bool zipOk = true;
4423 if ( !archive->zip( filename ) )
4424 {
4425 setError( tr( "Unable to perform zip" ) );
4426 zipOk = false;
4427 }
4428
4429 return auxiliaryStorageSavedOk && zipOk;
4430}
4431
4433{
4435
4436 return QgsZipUtils::isZipFile( mFile.fileName() );
4437}
4438
4439QList<QgsMapLayer *> QgsProject::addMapLayers(
4440 const QList<QgsMapLayer *> &layers,
4441 bool addToLegend,
4442 bool takeOwnership )
4443{
4445
4446 const QList<QgsMapLayer *> myResultList { mLayerStore->addMapLayers( layers, takeOwnership ) };
4447 if ( !myResultList.isEmpty() )
4448 {
4449 // Update transform context
4450 for ( auto &l : myResultList )
4451 {
4452 l->setTransformContext( transformContext() );
4453 }
4454 if ( addToLegend )
4455 {
4456 emit legendLayersAdded( myResultList );
4457 }
4458 }
4459
4460 if ( mAuxiliaryStorage )
4461 {
4462 for ( QgsMapLayer *mlayer : myResultList )
4463 {
4464 if ( mlayer->type() != Qgis::LayerType::Vector )
4465 continue;
4466
4467 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mlayer );
4468 if ( vl )
4469 {
4470 vl->loadAuxiliaryLayer( *mAuxiliaryStorage );
4471 }
4472 }
4473 }
4474
4475 mProjectScope.reset();
4476
4477 return myResultList;
4478}
4479
4482 bool addToLegend,
4483 bool takeOwnership )
4484{
4486
4487 QList<QgsMapLayer *> addedLayers;
4488 addedLayers = addMapLayers( QList<QgsMapLayer *>() << layer, addToLegend, takeOwnership );
4489 return addedLayers.isEmpty() ? nullptr : addedLayers[0];
4490}
4491
4492void QgsProject::removeAuxiliaryLayer( const QgsMapLayer *ml )
4493{
4495
4496 if ( ! ml || ml->type() != Qgis::LayerType::Vector )
4497 return;
4498
4499 const QgsVectorLayer *vl = qobject_cast<const QgsVectorLayer *>( ml );
4500 if ( vl && vl->auxiliaryLayer() )
4501 {
4502 const QgsDataSourceUri uri( vl->auxiliaryLayer()->source() );
4504 }
4505}
4506
4507void QgsProject::removeMapLayers( const QStringList &layerIds )
4508{
4510
4511 for ( const auto &layerId : layerIds )
4512 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4513
4514 mProjectScope.reset();
4515 mLayerStore->removeMapLayers( layerIds );
4516}
4517
4518void QgsProject::removeMapLayers( const QList<QgsMapLayer *> &layers )
4519{
4521
4522 for ( const auto &layer : layers )
4523 removeAuxiliaryLayer( layer );
4524
4525 mProjectScope.reset();
4526 mLayerStore->removeMapLayers( layers );
4527}
4528
4529void QgsProject::removeMapLayer( const QString &layerId )
4530{
4532
4533 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4534 mProjectScope.reset();
4535 mLayerStore->removeMapLayer( layerId );
4536}
4537
4539{
4541
4542 removeAuxiliaryLayer( layer );
4543 mProjectScope.reset();
4544 mLayerStore->removeMapLayer( layer );
4545}
4546
4548{
4550
4551 mProjectScope.reset();
4552 return mLayerStore->takeMapLayer( layer );
4553}
4554
4556{
4558
4559 return mMainAnnotationLayer;
4560}
4561
4563{
4565
4566 if ( mLayerStore->count() == 0 )
4567 return;
4568
4569 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
4570 mProjectScope.reset();
4571 mLayerStore->removeAllMapLayers();
4572
4573 snapSingleBlocker.release();
4574 mSnappingConfig.clearIndividualLayerSettings();
4575 if ( !mBlockSnappingUpdates )
4576 emit snappingConfigChanged( mSnappingConfig );
4577}
4578
4580{
4582
4583 const QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
4584 QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin();
4585 for ( ; it != layers.constEnd(); ++it )
4586 {
4587 it.value()->reload();
4588 }
4589}
4590
4591QMap<QString, QgsMapLayer *> QgsProject::mapLayers( const bool validOnly ) const
4592{
4593 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
4595
4596 return validOnly ? mLayerStore->validMapLayers() : mLayerStore->mapLayers();
4597}
4598
4599QgsTransactionGroup *QgsProject::transactionGroup( const QString &providerKey, const QString &connString )
4600{
4602
4603 return mTransactionGroups.value( qMakePair( providerKey, connString ) );
4604}
4605
4607{
4609
4610 return &mEditBufferGroup;
4611}
4612
4614{
4616
4618
4619 // TODO QGIS 4.0 -- remove this method, and place it somewhere in app (where it belongs)
4620 // 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)
4621 if ( mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), QStringLiteral( "NoAction" ), QgsSettings::App ).toString() == QStringLiteral( "UseProjectCrs" )
4622 || mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), 0, QgsSettings::App ).toString() == QLatin1String( "2" ) )
4623 {
4624 // for new layers if the new layer crs method is set to either prompt or use project, then we use the project crs
4625 defaultCrs = crs();
4626 }
4627 else
4628 {
4629 // global crs
4630 const QString layerDefaultCrs = mSettings.value( QStringLiteral( "/Projections/layerDefaultCrs" ), geoEpsgCrsAuthId() ).toString();
4631 defaultCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( layerDefaultCrs );
4632 }
4633
4634 return defaultCrs;
4635}
4636
4638{
4640
4642}
4643
4645{
4647
4649}
4650
4651bool QgsProject::saveAuxiliaryStorage( const QString &filename )
4652{
4654
4655 const QMap<QString, QgsMapLayer *> layers = mapLayers();
4656 bool empty = true;
4657 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
4658 {
4659 if ( it.value()->type() != Qgis::LayerType::Vector )
4660 continue;
4661
4662 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
4663 if ( vl && vl->auxiliaryLayer() )
4664 {
4665 vl->auxiliaryLayer()->save();
4666 empty &= vl->auxiliaryLayer()->auxiliaryFields().isEmpty();
4667 }
4668 }
4669
4670 if ( !mAuxiliaryStorage->exists( *this ) && empty )
4671 {
4672 return true; // it's not an error
4673 }
4674 else if ( !filename.isEmpty() )
4675 {
4676 return mAuxiliaryStorage->saveAs( filename );
4677 }
4678 else
4679 {
4680 return mAuxiliaryStorage->saveAs( *this );
4681 }
4682}
4683
4684QgsPropertiesDefinition &QgsProject::dataDefinedServerPropertyDefinitions()
4685{
4686 static QgsPropertiesDefinition sPropertyDefinitions
4687 {
4688 {
4690 QgsPropertyDefinition( "WMSOnlineResource", QObject::tr( "WMS Online Resource" ), QgsPropertyDefinition::String )
4691 },
4692 };
4693 return sPropertyDefinitions;
4694}
4695
4697{
4698 mElevationShadingRenderer = elevationShadingRenderer;
4700}
4701
4703{
4705
4706 return mAuxiliaryStorage.get();
4707}
4708
4710{
4712
4713 return mAuxiliaryStorage.get();
4714}
4715
4716QString QgsProject::createAttachedFile( const QString &nameTemplate )
4717{
4719
4720 const QDir archiveDir( mArchive->dir() );
4721 QTemporaryFile tmpFile( archiveDir.filePath( "XXXXXX_" + nameTemplate ), this );
4722 tmpFile.setAutoRemove( false );
4723 tmpFile.open();
4724 mArchive->addFile( tmpFile.fileName() );
4725 return tmpFile.fileName();
4726}
4727
4728QStringList QgsProject::attachedFiles() const
4729{
4731
4732 QStringList attachments;
4733 const QString baseName = QFileInfo( fileName() ).baseName();
4734 const QStringList files = mArchive->files();
4735 attachments.reserve( files.size() );
4736 for ( const QString &file : files )
4737 {
4738 if ( QFileInfo( file ).baseName() != baseName )
4739 {
4740 attachments.append( file );
4741 }
4742 }
4743 return attachments;
4744}
4745
4746bool QgsProject::removeAttachedFile( const QString &path )
4747{
4749
4750 return mArchive->removeFile( path );
4751}
4752
4753QString QgsProject::attachmentIdentifier( const QString &attachedFile ) const
4754{
4756
4757 return QStringLiteral( "attachment:///%1" ).arg( QFileInfo( attachedFile ).fileName() );
4758}
4759
4760QString QgsProject::resolveAttachmentIdentifier( const QString &identifier ) const
4761{
4763
4764 if ( identifier.startsWith( QLatin1String( "attachment:///" ) ) )
4765 {
4766 return QDir( mArchive->dir() ).absoluteFilePath( identifier.mid( 14 ) );
4767 }
4768 return QString();
4769}
4770
4772{
4773 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4775
4776 return mMetadata;
4777}
4778
4780{
4782
4783 if ( metadata == mMetadata )
4784 return;
4785
4786 mMetadata = metadata;
4787 mProjectScope.reset();
4788
4789 emit metadataChanged();
4790
4791 setDirty( true );
4792}
4793
4794QSet<QgsMapLayer *> QgsProject::requiredLayers() const
4795{
4797
4798 QSet<QgsMapLayer *> requiredLayers;
4799
4800 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
4801 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
4802 {
4803 if ( !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
4804 {
4805 requiredLayers.insert( it.value() );
4806 }
4807 }
4808 return requiredLayers;
4809}
4810
4811void QgsProject::setRequiredLayers( const QSet<QgsMapLayer *> &layers )
4812{
4814
4815 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
4816 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
4817 {
4818 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
4819 continue;
4820
4821 if ( layers.contains( it.value() ) )
4822 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Removable );
4823 else
4824 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Removable );
4825 }
4826}
4827
4829{
4831
4832 // save colors to project
4833 QStringList customColors;
4834 QStringList customColorLabels;
4835
4836 QgsNamedColorList::const_iterator colorIt = colors.constBegin();
4837 for ( ; colorIt != colors.constEnd(); ++colorIt )
4838 {
4839 const QString color = QgsColorUtils::colorToString( ( *colorIt ).first );
4840 const QString label = ( *colorIt ).second;
4841 customColors.append( color );
4842 customColorLabels.append( label );
4843 }
4844 writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ), customColors );
4845 writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ), customColorLabels );
4846 mProjectScope.reset();
4847 emit projectColorsChanged();
4848}
4849
4850void QgsProject::setBackgroundColor( const QColor &color )
4851{
4853
4854 if ( mBackgroundColor == color )
4855 return;
4856
4857 mBackgroundColor = color;
4859}
4860
4862{
4864
4865 return mBackgroundColor;
4866}
4867
4868void QgsProject::setSelectionColor( const QColor &color )
4869{
4871
4872 if ( mSelectionColor == color )
4873 return;
4874
4875 mSelectionColor = color;
4876 emit selectionColorChanged();
4877}
4878
4880{
4882
4883 return mSelectionColor;
4884}
4885
4886void QgsProject::setMapScales( const QVector<double> &scales )
4887{
4889
4890 mViewSettings->setMapScales( scales );
4891}
4892
4893QVector<double> QgsProject::mapScales() const
4894{
4896
4897 return mViewSettings->mapScales();
4898}
4899
4901{
4903
4904 mViewSettings->setUseProjectScales( enabled );
4905}
4906
4908{
4910
4911 return mViewSettings->useProjectScales();
4912}
4913
4914void QgsProject::generateTsFile( const QString &locale )
4915{
4917
4918 QgsTranslationContext translationContext;
4919 translationContext.setProject( this );
4920 translationContext.setFileName( QStringLiteral( "%1/%2.ts" ).arg( absolutePath(), baseName() ) );
4921
4922 QgsApplication::instance()->collectTranslatableObjects( &translationContext );
4923
4924 translationContext.writeTsFile( locale );
4925}
4926
4927QString QgsProject::translate( const QString &context, const QString &sourceText, const char *disambiguation, int n ) const
4928{
4930
4931 if ( !mTranslator )
4932 {
4933 return sourceText;
4934 }
4935
4936 QString result = mTranslator->translate( context.toUtf8(), sourceText.toUtf8(), disambiguation, n );
4937
4938 if ( result.isEmpty() )
4939 {
4940 return sourceText;
4941 }
4942 return result;
4943}
4944
4946{
4948
4949 const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
4950 if ( !layers.empty() )
4951 {
4952 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
4953 {
4954 // NOTE: if visitEnter returns false it means "don't visit this layer", not "abort all further visitations"
4955 if ( visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
4956 {
4957 if ( !( ( *it )->accept( visitor ) ) )
4958 return false;
4959
4960 if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
4961 return false;
4962 }
4963 }
4964 }
4965
4966 if ( !mLayoutManager->accept( visitor ) )
4967 return false;
4968
4969 if ( !mAnnotationManager->accept( visitor ) )
4970 return false;
4971
4972 return true;
4973}
4974
4976{
4977 return mElevationShadingRenderer;
4978}
4979
4980void QgsProject::loadProjectFlags( const QDomDocument *doc )
4981{
4983
4984 QDomElement element = doc->documentElement().firstChildElement( QStringLiteral( "projectFlags" ) );
4986 if ( !element.isNull() )
4987 {
4988 flags = qgsFlagKeysToValue( element.attribute( QStringLiteral( "set" ) ), Qgis::ProjectFlags() );
4989 }
4990 else
4991 {
4992 // older project compatibility
4993 element = doc->documentElement().firstChildElement( QStringLiteral( "evaluateDefaultValues" ) );
4994 if ( !element.isNull() )
4995 {
4996 if ( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
4998 }
4999
5000 // Read trust layer metadata config in the project
5001 element = doc->documentElement().firstChildElement( QStringLiteral( "trust" ) );
5002 if ( !element.isNull() )
5003 {
5004 if ( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
5006 }
5007 }
5008
5009 setFlags( flags );
5010}
5011
5013GetNamedProjectColor::GetNamedProjectColor( const QgsProject *project )
5014 : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
5015{
5016 if ( !project )
5017 return;
5018
5019 //build up color list from project. Do this in advance for speed
5020 QStringList colorStrings = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ) );
5021 const QStringList colorLabels = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ) );
5022
5023 //generate list from custom colors
5024 int colorIndex = 0;
5025 for ( QStringList::iterator it = colorStrings.begin();
5026 it != colorStrings.end(); ++it )
5027 {
5028 const QColor color = QgsColorUtils::colorFromString( *it );
5029 QString label;
5030 if ( colorLabels.length() > colorIndex )
5031 {
5032 label = colorLabels.at( colorIndex );
5033 }
5034
5035 mColors.insert( label.toLower(), color );
5036 colorIndex++;
5037 }
5038}
5039
5040GetNamedProjectColor::GetNamedProjectColor( const QHash<QString, QColor> &colors )
5041 : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
5042 , mColors( colors )
5043{
5044}
5045
5046QVariant GetNamedProjectColor::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5047{
5048 const QString colorName = values.at( 0 ).toString().toLower();
5049 if ( mColors.contains( colorName ) )
5050 {
5051 return QStringLiteral( "%1,%2,%3" ).arg( mColors.value( colorName ).red() ).arg( mColors.value( colorName ).green() ).arg( mColors.value( colorName ).blue() );
5052 }
5053 else
5054 return QVariant();
5055}
5056
5057QgsScopedExpressionFunction *GetNamedProjectColor::clone() const
5058{
5059 return new GetNamedProjectColor( mColors );
5060}
5061
5062// ----------------
5063
5064GetSensorData::GetSensorData( const QMap<QString, QgsAbstractSensor::SensorData> &sensorData )
5065 : QgsScopedExpressionFunction( QStringLiteral( "sensor_data" ),
5066 QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "name" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "expiration" ), true, 0 ),
5067 QStringLiteral( "Sensors" ) )
5068 , mSensorData( sensorData )
5069{
5070}
5071
5072QVariant GetSensorData::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5073{
5074 const QString sensorName = values.at( 0 ).toString();
5075 const int expiration = values.at( 1 ).toInt();
5076 const qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
5077 if ( mSensorData.contains( sensorName ) )
5078 {
5079 if ( expiration <= 0 || ( timestamp - mSensorData[sensorName].lastTimestamp.toMSecsSinceEpoch() ) < expiration )
5080 {
5081 return mSensorData[sensorName].lastValue;
5082 }
5083 }
5084
5085 return QVariant();
5086}
5087
5088QgsScopedExpressionFunction *GetSensorData::clone() const
5089{
5090 return new GetSensorData( mSensorData );
5091}
@ DontLoad3DViews
Skip loading 3D views (since QGIS 3.26)
@ 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. (since QGIS 3.28)
@ TrustLayerMetadata
Trust layer metadata. Improves project read time. Do not use it if layers' extent is not fixed during...
@ 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:258
QFlags< ProjectCapability > ProjectCapabilities
Flags which control project capabilities.
Definition: qgis.h:3584
QFlags< ProjectReadFlag > ProjectReadFlags
Project load flags.
Definition: qgis.h:3562
DistanceUnit
Units of distance.
Definition: qgis.h:4124
FilePathType
File path types.
Definition: qgis.h:1280
@ Relative
Relative path.
@ Absolute
Absolute path.
TransactionMode
Transaction mode.
Definition: qgis.h:3225
@ 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:4162
@ SquareMeters
Square meters.
AvoidIntersectionsMode
Flags which control how intersections of pre-existing feature are handled when digitizing new feature...
Definition: qgis.h:3515
@ 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:3347
@ 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...
LayerType
Types of layers that can be added to a map.
Definition: qgis.h:114
@ 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.
QFlags< ProjectFlag > ProjectFlags
Definition: qgis.h:3354
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
@ 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 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.
Class allowing to manage the zip/unzip actions.
Definition: qgsarchive.h:35
void addFile(const QString &filename)
Add a new file to this archive.
Definition: qgsarchive.cpp:113
This is a container for attribute editors, used to group them visually in the attribute form if it is...
QList< QgsAttributeEditorElement * > children() const
Gets a list of the children elements of this container.
This is 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.
Class 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.
This class 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.
Q_GADGET Qgis::DistanceUnit mapUnits
QString toProj() const
Returns a Proj string representation of this CRS.
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
QString projectionAcronym() const
Returns the projection acronym for the projection used by the CRS.
static QgsCoordinateReferenceSystem fromProj(const QString &proj)
Creates a CRS from a proj style formatted string.
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
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.
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.
@ ParallelThreadLoading
Skip credentials if the provided one are not valid, let the provider be invalid, avoiding to block th...
QFlags< ReadFlag > ReadFlags
@ EvaluateDefaultValues
Evaluate default values on provider side when calling QgsVectorDataProvider::defaultValue( int index ...
Class for storing the component parts of a RDBMS data source URI (e.g.
QgsAttributeEditorContainer * invisibleRootContainer()
Gets the invisible root container for the drag and drop designer form (EditorLayout::TabLayout).
This class can render elevation shading on an image with different methods (eye dome lighting,...
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...
A abstract base class for defining QgsExpression functions.
An expression node for expression functions.
Class for 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:45
bool isEmpty() const
Checks whether the container is empty.
Definition: qgsfields.cpp:128
Stores global configuration for labeling engine.
Class used to work with layer dependencies stored in a XML project or layer definition file.
Layer tree group node serves as a container for layers and further groups.
void resolveReferences(const QgsProject *project, bool looseMatching=false) override
Calls resolveReferences() on child tree nodes.
QgsLayerTreeGroup * findGroup(const QString &name)
Find group node with specified name.
QList< QgsLayerTreeGroup * > findGroups(bool recursive=false) const
Find group layer nodes.
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.
void readChildrenFromXml(QDomElement &element, const QgsReadWriteContext &context)
Read children from XML and append them to the group.
QgsLayerTreeGroup * clone() const override
Returns a clone of the group.
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
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)
This class is a 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.
void removeCustomProperty(const QString &key)
Remove a custom property from layer. Properties are stored in a map and saved in project file.
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)
Listens to the updates in map layer registry and does changes in layer tree.
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.
Definition: qgslayertree.h:32
void readLayerOrderFromXml(const QDomElement &doc)
Load the layer order from an XML element.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:70
void clear()
Clear any information from this layer tree.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:50
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
Definition: qgslayertree.h:41
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
Definition: qgslayertree.h:60
QList< QgsMapLayer * > customLayerOrder() const
The order in which layers will be rendered on the canvas.
QgsLayerTree * clone() const override
Create a copy of the node. Returns new instance.
Manages storage of a set of layouts.
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:131
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:75
QFlags< ReadFlag > ReadFlags
Definition: qgsmaplayer.h:645
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.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:81
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
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:82
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.
bool isValid
Definition: qgsmaplayer.h:83
@ Identifiable
If the layer is identifiable using the identify map tool and as a WMS layer.
Definition: qgsmaplayer.h:151
@ Removable
If the layer can be removed from the project. The layer will not be removable from the legend menu en...
Definition: qgsmaplayer.h:152
@ FlagTrustLayerMetadata
Trust layer metadata. Improves layer load time by skipping expensive checks like primary key unicity,...
Definition: qgsmaplayer.h:641
@ FlagForceReadOnly
Force open as read only.
Definition: qgsmaplayer.h:643
@ FlagDontResolveLayers
Don't resolve layer paths or create data providers for layers.
Definition: qgsmaplayer.h:640
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.
static QgsDataProvider::ReadFlags providerReadFlags(const QDomNode &layerNode, QgsMapLayer::ReadFlags layerReadFlags)
Returns provider read flag deduced from layer read flags layerReadFlags and a dom node layerNode that...
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)
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.
Class allowing to manage the zip/unzip actions on project file.
Definition: qgsarchive.h:120
QString projectFile() const
Returns the current .qgs project file or an empty string if there's none.
Definition: qgsarchive.cpp:140
QString auxiliaryStorageFile() const
Returns the current .qgd auxiliary storage file or an empty string if there's none.
Definition: qgsarchive.cpp:166
bool unzip(const QString &zipFilename) override
Clear the current content of this archive and unzip.
Definition: qgsarchive.cpp:153
Interface for classes that handle missing layer files when reading project file.
virtual void handleBadLayers(const QList< QDomNode > &layers)
This method will be called whenever the project tries to load layers which cannot be accessed.
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.
Class to 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 map layer.
bool writeMetadataXml(QDomElement &metadataElement, QDomDocument &document) const override
Stores state in a DOM node.
void setCreationDateTime(const QDateTime &creationDateTime)
Sets the project's creation date/timestamp.
bool readMetadataXml(const QDomElement &metadataElement) override
Sets state from DOM document.
void setAuthor(const QString &author)
Sets the project author string.
Project property key node.
QString name() const
The name of the property is used as identifier.
QgsProjectProperty * find(const QString &propertyName) const
Attempts to find a property with a matching sub-key name.
void removeKey(const QString &keyName)
Removes the specified key.
void dump(int tabs=0) const override
Dumps out the keys and values.
bool isEmpty() const
Returns true if this property contains no sub-keys.
virtual void clearKeys()
Deletes any sub-nodes from the property.
bool writeXml(const QString &nodeName, QDomElement &element, QDomDocument &document) override
Writes the property hierarchy to a specified DOM element.
void subkeyList(QStringList &entries) const
Returns any sub-keys contained by this property which themselves contain other keys.
void setName(const QString &name)
The name of the property is used as identifier.
QgsProjectPropertyKey * addKey(const QString &keyName)
Adds the specified property key as a sub-key.
QVariant value() const override
If this key has a value, it will be stored by its name in its properties.
QgsProjectPropertyValue * setValue(const QString &name, const QVariant &value)
Sets the value associated with this key.
void entryList(QStringList &entries) const
Returns any sub-keys contained by this property that do not contain other keys.
int count() const
Returns the number of sub-keys contained by this property.
bool readXml(const QDomNode &keyNode) override
Restores the property hierarchy from a specified DOM node.
An Abstract Base Class for QGIS project property hierarchys.
virtual bool isKey() const =0
Returns true if the property is a QgsProjectPropertyKey.
virtual bool isValue() const =0
Returns true if the property is a QgsProjectPropertyValue.
QgsProjectStorage * projectStorageFromUri(const QString &uri)
Returns storage implementation if the URI matches one. Returns nullptr otherwise (it is a normal file...
Metadata associated with a project.
Abstract interface for project storage - to be implemented by various backends and registered in QgsP...
Contains settings and properties relating to how a QgsProject should handle styling.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Returns a DOM element representing the settings.
void setDefaultSymbol(Qgis::SymbolType symbolType, QgsSymbol *symbol)
Sets the project default symbol for a given type.
void reset()
Resets the settings to a default state.
void removeProjectStyle()
Removes and deletes the project style database.
void setRandomizeDefaultSymbolColor(bool randomized)
Sets whether the default symbol fill color is randomized.
void setDefaultColorRamp(QgsColorRamp *colorRamp)
Sets the project default color ramp.
bool readXml(const QDomElement &element, const QgsReadWriteContext &context, Qgis::ProjectReadFlags flags=Qgis::ProjectReadFlags())
Reads the settings's state from a DOM element.
void setDefaultSymbolOpacity(double opacity)
Sets the default symbol opacity.
Contains temporal settings and properties for the project, this may be used when animating maps or sh...
bool readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads the settings's state from a DOM element.
void reset()
Resets the settings to a default state.
QDomElement writeXml(QDomDocument &document, const QgsReadWriteContext &context) const
Returns a DOM element representing the settings.
A class to describe 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.
Definition: qgsproject.cpp:369
void removeMapLayer(const QString &layerId)
Remove a layer from the registry by layer ID.
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
Definition: qgsproject.cpp:459
QString error() const
Returns error message from previous read/write.
Q_DECL_DEPRECATED void setUseProjectScales(bool enabled)
Sets whether project mapScales() are enabled.
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:207
Q_DECL_DEPRECATED QFileInfo fileInfo() const
Returns QFileInfo object for the project's associated file.
Definition: qgsproject.cpp:838
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.
Definition: qgsproject.cpp:944
QColor selectionColor
Definition: qgsproject.h:122
QString title() const
Returns the project's title.
Definition: qgsproject.cpp:506
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...
Definition: qgsproject.cpp:742
void mapThemeCollectionChanged()
Emitted when the map theme collection changes.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:481
Qgis::FilePathType filePathStorage() const
Returns the type of paths used when storing file paths in a QGS/QGZ project file.
Definition: qgsproject.cpp:913
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.
Definition: qgsproject.cpp:714
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...
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.
Definition: qgsproject.cpp:768
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 the project with QTranslator and qm file.
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.
Definition: qgsproject.cpp:794
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.
Definition: qgsproject.cpp:707
void registerTranslatableObjects(QgsTranslationContext *translationContext)
Registers the objects that require translation into the translationContext.
Definition: qgsproject.cpp:652
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.
Definition: qgsproject.cpp:721
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...
Q_DECL_DEPRECATED void oldProjectVersionWarning(const QString &)
Emitted when an old project file is read.
void setLabelingEngineSettings(const QgsLabelingEngineSettings &