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