QGIS API Documentation 3.39.0-Master (8f1a6e30482)
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
482QgsProject *QgsProject::instance() // skip-keyword-check
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
1727 {
1728 profile.switchTask( tr( "Load providers in parallel" ) );
1729 for ( const QDomNode &node : sortedLayerNodes )
1730 {
1731 const QDomElement element = node.toElement();
1732 if ( element.attribute( QStringLiteral( "embedded" ) ) != QLatin1String( "1" ) )
1733 {
1734 const QString layerId = node.namedItem( QStringLiteral( "id" ) ).toElement().text();
1735 if ( !depSorter.isLayerDependent( layerId ) )
1736 {
1737 const QDomNode mnl = element.namedItem( QStringLiteral( "provider" ) );
1738 const QDomElement mne = mnl.toElement();
1739 const QString provider = mne.text();
1741 if ( meta && meta->providerCapabilities().testFlag( QgsProviderMetadata::ParallelCreateProvider ) )
1742 {
1743 parallelLoading.append( node );
1744 continue;
1745 }
1746 }
1747 }
1748 }
1749
1750 QgsReadWriteContext context;
1751 context.setPathResolver( pathResolver() );
1752 if ( !parallelLoading.isEmpty() )
1753 preloadProviders( parallelLoading, context, loadedProviders, projectFlagsToLayerReadFlags( flags, mFlags ), sortedLayerNodes.count() );
1754 }
1755
1756 int i = loadedProviders.count();
1757 for ( const QDomNode &node : std::as_const( sortedLayerNodes ) )
1758 {
1759 const QDomElement element = node.toElement();
1760 const QString name = translate( QStringLiteral( "project:layers:%1" ).arg( node.namedItem( QStringLiteral( "id" ) ).toElement().text() ), node.namedItem( QStringLiteral( "layername" ) ).toElement().text() );
1761 if ( !name.isNull() )
1762 emit loadingLayer( tr( "Loading layer %1" ).arg( name ) );
1763
1764 profile.switchTask( name );
1765 if ( element.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
1766 {
1767 createEmbeddedLayer( element.attribute( QStringLiteral( "id" ) ), readPath( element.attribute( QStringLiteral( "project" ) ) ), brokenNodes, true, flags );
1768 }
1769 else
1770 {
1771 QgsReadWriteContext context;
1772 context.setPathResolver( pathResolver() );
1773 context.setProjectTranslator( this );
1775 QString layerId = element.namedItem( QStringLiteral( "id" ) ).toElement().text();
1776
1777 if ( !addLayer( element, brokenNodes, context, flags, loadedProviders.take( layerId ) ) )
1778 {
1779 returnStatus = false;
1780 }
1781 const auto messages = context.takeMessages();
1782 if ( !messages.isEmpty() )
1783 {
1784 emit loadingLayerMessageReceived( tr( "Loading layer %1" ).arg( name ), messages );
1785 }
1786 }
1787 emit layerLoaded( i + 1, totalLayerCount );
1788 i++;
1789 }
1790
1791 return returnStatus;
1792}
1793
1794bool QgsProject::addLayer( const QDomElement &layerElem,
1795 QList<QDomNode> &brokenNodes,
1796 QgsReadWriteContext &context,
1798 QgsDataProvider *provider )
1799{
1801
1802 const QString type = layerElem.attribute( QStringLiteral( "type" ) );
1803 QgsDebugMsgLevel( "Layer type is " + type, 4 );
1804 std::unique_ptr<QgsMapLayer> mapLayer;
1805
1806 QgsScopedRuntimeProfile profile( tr( "Create layer" ), QStringLiteral( "projectload" ) );
1807
1808 bool ok = false;
1809 const Qgis::LayerType layerType( QgsMapLayerFactory::typeFromString( type, ok ) );
1810 if ( !ok )
1811 {
1812 QgsDebugError( QStringLiteral( "Unknown layer type \"%1\"" ).arg( type ) );
1813 return false;
1814 }
1815
1816 switch ( layerType )
1817 {
1819 mapLayer = std::make_unique<QgsVectorLayer>();
1820 break;
1821
1823 mapLayer = std::make_unique<QgsRasterLayer>();
1824 break;
1825
1827 mapLayer = std::make_unique<QgsMeshLayer>();
1828 break;
1829
1831 mapLayer = std::make_unique<QgsVectorTileLayer>();
1832 break;
1833
1835 mapLayer = std::make_unique<QgsPointCloudLayer>();
1836 break;
1837
1839 mapLayer = std::make_unique<QgsTiledSceneLayer>();
1840 break;
1841
1843 {
1844 const QString typeName = layerElem.attribute( QStringLiteral( "name" ) );
1845 mapLayer.reset( QgsApplication::pluginLayerRegistry()->createLayer( typeName ) );
1846 break;
1847 }
1848
1850 {
1851 const QgsAnnotationLayer::LayerOptions options( mTransformContext );
1852 mapLayer = std::make_unique<QgsAnnotationLayer>( QString(), options );
1853 break;
1854 }
1855
1857 {
1858 const QgsGroupLayer::LayerOptions options( mTransformContext );
1859 mapLayer = std::make_unique<QgsGroupLayer>( QString(), options );
1860 break;
1861 }
1862 }
1863
1864 if ( !mapLayer )
1865 {
1866 QgsDebugError( QStringLiteral( "Unable to create layer" ) );
1867 return false;
1868 }
1869
1870 Q_CHECK_PTR( mapLayer ); // NOLINT
1871
1872 // This is tricky: to avoid a leak we need to check if the layer was already in the store
1873 // because if it was, the newly created layer will not be added to the store and it would leak.
1874 const QString layerId { layerElem.namedItem( QStringLiteral( "id" ) ).toElement().text() };
1875 Q_ASSERT( ! layerId.isEmpty() );
1876 const bool layerWasStored = layerStore()->mapLayer( layerId );
1877
1878 // have the layer restore state that is stored in Dom node
1879 QgsMapLayer::ReadFlags layerFlags = projectFlagsToLayerReadFlags( flags, mFlags );
1880
1881 profile.switchTask( tr( "Load layer source" ) );
1882 const bool layerIsValid = mapLayer->readLayerXml( layerElem, context, layerFlags, provider ) && mapLayer->isValid();
1883
1884 // apply specific settings to vector layer
1885 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1886 {
1887 vl->setReadExtentFromXml( layerFlags & QgsMapLayer::FlagReadExtentFromXml );
1888 if ( vl->dataProvider() )
1889 {
1891 vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, evaluateDefaultValues );
1892 }
1893 }
1894
1895 profile.switchTask( tr( "Add layer to project" ) );
1896 QList<QgsMapLayer *> newLayers;
1897 newLayers << mapLayer.get();
1898 if ( layerIsValid || flags & Qgis::ProjectReadFlag::DontResolveLayers )
1899 {
1900 emit readMapLayer( mapLayer.get(), layerElem );
1901 addMapLayers( newLayers );
1902 // Try to resolve references here (this is necessary to set up joined fields that will be possibly used by
1903 // virtual layers that point to this layer's joined field in their query otherwise they won't be valid ),
1904 // a second attempt to resolve references will be done after all layers are loaded
1905 // see https://github.com/qgis/QGIS/issues/46834
1906 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1907 {
1908 vLayer->joinBuffer()->resolveReferences( this );
1909 }
1910 }
1911 else
1912 {
1913 // It's a bad layer: do not add to legend (the user will decide if she wants to do so)
1914 addMapLayers( newLayers, false );
1915 newLayers.first();
1916 QgsDebugError( "Unable to load " + type + " layer" );
1917 brokenNodes.push_back( layerElem );
1918 }
1919
1920 const bool wasEditable = layerElem.attribute( QStringLiteral( "editable" ), QStringLiteral( "0" ) ).toInt();
1921 if ( wasEditable )
1922 {
1923 mapLayer->setCustomProperty( QStringLiteral( "_layer_was_editable" ), true );
1924 }
1925 else
1926 {
1927 mapLayer->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
1928 }
1929
1930 // It should be safe to delete the layer now if layer was stored, because all the store
1931 // had to to was to reset the data source in case the validity changed.
1932 if ( ! layerWasStored )
1933 {
1934 mapLayer.release();
1935 }
1936
1937 return layerIsValid;
1938}
1939
1940bool QgsProject::read( const QString &filename, Qgis::ProjectReadFlags flags )
1941{
1943
1944 mFile.setFileName( filename );
1945 mCachedHomePath.clear();
1946 mProjectScope.reset();
1947
1948 return read( flags );
1949}
1950
1952{
1954
1955 const QString filename = mFile.fileName();
1956 bool returnValue;
1957
1958 if ( QgsProjectStorage *storage = projectStorage() )
1959 {
1960 QTemporaryFile inDevice;
1961 if ( !inDevice.open() )
1962 {
1963 setError( tr( "Unable to open %1" ).arg( inDevice.fileName() ) );
1964 return false;
1965 }
1966
1967 QgsReadWriteContext context;
1968 context.setProjectTranslator( this );
1969 if ( !storage->readProject( filename, &inDevice, context ) )
1970 {
1971 QString err = tr( "Unable to open %1" ).arg( filename );
1972 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
1973 if ( !messages.isEmpty() )
1974 err += QStringLiteral( "\n\n" ) + messages.last().message();
1975 setError( err );
1976 return false;
1977 }
1978 returnValue = unzip( inDevice.fileName(), flags ); // calls setError() if returning false
1979 }
1980 else
1981 {
1982 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
1983 {
1984 returnValue = unzip( mFile.fileName(), flags );
1985 }
1986 else
1987 {
1988 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
1989 const QFileInfo finfo( mFile.fileName() );
1990 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
1991 if ( QFile( attachmentsZip ).exists() )
1992 {
1993 std::unique_ptr<QgsArchive> archive( new QgsArchive() );
1994 if ( archive->unzip( attachmentsZip ) )
1995 {
1996 releaseHandlesToProjectArchive();
1997 mArchive = std::move( archive );
1998 }
1999 }
2000 returnValue = readProjectFile( mFile.fileName(), flags );
2001 }
2002
2003 //on translation we should not change the filename back
2004 if ( !mTranslator )
2005 {
2006 mFile.setFileName( filename );
2007 mCachedHomePath.clear();
2008 mProjectScope.reset();
2009 }
2010 else
2011 {
2012 //but delete the translator
2013 mTranslator.reset( nullptr );
2014 }
2015 }
2016 emit homePathChanged();
2017 return returnValue;
2018}
2019
2020bool QgsProject::readProjectFile( const QString &filename, Qgis::ProjectReadFlags flags )
2021{
2023
2024 // avoid multiple emission of snapping updated signals
2025 ScopedIntIncrementor snapSignalBlock( &mBlockSnappingUpdates );
2026
2027 QFile projectFile( filename );
2028 clearError();
2029
2030 QgsApplication::profiler()->clear( QStringLiteral( "projectload" ) );
2031 QgsScopedRuntimeProfile profile( tr( "Setting up translations" ), QStringLiteral( "projectload" ) );
2032
2033 const QString localeFileName = QStringLiteral( "%1_%2" ).arg( QFileInfo( projectFile.fileName() ).baseName(), QgsApplication::settingsLocaleUserLocale->value() );
2034
2035 if ( QFile( QStringLiteral( "%1/%2.qm" ).arg( QFileInfo( projectFile.fileName() ).absolutePath(), localeFileName ) ).exists() )
2036 {
2037 mTranslator.reset( new QTranslator() );
2038 ( void )mTranslator->load( localeFileName, QFileInfo( projectFile.fileName() ).absolutePath() );
2039 }
2040
2041 profile.switchTask( tr( "Reading project file" ) );
2042 std::unique_ptr<QDomDocument> doc( new QDomDocument( QStringLiteral( "qgis" ) ) );
2043
2044 if ( !projectFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
2045 {
2046 projectFile.close();
2047
2048 setError( tr( "Unable to open %1" ).arg( projectFile.fileName() ) );
2049
2050 return false;
2051 }
2052
2053 QTextStream textStream( &projectFile );
2054#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2055 textStream.setCodec( "UTF-8" );
2056#endif
2057 QString projectString = textStream.readAll();
2058 projectFile.close();
2059
2060 for ( int i = 0; i < 32; i++ )
2061 {
2062 if ( i == 9 || i == 10 || i == 13 )
2063 {
2064 continue;
2065 }
2066 projectString.replace( QChar( i ), QStringLiteral( "%1%2%1" ).arg( FONTMARKER_CHR_FIX, QString::number( i ) ) );
2067 }
2068
2069 // location of problem associated with errorMsg
2070 int line, column;
2071 QString errorMsg;
2072 if ( !doc->setContent( projectString, &errorMsg, &line, &column ) )
2073 {
2074 const QString errorString = tr( "Project file read error in file %1: %2 at line %3 column %4" )
2075 .arg( projectFile.fileName(), errorMsg ).arg( line ).arg( column );
2076 QgsDebugError( errorString );
2077 setError( errorString );
2078
2079 return false;
2080 }
2081
2082 projectFile.close();
2083
2084 QgsDebugMsgLevel( "Opened document " + projectFile.fileName(), 2 );
2085
2086 // get project version string, if any
2087 const QgsProjectVersion fileVersion = getVersion( *doc );
2088 const QgsProjectVersion thisVersion( Qgis::version() );
2089
2090 profile.switchTask( tr( "Updating project file" ) );
2091 if ( thisVersion > fileVersion )
2092 {
2093 const bool isOlderMajorVersion = fileVersion.majorVersion() < thisVersion.majorVersion();
2094
2095 if ( isOlderMajorVersion )
2096 {
2097 QgsLogger::warning( "Loading a file that was saved with an older "
2098 "version of qgis (saved in " + fileVersion.text() +
2099 ", loaded in " + Qgis::version() +
2100 "). Problems may occur." );
2101 }
2102
2103 QgsProjectFileTransform projectFile( *doc, fileVersion );
2104
2105 // Shows a warning when an old project file is read.
2107 emit oldProjectVersionWarning( fileVersion.text() );
2109 emit readVersionMismatchOccurred( fileVersion.text() );
2110
2111 projectFile.updateRevision( thisVersion );
2112 }
2113 else if ( fileVersion > thisVersion )
2114 {
2115 QgsLogger::warning( "Loading a file that was saved with a newer "
2116 "version of qgis (saved in " + fileVersion.text() +
2117 ", loaded in " + Qgis::version() +
2118 "). Problems may occur." );
2119
2120 emit readVersionMismatchOccurred( fileVersion.text() );
2121 }
2122
2123 // start new project, just keep the file name and auxiliary storage
2124 profile.switchTask( tr( "Creating auxiliary storage" ) );
2125 const QString fileName = mFile.fileName();
2126
2127 const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
2128 const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
2129
2130 // NOTE [ND] -- I suspect this is wrong, as the archive may contain any number of non-auxiliary
2131 // storage related files from the previously loaded project.
2132 std::unique_ptr<QgsAuxiliaryStorage> aStorage = std::move( mAuxiliaryStorage );
2133 std::unique_ptr<QgsArchive> archive = std::move( mArchive );
2134
2135 // don't emit xxxChanged signals during the clear() call, as we'll be emitting
2136 // them again after reading the properties from the project file
2137 mBlockChangeSignalsDuringClear = true;
2138 clear();
2139 mBlockChangeSignalsDuringClear = false;
2140
2141 // this is ugly, but clear() will have created a new archive and started populating it. We
2142 // need to release handles to this archive now as the subsequent call to move will need
2143 // to delete it, and requires free access to do so.
2144 releaseHandlesToProjectArchive();
2145
2146 mAuxiliaryStorage = std::move( aStorage );
2147 mArchive = std::move( archive );
2148
2149 mFile.setFileName( fileName );
2150 mCachedHomePath.clear();
2151 mProjectScope.reset();
2152 mSaveVersion = fileVersion;
2153
2154 // now get any properties
2155 profile.switchTask( tr( "Reading properties" ) );
2156 _getProperties( *doc, mProperties );
2157
2158 // now get the data defined server properties
2159 mDataDefinedServerProperties = getDataDefinedServerProperties( *doc, dataDefinedServerPropertyDefinitions() );
2160
2161 QgsDebugMsgLevel( QString::number( mProperties.count() ) + " properties read", 2 );
2162
2163#if 0
2164 dump_( mProperties );
2165#endif
2166
2167 // get older style project title
2168 QString oldTitle;
2169 _getTitle( *doc, oldTitle );
2170
2171 readProjectFileMetadata( *doc, mSaveUser, mSaveUserFull, mSaveDateTime );
2172
2173 const QDomNodeList homePathNl = doc->elementsByTagName( QStringLiteral( "homePath" ) );
2174 if ( homePathNl.count() > 0 )
2175 {
2176 const QDomElement homePathElement = homePathNl.at( 0 ).toElement();
2177 const QString homePath = homePathElement.attribute( QStringLiteral( "path" ) );
2178 if ( !homePath.isEmpty() )
2180 }
2181 else
2182 {
2183 emit homePathChanged();
2184 }
2185
2186 const QColor backgroundColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), 255 ),
2187 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), 255 ),
2188 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), 255 ) );
2190 const QColor selectionColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), 255 ),
2191 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), 255 ),
2192 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), 255 ),
2193 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), 255 ) );
2195
2196
2197 const QString distanceUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QString() );
2198 if ( !distanceUnitString.isEmpty() )
2199 setDistanceUnits( QgsUnitTypes::decodeDistanceUnit( distanceUnitString ) );
2200
2201 const QString areaUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QString() );
2202 if ( !areaUnitString.isEmpty() )
2203 setAreaUnits( QgsUnitTypes::decodeAreaUnit( areaUnitString ) );
2204
2205 QgsReadWriteContext context;
2206 context.setPathResolver( pathResolver() );
2207 context.setProjectTranslator( this );
2208
2209 //crs
2211 if ( readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), 0 ) )
2212 {
2213 // first preference - dedicated projectCrs node
2214 const QDomNode srsNode = doc->documentElement().namedItem( QStringLiteral( "projectCrs" ) );
2215 if ( !srsNode.isNull() )
2216 {
2217 projectCrs.readXml( srsNode );
2218 }
2219
2220 if ( !projectCrs.isValid() )
2221 {
2222 const QString projCrsString = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSProj4String" ) );
2223 const long currentCRS = readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSID" ), -1 );
2224 const QString authid = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCrs" ) );
2225
2226 // authid should be prioritized over all
2227 const bool isUserAuthId = authid.startsWith( QLatin1String( "USER:" ), Qt::CaseInsensitive );
2228 if ( !authid.isEmpty() && !isUserAuthId )
2229 projectCrs = QgsCoordinateReferenceSystem( authid );
2230
2231 // try the CRS
2232 if ( !projectCrs.isValid() && currentCRS >= 0 )
2233 {
2234 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2235 }
2236
2237 // if that didn't produce a match, try the proj.4 string
2238 if ( !projCrsString.isEmpty() && ( authid.isEmpty() || isUserAuthId ) && ( !projectCrs.isValid() || projectCrs.toProj() != projCrsString ) )
2239 {
2240 projectCrs = QgsCoordinateReferenceSystem::fromProj( projCrsString );
2241 }
2242
2243 // last just take the given id
2244 if ( !projectCrs.isValid() )
2245 {
2246 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2247 }
2248 }
2249 }
2250 mCrs = projectCrs;
2251
2252 //vertical CRS
2253 {
2255 const QDomNode verticalCrsNode = doc->documentElement().namedItem( QStringLiteral( "verticalCrs" ) );
2256 if ( !verticalCrsNode.isNull() )
2257 {
2258 verticalCrs.readXml( verticalCrsNode );
2259 }
2260 mVerticalCrs = verticalCrs;
2261 }
2262 rebuildCrs3D();
2263
2264 QStringList datumErrors;
2265 if ( !mTransformContext.readXml( doc->documentElement(), context, datumErrors ) && !datumErrors.empty() )
2266 {
2267 emit missingDatumTransforms( datumErrors );
2268 }
2270
2271 // map shading
2272 const QDomNode elevationShadingNode = doc->documentElement().namedItem( QStringLiteral( "elevation-shading-renderer" ) );
2273 if ( !elevationShadingNode.isNull() )
2274 {
2275 mElevationShadingRenderer.readXml( elevationShadingNode.toElement(), context );
2276 }
2278
2279
2280 //add variables defined in project file - do this early in the reading cycle, as other components
2281 //(e.g. layouts) may depend on these variables
2282 const QStringList variableNames = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ) );
2283 const QStringList variableValues = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ) );
2284
2285 mCustomVariables.clear();
2286 if ( variableNames.length() == variableValues.length() )
2287 {
2288 for ( int i = 0; i < variableNames.length(); ++i )
2289 {
2290 mCustomVariables.insert( variableNames.at( i ), variableValues.at( i ) );
2291 }
2292 }
2293 else
2294 {
2295 QgsMessageLog::logMessage( tr( "Project Variables Invalid" ), tr( "The project contains invalid variable settings." ) );
2296 }
2297
2298 // Register expression functions stored in the project.
2299 // They might be using project variables and might be
2300 // in turn being used by other components (e.g., layouts).
2302
2303 QDomElement element = doc->documentElement().firstChildElement( QStringLiteral( "projectMetadata" ) );
2304
2305 if ( !element.isNull() )
2306 {
2307 mMetadata.readMetadataXml( element );
2308 }
2309 else
2310 {
2311 // older project, no metadata => remove auto generated metadata which is populated on QgsProject::clear()
2312 mMetadata = QgsProjectMetadata();
2313 }
2314 if ( mMetadata.title().isEmpty() && !oldTitle.isEmpty() )
2315 {
2316 // upgrade older title storage to storing within project metadata.
2317 mMetadata.setTitle( oldTitle );
2318 }
2319 emit metadataChanged();
2320
2321 // Transaction mode
2322 element = doc->documentElement().firstChildElement( QStringLiteral( "transaction" ) );
2323 if ( !element.isNull() )
2324 {
2325 mTransactionMode = qgsEnumKeyToValue( element.attribute( QStringLiteral( "mode" ) ), Qgis::TransactionMode::Disabled );
2326 }
2327 else
2328 {
2329 // maybe older project => try read autotransaction
2330 element = doc->documentElement().firstChildElement( QStringLiteral( "autotransaction" ) );
2331 if ( ! element.isNull() )
2332 {
2333 mTransactionMode = static_cast<Qgis::TransactionMode>( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() );
2334 }
2335 }
2336
2337 // read the layer tree from project file
2338 profile.switchTask( tr( "Loading layer tree" ) );
2339 mRootGroup->setCustomProperty( QStringLiteral( "loading" ), 1 );
2340
2341 QDomElement layerTreeElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
2342 if ( !layerTreeElem.isNull() )
2343 {
2344 // Use a temporary tree to read the nodes to prevent signals being delivered to the models
2345 QgsLayerTree tempTree;
2346 tempTree.readChildrenFromXml( layerTreeElem, context );
2347 mRootGroup->insertChildNodes( -1, tempTree.abandonChildren() );
2348 }
2349 else
2350 {
2351 QgsLayerTreeUtils::readOldLegend( mRootGroup, doc->documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
2352 }
2353
2354 mLayerTreeRegistryBridge->setEnabled( false );
2355
2356 // get the map layers
2357 profile.switchTask( tr( "Reading map layers" ) );
2358
2359 loadProjectFlags( doc.get() );
2360
2361 QList<QDomNode> brokenNodes;
2362 const bool clean = _getMapLayers( *doc, brokenNodes, flags );
2363
2364 // review the integrity of the retrieved map layers
2365 if ( !clean && !( flags & Qgis::ProjectReadFlag::DontResolveLayers ) )
2366 {
2367 QgsDebugError( QStringLiteral( "Unable to get map layers from project file." ) );
2368
2369 if ( !brokenNodes.isEmpty() )
2370 {
2371 QgsDebugError( "there are " + QString::number( brokenNodes.size() ) + " broken layers" );
2372 }
2373
2374 // we let a custom handler decide what to do with missing layers
2375 // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
2376 mBadLayerHandler->handleBadLayers( brokenNodes );
2377 }
2378
2379 mMainAnnotationLayer->readLayerXml( doc->documentElement().firstChildElement( QStringLiteral( "main-annotation-layer" ) ), context );
2380 mMainAnnotationLayer->setTransformContext( mTransformContext );
2381
2382 // load embedded groups and layers
2383 profile.switchTask( tr( "Loading embedded layers" ) );
2384 loadEmbeddedNodes( mRootGroup, flags );
2385
2386 // Resolve references to other layers
2387 // Needs to be done here once all dependent layers are loaded
2388 profile.switchTask( tr( "Resolving layer references" ) );
2389 QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
2390 for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
2391 {
2392 it.value()->resolveReferences( this );
2393 }
2394 mMainAnnotationLayer->resolveReferences( this );
2395
2396 mLayerTreeRegistryBridge->setEnabled( true );
2397
2398 // now that layers are loaded, we can resolve layer tree's references to the layers
2399 profile.switchTask( tr( "Resolving references" ) );
2400 mRootGroup->resolveReferences( this );
2401
2402 // we need to migrate old fashion designed QgsSymbolLayerReference to new ones
2403 if ( QgsProjectVersion( 3, 28, 0 ) > mSaveVersion )
2404 {
2408 }
2409
2410 if ( !layerTreeElem.isNull() )
2411 {
2412 mRootGroup->readLayerOrderFromXml( layerTreeElem );
2413 }
2414
2415 // Load pre 3.0 configuration
2416 const QDomElement layerTreeCanvasElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-canvas" ) );
2417 if ( !layerTreeCanvasElem.isNull( ) )
2418 {
2419 mRootGroup->readLayerOrderFromXml( layerTreeCanvasElem );
2420 }
2421
2422 // Convert pre 3.4 to create layers flags
2423 if ( QgsProjectVersion( 3, 4, 0 ) > mSaveVersion )
2424 {
2425 const QStringList requiredLayerIds = readListEntry( QStringLiteral( "RequiredLayers" ), QStringLiteral( "Layers" ) );
2426 for ( const QString &layerId : requiredLayerIds )
2427 {
2428 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2429 {
2430 layer->setFlags( layer->flags() & ~QgsMapLayer::Removable );
2431 }
2432 }
2433 const QStringList disabledLayerIds = readListEntry( QStringLiteral( "Identify" ), QStringLiteral( "/disabledLayers" ) );
2434 for ( const QString &layerId : disabledLayerIds )
2435 {
2436 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2437 {
2438 layer->setFlags( layer->flags() & ~QgsMapLayer::Identifiable );
2439 }
2440 }
2441 }
2442
2443 // Convert pre 3.26 default styles
2444 if ( QgsProjectVersion( 3, 26, 0 ) > mSaveVersion )
2445 {
2446 // Convert default symbols
2447 QString styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Marker" ) );
2448 if ( !styleName.isEmpty() )
2449 {
2450 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2452 }
2453 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Line" ) );
2454 if ( !styleName.isEmpty() )
2455 {
2456 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2458 }
2459 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Fill" ) );
2460 if ( !styleName.isEmpty() )
2461 {
2462 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2464 }
2465 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ) );
2466 if ( !styleName.isEmpty() )
2467 {
2468 std::unique_ptr<QgsColorRamp> colorRamp( QgsStyle::defaultStyle()->colorRamp( styleName ) );
2469 styleSettings()->setDefaultColorRamp( colorRamp.get() );
2470 }
2471
2472 // Convert randomize default symbol fill color
2473 styleSettings()->setRandomizeDefaultSymbolColor( readBoolEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ), true ) );
2474
2475 // Convert default symbol opacity
2476 double opacity = 1.0;
2477 bool ok = false;
2478 // upgrade old setting
2479 double alpha = readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/AlphaInt" ), 255, &ok );
2480 if ( ok )
2481 opacity = alpha / 255.0;
2482 double newOpacity = readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Opacity" ), 1.0, &ok );
2483 if ( ok )
2484 opacity = newOpacity;
2486
2487 // Cleanup
2488 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Marker" ) );
2489 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Line" ) );
2490 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Fill" ) );
2491 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ) );
2492 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ) );
2493 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/AlphaInt" ) );
2494 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Opacity" ) );
2495 }
2496
2497 // After bad layer handling we might still have invalid layers,
2498 // store them in case the user wanted to handle them later
2499 // or wanted to pass them through when saving
2501 {
2502 profile.switchTask( tr( "Storing original layer properties" ) );
2503 QgsLayerTreeUtils::storeOriginalLayersProperties( mRootGroup, doc.get() );
2504 }
2505
2506 mRootGroup->removeCustomProperty( QStringLiteral( "loading" ) );
2507
2508 profile.switchTask( tr( "Loading map themes" ) );
2509 mMapThemeCollection.reset( new QgsMapThemeCollection( this ) );
2511 mMapThemeCollection->readXml( *doc );
2512
2513 profile.switchTask( tr( "Loading label settings" ) );
2514 mLabelingEngineSettings->readSettingsFromProject( this );
2515 {
2516 const QDomElement labelEngineSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "labelEngineSettings" ) );
2517 mLabelingEngineSettings->readXml( labelEngineSettingsElement, context );
2518 }
2519 mLabelingEngineSettings->resolveReferences( this );
2520
2522
2523 profile.switchTask( tr( "Loading annotations" ) );
2525 {
2526 mAnnotationManager->readXml( doc->documentElement(), context );
2527 }
2528 else
2529 {
2530 mAnnotationManager->readXmlAndUpgradeToAnnotationLayerItems( doc->documentElement(), context, mMainAnnotationLayer, mTransformContext );
2531 }
2533 {
2534 profile.switchTask( tr( "Loading layouts" ) );
2535 mLayoutManager->readXml( doc->documentElement(), *doc );
2536 }
2537
2539 {
2540 profile.switchTask( tr( "Loading 3D Views" ) );
2541 m3DViewsManager->readXml( doc->documentElement(), *doc );
2542 }
2543
2544 profile.switchTask( tr( "Loading bookmarks" ) );
2545 mBookmarkManager->readXml( doc->documentElement(), *doc );
2546
2547 profile.switchTask( tr( "Loading sensors" ) );
2548 mSensorManager->readXml( doc->documentElement(), *doc );
2549
2550 // reassign change dependencies now that all layers are loaded
2551 QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2552 for ( QMap<QString, QgsMapLayer *>::iterator it = existingMaps.begin(); it != existingMaps.end(); ++it )
2553 {
2554 it.value()->setDependencies( it.value()->dependencies() );
2555 }
2556
2557 profile.switchTask( tr( "Loading snapping settings" ) );
2558 mSnappingConfig.readProject( *doc );
2559 mAvoidIntersectionsMode = static_cast<Qgis::AvoidIntersectionsMode>( readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( Qgis::AvoidIntersectionsMode::AvoidIntersectionsLayers ) ) );
2560
2561 profile.switchTask( tr( "Loading view settings" ) );
2562 // restore older project scales settings
2563 mViewSettings->setUseProjectScales( readBoolEntry( QStringLiteral( "Scales" ), QStringLiteral( "/useProjectScales" ) ) );
2564 const QStringList scales = readListEntry( QStringLiteral( "Scales" ), QStringLiteral( "/ScalesList" ) );
2565 QVector<double> res;
2566 for ( const QString &scale : scales )
2567 {
2568 const QStringList parts = scale.split( ':' );
2569 if ( parts.size() != 2 )
2570 continue;
2571
2572 bool ok = false;
2573 const double denominator = QLocale().toDouble( parts[1], &ok );
2574 if ( ok )
2575 {
2576 res << denominator;
2577 }
2578 }
2579 mViewSettings->setMapScales( res );
2580 const QDomElement viewSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectViewSettings" ) );
2581 if ( !viewSettingsElement.isNull() )
2582 mViewSettings->readXml( viewSettingsElement, context );
2583
2584 // restore style settings
2585 profile.switchTask( tr( "Loading style properties" ) );
2586 const QDomElement styleSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectStyleSettings" ) );
2587 if ( !styleSettingsElement.isNull() )
2588 {
2589 mStyleSettings->removeProjectStyle();
2590 mStyleSettings->readXml( styleSettingsElement, context, flags );
2591 }
2592
2593 // restore time settings
2594 profile.switchTask( tr( "Loading temporal settings" ) );
2595 const QDomElement timeSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectTimeSettings" ) );
2596 if ( !timeSettingsElement.isNull() )
2597 mTimeSettings->readXml( timeSettingsElement, context );
2598
2599
2600 profile.switchTask( tr( "Loading elevation properties" ) );
2601 const QDomElement elevationPropertiesElement = doc->documentElement().firstChildElement( QStringLiteral( "ElevationProperties" ) );
2602 if ( !elevationPropertiesElement.isNull() )
2603 mElevationProperties->readXml( elevationPropertiesElement, context );
2604 mElevationProperties->resolveReferences( this );
2605
2606 profile.switchTask( tr( "Loading display settings" ) );
2607 {
2608 const QDomElement displaySettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectDisplaySettings" ) );
2609 if ( !displaySettingsElement.isNull() )
2610 mDisplaySettings->readXml( displaySettingsElement, context );
2611 }
2612
2613 profile.switchTask( tr( "Loading GPS settings" ) );
2614 {
2615 const QDomElement gpsSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectGpsSettings" ) );
2616 if ( !gpsSettingsElement.isNull() )
2617 mGpsSettings->readXml( gpsSettingsElement, context );
2618 mGpsSettings->resolveReferences( this );
2619 }
2620
2621 profile.switchTask( tr( "Updating variables" ) );
2623 profile.switchTask( tr( "Updating CRS" ) );
2624 emit crsChanged();
2625 if ( verticalCrs() != oldVerticalCrs )
2626 emit verticalCrsChanged();
2627 if ( mCrs3D != oldCrs3D )
2628 emit crs3DChanged();
2629 emit ellipsoidChanged( ellipsoid() );
2630
2631 // read the project: used by map canvas and legend
2632 profile.switchTask( tr( "Reading external settings" ) );
2633 emit readProject( *doc );
2634 emit readProjectWithContext( *doc, context );
2635
2636 profile.switchTask( tr( "Updating interface" ) );
2637
2638 snapSignalBlock.release();
2639 if ( !mBlockSnappingUpdates )
2640 emit snappingConfigChanged( mSnappingConfig );
2641
2644 emit projectColorsChanged();
2645
2646 // if all went well, we're allegedly in pristine state
2647 if ( clean )
2648 setDirty( false );
2649
2650 QgsDebugMsgLevel( QStringLiteral( "Project save user: %1" ).arg( mSaveUser ), 2 );
2651 QgsDebugMsgLevel( QStringLiteral( "Project save user: %1" ).arg( mSaveUserFull ), 2 );
2652
2656
2657 if ( mTranslator )
2658 {
2659 //project possibly translated -> rename it with locale postfix
2660 const QString newFileName( QStringLiteral( "%1/%2.qgs" ).arg( QFileInfo( projectFile.fileName() ).absolutePath(), localeFileName ) );
2661 setFileName( newFileName );
2662
2663 if ( write() )
2664 {
2665 setTitle( localeFileName );
2666 QgsMessageLog::logMessage( tr( "Translated project saved with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Success );
2667 }
2668 else
2669 {
2670 QgsMessageLog::logMessage( tr( "Error saving translated project with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Critical );
2671 }
2672 }
2673
2674 // lastly, make any previously editable layers editable
2675 const QMap<QString, QgsMapLayer *> loadedLayers = mapLayers();
2676 for ( auto it = loadedLayers.constBegin(); it != loadedLayers.constEnd(); ++it )
2677 {
2678 if ( it.value()->isValid() && it.value()->customProperty( QStringLiteral( "_layer_was_editable" ) ).toBool() )
2679 {
2680 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( it.value() ) )
2681 vl->startEditing();
2682 it.value()->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
2683 }
2684 }
2685
2686 return true;
2687}
2688
2689bool QgsProject::loadEmbeddedNodes( QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
2690{
2692
2693 bool valid = true;
2694 const auto constChildren = group->children();
2695 for ( QgsLayerTreeNode *child : constChildren )
2696 {
2697 if ( QgsLayerTree::isGroup( child ) )
2698 {
2699 QgsLayerTreeGroup *childGroup = QgsLayerTree::toGroup( child );
2700 if ( childGroup->customProperty( QStringLiteral( "embedded" ) ).toInt() )
2701 {
2702 // make sure to convert the path from relative to absolute
2703 const QString projectPath = readPath( childGroup->customProperty( QStringLiteral( "embedded_project" ) ).toString() );
2704 childGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectPath );
2705 QgsLayerTreeGroup *newGroup = createEmbeddedGroup( childGroup->name(), projectPath, childGroup->customProperty( QStringLiteral( "embedded-invisible-layers" ) ).toStringList(), flags );
2706 if ( newGroup )
2707 {
2708 QList<QgsLayerTreeNode *> clonedChildren;
2709 const QList<QgsLayerTreeNode *> constChildren = newGroup->children();
2710 clonedChildren.reserve( constChildren.size() );
2711 for ( QgsLayerTreeNode *newGroupChild : constChildren )
2712 clonedChildren << newGroupChild->clone();
2713 delete newGroup;
2714
2715 childGroup->insertChildNodes( 0, clonedChildren );
2716 }
2717 }
2718 else
2719 {
2720 loadEmbeddedNodes( childGroup, flags );
2721 }
2722 }
2723 else if ( QgsLayerTree::isLayer( child ) )
2724 {
2725 if ( child->customProperty( QStringLiteral( "embedded" ) ).toInt() )
2726 {
2727 QList<QDomNode> brokenNodes;
2728 if ( ! createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), readPath( child->customProperty( QStringLiteral( "embedded_project" ) ).toString() ), brokenNodes, true, flags ) )
2729 {
2730 valid = valid && false;
2731 }
2732 }
2733 }
2734
2735 }
2736
2737 return valid;
2738}
2739
2741{
2742 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
2744
2745 return mCustomVariables;
2746}
2747
2748void QgsProject::setCustomVariables( const QVariantMap &variables )
2749{
2751
2752 if ( variables == mCustomVariables )
2753 return;
2754
2755 //write variable to project
2756 QStringList variableNames;
2757 QStringList variableValues;
2758
2759 QVariantMap::const_iterator it = variables.constBegin();
2760 for ( ; it != variables.constEnd(); ++it )
2761 {
2762 variableNames << it.key();
2763 variableValues << it.value().toString();
2764 }
2765
2766 writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ), variableNames );
2767 writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ), variableValues );
2768
2769 mCustomVariables = variables;
2770 mProjectScope.reset();
2771
2773}
2774
2776{
2778
2779 *mLabelingEngineSettings = settings;
2781}
2782
2784{
2786
2787 return *mLabelingEngineSettings;
2788}
2789
2791{
2793
2794 mProjectScope.reset();
2795 return mLayerStore.get();
2796}
2797
2799{
2801
2802 return mLayerStore.get();
2803}
2804
2805QList<QgsVectorLayer *> QgsProject::avoidIntersectionsLayers() const
2806{
2808
2809 QList<QgsVectorLayer *> layers;
2810 const QStringList layerIds = readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), QStringList() );
2811 const auto constLayerIds = layerIds;
2812 for ( const QString &layerId : constLayerIds )
2813 {
2814 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer( layerId ) ) )
2815 layers << vlayer;
2816 }
2817 return layers;
2818}
2819
2820void QgsProject::setAvoidIntersectionsLayers( const QList<QgsVectorLayer *> &layers )
2821{
2823
2824 QStringList list;
2825 list.reserve( layers.size() );
2826 for ( QgsVectorLayer *layer : layers )
2827 list << layer->id();
2828 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), list );
2830}
2831
2843
2845{
2846 // this method is called quite extensively using QgsProject::instance() skip-keyword-check
2848
2849 // MUCH cheaper to clone than build
2850 if ( mProjectScope )
2851 {
2852 std::unique_ptr< QgsExpressionContextScope > projectScope = std::make_unique< QgsExpressionContextScope >( *mProjectScope );
2853
2854 // we can't cache these variables
2855 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_distance_units" ), QgsUnitTypes::toString( distanceUnits() ), true, true ) );
2856 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_area_units" ), QgsUnitTypes::toString( areaUnits() ), true, true ) );
2857
2858 // neither this function
2859 projectScope->addFunction( QStringLiteral( "sensor_data" ), new GetSensorData( sensorManager()->sensorsData() ) );
2860
2861 return projectScope.release();
2862 }
2863
2864 mProjectScope = std::make_unique< QgsExpressionContextScope >( QObject::tr( "Project" ) );
2865
2866 const QVariantMap vars = customVariables();
2867
2868 QVariantMap::const_iterator it = vars.constBegin();
2869
2870 for ( ; it != vars.constEnd(); ++it )
2871 {
2872 mProjectScope->setVariable( it.key(), it.value(), true );
2873 }
2874
2875 QString projectPath = projectStorage() ? fileName() : absoluteFilePath();
2876 if ( projectPath.isEmpty() )
2877 projectPath = mOriginalPath;
2878 const QString projectFolder = QFileInfo( projectPath ).path();
2879 const QString projectFilename = QFileInfo( projectPath ).fileName();
2880 const QString projectBasename = baseName();
2881
2882 //add other known project variables
2883 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_title" ), title(), true, true ) );
2884 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_path" ), QDir::toNativeSeparators( projectPath ), true, true ) );
2885 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_folder" ), QDir::toNativeSeparators( projectFolder ), true, true ) );
2886 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_filename" ), projectFilename, true, true ) );
2887 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_basename" ), projectBasename, true, true ) );
2888 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_home" ), QDir::toNativeSeparators( homePath() ), true, true ) );
2889 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_last_saved" ), mSaveDateTime.isNull() ? QVariant() : QVariant( mSaveDateTime ), true, true ) );
2890
2891 const QgsCoordinateReferenceSystem projectCrs = crs();
2892 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs" ), projectCrs.authid(), true, true ) );
2893 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_definition" ), projectCrs.toProj(), true, true ) );
2894 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_description" ), projectCrs.description(), true, true ) );
2895 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_acronym" ), projectCrs.projectionAcronym(), true ) );
2896 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_ellipsoid" ), projectCrs.ellipsoidAcronym(), true ) );
2897 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_proj4" ), projectCrs.toProj(), true ) );
2898 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_wkt" ), projectCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
2899
2900 const QgsCoordinateReferenceSystem projectVerticalCrs = QgsProject::verticalCrs();
2901 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_vertical_crs" ), projectVerticalCrs.authid(), true, true ) );
2902 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_vertical_crs_definition" ), projectVerticalCrs.toProj(), true, true ) );
2903 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_vertical_crs_description" ), projectVerticalCrs.description(), true, true ) );
2904 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_vertical_crs_wkt" ), projectVerticalCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
2905
2906 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_ellipsoid" ), ellipsoid(), true, true ) );
2907 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "_project_transform_context" ), QVariant::fromValue<QgsCoordinateTransformContext>( transformContext() ), true, true ) );
2908 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_units" ), QgsUnitTypes::toString( projectCrs.mapUnits() ), true ) );
2909
2910 // metadata
2911 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_author" ), metadata().author(), true, true ) );
2912 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_abstract" ), metadata().abstract(), true, true ) );
2913 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_creation_date" ), metadata().creationDateTime(), true, true ) );
2914 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_identifier" ), metadata().identifier(), true, true ) );
2915
2916 // keywords
2917 QVariantMap keywords;
2918 const QgsAbstractMetadataBase::KeywordMap metadataKeywords = metadata().keywords();
2919 for ( auto it = metadataKeywords.constBegin(); it != metadataKeywords.constEnd(); ++it )
2920 {
2921 keywords.insert( it.key(), it.value() );
2922 }
2923 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_keywords" ), keywords, true, true ) );
2924
2925 // layers
2926 QVariantList layersIds;
2927 QVariantList layers;
2928 const QMap<QString, QgsMapLayer *> layersInProject = mLayerStore->mapLayers();
2929 layersIds.reserve( layersInProject.count() );
2930 layers.reserve( layersInProject.count() );
2931 for ( auto it = layersInProject.constBegin(); it != layersInProject.constEnd(); ++it )
2932 {
2933 layersIds << it.value()->id();
2934 layers << QVariant::fromValue<QgsWeakMapLayerPointer>( QgsWeakMapLayerPointer( it.value() ) );
2935 }
2936 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer_ids" ), layersIds, true ) );
2937 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layers" ), layers, true ) );
2938
2939 mProjectScope->addFunction( QStringLiteral( "project_color" ), new GetNamedProjectColor( this ) );
2940 mProjectScope->addFunction( QStringLiteral( "project_color_object" ), new GetNamedProjectColorObject( this ) );
2941
2943}
2944
2945void QgsProject::onMapLayersAdded( const QList<QgsMapLayer *> &layers )
2946{
2948
2949 const QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2950
2951 const auto constLayers = layers;
2952 for ( QgsMapLayer *layer : constLayers )
2953 {
2954 if ( ! layer->isValid() )
2955 return;
2956
2957 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
2958 {
2959 vlayer->setReadExtentFromXml( mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics );
2960 if ( vlayer->dataProvider() )
2961 vlayer->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues,
2963 }
2964
2965 connect( layer, &QgsMapLayer::configChanged, this, [this] { setDirty(); } );
2966
2967 // check if we have to update connections for layers with dependencies
2968 for ( QMap<QString, QgsMapLayer *>::const_iterator it = existingMaps.cbegin(); it != existingMaps.cend(); ++it )
2969 {
2970 const QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
2971 if ( deps.contains( layer->id() ) )
2972 {
2973 // reconnect to change signals
2974 it.value()->setDependencies( deps );
2975 }
2976 }
2977 }
2978
2979 updateTransactionGroups();
2980
2981 if ( !mBlockSnappingUpdates && mSnappingConfig.addLayers( layers ) )
2982 emit snappingConfigChanged( mSnappingConfig );
2983}
2984
2985void QgsProject::onMapLayersRemoved( const QList<QgsMapLayer *> &layers )
2986{
2988
2989 if ( !mBlockSnappingUpdates && mSnappingConfig.removeLayers( layers ) )
2990 emit snappingConfigChanged( mSnappingConfig );
2991}
2992
2993void QgsProject::cleanTransactionGroups( bool force )
2994{
2996
2997 bool changed = false;
2998 for ( QMap< QPair< QString, QString>, QgsTransactionGroup *>::Iterator tg = mTransactionGroups.begin(); tg != mTransactionGroups.end(); )
2999 {
3000 if ( tg.value()->isEmpty() || force )
3001 {
3002 delete tg.value();
3003 tg = mTransactionGroups.erase( tg );
3004 changed = true;
3005 }
3006 else
3007 {
3008 ++tg;
3009 }
3010 }
3011 if ( changed )
3013}
3014
3015void QgsProject::updateTransactionGroups()
3016{
3018
3019 mEditBufferGroup.clear();
3020
3021 switch ( mTransactionMode )
3022 {
3024 {
3025 cleanTransactionGroups( true );
3026 return;
3027 }
3028 break;
3030 cleanTransactionGroups( true );
3031 break;
3033 cleanTransactionGroups( false );
3034 break;
3035 }
3036
3037 bool tgChanged = false;
3038 const auto constLayers = mapLayers().values();
3039 for ( QgsMapLayer *layer : constLayers )
3040 {
3041 if ( ! layer->isValid() )
3042 continue;
3043
3044 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
3045 if ( ! vlayer )
3046 continue;
3047
3048 switch ( mTransactionMode )
3049 {
3051 Q_ASSERT( false );
3052 break;
3054 {
3056 {
3057 const QString connString = QgsTransaction::connectionString( vlayer->source() );
3058 const QString key = vlayer->providerType();
3059
3060 QgsTransactionGroup *tg = mTransactionGroups.value( qMakePair( key, connString ) );
3061
3062 if ( !tg )
3063 {
3064 tg = new QgsTransactionGroup();
3065 mTransactionGroups.insert( qMakePair( key, connString ), tg );
3066 tgChanged = true;
3067 }
3068 tg->addLayer( vlayer );
3069 }
3070 }
3071 break;
3073 {
3074 if ( vlayer->supportsEditing() )
3075 mEditBufferGroup.addLayer( vlayer );
3076 }
3077 break;
3078 }
3079 }
3080
3081 if ( tgChanged )
3083}
3084
3085bool QgsProject::readLayer( const QDomNode &layerNode )
3086{
3088
3089 QgsReadWriteContext context;
3090 context.setPathResolver( pathResolver() );
3091 context.setProjectTranslator( this );
3092 context.setTransformContext( transformContext() );
3093 QList<QDomNode> brokenNodes;
3094 if ( addLayer( layerNode.toElement(), brokenNodes, context ) )
3095 {
3096 // have to try to update joins for all layers now - a previously added layer may be dependent on this newly
3097 // added layer for joins
3098 const QVector<QgsVectorLayer *> vectorLayers = layers<QgsVectorLayer *>();
3099 for ( QgsVectorLayer *layer : vectorLayers )
3100 {
3101 // TODO: should be only done later - and with all layers (other layers may have referenced this layer)
3102 layer->resolveReferences( this );
3103
3104 if ( layer->isValid() && layer->customProperty( QStringLiteral( "_layer_was_editable" ) ).toBool() )
3105 {
3106 layer->startEditing();
3107 layer->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
3108 }
3109 }
3110 return true;
3111 }
3112 return false;
3113}
3114
3115bool QgsProject::write( const QString &filename )
3116{
3118
3119 mFile.setFileName( filename );
3120 mCachedHomePath.clear();
3121 return write();
3122}
3123
3125{
3127
3128 mProjectScope.reset();
3129 if ( QgsProjectStorage *storage = projectStorage() )
3130 {
3131 QgsReadWriteContext context;
3132 // for projects stored in a custom storage, we have to check for the support
3133 // of relative paths since the storage most likely will not be in a file system
3134 const QString storageFilePath { storage->filePath( mFile.fileName() ) };
3135 if ( storageFilePath.isEmpty() )
3136 {
3138 }
3139 context.setPathResolver( pathResolver() );
3140
3141 const QString tempPath = QStandardPaths::standardLocations( QStandardPaths::TempLocation ).at( 0 );
3142 const QString tmpZipFilename( tempPath + QDir::separator() + QUuid::createUuid().toString() );
3143
3144 if ( !zip( tmpZipFilename ) )
3145 return false; // zip() already calls setError() when returning false
3146
3147 QFile tmpZipFile( tmpZipFilename );
3148 if ( !tmpZipFile.open( QIODevice::ReadOnly ) )
3149 {
3150 setError( tr( "Unable to read file %1" ).arg( tmpZipFilename ) );
3151 return false;
3152 }
3153
3155 if ( !storage->writeProject( mFile.fileName(), &tmpZipFile, context ) )
3156 {
3157 QString err = tr( "Unable to save project to storage %1" ).arg( mFile.fileName() );
3158 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
3159 if ( !messages.isEmpty() )
3160 err += QStringLiteral( "\n\n" ) + messages.last().message();
3161 setError( err );
3162 return false;
3163 }
3164
3165 tmpZipFile.close();
3166 QFile::remove( tmpZipFilename );
3167
3168 return true;
3169 }
3170
3171 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
3172 {
3173 return zip( mFile.fileName() );
3174 }
3175 else
3176 {
3177 // write project file even if the auxiliary storage is not correctly
3178 // saved
3179 const bool asOk = saveAuxiliaryStorage();
3180 const bool writeOk = writeProjectFile( mFile.fileName() );
3181 bool attachmentsOk = true;
3182 if ( !mArchive->files().isEmpty() )
3183 {
3184 const QFileInfo finfo( mFile.fileName() );
3185 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
3186 attachmentsOk = mArchive->zip( attachmentsZip );
3187 }
3188
3189 // errors raised during writing project file are more important
3190 if ( ( !asOk || !attachmentsOk ) && writeOk )
3191 {
3192 QStringList errorMessage;
3193 if ( !asOk )
3194 {
3195 const QString err = mAuxiliaryStorage->errorString();
3196 errorMessage.append( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
3197 }
3198 if ( !attachmentsOk )
3199 {
3200 errorMessage.append( tr( "Unable to save attachments archive" ) );
3201 }
3202 setError( errorMessage.join( '\n' ) );
3203 }
3204
3205 return asOk && writeOk && attachmentsOk;
3206 }
3207}
3208
3209bool QgsProject::writeProjectFile( const QString &filename )
3210{
3212
3213 QFile projectFile( filename );
3214 clearError();
3215
3216 // if we have problems creating or otherwise writing to the project file,
3217 // let's find out up front before we go through all the hand-waving
3218 // necessary to create all the Dom objects
3219 const QFileInfo myFileInfo( projectFile );
3220 if ( myFileInfo.exists() && !myFileInfo.isWritable() )
3221 {
3222 setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." )
3223 .arg( projectFile.fileName() ) );
3224 return false;
3225 }
3226
3227 QgsReadWriteContext context;
3228 context.setPathResolver( pathResolver() );
3230
3231 QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
3232
3233 const QDomDocumentType documentType =
3234 QDomImplementation().createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ),
3235 QStringLiteral( "SYSTEM" ) );
3236 std::unique_ptr<QDomDocument> doc( new QDomDocument( documentType ) );
3237
3238 QDomElement qgisNode = doc->createElement( QStringLiteral( "qgis" ) );
3239 qgisNode.setAttribute( QStringLiteral( "projectname" ), title() );
3240 qgisNode.setAttribute( QStringLiteral( "version" ), Qgis::version() );
3241
3242 if ( !mSettings.value( QStringLiteral( "projects/anonymize_saved_projects" ), false, QgsSettings::Core ).toBool() )
3243 {
3244 const QString newSaveUser = QgsApplication::userLoginName();
3245 const QString newSaveUserFull = QgsApplication::userFullName();
3246 qgisNode.setAttribute( QStringLiteral( "saveUser" ), newSaveUser );
3247 qgisNode.setAttribute( QStringLiteral( "saveUserFull" ), newSaveUserFull );
3248 mSaveUser = newSaveUser;
3249 mSaveUserFull = newSaveUserFull;
3250 mSaveDateTime = QDateTime::currentDateTime();
3251 qgisNode.setAttribute( QStringLiteral( "saveDateTime" ), mSaveDateTime.toString( Qt::ISODate ) );
3252 }
3253 else
3254 {
3255 mSaveUser.clear();
3256 mSaveUserFull.clear();
3257 mSaveDateTime = QDateTime();
3258 }
3259 doc->appendChild( qgisNode );
3260 mSaveVersion = QgsProjectVersion( Qgis::version() );
3261
3262 QDomElement homePathNode = doc->createElement( QStringLiteral( "homePath" ) );
3263 homePathNode.setAttribute( QStringLiteral( "path" ), mHomePath );
3264 qgisNode.appendChild( homePathNode );
3265
3266 // title
3267 QDomElement titleNode = doc->createElement( QStringLiteral( "title" ) );
3268 qgisNode.appendChild( titleNode );
3269
3270 QDomElement transactionNode = doc->createElement( QStringLiteral( "transaction" ) );
3271 transactionNode.setAttribute( QStringLiteral( "mode" ), qgsEnumValueToKey( mTransactionMode ) );
3272 qgisNode.appendChild( transactionNode );
3273
3274 QDomElement flagsNode = doc->createElement( QStringLiteral( "projectFlags" ) );
3275 flagsNode.setAttribute( QStringLiteral( "set" ), qgsFlagValueToKeys( mFlags ) );
3276 qgisNode.appendChild( flagsNode );
3277
3278 const QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
3279 titleNode.appendChild( titleText );
3280
3281 // write project CRS
3282 {
3283 QDomElement srsNode = doc->createElement( QStringLiteral( "projectCrs" ) );
3284 mCrs.writeXml( srsNode, *doc );
3285 qgisNode.appendChild( srsNode );
3286 }
3287 {
3288 QDomElement verticalSrsNode = doc->createElement( QStringLiteral( "verticalCrs" ) );
3289 mVerticalCrs.writeXml( verticalSrsNode, *doc );
3290 qgisNode.appendChild( verticalSrsNode );
3291 }
3292
3293 QDomElement elevationShadingNode = doc->createElement( QStringLiteral( "elevation-shading-renderer" ) );
3294 mElevationShadingRenderer.writeXml( elevationShadingNode, context );
3295 qgisNode.appendChild( elevationShadingNode );
3296
3297 // write layer tree - make sure it is without embedded subgroups
3298 QgsLayerTreeNode *clonedRoot = mRootGroup->clone();
3300 QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ), this ); // convert absolute paths to relative paths if required
3301
3302 clonedRoot->writeXml( qgisNode, context );
3303 delete clonedRoot;
3304
3305 mSnappingConfig.writeProject( *doc );
3306 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( mAvoidIntersectionsMode ) );
3307
3308 // let map canvas and legend write their information
3309 emit writeProject( *doc );
3310
3311 // within top level node save list of layers
3312 const QMap<QString, QgsMapLayer *> layers = mapLayers();
3313
3314 QDomElement annotationLayerNode = doc->createElement( QStringLiteral( "main-annotation-layer" ) );
3315 mMainAnnotationLayer->writeLayerXml( annotationLayerNode, *doc, context );
3316 qgisNode.appendChild( annotationLayerNode );
3317
3318 // Iterate over layers in zOrder
3319 // Call writeXml() on each
3320 QDomElement projectLayersNode = doc->createElement( QStringLiteral( "projectlayers" ) );
3321
3322 QMap<QString, QgsMapLayer *>::ConstIterator li = layers.constBegin();
3323 while ( li != layers.end() )
3324 {
3325 QgsMapLayer *ml = li.value();
3326
3327 if ( ml )
3328 {
3329 const QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.constFind( ml->id() );
3330 if ( emIt == mEmbeddedLayers.constEnd() )
3331 {
3332 QDomElement maplayerElem;
3333 // If layer is not valid, prefer to restore saved properties from invalidLayerProperties. But if that's
3334 // not available, just write what we DO have
3335 if ( ml->isValid() || ml->originalXmlProperties().isEmpty() )
3336 {
3337 // general layer metadata
3338 maplayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
3339 ml->writeLayerXml( maplayerElem, *doc, context );
3340
3342 maplayerElem.setAttribute( QStringLiteral( "editable" ), QStringLiteral( "1" ) );
3343 }
3344 else if ( ! ml->originalXmlProperties().isEmpty() )
3345 {
3346 QDomDocument document;
3347 if ( document.setContent( ml->originalXmlProperties() ) )
3348 {
3349 maplayerElem = document.firstChildElement();
3350 }
3351 else
3352 {
3353 QgsDebugError( QStringLiteral( "Could not restore layer properties for layer %1" ).arg( ml->id() ) );
3354 }
3355 }
3356
3357 emit writeMapLayer( ml, maplayerElem, *doc );
3358
3359 projectLayersNode.appendChild( maplayerElem );
3360 }
3361 else
3362 {
3363 // layer defined in an external project file
3364 // only save embedded layer if not managed by a legend group
3365 if ( emIt.value().second )
3366 {
3367 QDomElement mapLayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
3368 mapLayerElem.setAttribute( QStringLiteral( "embedded" ), 1 );
3369 mapLayerElem.setAttribute( QStringLiteral( "project" ), writePath( emIt.value().first ) );
3370 mapLayerElem.setAttribute( QStringLiteral( "id" ), ml->id() );
3371 projectLayersNode.appendChild( mapLayerElem );
3372 }
3373 }
3374 }
3375 li++;
3376 }
3377
3378 qgisNode.appendChild( projectLayersNode );
3379
3380 QDomElement layerOrderNode = doc->createElement( QStringLiteral( "layerorder" ) );
3381 const auto constCustomLayerOrder = mRootGroup->customLayerOrder();
3382 for ( QgsMapLayer *layer : constCustomLayerOrder )
3383 {
3384 QDomElement mapLayerElem = doc->createElement( QStringLiteral( "layer" ) );
3385 mapLayerElem.setAttribute( QStringLiteral( "id" ), layer->id() );
3386 layerOrderNode.appendChild( mapLayerElem );
3387 }
3388 qgisNode.appendChild( layerOrderNode );
3389
3390 mLabelingEngineSettings->writeSettingsToProject( this );
3391 {
3392 QDomElement labelEngineSettingsElement = doc->createElement( QStringLiteral( "labelEngineSettings" ) );
3393 mLabelingEngineSettings->writeXml( *doc, labelEngineSettingsElement, context );
3394 qgisNode.appendChild( labelEngineSettingsElement );
3395 }
3396
3397 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), mBackgroundColor.red() );
3398 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), mBackgroundColor.green() );
3399 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), mBackgroundColor.blue() );
3400
3401 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), mSelectionColor.red() );
3402 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), mSelectionColor.green() );
3403 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), mSelectionColor.blue() );
3404 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), mSelectionColor.alpha() );
3405
3406 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QgsUnitTypes::encodeUnit( mDistanceUnits ) );
3407 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QgsUnitTypes::encodeUnit( mAreaUnits ) );
3408
3409 // now add the optional extra properties
3410#if 0
3411 dump_( mProperties );
3412#endif
3413
3414 QgsDebugMsgLevel( QStringLiteral( "there are %1 property scopes" ).arg( static_cast<int>( mProperties.count() ) ), 2 );
3415
3416 if ( !mProperties.isEmpty() ) // only worry about properties if we
3417 // actually have any properties
3418 {
3419 mProperties.writeXml( QStringLiteral( "properties" ), qgisNode, *doc );
3420 }
3421
3422 QDomElement ddElem = doc->createElement( QStringLiteral( "dataDefinedServerProperties" ) );
3423 mDataDefinedServerProperties.writeXml( ddElem, dataDefinedServerPropertyDefinitions() );
3424 qgisNode.appendChild( ddElem );
3425
3426 mMapThemeCollection->writeXml( *doc );
3427
3428 mTransformContext.writeXml( qgisNode, context );
3429
3430 QDomElement metadataElem = doc->createElement( QStringLiteral( "projectMetadata" ) );
3431 mMetadata.writeMetadataXml( metadataElem, *doc );
3432 qgisNode.appendChild( metadataElem );
3433
3434 {
3435 const QDomElement annotationsElem = mAnnotationManager->writeXml( *doc, context );
3436 qgisNode.appendChild( annotationsElem );
3437 }
3438
3439 {
3440 const QDomElement layoutElem = mLayoutManager->writeXml( *doc );
3441 qgisNode.appendChild( layoutElem );
3442 }
3443
3444 {
3445 const QDomElement views3DElem = m3DViewsManager->writeXml( *doc );
3446 qgisNode.appendChild( views3DElem );
3447 }
3448
3449 {
3450 const QDomElement bookmarkElem = mBookmarkManager->writeXml( *doc );
3451 qgisNode.appendChild( bookmarkElem );
3452 }
3453
3454 {
3455 const QDomElement sensorElem = mSensorManager->writeXml( *doc );
3456 qgisNode.appendChild( sensorElem );
3457 }
3458
3459 {
3460 const QDomElement viewSettingsElem = mViewSettings->writeXml( *doc, context );
3461 qgisNode.appendChild( viewSettingsElem );
3462 }
3463
3464 {
3465 const QDomElement styleSettingsElem = mStyleSettings->writeXml( *doc, context );
3466 qgisNode.appendChild( styleSettingsElem );
3467 }
3468
3469 {
3470 const QDomElement timeSettingsElement = mTimeSettings->writeXml( *doc, context );
3471 qgisNode.appendChild( timeSettingsElement );
3472 }
3473
3474 {
3475 const QDomElement elevationPropertiesElement = mElevationProperties->writeXml( *doc, context );
3476 qgisNode.appendChild( elevationPropertiesElement );
3477 }
3478
3479 {
3480 const QDomElement displaySettingsElem = mDisplaySettings->writeXml( *doc, context );
3481 qgisNode.appendChild( displaySettingsElem );
3482 }
3483
3484 {
3485 const QDomElement gpsSettingsElem = mGpsSettings->writeXml( *doc, context );
3486 qgisNode.appendChild( gpsSettingsElem );
3487 }
3488
3489 // now wrap it up and ship it to the project file
3490 doc->normalize(); // XXX I'm not entirely sure what this does
3491
3492 // Create backup file
3493 if ( QFile::exists( fileName() ) )
3494 {
3495 QFile backupFile( QStringLiteral( "%1~" ).arg( filename ) );
3496 bool ok = true;
3497 ok &= backupFile.open( QIODevice::WriteOnly | QIODevice::Truncate );
3498 ok &= projectFile.open( QIODevice::ReadOnly );
3499
3500 QByteArray ba;
3501 while ( ok && !projectFile.atEnd() )
3502 {
3503 ba = projectFile.read( 10240 );
3504 ok &= backupFile.write( ba ) == ba.size();
3505 }
3506
3507 projectFile.close();
3508 backupFile.close();
3509
3510 if ( !ok )
3511 {
3512 setError( tr( "Unable to create backup file %1" ).arg( backupFile.fileName() ) );
3513 return false;
3514 }
3515
3516 const QFileInfo fi( fileName() );
3517 struct utimbuf tb = { static_cast<time_t>( fi.lastRead().toSecsSinceEpoch() ), static_cast<time_t>( fi.lastModified().toSecsSinceEpoch() ) };
3518 utime( backupFile.fileName().toUtf8().constData(), &tb );
3519 }
3520
3521 if ( !projectFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
3522 {
3523 projectFile.close(); // even though we got an error, let's make
3524 // sure it's closed anyway
3525
3526 setError( tr( "Unable to save to file %1" ).arg( projectFile.fileName() ) );
3527 return false;
3528 }
3529
3530 QTemporaryFile tempFile;
3531 bool ok = tempFile.open();
3532 if ( ok )
3533 {
3534 QTextStream projectFileStream( &tempFile );
3535 doc->save( projectFileStream, 2 ); // save as utf-8
3536 ok &= projectFileStream.pos() > -1;
3537
3538 ok &= tempFile.seek( 0 );
3539
3540 QByteArray ba;
3541 while ( ok && !tempFile.atEnd() )
3542 {
3543 ba = tempFile.read( 10240 );
3544 ok &= projectFile.write( ba ) == ba.size();
3545 }
3546
3547 ok &= projectFile.error() == QFile::NoError;
3548
3549 projectFile.close();
3550 }
3551
3552 tempFile.close();
3553
3554 if ( !ok )
3555 {
3556 setError( tr( "Unable to save to file %1. Your project "
3557 "may be corrupted on disk. Try clearing some space on the volume and "
3558 "check file permissions before pressing save again." )
3559 .arg( projectFile.fileName() ) );
3560 return false;
3561 }
3562
3563 setDirty( false ); // reset to pristine state
3564
3565 emit projectSaved();
3566 return true;
3567}
3568
3569bool QgsProject::writeEntry( const QString &scope, QString const &key, bool value )
3570{
3572
3573 bool propertiesModified;
3574 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3575
3576 if ( propertiesModified )
3577 setDirty( true );
3578
3579 return success;
3580}
3581
3582bool QgsProject::writeEntry( const QString &scope, const QString &key, double value )
3583{
3585
3586 bool propertiesModified;
3587 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3588
3589 if ( propertiesModified )
3590 setDirty( true );
3591
3592 return success;
3593}
3594
3595bool QgsProject::writeEntry( const QString &scope, QString const &key, int value )
3596{
3598
3599 bool propertiesModified;
3600 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3601
3602 if ( propertiesModified )
3603 setDirty( true );
3604
3605 return success;
3606}
3607
3608bool QgsProject::writeEntry( const QString &scope, const QString &key, const QString &value )
3609{
3611
3612 bool propertiesModified;
3613 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3614
3615 if ( propertiesModified )
3616 setDirty( true );
3617
3618 return success;
3619}
3620
3621bool QgsProject::writeEntry( const QString &scope, const QString &key, const QStringList &value )
3622{
3624
3625 bool propertiesModified;
3626 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3627
3628 if ( propertiesModified )
3629 setDirty( true );
3630
3631 return success;
3632}
3633
3634QStringList QgsProject::readListEntry( const QString &scope,
3635 const QString &key,
3636 const QStringList &def,
3637 bool *ok ) const
3638{
3639 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
3641
3642 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3643
3644 QVariant value;
3645
3646 if ( property )
3647 {
3648 value = property->value();
3649
3650 const bool valid = QMetaType::Type::QStringList == value.userType();
3651 if ( ok )
3652 *ok = valid;
3653
3654 if ( valid )
3655 {
3656 return value.toStringList();
3657 }
3658 }
3659 else if ( ok )
3660 *ok = false;
3661
3662
3663 return def;
3664}
3665
3666QString QgsProject::readEntry( const QString &scope,
3667 const QString &key,
3668 const QString &def,
3669 bool *ok ) const
3670{
3672
3673 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3674
3675 QVariant value;
3676
3677 if ( property )
3678 {
3679 value = property->value();
3680
3681 const bool valid = value.canConvert( QMetaType::Type::QString );
3682 if ( ok )
3683 *ok = valid;
3684
3685 if ( valid )
3686 return value.toString();
3687 }
3688 else if ( ok )
3689 *ok = false;
3690
3691 return def;
3692}
3693
3694int QgsProject::readNumEntry( const QString &scope, const QString &key, int def,
3695 bool *ok ) const
3696{
3698
3699 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3700
3701 QVariant value;
3702
3703 if ( property )
3704 {
3705 value = property->value();
3706 }
3707
3708 const bool valid = value.canConvert( QMetaType::Type::Int );
3709
3710 if ( ok )
3711 {
3712 *ok = valid;
3713 }
3714
3715 if ( valid )
3716 {
3717 return value.toInt();
3718 }
3719
3720 return def;
3721}
3722
3723double QgsProject::readDoubleEntry( const QString &scope, const QString &key,
3724 double def,
3725 bool *ok ) const
3726{
3728
3729 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3730 if ( property )
3731 {
3732 const QVariant value = property->value();
3733
3734 const bool valid = value.canConvert( QMetaType::Type::Double );
3735 if ( ok )
3736 *ok = valid;
3737
3738 if ( valid )
3739 return value.toDouble();
3740 }
3741 else if ( ok )
3742 *ok = false;
3743
3744 return def;
3745}
3746
3747bool QgsProject::readBoolEntry( const QString &scope, const QString &key, bool def,
3748 bool *ok ) const
3749{
3751
3752 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3753
3754 if ( property )
3755 {
3756 const QVariant value = property->value();
3757
3758 const bool valid = value.canConvert( QMetaType::Type::Bool );
3759 if ( ok )
3760 *ok = valid;
3761
3762 if ( valid )
3763 return value.toBool();
3764 }
3765 else if ( ok )
3766 *ok = false;
3767
3768 return def;
3769}
3770
3771bool QgsProject::removeEntry( const QString &scope, const QString &key )
3772{
3774
3775 if ( findKey_( scope, key, mProperties ) )
3776 {
3777 removeKey_( scope, key, mProperties );
3778 setDirty( true );
3779 }
3780
3781 return !findKey_( scope, key, mProperties );
3782}
3783
3784QStringList QgsProject::entryList( const QString &scope, const QString &key ) const
3785{
3787
3788 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3789
3790 QStringList entries;
3791
3792 if ( foundProperty )
3793 {
3794 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3795
3796 if ( propertyKey )
3797 { propertyKey->entryList( entries ); }
3798 }
3799
3800 return entries;
3801}
3802
3803QStringList QgsProject::subkeyList( const QString &scope, const QString &key ) const
3804{
3806
3807 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3808
3809 QStringList entries;
3810
3811 if ( foundProperty )
3812 {
3813 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3814
3815 if ( propertyKey )
3816 { propertyKey->subkeyList( entries ); }
3817 }
3818
3819 return entries;
3820}
3821
3823{
3825
3826 dump_( mProperties );
3827}
3828
3830{
3832
3833 QString filePath;
3834 switch ( filePathStorage() )
3835 {
3837 break;
3838
3840 {
3841 // for projects stored in a custom storage, we need to ask to the
3842 // storage for the path, if the storage returns an empty path
3843 // relative paths are not supported
3844 if ( QgsProjectStorage *storage = projectStorage() )
3845 {
3846 filePath = storage->filePath( mFile.fileName() );
3847 }
3848 else
3849 {
3850 filePath = fileName();
3851 }
3852 break;
3853 }
3854 }
3855
3856 return QgsPathResolver( filePath, mArchive->dir() );
3857}
3858
3859QString QgsProject::readPath( const QString &src ) const
3860{
3862
3863 return pathResolver().readPath( src );
3864}
3865
3866QString QgsProject::writePath( const QString &src ) const
3867{
3869
3870 return pathResolver().writePath( src );
3871}
3872
3873void QgsProject::setError( const QString &errorMessage )
3874{
3876
3877 mErrorMessage = errorMessage;
3878}
3879
3880QString QgsProject::error() const
3881{
3883
3884 return mErrorMessage;
3885}
3886
3887void QgsProject::clearError()
3888{
3890
3891 setError( QString() );
3892}
3893
3895{
3897
3898 delete mBadLayerHandler;
3899 mBadLayerHandler = handler;
3900}
3901
3902QString QgsProject::layerIsEmbedded( const QString &id ) const
3903{
3905
3906 const QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
3907 if ( it == mEmbeddedLayers.constEnd() )
3908 {
3909 return QString();
3910 }
3911 return it.value().first;
3912}
3913
3914bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &projectFilePath, QList<QDomNode> &brokenNodes,
3915 bool saveFlag, Qgis::ProjectReadFlags flags )
3916{
3918
3920
3921 static QString sPrevProjectFilePath;
3922 static QDateTime sPrevProjectFileTimestamp;
3923 static QDomDocument sProjectDocument;
3924
3925 QString qgsProjectFile = projectFilePath;
3926 QgsProjectArchive archive;
3927 if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
3928 {
3929 archive.unzip( projectFilePath );
3930 qgsProjectFile = archive.projectFile();
3931 }
3932
3933 const QDateTime projectFileTimestamp = QFileInfo( projectFilePath ).lastModified();
3934
3935 if ( projectFilePath != sPrevProjectFilePath || projectFileTimestamp != sPrevProjectFileTimestamp )
3936 {
3937 sPrevProjectFilePath.clear();
3938
3939 QFile projectFile( qgsProjectFile );
3940 if ( !projectFile.open( QIODevice::ReadOnly ) )
3941 {
3942 return false;
3943 }
3944
3945 if ( !sProjectDocument.setContent( &projectFile ) )
3946 {
3947 return false;
3948 }
3949
3950 sPrevProjectFilePath = projectFilePath;
3951 sPrevProjectFileTimestamp = projectFileTimestamp;
3952 }
3953
3954 // does project store paths absolute or relative?
3955 bool useAbsolutePaths = true;
3956
3957 const QDomElement propertiesElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "properties" ) );
3958 if ( !propertiesElem.isNull() )
3959 {
3960 const QDomElement absElem = propertiesElem.firstChildElement( QStringLiteral( "Paths" ) ).firstChildElement( QStringLiteral( "Absolute" ) );
3961 if ( !absElem.isNull() )
3962 {
3963 useAbsolutePaths = absElem.text().compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
3964 }
3965 }
3966
3967 QgsReadWriteContext embeddedContext;
3968 if ( !useAbsolutePaths )
3969 embeddedContext.setPathResolver( QgsPathResolver( projectFilePath ) );
3970 embeddedContext.setProjectTranslator( this );
3971 embeddedContext.setTransformContext( transformContext() );
3972
3973 const QDomElement projectLayersElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "projectlayers" ) );
3974 if ( projectLayersElem.isNull() )
3975 {
3976 return false;
3977 }
3978
3979 QDomElement mapLayerElem = projectLayersElem.firstChildElement( QStringLiteral( "maplayer" ) );
3980 while ( ! mapLayerElem.isNull() )
3981 {
3982 // get layer id
3983 const QString id = mapLayerElem.firstChildElement( QStringLiteral( "id" ) ).text();
3984 if ( id == layerId )
3985 {
3986 // layer can be embedded only once
3987 if ( mapLayerElem.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
3988 {
3989 return false;
3990 }
3991
3992 mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
3993
3994 if ( addLayer( mapLayerElem, brokenNodes, embeddedContext, flags ) )
3995 {
3996 return true;
3997 }
3998 else
3999 {
4000 mEmbeddedLayers.remove( layerId );
4001 return false;
4002 }
4003 }
4004 mapLayerElem = mapLayerElem.nextSiblingElement( QStringLiteral( "maplayer" ) );
4005 }
4006
4007 return false;
4008}
4009
4010QgsLayerTreeGroup *QgsProject::createEmbeddedGroup( const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers, Qgis::ProjectReadFlags flags )
4011{
4013
4014 QString qgsProjectFile = projectFilePath;
4015 QgsProjectArchive archive;
4016 if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
4017 {
4018 archive.unzip( projectFilePath );
4019 qgsProjectFile = archive.projectFile();
4020 }
4021
4022 // open project file, get layer ids in group, add the layers
4023 QFile projectFile( qgsProjectFile );
4024 if ( !projectFile.open( QIODevice::ReadOnly ) )
4025 {
4026 return nullptr;
4027 }
4028
4029 QDomDocument projectDocument;
4030 if ( !projectDocument.setContent( &projectFile ) )
4031 {
4032 return nullptr;
4033 }
4034
4035 QgsReadWriteContext context;
4036 context.setPathResolver( pathResolver() );
4037 context.setProjectTranslator( this );
4039
4041
4042 QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
4043 if ( !layerTreeElem.isNull() )
4044 {
4045 root->readChildrenFromXml( layerTreeElem, context );
4046 }
4047 else
4048 {
4049 QgsLayerTreeUtils::readOldLegend( root, projectDocument.documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
4050 }
4051
4052 QgsLayerTreeGroup *group = root->findGroup( groupName );
4053 if ( !group || group->customProperty( QStringLiteral( "embedded" ) ).toBool() )
4054 {
4055 // embedded groups cannot be embedded again
4056 delete root;
4057 return nullptr;
4058 }
4059
4060 // clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
4061 QgsLayerTreeGroup *newGroup = QgsLayerTree::toGroup( group->clone() );
4062 delete root;
4063 root = nullptr;
4064
4065 newGroup->setCustomProperty( QStringLiteral( "embedded" ), 1 );
4066 newGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectFilePath );
4067
4068 // set "embedded" to all children + load embedded layers
4069 mLayerTreeRegistryBridge->setEnabled( false );
4070 initializeEmbeddedSubtree( projectFilePath, newGroup, flags );
4071 mLayerTreeRegistryBridge->setEnabled( true );
4072
4073 // consider the layers might be identify disabled in its project
4074 const auto constFindLayerIds = newGroup->findLayerIds();
4075 for ( const QString &layerId : constFindLayerIds )
4076 {
4077 QgsLayerTreeLayer *layer = newGroup->findLayer( layerId );
4078 if ( layer )
4079 {
4080 layer->resolveReferences( this );
4081 layer->setItemVisibilityChecked( !invisibleLayers.contains( layerId ) );
4082 }
4083 }
4084
4085 return newGroup;
4086}
4087
4088void QgsProject::initializeEmbeddedSubtree( const QString &projectFilePath, QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
4089{
4091
4092 const auto constChildren = group->children();
4093 for ( QgsLayerTreeNode *child : constChildren )
4094 {
4095 // all nodes in the subtree will have "embedded" custom property set
4096 child->setCustomProperty( QStringLiteral( "embedded" ), 1 );
4097
4098 if ( QgsLayerTree::isGroup( child ) )
4099 {
4100 initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ), flags );
4101 }
4102 else if ( QgsLayerTree::isLayer( child ) )
4103 {
4104 // load the layer into our project
4105 QList<QDomNode> brokenNodes;
4106 createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, false, flags );
4107 }
4108 }
4109}
4110
4117
4124
4126{
4128
4129 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), ( enabled ? 1 : 0 ) );
4131}
4132
4134{
4136
4137 return readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), 0 );
4138}
4139
4141{
4143
4144 if ( mDistanceUnits == unit )
4145 return;
4146
4147 mDistanceUnits = unit;
4148
4149 emit distanceUnitsChanged();
4150}
4151
4153{
4155
4156 if ( mAreaUnits == unit )
4157 return;
4158
4159 mAreaUnits = unit;
4160
4161 emit areaUnitsChanged();
4162}
4163
4165{
4166 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4168
4169 if ( !mCachedHomePath.isEmpty() )
4170 return mCachedHomePath;
4171
4172 const QFileInfo pfi( fileName() );
4173
4174 if ( !mHomePath.isEmpty() )
4175 {
4176 const QFileInfo homeInfo( mHomePath );
4177 if ( !homeInfo.isRelative() )
4178 {
4179 mCachedHomePath = mHomePath;
4180 return mHomePath;
4181 }
4182 }
4183 else if ( !fileName().isEmpty() )
4184 {
4185
4186 // If it's not stored in the file system, try to get the path from the storage
4187 if ( QgsProjectStorage *storage = projectStorage() )
4188 {
4189 const QString storagePath { storage->filePath( fileName() ) };
4190 if ( ! storagePath.isEmpty() && QFileInfo::exists( storagePath ) )
4191 {
4192 mCachedHomePath = QFileInfo( storagePath ).path();
4193 return mCachedHomePath;
4194 }
4195 }
4196
4197 mCachedHomePath = pfi.path();
4198 return mCachedHomePath;
4199 }
4200
4201 if ( !pfi.exists() )
4202 {
4203 mCachedHomePath = mHomePath;
4204 return mHomePath;
4205 }
4206
4207 if ( !mHomePath.isEmpty() )
4208 {
4209 // path is relative to project file
4210 mCachedHomePath = QDir::cleanPath( pfi.path() + '/' + mHomePath );
4211 }
4212 else
4213 {
4214 mCachedHomePath = pfi.canonicalPath();
4215 }
4216 return mCachedHomePath;
4217}
4218
4220{
4222
4223 return mHomePath;
4224}
4225
4227{
4228 // because relation aggregate functions are not thread safe
4230
4231 return mRelationManager;
4232}
4233
4235{
4237
4238 return mLayoutManager.get();
4239}
4240
4242{
4244
4245 return mLayoutManager.get();
4246}
4247
4249{
4251
4252 return m3DViewsManager.get();
4253}
4254
4256{
4258
4259 return m3DViewsManager.get();
4260}
4261
4263{
4265
4266 return mBookmarkManager;
4267}
4268
4270{
4272
4273 return mBookmarkManager;
4274}
4275
4277{
4279
4280 return mSensorManager;
4281}
4282
4284{
4286
4287 return mSensorManager;
4288}
4289
4291{
4293
4294 return mViewSettings;
4295}
4296
4303
4305{
4307
4308 return mStyleSettings;
4309}
4310
4312{
4313 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4315
4316 return mStyleSettings;
4317}
4318
4320{
4322
4323 return mTimeSettings;
4324}
4325
4332
4334{
4336
4337 return mElevationProperties;
4338}
4339
4346
4348{
4350
4351 return mDisplaySettings;
4352}
4353
4355{
4357
4358 return mDisplaySettings;
4359}
4360
4362{
4364
4365 return mGpsSettings;
4366}
4367
4374
4376{
4378
4379 return mRootGroup;
4380}
4381
4383{
4385
4386 return mMapThemeCollection.get();
4387}
4388
4390{
4392
4393 return mAnnotationManager.get();
4394}
4395
4397{
4399
4400 return mAnnotationManager.get();
4401}
4402
4403void QgsProject::setNonIdentifiableLayers( const QList<QgsMapLayer *> &layers )
4404{
4406
4407 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
4408 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
4409 {
4410 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4411 continue;
4412
4413 if ( layers.contains( it.value() ) )
4414 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Identifiable );
4415 else
4416 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Identifiable );
4417 }
4418
4422}
4423
4424void QgsProject::setNonIdentifiableLayers( const QStringList &layerIds )
4425{
4427
4428 QList<QgsMapLayer *> nonIdentifiableLayers;
4429 nonIdentifiableLayers.reserve( layerIds.count() );
4430 for ( const QString &layerId : layerIds )
4431 {
4432 QgsMapLayer *layer = mapLayer( layerId );
4433 if ( layer )
4434 nonIdentifiableLayers << layer;
4435 }
4439}
4440
4442{
4444
4445 QStringList nonIdentifiableLayers;
4446
4447 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
4448 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
4449 {
4450 if ( !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4451 {
4452 nonIdentifiableLayers.append( it.value()->id() );
4453 }
4454 }
4455 return nonIdentifiableLayers;
4456}
4457
4459{
4461
4462 return mTransactionMode == Qgis::TransactionMode::AutomaticGroups;
4463}
4464
4465void QgsProject::setAutoTransaction( bool autoTransaction )
4466{
4468
4469 if ( autoTransaction
4470 && mTransactionMode == Qgis::TransactionMode::AutomaticGroups )
4471 return;
4472
4473 if ( ! autoTransaction
4474 && mTransactionMode == Qgis::TransactionMode::Disabled )
4475 return;
4476
4477 if ( autoTransaction )
4479 else
4481
4482 updateTransactionGroups();
4483}
4484
4486{
4488
4489 return mTransactionMode;
4490}
4491
4493{
4495
4496 if ( transactionMode == mTransactionMode )
4497 return true;
4498
4499 // Check that all layer are not in edit mode
4500 const auto constLayers = mapLayers().values();
4501 for ( QgsMapLayer *layer : constLayers )
4502 {
4503 if ( layer->isEditable() )
4504 {
4505 QgsLogger::warning( tr( "Transaction mode can be changed only if all layers are not editable." ) );
4506 return false;
4507 }
4508 }
4509
4510 mTransactionMode = transactionMode;
4511 updateTransactionGroups();
4513 return true;
4514}
4515
4516QMap<QPair<QString, QString>, QgsTransactionGroup *> QgsProject::transactionGroups()
4517{
4519
4520 return mTransactionGroups;
4521}
4522
4523
4524//
4525// QgsMapLayerStore methods
4526//
4527
4528
4530{
4532
4533 return mLayerStore->count();
4534}
4535
4537{
4539
4540 return mLayerStore->validCount();
4541}
4542
4543QgsMapLayer *QgsProject::mapLayer( const QString &layerId ) const
4544{
4545 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
4547
4548 return mLayerStore->mapLayer( layerId );
4549}
4550
4551QList<QgsMapLayer *> QgsProject::mapLayersByName( const QString &layerName ) const
4552{
4554
4555 return mLayerStore->mapLayersByName( layerName );
4556}
4557
4558QList<QgsMapLayer *> QgsProject::mapLayersByShortName( const QString &shortName ) const
4559{
4561
4562 QList<QgsMapLayer *> layers;
4563 const auto constMapLayers { mLayerStore->mapLayers() };
4564 for ( const auto &l : constMapLayers )
4565 {
4566 if ( ! l->serverProperties()->shortName().isEmpty() )
4567 {
4568 if ( l->serverProperties()->shortName() == shortName )
4569 layers << l;
4570 }
4571 else if ( l->name() == shortName )
4572 {
4573 layers << l;
4574 }
4575 }
4576 return layers;
4577}
4578
4579bool QgsProject::unzip( const QString &filename, Qgis::ProjectReadFlags flags )
4580{
4582
4583 clearError();
4584 std::unique_ptr<QgsProjectArchive> archive( new QgsProjectArchive() );
4585
4586 // unzip the archive
4587 if ( !archive->unzip( filename ) )
4588 {
4589 setError( tr( "Unable to unzip file '%1'" ).arg( filename ) );
4590 return false;
4591 }
4592
4593 // test if zip provides a .qgs file
4594 if ( archive->projectFile().isEmpty() )
4595 {
4596 setError( tr( "Zip archive does not provide a project file" ) );
4597 return false;
4598 }
4599
4600 // Keep the archive
4601 releaseHandlesToProjectArchive();
4602 mArchive = std::move( archive );
4603
4604 // load auxiliary storage
4605 if ( !static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile().isEmpty() )
4606 {
4607 // database file is already a copy as it's been unzipped. So we don't open
4608 // auxiliary storage in copy mode in this case
4609 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile(), false ) );
4610 }
4611 else
4612 {
4613 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
4614 }
4615
4616 // read the project file
4617 if ( ! readProjectFile( static_cast<QgsProjectArchive *>( mArchive.get() )->projectFile(), flags ) )
4618 {
4619 setError( tr( "Cannot read unzipped qgs project file" ) + QStringLiteral( ": " ) + error() );
4620 return false;
4621 }
4622
4623 // Remove the temporary .qgs file
4624 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4625
4626 return true;
4627}
4628
4629bool QgsProject::zip( const QString &filename )
4630{
4632
4633 clearError();
4634
4635 // save the current project in a temporary .qgs file
4636 std::unique_ptr<QgsProjectArchive> archive( new QgsProjectArchive() );
4637 const QString baseName = QFileInfo( filename ).baseName();
4638 const QString qgsFileName = QStringLiteral( "%1.qgs" ).arg( baseName );
4639 QFile qgsFile( QDir( archive->dir() ).filePath( qgsFileName ) );
4640
4641 bool writeOk = false;
4642 if ( qgsFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
4643 {
4644 writeOk = writeProjectFile( qgsFile.fileName() );
4645 qgsFile.close();
4646 }
4647
4648 // stop here with an error message
4649 if ( ! writeOk )
4650 {
4651 setError( tr( "Unable to write temporary qgs file" ) );
4652 return false;
4653 }
4654
4655 // save auxiliary storage
4656 const QFileInfo info( qgsFile );
4657 const QString asExt = QStringLiteral( ".%1" ).arg( QgsAuxiliaryStorage::extension() );
4658 const QString asFileName = info.path() + QDir::separator() + info.completeBaseName() + asExt;
4659
4660 bool auxiliaryStorageSavedOk = true;
4661 if ( ! saveAuxiliaryStorage( asFileName ) )
4662 {
4663 const QString err = mAuxiliaryStorage->errorString();
4664 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 ) );
4665 auxiliaryStorageSavedOk = false;
4666
4667 // fixes the current archive and keep the previous version of qgd
4668 if ( !mArchive->exists() )
4669 {
4670 releaseHandlesToProjectArchive();
4671 mArchive.reset( new QgsProjectArchive() );
4672 mArchive->unzip( mFile.fileName() );
4673 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4674
4675 const QString auxiliaryStorageFile = static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile();
4676 if ( ! auxiliaryStorageFile.isEmpty() )
4677 {
4678 archive->addFile( auxiliaryStorageFile );
4679 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( auxiliaryStorageFile, false ) );
4680 }
4681 }
4682 }
4683 else
4684 {
4685 // in this case, an empty filename means that the auxiliary database is
4686 // empty, so we don't want to save it
4687 if ( QFile::exists( asFileName ) )
4688 {
4689 archive->addFile( asFileName );
4690 }
4691 }
4692
4693 // create the archive
4694 archive->addFile( qgsFile.fileName() );
4695
4696 // Add all other files
4697 const QStringList &files = mArchive->files();
4698 for ( const QString &file : files )
4699 {
4700 if ( !file.endsWith( QLatin1String( ".qgs" ), Qt::CaseInsensitive ) && !file.endsWith( asExt, Qt::CaseInsensitive ) )
4701 {
4702 archive->addFile( file );
4703 }
4704 }
4705
4706 // zip
4707 bool zipOk = true;
4708 if ( !archive->zip( filename ) )
4709 {
4710 setError( tr( "Unable to perform zip" ) );
4711 zipOk = false;
4712 }
4713
4714 return auxiliaryStorageSavedOk && zipOk;
4715}
4716
4718{
4720
4721 return QgsZipUtils::isZipFile( mFile.fileName() );
4722}
4723
4724QList<QgsMapLayer *> QgsProject::addMapLayers(
4725 const QList<QgsMapLayer *> &layers,
4726 bool addToLegend,
4727 bool takeOwnership )
4728{
4730
4731 const QList<QgsMapLayer *> myResultList { mLayerStore->addMapLayers( layers, takeOwnership ) };
4732 if ( !myResultList.isEmpty() )
4733 {
4734 // Update transform context
4735 for ( auto &l : myResultList )
4736 {
4737 l->setTransformContext( transformContext() );
4738 }
4739 if ( addToLegend )
4740 {
4741 emit legendLayersAdded( myResultList );
4742 }
4743 }
4744
4745 if ( mAuxiliaryStorage )
4746 {
4747 for ( QgsMapLayer *mlayer : myResultList )
4748 {
4749 if ( mlayer->type() != Qgis::LayerType::Vector )
4750 continue;
4751
4752 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mlayer );
4753 if ( vl )
4754 {
4755 vl->loadAuxiliaryLayer( *mAuxiliaryStorage );
4756 }
4757 }
4758 }
4759
4760 mProjectScope.reset();
4761
4762 return myResultList;
4763}
4764
4767 bool addToLegend,
4768 bool takeOwnership )
4769{
4771
4772 QList<QgsMapLayer *> addedLayers;
4773 addedLayers = addMapLayers( QList<QgsMapLayer *>() << layer, addToLegend, takeOwnership );
4774 return addedLayers.isEmpty() ? nullptr : addedLayers[0];
4775}
4776
4777void QgsProject::removeAuxiliaryLayer( const QgsMapLayer *ml )
4778{
4780
4781 if ( ! ml || ml->type() != Qgis::LayerType::Vector )
4782 return;
4783
4784 const QgsVectorLayer *vl = qobject_cast<const QgsVectorLayer *>( ml );
4785 if ( vl && vl->auxiliaryLayer() )
4786 {
4787 const QgsDataSourceUri uri( vl->auxiliaryLayer()->source() );
4789 }
4790}
4791
4792void QgsProject::removeMapLayers( const QStringList &layerIds )
4793{
4795
4796 for ( const auto &layerId : layerIds )
4797 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4798
4799 mProjectScope.reset();
4800 mLayerStore->removeMapLayers( layerIds );
4801}
4802
4803void QgsProject::removeMapLayers( const QList<QgsMapLayer *> &layers )
4804{
4806
4807 for ( const auto &layer : layers )
4808 removeAuxiliaryLayer( layer );
4809
4810 mProjectScope.reset();
4811 mLayerStore->removeMapLayers( layers );
4812}
4813
4814void QgsProject::removeMapLayer( const QString &layerId )
4815{
4817
4818 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4819 mProjectScope.reset();
4820 mLayerStore->removeMapLayer( layerId );
4821}
4822
4824{
4826
4827 removeAuxiliaryLayer( layer );
4828 mProjectScope.reset();
4829 mLayerStore->removeMapLayer( layer );
4830}
4831
4833{
4835
4836 mProjectScope.reset();
4837 return mLayerStore->takeMapLayer( layer );
4838}
4839
4841{
4843
4844 return mMainAnnotationLayer;
4845}
4846
4848{
4850
4851 if ( mLayerStore->count() == 0 )
4852 return;
4853
4854 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
4855 mProjectScope.reset();
4856 mLayerStore->removeAllMapLayers();
4857
4858 snapSingleBlocker.release();
4859 mSnappingConfig.clearIndividualLayerSettings();
4860 if ( !mBlockSnappingUpdates )
4861 emit snappingConfigChanged( mSnappingConfig );
4862}
4863
4865{
4867
4868 const QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
4869 QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin();
4870 for ( ; it != layers.constEnd(); ++it )
4871 {
4872 it.value()->reload();
4873 }
4874}
4875
4876QMap<QString, QgsMapLayer *> QgsProject::mapLayers( const bool validOnly ) const
4877{
4878 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
4880
4881 return validOnly ? mLayerStore->validMapLayers() : mLayerStore->mapLayers();
4882}
4883
4884QgsTransactionGroup *QgsProject::transactionGroup( const QString &providerKey, const QString &connString )
4885{
4887
4888 return mTransactionGroups.value( qMakePair( providerKey, connString ) );
4889}
4890
4897
4899{
4901
4903
4904 // TODO QGIS 4.0 -- remove this method, and place it somewhere in app (where it belongs)
4905 // 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)
4906 if ( mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), QStringLiteral( "NoAction" ), QgsSettings::App ).toString() == QStringLiteral( "UseProjectCrs" )
4907 || mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), 0, QgsSettings::App ).toString() == QLatin1String( "2" ) )
4908 {
4909 // for new layers if the new layer crs method is set to either prompt or use project, then we use the project crs
4910 defaultCrs = crs();
4911 }
4912 else
4913 {
4914 // global crs
4915 const QString layerDefaultCrs = mSettings.value( QStringLiteral( "/Projections/layerDefaultCrs" ), geoEpsgCrsAuthId() ).toString();
4916 defaultCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( layerDefaultCrs );
4917 }
4918
4919 return defaultCrs;
4920}
4921
4928
4935
4936bool QgsProject::saveAuxiliaryStorage( const QString &filename )
4937{
4939
4940 const QMap<QString, QgsMapLayer *> layers = mapLayers();
4941 bool empty = true;
4942 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
4943 {
4944 if ( it.value()->type() != Qgis::LayerType::Vector )
4945 continue;
4946
4947 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
4948 if ( vl && vl->auxiliaryLayer() )
4949 {
4950 vl->auxiliaryLayer()->save();
4951 empty &= vl->auxiliaryLayer()->auxiliaryFields().isEmpty();
4952 }
4953 }
4954
4955 if ( !mAuxiliaryStorage->exists( *this ) && empty )
4956 {
4957 return true; // it's not an error
4958 }
4959 else if ( !filename.isEmpty() )
4960 {
4961 return mAuxiliaryStorage->saveAs( filename );
4962 }
4963 else
4964 {
4965 return mAuxiliaryStorage->saveAs( *this );
4966 }
4967}
4968
4969QgsPropertiesDefinition &QgsProject::dataDefinedServerPropertyDefinitions()
4970{
4971 static QgsPropertiesDefinition sPropertyDefinitions
4972 {
4973 {
4975 QgsPropertyDefinition( "WMSOnlineResource", QObject::tr( "WMS Online Resource" ), QgsPropertyDefinition::String )
4976 },
4977 };
4978 return sPropertyDefinitions;
4979}
4980
4982{
4983 mElevationShadingRenderer = elevationShadingRenderer;
4985}
4986
4988{
4990
4991 return mAuxiliaryStorage.get();
4992}
4993
4995{
4997
4998 return mAuxiliaryStorage.get();
4999}
5000
5001QString QgsProject::createAttachedFile( const QString &nameTemplate )
5002{
5004
5005 const QDir archiveDir( mArchive->dir() );
5006 QTemporaryFile tmpFile( archiveDir.filePath( "XXXXXX_" + nameTemplate ), this );
5007 tmpFile.setAutoRemove( false );
5008 tmpFile.open();
5009 mArchive->addFile( tmpFile.fileName() );
5010 return tmpFile.fileName();
5011}
5012
5013QStringList QgsProject::attachedFiles() const
5014{
5016
5017 QStringList attachments;
5018 const QString baseName = QFileInfo( fileName() ).baseName();
5019 const QStringList files = mArchive->files();
5020 attachments.reserve( files.size() );
5021 for ( const QString &file : files )
5022 {
5023 if ( QFileInfo( file ).baseName() != baseName )
5024 {
5025 attachments.append( file );
5026 }
5027 }
5028 return attachments;
5029}
5030
5031bool QgsProject::removeAttachedFile( const QString &path )
5032{
5034
5035 return mArchive->removeFile( path );
5036}
5037
5038QString QgsProject::attachmentIdentifier( const QString &attachedFile ) const
5039{
5041
5042 return QStringLiteral( "attachment:///%1" ).arg( QFileInfo( attachedFile ).fileName() );
5043}
5044
5045QString QgsProject::resolveAttachmentIdentifier( const QString &identifier ) const
5046{
5048
5049 if ( identifier.startsWith( QLatin1String( "attachment:///" ) ) )
5050 {
5051 return QDir( mArchive->dir() ).absoluteFilePath( identifier.mid( 14 ) );
5052 }
5053 return QString();
5054}
5055
5057{
5058 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
5060
5061 return mMetadata;
5062}
5063
5065{
5067
5068 if ( metadata == mMetadata )
5069 return;
5070
5071 mMetadata = metadata;
5072 mProjectScope.reset();
5073
5074 emit metadataChanged();
5075
5076 setDirty( true );
5077}
5078
5079QSet<QgsMapLayer *> QgsProject::requiredLayers() const
5080{
5082
5083 QSet<QgsMapLayer *> requiredLayers;
5084
5085 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
5086 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
5087 {
5088 if ( !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
5089 {
5090 requiredLayers.insert( it.value() );
5091 }
5092 }
5093 return requiredLayers;
5094}
5095
5096void QgsProject::setRequiredLayers( const QSet<QgsMapLayer *> &layers )
5097{
5099
5100 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
5101 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
5102 {
5103 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
5104 continue;
5105
5106 if ( layers.contains( it.value() ) )
5107 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Removable );
5108 else
5109 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Removable );
5110 }
5111}
5112
5114{
5116
5117 // save colors to project
5118 QStringList customColors;
5119 QStringList customColorLabels;
5120
5121 QgsNamedColorList::const_iterator colorIt = colors.constBegin();
5122 for ( ; colorIt != colors.constEnd(); ++colorIt )
5123 {
5124 const QString color = QgsColorUtils::colorToString( ( *colorIt ).first );
5125 const QString label = ( *colorIt ).second;
5126 customColors.append( color );
5127 customColorLabels.append( label );
5128 }
5129 writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ), customColors );
5130 writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ), customColorLabels );
5131 mProjectScope.reset();
5132 emit projectColorsChanged();
5133}
5134
5135void QgsProject::setBackgroundColor( const QColor &color )
5136{
5138
5139 if ( mBackgroundColor == color )
5140 return;
5141
5142 mBackgroundColor = color;
5144}
5145
5147{
5149
5150 return mBackgroundColor;
5151}
5152
5153void QgsProject::setSelectionColor( const QColor &color )
5154{
5156
5157 if ( mSelectionColor == color )
5158 return;
5159
5160 mSelectionColor = color;
5161 emit selectionColorChanged();
5162}
5163
5165{
5167
5168 return mSelectionColor;
5169}
5170
5171void QgsProject::setMapScales( const QVector<double> &scales )
5172{
5174
5175 mViewSettings->setMapScales( scales );
5176}
5177
5178QVector<double> QgsProject::mapScales() const
5179{
5181
5182 return mViewSettings->mapScales();
5183}
5184
5186{
5188
5189 mViewSettings->setUseProjectScales( enabled );
5190}
5191
5193{
5195
5196 return mViewSettings->useProjectScales();
5197}
5198
5199void QgsProject::generateTsFile( const QString &locale )
5200{
5202
5203 QgsTranslationContext translationContext;
5204 translationContext.setProject( this );
5205 translationContext.setFileName( QStringLiteral( "%1/%2.ts" ).arg( absolutePath(), baseName() ) );
5206
5207 QgsApplication::instance()->collectTranslatableObjects( &translationContext );
5208
5209 translationContext.writeTsFile( locale );
5210}
5211
5212QString QgsProject::translate( const QString &context, const QString &sourceText, const char *disambiguation, int n ) const
5213{
5215
5216 if ( !mTranslator )
5217 {
5218 return sourceText;
5219 }
5220
5221 QString result = mTranslator->translate( context.toUtf8(), sourceText.toUtf8(), disambiguation, n );
5222
5223 if ( result.isEmpty() )
5224 {
5225 return sourceText;
5226 }
5227 return result;
5228}
5229
5231{
5233
5234 const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
5235 if ( !layers.empty() )
5236 {
5237 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5238 {
5239 // NOTE: if visitEnter returns false it means "don't visit this layer", not "abort all further visitations"
5240 if ( visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
5241 {
5242 if ( !( ( *it )->accept( visitor ) ) )
5243 return false;
5244
5245 if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
5246 return false;
5247 }
5248 }
5249 }
5250
5251 if ( !mLayoutManager->accept( visitor ) )
5252 return false;
5253
5254 if ( !mAnnotationManager->accept( visitor ) )
5255 return false;
5256
5257 return true;
5258}
5259
5261{
5262 return mElevationShadingRenderer;
5263}
5264
5265void QgsProject::loadProjectFlags( const QDomDocument *doc )
5266{
5268
5269 QDomElement element = doc->documentElement().firstChildElement( QStringLiteral( "projectFlags" ) );
5271 if ( !element.isNull() )
5272 {
5273 flags = qgsFlagKeysToValue( element.attribute( QStringLiteral( "set" ) ), Qgis::ProjectFlags() );
5274 }
5275 else
5276 {
5277 // older project compatibility
5278 element = doc->documentElement().firstChildElement( QStringLiteral( "evaluateDefaultValues" ) );
5279 if ( !element.isNull() )
5280 {
5281 if ( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
5283 }
5284
5285 // Read trust layer metadata config in the project
5286 element = doc->documentElement().firstChildElement( QStringLiteral( "trust" ) );
5287 if ( !element.isNull() )
5288 {
5289 if ( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
5291 }
5292 }
5293
5294 setFlags( flags );
5295}
5296
5298{
5300 {
5301 const Qgis::PythonEmbeddedMode pythonEmbeddedMode = QgsSettings().enumValue( QStringLiteral( "qgis/enablePythonEmbedded" ), Qgis::PythonEmbeddedMode::Ask );
5302
5303 if ( force || pythonEmbeddedMode == Qgis::PythonEmbeddedMode::SessionOnly || pythonEmbeddedMode == Qgis::PythonEmbeddedMode::Always )
5304 {
5305 const QString projectFunctions = readEntry( QStringLiteral( "ExpressionFunctions" ), QStringLiteral( "/pythonCode" ), QString() );
5306 if ( !projectFunctions.isEmpty() )
5307 {
5308 QgsPythonRunner::run( projectFunctions );
5309 return true;
5310 }
5311 }
5312 }
5313 return false;
5314}
5315
5317{
5319 {
5320 QgsPythonRunner::run( "qgis.utils.clean_project_expression_functions()" );
5321 }
5322}
5323
5325
5326QHash< QString, QColor > loadColorsFromProject( const QgsProject *project )
5327{
5328 QHash< QString, QColor > colors;
5329
5330 //build up color list from project. Do this in advance for speed
5331 QStringList colorStrings = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ) );
5332 const QStringList colorLabels = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ) );
5333
5334 //generate list from custom colors
5335 int colorIndex = 0;
5336 for ( QStringList::iterator it = colorStrings.begin();
5337 it != colorStrings.end(); ++it )
5338 {
5339 const QColor color = QgsColorUtils::colorFromString( *it );
5340 QString label;
5341 if ( colorLabels.length() > colorIndex )
5342 {
5343 label = colorLabels.at( colorIndex );
5344 }
5345
5346 colors.insert( label.toLower(), color );
5347 colorIndex++;
5348 }
5349
5350 return colors;
5351}
5352
5353
5354GetNamedProjectColor::GetNamedProjectColor( const QgsProject *project )
5355 : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
5356{
5357 if ( !project )
5358 return;
5359
5360 mColors = loadColorsFromProject( project );
5361}
5362
5363GetNamedProjectColor::GetNamedProjectColor( const QHash<QString, QColor> &colors )
5364 : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
5365 , mColors( colors )
5366{
5367}
5368
5369QVariant GetNamedProjectColor::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5370{
5371 const QString colorName = values.at( 0 ).toString().toLower();
5372 if ( mColors.contains( colorName ) )
5373 {
5374 return QStringLiteral( "%1,%2,%3" ).arg( mColors.value( colorName ).red() ).arg( mColors.value( colorName ).green() ).arg( mColors.value( colorName ).blue() );
5375 }
5376 else
5377 return QVariant();
5378}
5379
5380QgsScopedExpressionFunction *GetNamedProjectColor::clone() const
5381{
5382 return new GetNamedProjectColor( mColors );
5383}
5384
5385GetNamedProjectColorObject::GetNamedProjectColorObject( const QgsProject *project )
5386 : QgsScopedExpressionFunction( QStringLiteral( "project_color_object" ), 1, QStringLiteral( "Color" ) )
5387{
5388 if ( !project )
5389 return;
5390
5391 mColors = loadColorsFromProject( project );
5392}
5393
5394GetNamedProjectColorObject::GetNamedProjectColorObject( const QHash<QString, QColor> &colors )
5395 : QgsScopedExpressionFunction( QStringLiteral( "project_color_object" ), 1, QStringLiteral( "Color" ) )
5396 , mColors( colors )
5397{
5398}
5399
5400QVariant GetNamedProjectColorObject::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5401{
5402 const QString colorName = values.at( 0 ).toString().toLower();
5403 if ( mColors.contains( colorName ) )
5404 {
5405 return mColors.value( colorName );
5406 }
5407 else
5408 return QVariant();
5409}
5410
5411QgsScopedExpressionFunction *GetNamedProjectColorObject::clone() const
5412{
5413 return new GetNamedProjectColorObject( mColors );
5414}
5415
5416// ----------------
5417
5418GetSensorData::GetSensorData( const QMap<QString, QgsAbstractSensor::SensorData> &sensorData )
5419 : QgsScopedExpressionFunction( QStringLiteral( "sensor_data" ),
5420 QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "name" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "expiration" ), true, 0 ),
5421 QStringLiteral( "Sensors" ) )
5422 , mSensorData( sensorData )
5423{
5424}
5425
5426QVariant GetSensorData::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5427{
5428 const QString sensorName = values.at( 0 ).toString();
5429 const int expiration = values.at( 1 ).toInt();
5430 const qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
5431 if ( mSensorData.contains( sensorName ) )
5432 {
5433 if ( expiration <= 0 || ( timestamp - mSensorData[sensorName].lastTimestamp.toMSecsSinceEpoch() ) < expiration )
5434 {
5435 return mSensorData[sensorName].lastValue;
5436 }
5437 }
5438
5439 return QVariant();
5440}
5441
5442QgsScopedExpressionFunction *GetSensorData::clone() const
5443{
5444 return new GetSensorData( mSensorData );
5445}
@ 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:4039
QFlags< ProjectReadFlag > ProjectReadFlags
Project load flags.
Definition qgis.h:4017
DistanceUnit
Units of distance.
Definition qgis.h:4669
FilePathType
File path types.
Definition qgis.h:1523
@ Relative
Relative path.
@ Absolute
Absolute path.
TransactionMode
Transaction mode.
Definition qgis.h:3678
@ 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:4746
@ 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:3969
@ 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:3801
@ 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:3808
@ 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:6412
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:6127
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6494
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:6108
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:6166
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:6188
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6493
CONSTLATIN1STRING geoEpsgCrsAuthId()
Geographic coord sys from EPSG authority.
Definition qgis.h:6406
#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.