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