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