QGIS API Documentation 4.1.0-Master (467af3bbe65)
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 <algorithm>
21
22#include "qgsaction.h"
23#include "qgsactionmanager.h"
24#include "qgsannotationlayer.h"
26#include "qgsapplication.h"
28#include "qgsauxiliarystorage.h"
29#include "qgsbookmarkmanager.h"
30#include "qgscolorutils.h"
32#include "qgsdatasourceuri.h"
35#include "qgsgrouplayer.h"
37#include "qgslayerdefinition.h"
38#include "qgslayertree.h"
40#include "qgslayertreeutils.h"
41#include "qgslayoutmanager.h"
42#include "qgslogger.h"
43#include "qgsmaplayerfactory.h"
44#include "qgsmaplayerstore.h"
46#include "qgsmapviewsmanager.h"
47#include "qgsmeshlayer.h"
48#include "qgsmessagelog.h"
49#include "qgsobjectvisitor.h"
50#include "qgspathresolver.h"
51#include "qgspluginlayer.h"
53#include "qgspointcloudlayer.h"
58#include "qgsprojectstorage.h"
62#include "qgsprojectutils.h"
63#include "qgsprojectversion.h"
65#include "qgsproviderregistry.h"
66#include "qgspythonrunner.h"
67#include "qgsrasterlayer.h"
68#include "qgsreadwritecontext.h"
69#include "qgsrelationmanager.h"
71#include "qgsruntimeprofiler.h"
73#include "qgssensormanager.h"
75#include "qgssnappingconfig.h"
77#include "qgsthreadingutils.h"
78#include "qgstiledscenelayer.h"
79#include "qgstransaction.h"
80#include "qgstransactiongroup.h"
81#include "qgsunittypes.h"
84#include "qgsvectortilelayer.h"
85#include "qgsziputils.h"
86
87#include <QApplication>
88#include <QDir>
89#include <QDomNode>
90#include <QFileInfo>
91#include <QObject>
92#include <QRegularExpression>
93#include <QStandardPaths>
94#include <QString>
95#include <QTemporaryFile>
96#include <QTextStream>
97#include <QThreadPool>
98#include <QUrl>
99#include <QUuid>
100
101#include "moc_qgsproject.cpp"
102
103using namespace Qt::StringLiterals;
104
105#ifdef _MSC_VER
106#include <sys/utime.h>
107#else
108#include <utime.h>
109#endif
110
111// canonical project instance
112QgsProject *QgsProject::sProject = nullptr;
113
122QStringList makeKeyTokens_( const QString &scope, const QString &key )
123{
124 QStringList keyTokens = QStringList( scope );
125 keyTokens += key.split( '/', Qt::SkipEmptyParts );
126
127 // be sure to include the canonical root node
128 keyTokens.push_front( u"properties"_s );
129
130 return keyTokens;
131}
132
133
143QgsProjectProperty *findKey_( const QString &scope, const QString &key, QgsProjectPropertyKey &rootProperty )
144{
145 QgsProjectPropertyKey *currentProperty = &rootProperty;
146 QgsProjectProperty *nextProperty; // link to next property down hierarchy
147
148 QStringList keySequence = makeKeyTokens_( scope, key );
149
150 while ( !keySequence.isEmpty() )
151 {
152 // if the current head of the sequence list matches the property name,
153 // then traverse down the property hierarchy
154 if ( keySequence.first() == currentProperty->name() )
155 {
156 // remove front key since we're traversing down a level
157 keySequence.pop_front();
158
159 if ( 1 == keySequence.count() )
160 {
161 // if we have only one key name left, then return the key found
162 return currentProperty->find( keySequence.front() );
163 }
164 else if ( keySequence.isEmpty() )
165 {
166 // if we're out of keys then the current property is the one we
167 // want; i.e., we're in the rate case of being at the top-most
168 // property node
169 return currentProperty;
170 }
171 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
172 {
173 if ( nextProperty->isKey() )
174 {
175 currentProperty = static_cast<QgsProjectPropertyKey *>( nextProperty );
176 }
177 else if ( nextProperty->isValue() && 1 == keySequence.count() )
178 {
179 // it may be that this may be one of several property value
180 // nodes keyed by QDict string; if this is the last remaining
181 // key token and the next property is a value node, then
182 // that's the situation, so return the currentProperty
183 return currentProperty;
184 }
185 else
186 {
187 // QgsProjectPropertyValue not Key, so return null
188 return nullptr;
189 }
190 }
191 else
192 {
193 // if the next key down isn't found
194 // then the overall key sequence doesn't exist
195 return nullptr;
196 }
197 }
198 else
199 {
200 return nullptr;
201 }
202 }
203
204 return nullptr;
205}
206
207
217QgsProjectProperty *addKey_( const QString &scope, const QString &key, QgsProjectPropertyKey *rootProperty, const QVariant &value, bool &propertiesModified )
218{
219 QStringList keySequence = makeKeyTokens_( scope, key );
220
221 // cursor through property key/value hierarchy
222 QgsProjectPropertyKey *currentProperty = rootProperty;
223 QgsProjectProperty *nextProperty; // link to next property down hierarchy
224 QgsProjectPropertyKey *newPropertyKey = nullptr;
225
226 propertiesModified = false;
227 while ( !keySequence.isEmpty() )
228 {
229 // if the current head of the sequence list matches the property name,
230 // then traverse down the property hierarchy
231 if ( keySequence.first() == currentProperty->name() )
232 {
233 // remove front key since we're traversing down a level
234 keySequence.pop_front();
235
236 // if key sequence has one last element, then we use that as the
237 // name to store the value
238 if ( 1 == keySequence.count() )
239 {
240 QgsProjectProperty *property = currentProperty->find( keySequence.front() );
241 if ( !property || property->value() != value )
242 {
243 currentProperty->setValue( keySequence.front(), value );
244 propertiesModified = true;
245 }
246
247 return currentProperty;
248 }
249 // we're at the top element if popping the keySequence element
250 // will leave it empty; in that case, just add the key
251 else if ( keySequence.isEmpty() )
252 {
253 if ( currentProperty->value() != value )
254 {
255 currentProperty->setValue( value );
256 propertiesModified = true;
257 }
258
259 return currentProperty;
260 }
261 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
262 {
263 currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
264
265 if ( currentProperty )
266 {
267 continue;
268 }
269 else // QgsProjectPropertyValue not Key, so return null
270 {
271 return nullptr;
272 }
273 }
274 else // the next subkey doesn't exist, so add it
275 {
276 if ( ( newPropertyKey = currentProperty->addKey( keySequence.first() ) ) )
277 {
278 currentProperty = newPropertyKey;
279 }
280 continue;
281 }
282 }
283 else
284 {
285 return nullptr;
286 }
287 }
288
289 return nullptr;
290}
291
299void removeKey_( const QString &scope, const QString &key, QgsProjectPropertyKey &rootProperty )
300{
301 QgsProjectPropertyKey *currentProperty = &rootProperty;
302
303 QgsProjectProperty *nextProperty = nullptr; // link to next property down hierarchy
304 QgsProjectPropertyKey *previousQgsPropertyKey = nullptr; // link to previous property up hierarchy
305
306 QStringList keySequence = makeKeyTokens_( scope, key );
307
308 while ( !keySequence.isEmpty() )
309 {
310 // if the current head of the sequence list matches the property name,
311 // then traverse down the property hierarchy
312 if ( keySequence.first() == currentProperty->name() )
313 {
314 // remove front key since we're traversing down a level
315 keySequence.pop_front();
316
317 // if we have only one key name left, then try to remove the key
318 // with that name
319 if ( 1 == keySequence.count() )
320 {
321 currentProperty->removeKey( keySequence.front() );
322 }
323 // if we're out of keys then the current property is the one we
324 // want to remove, but we can't delete it directly; we need to
325 // delete it from the parent property key container
326 else if ( keySequence.isEmpty() )
327 {
328 previousQgsPropertyKey->removeKey( currentProperty->name() );
329 }
330 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
331 {
332 previousQgsPropertyKey = currentProperty;
333 currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
334
335 if ( currentProperty )
336 {
337 continue;
338 }
339 else // QgsProjectPropertyValue not Key, so return null
340 {
341 return;
342 }
343 }
344 else // if the next key down isn't found
345 {
346 // then the overall key sequence doesn't exist
347 return;
348 }
349 }
350 else
351 {
352 return;
353 }
354 }
355}
356
358 : QObject( parent )
359 , mCapabilities( capabilities )
360 , mLayerStore( new QgsMapLayerStore( this ) )
361 , mBadLayerHandler( std::make_unique<QgsProjectBadLayerHandler>() )
362 , mSnappingConfig( this )
363 , mRelationManager( std::make_unique<QgsRelationManager>( this ) )
364 , mAnnotationManager( new QgsAnnotationManager( this ) )
365 , mLayoutManager( new QgsLayoutManager( this ) )
366 , mElevationProfileManager( new QgsElevationProfileManager( this ) )
367 , mSelectiveMaskingSourceSetManager( new QgsSelectiveMaskingSourceSetManager( this ) )
368 , m3DViewsManager( new QgsMapViewsManager( this ) )
369 , mBookmarkManager( QgsBookmarkManager::createProjectBasedManager( this ) )
370 , mSensorManager( new QgsSensorManager( this ) )
371 , mViewSettings( new QgsProjectViewSettings( this ) )
372 , mStyleSettings( new QgsProjectStyleSettings( this ) )
373 , mTimeSettings( new QgsProjectTimeSettings( this ) )
374 , mElevationProperties( new QgsProjectElevationProperties( this ) )
375 , mDisplaySettings( new QgsProjectDisplaySettings( this ) )
376 , mGpsSettings( new QgsProjectGpsSettings( this ) )
377 , mRootGroup( std::make_unique<QgsLayerTree>() )
378 , mLabelingEngineSettings( new QgsLabelingEngineSettings )
379 , mArchive( new QgsArchive() )
380 , mAuxiliaryStorage( new QgsAuxiliaryStorage() )
381{
382 mProperties.setName( u"properties"_s );
383
384 mMainAnnotationLayer = new QgsAnnotationLayer( QObject::tr( "Annotations" ), QgsAnnotationLayer::LayerOptions( mTransformContext ) );
385 mMainAnnotationLayer->setParent( this );
386
387 clear();
388
389 // bind the layer tree to the map layer registry.
390 // whenever layers are added to or removed from the registry,
391 // layer tree will be updated
392 mLayerTreeRegistryBridge = std::make_unique<QgsLayerTreeRegistryBridge>( mRootGroup.get(), this, this );
393 connect( this, &QgsProject::layersAdded, this, &QgsProject::onMapLayersAdded );
394 connect( this, &QgsProject::layersRemoved, this, [this] { cleanTransactionGroups(); } );
395 connect( this, qOverload< const QList<QgsMapLayer *> & >( &QgsProject::layersWillBeRemoved ), this, &QgsProject::onMapLayersRemoved );
396
397 // proxy map layer store signals to this
398 connect( mLayerStore.get(), qOverload<const QStringList &>( &QgsMapLayerStore::layersWillBeRemoved ), this, [this]( const QStringList &layers ) {
399 mProjectScope.reset();
400 emit layersWillBeRemoved( layers );
401 } );
402 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersWillBeRemoved ), this, [this]( const QList<QgsMapLayer *> &layers ) {
403 mProjectScope.reset();
404 emit layersWillBeRemoved( layers );
405 } );
406 connect( mLayerStore.get(), qOverload< const QString & >( &QgsMapLayerStore::layerWillBeRemoved ), this, [this]( const QString &layer ) {
407 mProjectScope.reset();
408 emit layerWillBeRemoved( layer );
409 } );
410 connect( mLayerStore.get(), qOverload< QgsMapLayer * >( &QgsMapLayerStore::layerWillBeRemoved ), this, [this]( QgsMapLayer *layer ) {
411 mProjectScope.reset();
412 emit layerWillBeRemoved( layer );
413 } );
414 connect( mLayerStore.get(), qOverload<const QStringList & >( &QgsMapLayerStore::layersRemoved ), this, [this]( const QStringList &layers ) {
415 mProjectScope.reset();
416 emit layersRemoved( layers );
417 } );
418 connect( mLayerStore.get(), &QgsMapLayerStore::layerRemoved, this, [this]( const QString &layer ) {
419 mProjectScope.reset();
420 emit layerRemoved( layer );
421 } );
422 connect( mLayerStore.get(), &QgsMapLayerStore::allLayersRemoved, this, [this]() {
423 mProjectScope.reset();
424 emit removeAll();
425 } );
426 connect( mLayerStore.get(), &QgsMapLayerStore::layersAdded, this, [this]( const QList< QgsMapLayer * > &layers ) {
427 mProjectScope.reset();
428 emit layersAdded( layers );
429 } );
430 connect( mLayerStore.get(), &QgsMapLayerStore::layerWasAdded, this, [this]( QgsMapLayer *layer ) {
431 mProjectScope.reset();
432 emit layerWasAdded( layer );
433 } );
434
436 {
438 }
439
440 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersWillBeRemoved ), this, [this]( const QList<QgsMapLayer *> &layers ) {
441 for ( const auto &layer : layers )
442 {
443 disconnect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager.get(), &QgsRelationManager::updateRelationsStatus );
444 }
445 } );
446 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersAdded ), this, [this]( const QList<QgsMapLayer *> &layers ) {
447 for ( const auto &layer : layers )
448 {
449 connect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager.get(), &QgsRelationManager::updateRelationsStatus );
450 }
451 } );
452
456
457 mStyleSettings->combinedStyleModel()->addDefaultStyle();
458}
459
460
462{
463 mIsBeingDeleted = true;
464
465 clear();
466 releaseHandlesToProjectArchive();
467
468 if ( this == sProject )
469 {
470 sProject = nullptr;
471 }
472}
473
475{
476 sProject = project;
477}
478
479
480QgsProject *QgsProject::instance() // skip-keyword-check
481{
482 if ( !sProject )
483 {
484 sProject = new QgsProject;
485
487 }
488 return sProject;
489}
490
491void QgsProject::setTitle( const QString &title )
492{
494
495 if ( title == mMetadata.title() )
496 return;
497
498 mMetadata.setTitle( title );
499 mProjectScope.reset();
500 emit metadataChanged();
501 emit titleChanged();
502
503 setDirty( true );
504}
505
506QString QgsProject::title() const
507{
508 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
510
511 return mMetadata.title();
512}
513
515{
517
518 const bool oldEvaluateDefaultValues = mFlags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide;
519 const bool newEvaluateDefaultValues = flags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide;
520 if ( oldEvaluateDefaultValues != newEvaluateDefaultValues )
521 {
522 const QMap<QString, QgsMapLayer *> layers = mapLayers();
523 for ( auto layerIt = layers.constBegin(); layerIt != layers.constEnd(); ++layerIt )
524 {
525 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() ) )
526 if ( vl->dataProvider() )
527 vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, newEvaluateDefaultValues );
528 }
529 }
530
531 const bool oldTrustLayerMetadata = mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics;
532 const bool newTrustLayerMetadata = flags & Qgis::ProjectFlag::TrustStoredLayerStatistics;
533 if ( oldTrustLayerMetadata != newTrustLayerMetadata )
534 {
535 const QMap<QString, QgsMapLayer *> layers = mapLayers();
536 for ( auto layerIt = layers.constBegin(); layerIt != layers.constEnd(); ++layerIt )
537 {
538 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() ) )
539 {
540 vl->setReadExtentFromXml( newTrustLayerMetadata );
541 }
542 }
543 }
544
545 if ( mFlags != flags )
546 {
547 mFlags = flags;
548 setDirty( true );
549 }
550}
551
552void QgsProject::setFlag( Qgis::ProjectFlag flag, bool enabled )
553{
555
556 Qgis::ProjectFlags newFlags = mFlags;
557 if ( enabled )
558 newFlags |= flag;
559 else
560 newFlags &= ~( static_cast< int >( flag ) );
561 setFlags( newFlags );
562}
563
564QString QgsProject::saveUser() const
565{
567
568 return mSaveUser;
569}
570
572{
574
575 return mSaveUserFull;
576}
577
579{
581
582 return mSaveDateTime;
583}
584
591
593{
595
596 return mDirty;
597}
598
599void QgsProject::setDirty( const bool dirty )
600{
602
603 if ( dirty && mDirtyBlockCount > 0 )
604 return;
605
606 if ( dirty )
607 emit dirtySet();
608
609 if ( mDirty == dirty )
610 return;
611
612 mDirty = dirty;
613 emit isDirtyChanged( mDirty );
614}
615
616void QgsProject::setPresetHomePath( const QString &path )
617{
619
620 if ( path == mHomePath )
621 return;
622
623 mHomePath = path;
624 mCachedHomePath.clear();
625 mProjectScope.reset();
626
627 emit homePathChanged();
628
629 setDirty( true );
630}
631
632void QgsProject::registerTranslatableContainers( QgsTranslationContext *translationContext, QgsAttributeEditorContainer *parent, const QString &layerId )
633{
635
636 const QList<QgsAttributeEditorElement *> elements = parent->children();
637
638 for ( QgsAttributeEditorElement *element : elements )
639 {
640 if ( element->type() == Qgis::AttributeEditorType::Container )
641 {
642 QgsAttributeEditorContainer *container = qgis::down_cast<QgsAttributeEditorContainer *>( element );
643
644 translationContext->registerTranslation( u"project:layers:%1:formcontainers"_s.arg( layerId ), container->name() );
645
646 if ( !container->children().empty() )
647 registerTranslatableContainers( translationContext, container, layerId );
648 }
649 }
650}
651
653{
655
656 //register layers
657 const QList<QgsLayerTreeLayer *> layers = mRootGroup->findLayers();
658
659 for ( const QgsLayerTreeLayer *layer : layers )
660 {
661 translationContext->registerTranslation( u"project:layers:%1"_s.arg( layer->layerId() ), layer->name() );
662
663 if ( QgsMapLayer *mapLayer = layer->layer() )
664 {
665 switch ( mapLayer->type() )
666 {
668 {
669 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer );
670
671 //register general (like alias) and widget specific field settings (like value map descriptions)
672 const QgsFields fields = vlayer->fields();
673 for ( const QgsField &field : fields )
674 {
675 //general
676 //alias
677 QString fieldName;
678 if ( field.alias().isEmpty() )
679 fieldName = field.name();
680 else
681 fieldName = field.alias();
682
683 translationContext->registerTranslation( u"project:layers:%1:fieldaliases"_s.arg( vlayer->id() ), fieldName );
684
685 //constraint description
686 if ( !field.constraints().constraintDescription().isEmpty() )
687 translationContext->registerTranslation( u"project:layers:%1:constraintdescriptions"_s.arg( vlayer->id() ), field.constraints().constraintDescription() );
688
689 //widget specific
690 //value relation
691 if ( field.editorWidgetSetup().type() == "ValueRelation"_L1 )
692 {
693 translationContext->registerTranslation( u"project:layers:%1:fields:%2:valuerelationvalue"_s.arg( vlayer->id(), field.name() ), field.editorWidgetSetup().config().value( u"Value"_s ).toString() );
694 translationContext
695 ->registerTranslation( u"project:layers:%1:fields:%2:valuerelationdescription"_s.arg( vlayer->id(), field.name() ), field.editorWidgetSetup().config().value( u"Description"_s ).toString() );
696 }
697
698 //value map
699 if ( field.editorWidgetSetup().type() == "ValueMap"_L1 )
700 {
701 if ( field.editorWidgetSetup().config().value( u"map"_s ).canConvert<QList<QVariant>>() )
702 {
703 const QList<QVariant> valueList = field.editorWidgetSetup().config().value( u"map"_s ).toList();
704
705 for ( int i = 0, row = 0; i < valueList.count(); i++, row++ )
706 {
707 translationContext->registerTranslation( u"project:layers:%1:fields:%2:valuemapdescriptions"_s.arg( vlayer->id(), field.name() ), valueList[i].toMap().constBegin().key() );
708 }
709 }
710 }
711 }
712
713 //register formcontainers
714 registerTranslatableContainers( translationContext, vlayer->editFormConfig().invisibleRootContainer(), vlayer->id() );
715
716 //actions
717 for ( const QgsAction &action : vlayer->actions()->actions() )
718 {
719 translationContext->registerTranslation( u"project:layers:%1:actiondescriptions"_s.arg( vlayer->id() ), action.name() );
720 translationContext->registerTranslation( u"project:layers:%1:actionshorttitles"_s.arg( vlayer->id() ), action.shortTitle() );
721 }
722
723 //legend
724 if ( vlayer->renderer() )
725 {
726 for ( const QgsLegendSymbolItem &item : vlayer->renderer()->legendSymbolItems() )
727 {
728 translationContext->registerTranslation( u"project:layers:%1:legendsymbollabels"_s.arg( vlayer->id() ), item.label() );
729 }
730 }
731 break;
732 }
733
742 break;
743 }
744
745 //register metadata
746 mapLayer->metadata().registerTranslations( translationContext );
747 }
748 }
749
750 //register layergroups and subgroups
751 const QList<QgsLayerTreeGroup *> groupLayers = mRootGroup->findGroups( true );
752 for ( const QgsLayerTreeGroup *groupLayer : groupLayers )
753 {
754 translationContext->registerTranslation( u"project:layergroups"_s, groupLayer->name() );
755 }
756
757 //register relations
758 const QList<QgsRelation> &relations = mRelationManager->relations().values();
759 for ( const QgsRelation &relation : relations )
760 {
761 translationContext->registerTranslation( u"project:relations"_s, relation.name() );
762 }
763
764 //register metadata
765 mMetadata.registerTranslations( translationContext );
766}
767
769{
771
772 mDataDefinedServerProperties = properties;
773}
774
776{
778
779 return mDataDefinedServerProperties;
780}
781
783{
785
786 switch ( mTransactionMode )
787 {
790 {
791 if ( !vectorLayer )
792 return false;
793 return vectorLayer->startEditing();
794 }
795
797 return mEditBufferGroup.startEditing();
798 }
799
800 return false;
801}
802
803bool QgsProject::commitChanges( QStringList &commitErrors, bool stopEditing, QgsVectorLayer *vectorLayer )
804{
806
807 switch ( mTransactionMode )
808 {
811 {
812 if ( !vectorLayer )
813 {
814 commitErrors.append( tr( "Trying to commit changes without a layer specified. This only works if the transaction mode is buffered" ) );
815 return false;
816 }
817 bool success = vectorLayer->commitChanges( stopEditing );
818 commitErrors = vectorLayer->commitErrors();
819 return success;
820 }
821
823 return mEditBufferGroup.commitChanges( commitErrors, stopEditing );
824 }
825
826 return false;
827}
828
829bool QgsProject::rollBack( QStringList &rollbackErrors, bool stopEditing, QgsVectorLayer *vectorLayer )
830{
832
833 switch ( mTransactionMode )
834 {
837 {
838 if ( !vectorLayer )
839 {
840 rollbackErrors.append( tr( "Trying to roll back changes without a layer specified. This only works if the transaction mode is buffered" ) );
841 return false;
842 }
843 bool success = vectorLayer->rollBack( stopEditing );
844 rollbackErrors = vectorLayer->commitErrors();
845 return success;
846 }
847
849 return mEditBufferGroup.rollBack( rollbackErrors, stopEditing );
850 }
851
852 return false;
853}
854
855void QgsProject::setFileName( const QString &name )
856{
858
859 if ( name == mFile.fileName() )
860 return;
861
862 const QString oldHomePath = homePath();
863
864 mFile.setFileName( name );
865 mCachedHomePath.clear();
866 mProjectScope.reset();
867
868 emit fileNameChanged();
869
870 const QString newHomePath = homePath();
871 if ( newHomePath != oldHomePath )
872 emit homePathChanged();
873
874 setDirty( true );
875}
876
877QString QgsProject::fileName() const
878{
879 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
881
882 return mFile.fileName();
883}
884
885void QgsProject::setOriginalPath( const QString &path )
886{
888
889 mOriginalPath = path;
890}
891
893{
895
896 return mOriginalPath;
897}
898
899QFileInfo QgsProject::fileInfo() const
900{
902
903 return QFileInfo( mFile );
904}
905
907{
908 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
910
912}
913
915{
917
918 if ( QgsProjectStorage *storage = projectStorage() )
919 {
921 storage->readProjectStorageMetadata( mFile.fileName(), metadata );
922 return metadata.lastModified;
923 }
924 else
925 {
926 return QFileInfo( mFile.fileName() ).lastModified();
927 }
928}
929
931{
933
934 if ( projectStorage() )
935 return QString();
936
937 if ( mFile.fileName().isEmpty() )
938 return QString(); // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
939
940 return QFileInfo( mFile.fileName() ).absolutePath();
941}
942
944{
945 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
947
948 if ( projectStorage() )
949 return QString();
950
951 if ( mFile.fileName().isEmpty() )
952 return QString(); // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
953
954 return QFileInfo( mFile.fileName() ).absoluteFilePath();
955}
956
957QString QgsProject::baseName() const
958{
959 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
961
962 if ( QgsProjectStorage *storage = projectStorage() )
963 {
965 storage->readProjectStorageMetadata( mFile.fileName(), metadata );
966 return metadata.name;
967 }
968 else
969 {
970 return QFileInfo( mFile.fileName() ).completeBaseName();
971 }
972}
973
975{
977
978 const bool absolutePaths = readBoolEntry( u"Paths"_s, u"/Absolute"_s, false );
980}
981
983{
985
986 switch ( type )
987 {
989 writeEntry( u"Paths"_s, u"/Absolute"_s, true );
990 break;
992 writeEntry( u"Paths"_s, u"/Absolute"_s, false );
993 break;
994 }
995}
996
998{
999 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
1001
1002 return mCrs;
1003}
1004
1006{
1008
1009 return mCrs3D.isValid() ? mCrs3D : mCrs;
1010}
1011
1012void QgsProject::setCrs( const QgsCoordinateReferenceSystem &crs, bool adjustEllipsoid )
1013{
1015
1016 if ( crs != mCrs )
1017 {
1018 // if new crs is set that is not on the same celestial body as previous one and ellipsoid is to be adjusted,
1019 // there is a need to first set ellipsoid to NONE without raising signal
1020 // this prevents various classes that listen to crsChanged() to try to convert the the new crs to the older ellipsoid
1021 // that is only updated after the crs signals are raised (end of the this function)
1022 // setting the ellipsoid to none prevents that as conversions do not make sense when change not only crs but also celestial body
1023 if ( adjustEllipsoid && !mCrs.isSameCelestialBody( crs ) )
1024 {
1025 mBlockEllipsoidChangedSignal = true;
1027 mBlockEllipsoidChangedSignal = false;
1028 }
1029
1030 const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
1031 const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
1032 mCrs = crs;
1033 writeEntry( u"SpatialRefSys"_s, u"/ProjectionsEnabled"_s, crs.isValid() ? 1 : 0 );
1034 mProjectScope.reset();
1035
1036 // if annotation layer doesn't have a crs (i.e. in a newly created project), it should
1037 // initially inherit the project CRS
1038 if ( !mMainAnnotationLayer->crs().isValid() || mMainAnnotationLayer->isEmpty() )
1039 mMainAnnotationLayer->setCrs( crs );
1040
1041 rebuildCrs3D();
1042
1043 setDirty( true );
1044 emit crsChanged();
1045 // Did vertical crs also change as a result of this? If so, emit signal
1046 if ( oldVerticalCrs != verticalCrs() )
1047 emit verticalCrsChanged();
1048 if ( oldCrs3D != mCrs3D )
1049 emit crs3DChanged();
1050 }
1051
1052 if ( adjustEllipsoid )
1053 setEllipsoid( crs.ellipsoidAcronym() );
1054}
1055
1057{
1058 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
1060
1061 if ( !crs().isValid() )
1062 return Qgis::geoNone();
1063
1064 return readEntry( u"Measure"_s, u"/Ellipsoid"_s, Qgis::geoNone() );
1065}
1066
1068{
1070
1071 if ( ellipsoid == readEntry( u"Measure"_s, u"/Ellipsoid"_s ) )
1072 return;
1073
1074 mProjectScope.reset();
1075 writeEntry( u"Measure"_s, u"/Ellipsoid"_s, ellipsoid );
1076
1077 if ( !mBlockEllipsoidChangedSignal )
1079}
1080
1082{
1083 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
1085
1086 switch ( mCrs.type() )
1087 {
1088 case Qgis::CrsType::Vertical: // would hope this never happens!
1089 QgsDebugError( u"Project has a vertical CRS set as the horizontal CRS!"_s );
1090 return mCrs;
1091
1093 return mCrs.verticalCrs();
1094
1106 break;
1107 }
1108 return mVerticalCrs;
1109}
1110
1112{
1114 bool res = true;
1115 if ( crs.isValid() )
1116 {
1117 // validate that passed crs is a vertical crs
1118 switch ( crs.type() )
1119 {
1121 break;
1122
1135 if ( errorMessage )
1136 *errorMessage = QObject::tr( "Specified CRS is a %1 CRS, not a Vertical CRS" ).arg( qgsEnumValueToKey( crs.type() ) );
1137 return false;
1138 }
1139 }
1140
1141 if ( crs != mVerticalCrs )
1142 {
1143 const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
1144 const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
1145
1146 switch ( mCrs.type() )
1147 {
1149 if ( crs != oldVerticalCrs )
1150 {
1151 if ( errorMessage )
1152 *errorMessage = QObject::tr( "Project CRS is a Compound CRS, specified Vertical CRS will be ignored" );
1153 return false;
1154 }
1155 break;
1156
1158 if ( crs != oldVerticalCrs )
1159 {
1160 if ( errorMessage )
1161 *errorMessage = QObject::tr( "Project CRS is a Geographic 3D CRS, specified Vertical CRS will be ignored" );
1162 return false;
1163 }
1164 break;
1165
1167 if ( crs != oldVerticalCrs )
1168 {
1169 if ( errorMessage )
1170 *errorMessage = QObject::tr( "Project CRS is a Geocentric CRS, specified Vertical CRS will be ignored" );
1171 return false;
1172 }
1173 break;
1174
1176 if ( mCrs.hasVerticalAxis() && crs != oldVerticalCrs )
1177 {
1178 if ( errorMessage )
1179 *errorMessage = QObject::tr( "Project CRS is a Projected 3D CRS, specified Vertical CRS will be ignored" );
1180 return false;
1181 }
1182 break;
1183
1193 break;
1194 }
1195
1196 mVerticalCrs = crs;
1197 res = rebuildCrs3D( errorMessage );
1198 mProjectScope.reset();
1199
1200 setDirty( true );
1201 // only emit signal if vertical crs was actually changed, so eg if mCrs is compound
1202 // then we haven't actually changed the vertical crs by this call!
1203 if ( verticalCrs() != oldVerticalCrs )
1204 emit verticalCrsChanged();
1205 if ( mCrs3D != oldCrs3D )
1206 emit crs3DChanged();
1207 }
1208 return res;
1209}
1210
1212{
1213 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
1215
1216 return mTransformContext;
1217}
1218
1220{
1222
1223 if ( context == mTransformContext )
1224 return;
1225
1226 mTransformContext = context;
1227 mProjectScope.reset();
1228
1229 mMainAnnotationLayer->setTransformContext( context );
1230 for ( auto &layer : mLayerStore.get()->mapLayers() )
1231 {
1232 layer->setTransformContext( context );
1233 }
1235}
1236
1238{
1240
1241 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
1242
1243 emit aboutToBeCleared();
1244
1245 if ( !mIsBeingDeleted )
1246 {
1247 // Unregister expression functions stored in the project.
1248 // If we clean on destruction we may end-up with a non-valid
1249 // mPythonUtils, so be safe and only clean when not destroying.
1250 // This should be called before calling mProperties.clearKeys().
1252 }
1253
1254 mProjectScope.reset();
1255 mFile.setFileName( QString() );
1256 mProperties.clearKeys();
1257 mSaveUser.clear();
1258 mSaveUserFull.clear();
1259 mSaveDateTime = QDateTime();
1260 mSaveVersion = QgsProjectVersion();
1261 mHomePath.clear();
1262 mCachedHomePath.clear();
1263 mTransactionMode = Qgis::TransactionMode::Disabled;
1264 mFlags = Qgis::ProjectFlags();
1265 mDirty = false;
1266 mCustomVariables.clear();
1268 mVerticalCrs = QgsCoordinateReferenceSystem();
1270 mMetadata = QgsProjectMetadata();
1271 mElevationShadingRenderer = QgsElevationShadingRenderer();
1272 if ( !mSettings.value( u"projects/anonymize_new_projects"_s, false, QgsSettings::Core ).toBool() )
1273 {
1274 mMetadata.setCreationDateTime( QDateTime::currentDateTime() );
1275 mMetadata.setAuthor( QgsApplication::userFullName() );
1276 }
1277 emit metadataChanged();
1278
1280 context.readSettings();
1281 setTransformContext( context );
1282
1283 //fallback to QGIS default measurement unit
1284 bool ok = false;
1286 setDistanceUnits( ok ? distanceUnit : Qgis::DistanceUnit::Meters );
1287 ok = false;
1288 const Qgis::AreaUnit areaUnits = QgsUnitTypes::decodeAreaUnit( mSettings.value( u"/qgis/measure/areaunits"_s ).toString(), &ok );
1290
1292
1293 mEmbeddedLayers.clear();
1294 mRelationManager->clear();
1295 mAnnotationManager->clear();
1296 mLayoutManager->clear();
1297 mElevationProfileManager->clear();
1298 mSelectiveMaskingSourceSetManager->clear();
1299 m3DViewsManager->clear();
1300 mBookmarkManager->clear();
1301 mSensorManager->clear();
1302 mViewSettings->reset();
1303 mTimeSettings->reset();
1304 mElevationProperties->reset();
1305 mDisplaySettings->reset();
1306 mGpsSettings->reset();
1307 mSnappingConfig.reset();
1308 mAvoidIntersectionsMode = Qgis::AvoidIntersectionsMode::AllowIntersections;
1311
1312 mMapThemeCollection = std::make_unique< QgsMapThemeCollection >( this );
1314
1315 mLabelingEngineSettings->clear();
1316
1317 // must happen BEFORE archive reset, because we need to release the hold on any files which
1318 // exists within the archive. Otherwise the archive can't be removed.
1319 releaseHandlesToProjectArchive();
1320
1321 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >();
1322 mArchive = std::make_unique< QgsArchive >();
1323
1324 // must happen AFTER archive reset, as it will populate a new style database within the new archive
1325 mStyleSettings->reset();
1326
1328
1329 if ( !mIsBeingDeleted )
1330 {
1331 // possibly other signals should also not be thrown on destruction -- e.g. labelEngineSettingsChanged, etc.
1332 emit projectColorsChanged();
1333 }
1334
1335 // reset some default project properties
1336 // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
1337 writeEntry( u"PositionPrecision"_s, u"/Automatic"_s, true );
1338 writeEntry( u"PositionPrecision"_s, u"/DecimalPlaces"_s, 2 );
1339
1340 const bool defaultRelativePaths = mSettings.value( u"/qgis/defaultProjectPathsRelative"_s, true ).toBool();
1342
1344
1346
1347 mSnappingConfig.clearIndividualLayerSettings();
1348
1350 mRootGroup->clear();
1351 if ( mMainAnnotationLayer )
1352 mMainAnnotationLayer->reset();
1353
1354 snapSingleBlocker.release();
1355
1356 if ( !mBlockSnappingUpdates )
1357 emit snappingConfigChanged( mSnappingConfig );
1358
1359 setDirty( false );
1360 emit homePathChanged();
1361 emit fileNameChanged();
1362 if ( !mBlockChangeSignalsDuringClear )
1363 {
1364 emit verticalCrsChanged();
1365 emit crs3DChanged();
1366 }
1367 emit cleared();
1368}
1369
1370// basically a debugging tool to dump property list values
1371void dump_( const QgsProjectPropertyKey &topQgsPropertyKey )
1372{
1373 QgsDebugMsgLevel( u"current properties:"_s, 3 );
1374 topQgsPropertyKey.dump();
1375}
1376
1405void _getProperties( const QDomDocument &doc, QgsProjectPropertyKey &project_properties )
1406{
1407 const QDomElement propertiesElem = doc.documentElement().firstChildElement( u"properties"_s );
1408
1409 if ( propertiesElem.isNull() ) // no properties found, so we're done
1410 {
1411 return;
1412 }
1413
1414 const QDomNodeList scopes = propertiesElem.childNodes();
1415
1416 if ( propertiesElem.firstChild().isNull() )
1417 {
1418 QgsDebugError( u"empty ``properties'' XML tag ... bailing"_s );
1419 return;
1420 }
1421
1422 if ( !project_properties.readXml( propertiesElem ) )
1423 {
1424 QgsDebugError( u"Project_properties.readXml() failed"_s );
1425 }
1426}
1427
1434QgsPropertyCollection getDataDefinedServerProperties( const QDomDocument &doc, const QgsPropertiesDefinition &dataDefinedServerPropertyDefinitions )
1435{
1436 QgsPropertyCollection ddServerProperties;
1437 // Read data defined server properties
1438 const QDomElement ddElem = doc.documentElement().firstChildElement( u"dataDefinedServerProperties"_s );
1439 if ( !ddElem.isNull() )
1440 {
1441 if ( !ddServerProperties.readXml( ddElem, dataDefinedServerPropertyDefinitions ) )
1442 {
1443 QgsDebugError( u"dataDefinedServerProperties.readXml() failed"_s );
1444 }
1445 }
1446 return ddServerProperties;
1447}
1448
1453static void _getTitle( const QDomDocument &doc, QString &title )
1454{
1455 const QDomElement titleNode = doc.documentElement().firstChildElement( u"title"_s );
1456
1457 title.clear(); // by default the title will be empty
1458
1459 if ( titleNode.isNull() )
1460 {
1461 QgsDebugMsgLevel( u"unable to find title element"_s, 2 );
1462 return;
1463 }
1464
1465 if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text
1466 {
1467 QgsDebugMsgLevel( u"unable to find title element"_s, 2 );
1468 return;
1469 }
1470
1471 const QDomNode titleTextNode = titleNode.firstChild(); // should only have one child
1472
1473 if ( !titleTextNode.isText() )
1474 {
1475 QgsDebugMsgLevel( u"unable to find title element"_s, 2 );
1476 return;
1477 }
1478
1479 const QDomText titleText = titleTextNode.toText();
1480
1481 title = titleText.data();
1482}
1483
1484static void readProjectFileMetadata( const QDomDocument &doc, QString &lastUser, QString &lastUserFull, QDateTime &lastSaveDateTime )
1485{
1486 const QDomNodeList nl = doc.elementsByTagName( u"qgis"_s );
1487
1488 if ( !nl.count() )
1489 {
1490 QgsDebugError( u"unable to find qgis element"_s );
1491 return;
1492 }
1493
1494 const QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1495
1496 const QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1497 lastUser = qgisElement.attribute( u"saveUser"_s, QString() );
1498 lastUserFull = qgisElement.attribute( u"saveUserFull"_s, QString() );
1499 lastSaveDateTime = QDateTime::fromString( qgisElement.attribute( u"saveDateTime"_s, QString() ), Qt::ISODate );
1500}
1501
1502QgsProjectVersion getVersion( const QDomDocument &doc )
1503{
1504 const QDomNodeList nl = doc.elementsByTagName( u"qgis"_s );
1505
1506 if ( !nl.count() )
1507 {
1508 QgsDebugError( u" unable to find qgis element in project file"_s );
1509 return QgsProjectVersion( 0, 0, 0, QString() );
1510 }
1511
1512 const QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1513
1514 const QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1515 QgsProjectVersion projectVersion( qgisElement.attribute( u"version"_s ) );
1516 return projectVersion;
1517}
1518
1520{
1522
1523 return mSnappingConfig;
1524}
1525
1527{
1529
1530 if ( mSnappingConfig == snappingConfig )
1531 return;
1532
1533 mSnappingConfig = snappingConfig;
1534 setDirty( true );
1535 emit snappingConfigChanged( mSnappingConfig );
1536}
1537
1539{
1541
1542 if ( mAvoidIntersectionsMode == mode )
1543 return;
1544
1545 mAvoidIntersectionsMode = mode;
1547}
1548
1549static QgsMapLayer::ReadFlags projectFlagsToLayerReadFlags( Qgis::ProjectReadFlags projectReadFlags, Qgis::ProjectFlags projectFlags )
1550{
1552 // Propagate don't resolve layers
1553 if ( projectReadFlags & Qgis::ProjectReadFlag::DontResolveLayers )
1555 // Propagate trust layer metadata flag
1556 // Propagate read extent from XML based trust layer metadata flag
1557 if ( ( projectFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics ) || ( projectReadFlags & Qgis::ProjectReadFlag::TrustLayerMetadata ) )
1558 {
1561 }
1562 // Propagate open layers in read-only mode
1563 if ( ( projectReadFlags & Qgis::ProjectReadFlag::ForceReadOnlyLayers ) )
1564 layerFlags |= QgsMapLayer::FlagForceReadOnly;
1565
1566 return layerFlags;
1567}
1568
1578
1579void QgsProject::preloadProviders(
1580 const QVector<QDomNode> &parallelLayerNodes, const QgsReadWriteContext &context, QMap<QString, QgsDataProvider *> &loadedProviders, QgsMapLayer::ReadFlags layerReadFlags, int totalProviderCount
1581)
1582{
1583 int i = 0;
1584 QEventLoop loop;
1585
1586 QMap<QString, LayerToLoad> layersToLoad;
1587
1588 for ( const QDomNode &node : parallelLayerNodes )
1589 {
1590 LayerToLoad layerToLoad;
1591
1592 const QDomElement layerElement = node.toElement();
1593 layerToLoad.layerElement = layerElement;
1594 layerToLoad.layerId = layerElement.namedItem( u"id"_s ).toElement().text();
1595 layerToLoad.provider = layerElement.namedItem( u"provider"_s ).toElement().text();
1596 layerToLoad.dataSource = layerElement.namedItem( u"datasource"_s ).toElement().text();
1597
1598 layerToLoad.dataSource = QgsProviderRegistry::instance()->relativeToAbsoluteUri( layerToLoad.provider, layerToLoad.dataSource, context );
1599
1600 layerToLoad.options = QgsDataProvider::ProviderOptions( { context.transformContext() } );
1601 layerToLoad.flags = QgsMapLayer::providerReadFlags( node, layerReadFlags );
1602
1603 // Requesting credential from worker thread could lead to deadlocks because the main thread is waiting for worker thread to fininsh
1604 layerToLoad.flags.setFlag( Qgis::DataProviderReadFlag::SkipCredentialsRequest, true );
1605 layerToLoad.flags.setFlag( Qgis::DataProviderReadFlag::ParallelThreadLoading, true );
1606
1607 layersToLoad.insert( layerToLoad.layerId, layerToLoad );
1608 }
1609
1610 while ( !layersToLoad.isEmpty() )
1611 {
1612 const QList<LayerToLoad> layersToAttemptInParallel = layersToLoad.values();
1613 QString layerToAttemptInMainThread;
1614
1615 QHash<QString, QgsRunnableProviderCreator *> runnables;
1616 QThreadPool threadPool;
1617 threadPool.setMaxThreadCount( QgsSettingsRegistryCore::settingsLayerParallelLoadingMaxCount->value() );
1618
1619 for ( const LayerToLoad &lay : layersToAttemptInParallel )
1620 {
1621 QgsRunnableProviderCreator *run = new QgsRunnableProviderCreator( lay.layerId, lay.provider, lay.dataSource, lay.options, lay.flags );
1622 runnables.insert( lay.layerId, run );
1623
1624 QObject::connect( run, &QgsRunnableProviderCreator::providerCreated, run, [&]( bool isValid, const QString &layId ) {
1625 if ( isValid )
1626 {
1627 layersToLoad.remove( layId );
1628 i++;
1629 QgsRunnableProviderCreator *finishedRun = runnables.value( layId, nullptr );
1630 Q_ASSERT( finishedRun );
1631
1632 std::unique_ptr<QgsDataProvider> provider( finishedRun->dataProvider() );
1633 Q_ASSERT( provider && provider->isValid() );
1634
1635 provider->moveToThread( QThread::currentThread() );
1636 QgsDebugMsgLevel( u"Retrieved created provider for %1 (belongs to thread %2)"_s.arg( layId, QgsThreadingUtils::threadDescription( provider->thread() ) ), 2 );
1637
1638 loadedProviders.insert( layId, provider.release() );
1639 emit layerLoaded( i, totalProviderCount );
1640 }
1641 else
1642 {
1643 if ( layerToAttemptInMainThread.isEmpty() )
1644 layerToAttemptInMainThread = layId;
1645 threadPool.clear(); //we have to stop all loading provider to try this layer in main thread and maybe have credentials
1646 }
1647
1648 if ( i == parallelLayerNodes.count() || !isValid )
1649 loop.quit();
1650 } );
1651 threadPool.start( run );
1652 }
1653 loop.exec();
1654
1655 threadPool.waitForDone(); // to be sure all threads are finished
1656
1657 qDeleteAll( runnables );
1658
1659 // 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
1660 auto it = layersToLoad.find( layerToAttemptInMainThread );
1661 if ( it != layersToLoad.end() )
1662 {
1663 std::unique_ptr<QgsDataProvider> provider;
1664 QString layerId;
1665 {
1666 const LayerToLoad &lay = it.value();
1667 Qgis::DataProviderReadFlags providerFlags = lay.flags;
1668 providerFlags.setFlag( Qgis::DataProviderReadFlag::SkipCredentialsRequest, false );
1669 providerFlags.setFlag( Qgis::DataProviderReadFlag::ParallelThreadLoading, false );
1670 QgsScopedRuntimeProfile profile( "Create data providers/" + lay.layerId, u"projectload"_s );
1671 provider.reset( QgsProviderRegistry::instance()->createProvider( lay.provider, lay.dataSource, lay.options, providerFlags ) );
1672 i++;
1673 if ( provider && provider->isValid() )
1674 {
1675 emit layerLoaded( i, totalProviderCount );
1676 }
1677 layerId = lay.layerId;
1678 layersToLoad.erase( it );
1679 // can't access "lay" anymore -- it's now been freed
1680 }
1681 loadedProviders.insert( layerId, provider.release() );
1682 }
1683
1684 // if there still are some not loaded providers or some invalid in parallel thread we start again
1685 }
1686}
1687
1688void QgsProject::releaseHandlesToProjectArchive()
1689{
1690 mStyleSettings->removeProjectStyle();
1691}
1692
1693bool QgsProject::rebuildCrs3D( QString *error )
1694{
1695 bool res = true;
1696 if ( !mCrs.isValid() )
1697 {
1698 mCrs3D = QgsCoordinateReferenceSystem();
1699 }
1700 else if ( !mVerticalCrs.isValid() )
1701 {
1702 mCrs3D = mCrs;
1703 }
1704 else
1705 {
1706 switch ( mCrs.type() )
1707 {
1711 mCrs3D = mCrs;
1712 break;
1713
1715 {
1716 QString tempError;
1717 mCrs3D = mCrs.hasVerticalAxis() ? mCrs : QgsCoordinateReferenceSystem::createCompoundCrs( mCrs, mVerticalCrs, error ? *error : tempError );
1718 res = mCrs3D.isValid();
1719 break;
1720 }
1721
1723 // nonsense situation
1724 mCrs3D = QgsCoordinateReferenceSystem();
1725 res = false;
1726 break;
1727
1736 {
1737 QString tempError;
1738 mCrs3D = QgsCoordinateReferenceSystem::createCompoundCrs( mCrs, mVerticalCrs, error ? *error : tempError );
1739 res = mCrs3D.isValid();
1740 break;
1741 }
1742 }
1743 }
1744 return res;
1745}
1746
1747bool QgsProject::_getMapLayers( const QDomDocument &doc, QList<QDomNode> &brokenNodes, Qgis::ProjectReadFlags flags )
1748{
1750
1751 // Layer order is set by the restoring the legend settings from project file.
1752 // This is done on the 'readProject( ... )' signal
1753
1754 QDomElement layerElement = doc.documentElement().firstChildElement( u"projectlayers"_s ).firstChildElement( u"maplayer"_s );
1755
1756 // process the map layer nodes
1757
1758 if ( layerElement.isNull() ) // if we have no layers to process, bail
1759 {
1760 return true; // Decided to return "true" since it's
1761 // possible for there to be a project with no
1762 // layers; but also, more imporantly, this
1763 // would cause the tests/qgsproject to fail
1764 // since the test suite doesn't currently
1765 // support test layers
1766 }
1767
1768 bool returnStatus = true;
1769 int numLayers = 0;
1770
1771 while ( !layerElement.isNull() )
1772 {
1773 numLayers++;
1774 layerElement = layerElement.nextSiblingElement( u"maplayer"_s );
1775 }
1776
1777 // order layers based on their dependencies
1778 QgsScopedRuntimeProfile profile( tr( "Sorting layers" ), u"projectload"_s );
1779 const QgsLayerDefinition::DependencySorter depSorter( doc );
1780 if ( depSorter.hasCycle() )
1781 return false;
1782
1783 // Missing a dependency? We still load all the layers, otherwise the project is completely broken!
1784 if ( depSorter.hasMissingDependency() )
1785 returnStatus = false;
1786
1787 emit layerLoaded( 0, numLayers );
1788
1789 const QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
1790 const int totalLayerCount = sortedLayerNodes.count();
1791
1792 QVector<QDomNode> parallelLoading;
1793 QMap<QString, QgsDataProvider *> loadedProviders;
1794
1796 {
1797 profile.switchTask( tr( "Load providers in parallel" ) );
1798 for ( const QDomNode &node : sortedLayerNodes )
1799 {
1800 const QDomElement element = node.toElement();
1801 if ( element.attribute( u"embedded"_s ) != "1"_L1 )
1802 {
1803 const QString layerId = node.namedItem( u"id"_s ).toElement().text();
1804 if ( !depSorter.isLayerDependent( layerId ) )
1805 {
1806 const QDomNode mnl = element.namedItem( u"provider"_s );
1807 const QDomElement mne = mnl.toElement();
1808 const QString provider = mne.text();
1809 QgsProviderMetadata *meta = QgsProviderRegistry::instance()->providerMetadata( provider );
1810 if ( meta && meta->providerCapabilities().testFlag( QgsProviderMetadata::ParallelCreateProvider ) )
1811 {
1812 parallelLoading.append( node );
1813 continue;
1814 }
1815 }
1816 }
1817 }
1818
1819 QgsReadWriteContext context;
1820 context.setPathResolver( pathResolver() );
1821 if ( !parallelLoading.isEmpty() )
1822 preloadProviders( parallelLoading, context, loadedProviders, projectFlagsToLayerReadFlags( flags, mFlags ), sortedLayerNodes.count() );
1823 }
1824
1825 int i = loadedProviders.count();
1826 for ( const QDomNode &node : std::as_const( sortedLayerNodes ) )
1827 {
1828 const QDomElement element = node.toElement();
1829 const QString name = translate( u"project:layers:%1"_s.arg( node.namedItem( u"id"_s ).toElement().text() ), node.namedItem( u"layername"_s ).toElement().text() );
1830 if ( !name.isNull() )
1831 emit loadingLayer( tr( "Loading layer %1" ).arg( name ) );
1832
1833 profile.switchTask( name );
1834 if ( element.attribute( u"embedded"_s ) == "1"_L1 )
1835 {
1836 createEmbeddedLayer( element.attribute( u"id"_s ), readPath( element.attribute( u"project"_s ) ), brokenNodes, true, flags );
1837 }
1838 else
1839 {
1840 QgsReadWriteContext context;
1841 context.setPathResolver( pathResolver() );
1842 context.setProjectTranslator( this );
1844 QString layerId = element.namedItem( u"id"_s ).toElement().text();
1845 context.setCurrentLayerId( layerId );
1846 if ( !addLayer( element, brokenNodes, context, flags, loadedProviders.take( layerId ) ) )
1847 {
1848 returnStatus = false;
1849 }
1850 const auto messages = context.takeMessages();
1851 if ( !messages.isEmpty() )
1852 {
1853 emit loadingLayerMessageReceived( tr( "Loading layer %1" ).arg( name ), messages );
1854 }
1855 }
1856 emit layerLoaded( i + 1, totalLayerCount );
1857 i++;
1858 }
1859
1860 return returnStatus;
1861}
1862
1863bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &brokenNodes, QgsReadWriteContext &context, Qgis::ProjectReadFlags flags, QgsDataProvider *provider )
1864{
1866
1867 const QString type = layerElem.attribute( u"type"_s );
1868 QgsDebugMsgLevel( "Layer type is " + type, 4 );
1869 std::unique_ptr<QgsMapLayer> mapLayer;
1870
1871 QgsScopedRuntimeProfile profile( tr( "Create layer" ), u"projectload"_s );
1872
1873 bool ok = false;
1874 const Qgis::LayerType layerType( QgsMapLayerFactory::typeFromString( type, ok ) );
1875 if ( !ok )
1876 {
1877 QgsDebugError( u"Unknown layer type \"%1\""_s.arg( type ) );
1878 return false;
1879 }
1880
1881 switch ( layerType )
1882 {
1884 mapLayer = std::make_unique<QgsVectorLayer>();
1885 break;
1886
1888 mapLayer = std::make_unique<QgsRasterLayer>();
1889 break;
1890
1892 mapLayer = std::make_unique<QgsMeshLayer>();
1893 break;
1894
1896 mapLayer = std::make_unique<QgsVectorTileLayer>();
1897 break;
1898
1900 mapLayer = std::make_unique<QgsPointCloudLayer>();
1901 break;
1902
1904 mapLayer = std::make_unique<QgsTiledSceneLayer>();
1905 break;
1906
1908 {
1909 const QString typeName = layerElem.attribute( u"name"_s );
1910 mapLayer.reset( QgsApplication::pluginLayerRegistry()->createLayer( typeName ) );
1911 break;
1912 }
1913
1915 {
1916 const QgsAnnotationLayer::LayerOptions options( mTransformContext );
1917 mapLayer = std::make_unique<QgsAnnotationLayer>( QString(), options );
1918 break;
1919 }
1920
1922 {
1923 const QgsGroupLayer::LayerOptions options( mTransformContext );
1924 mapLayer = std::make_unique<QgsGroupLayer>( QString(), options );
1925 break;
1926 }
1927 }
1928
1929 if ( !mapLayer )
1930 {
1931 QgsDebugError( u"Unable to create layer"_s );
1932 return false;
1933 }
1934
1935 Q_CHECK_PTR( mapLayer ); // NOLINT
1936
1937 // This is tricky: to avoid a leak we need to check if the layer was already in the store
1938 // because if it was, the newly created layer will not be added to the store and it would leak.
1939 const QString layerId { layerElem.namedItem( u"id"_s ).toElement().text() };
1940 Q_ASSERT( !layerId.isEmpty() );
1941 const bool layerWasStored = layerStore()->mapLayer( layerId );
1942
1943 // have the layer restore state that is stored in Dom node
1944 QgsMapLayer::ReadFlags layerFlags = projectFlagsToLayerReadFlags( flags, mFlags );
1945
1946 profile.switchTask( tr( "Load layer source" ) );
1947 const bool layerIsValid = mapLayer->readLayerXml( layerElem, context, layerFlags, provider ) && mapLayer->isValid();
1948
1949 // apply specific settings to vector layer
1950 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1951 {
1952 vl->setReadExtentFromXml( layerFlags & QgsMapLayer::FlagReadExtentFromXml );
1953 if ( vl->dataProvider() )
1954 {
1956 vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, evaluateDefaultValues );
1957 }
1958 }
1959
1960 profile.switchTask( tr( "Add layer to project" ) );
1961 QList<QgsMapLayer *> newLayers;
1962 newLayers << mapLayer.get();
1963 if ( layerIsValid || flags & Qgis::ProjectReadFlag::DontResolveLayers )
1964 {
1965 emit readMapLayer( mapLayer.get(), layerElem );
1966 addMapLayers( newLayers );
1967 // Try to resolve references here (this is necessary to set up joined fields that will be possibly used by
1968 // virtual layers that point to this layer's joined field in their query otherwise they won't be valid ),
1969 // a second attempt to resolve references will be done after all layers are loaded
1970 // see https://github.com/qgis/QGIS/issues/46834
1971 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1972 {
1973 vLayer->joinBuffer()->resolveReferences( this );
1974 }
1975 }
1976 else
1977 {
1978 // It's a bad layer: do not add to legend (the user will decide if she wants to do so)
1979 addMapLayers( newLayers, false );
1980 newLayers.first();
1981 QgsDebugError( "Unable to load " + type + " layer" );
1982 brokenNodes.push_back( layerElem );
1983 }
1984
1985 const bool wasEditable = layerElem.attribute( u"editable"_s, u"0"_s ).toInt();
1986 if ( wasEditable )
1987 {
1988 mapLayer->setCustomProperty( u"_layer_was_editable"_s, true );
1989 }
1990 else
1991 {
1992 mapLayer->removeCustomProperty( u"_layer_was_editable"_s );
1993 }
1994
1995 // It should be safe to delete the layer now if layer was stored, because all the store
1996 // had to to was to reset the data source in case the validity changed.
1997 if ( !layerWasStored )
1998 {
1999 mapLayer.release();
2000 }
2001
2002 return layerIsValid;
2003}
2004
2005bool QgsProject::read( const QString &filename, Qgis::ProjectReadFlags flags )
2006{
2008
2009 mFile.setFileName( filename );
2010 mCachedHomePath.clear();
2011 mProjectScope.reset();
2012
2013 return read( flags );
2014}
2015
2017{
2019
2020 const QString filename = mFile.fileName();
2021 bool returnValue;
2022
2023 if ( QgsProjectStorage *storage = projectStorage() )
2024 {
2025 QTemporaryFile inDevice;
2026 if ( !inDevice.open() )
2027 {
2028 setError( tr( "Unable to open %1" ).arg( inDevice.fileName() ) );
2029 return false;
2030 }
2031
2032 QgsReadWriteContext context;
2033 context.setProjectTranslator( this );
2034 if ( !storage->readProject( filename, &inDevice, context ) )
2035 {
2036 QString err = tr( "Unable to open %1" ).arg( filename );
2037 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
2038 if ( !messages.isEmpty() )
2039 err += u"\n\n"_s + messages.last().message();
2040 setError( err );
2041 return false;
2042 }
2043 returnValue = unzip( inDevice.fileName(), flags ); // calls setError() if returning false
2044 }
2045 else
2046 {
2047 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
2048 {
2049 returnValue = unzip( mFile.fileName(), flags );
2050 }
2051 else
2052 {
2053 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( *this );
2054 const QFileInfo finfo( mFile.fileName() );
2055 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( u"%1_attachments.zip"_s.arg( finfo.completeBaseName() ) );
2056 if ( QFile( attachmentsZip ).exists() )
2057 {
2058 auto archive = std::make_unique<QgsArchive>();
2059 if ( archive->unzip( attachmentsZip ) )
2060 {
2061 releaseHandlesToProjectArchive();
2062 mArchive = std::move( archive );
2063 }
2064 }
2065 returnValue = readProjectFile( mFile.fileName(), flags );
2066 }
2067
2068 //on translation we should not change the filename back
2069 if ( !mTranslator )
2070 {
2071 mFile.setFileName( filename );
2072 mCachedHomePath.clear();
2073 mProjectScope.reset();
2074 }
2075 else
2076 {
2077 //but delete the translator
2078 mTranslator.reset( nullptr );
2079 }
2080 }
2081 emit fileNameChanged();
2082 emit homePathChanged();
2083 return returnValue;
2084}
2085
2086bool QgsProject::readProjectFile( const QString &filename, Qgis::ProjectReadFlags flags )
2087{
2089
2090 // avoid multiple emission of snapping updated signals
2091 ScopedIntIncrementor snapSignalBlock( &mBlockSnappingUpdates );
2092
2093 QFile projectFile( filename );
2094 clearError();
2095
2096 QgsApplication::profiler()->clear( u"projectload"_s );
2097 QgsScopedRuntimeProfile profile( tr( "Setting up translations" ), u"projectload"_s );
2098
2099 const QString locale = QgsApplication::settingsLocaleUserLocale->value();
2100 const QString projectBaseName = QFileInfo( mFile ).baseName();
2101 const QString projectDir = QFileInfo( mFile ).absolutePath();
2102 QString localeFileName = u"%1_%2"_s.arg( projectBaseName, locale );
2103
2104 if ( !QFile( u"%1/%2.qm"_s.arg( projectDir, localeFileName ) ).exists() && locale.contains( '_' ) )
2105 {
2106 // Fallback: try language-only locale (e.g., "fr" from "fr_CH")
2107 localeFileName = u"%1_%2"_s.arg( projectBaseName, locale.left( locale.indexOf( '_' ) ) );
2108 }
2109
2110 if ( QFile( u"%1/%2.qm"_s.arg( projectDir, localeFileName ) ).exists() )
2111 {
2112 mTranslator = std::make_unique< QTranslator >();
2113 ( void ) mTranslator->load( localeFileName, projectDir );
2114 }
2115
2116 profile.switchTask( tr( "Reading project file" ) );
2117 auto doc = std::make_unique<QDomDocument>( u"qgis"_s );
2118
2119 if ( !projectFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
2120 {
2121 projectFile.close();
2122
2123 setError( tr( "Unable to open %1" ).arg( projectFile.fileName() ) );
2124
2125 return false;
2126 }
2127
2128 QTextStream textStream( &projectFile );
2129 QString projectString = textStream.readAll();
2130 projectFile.close();
2131
2132 for ( int i = 0; i < 32; i++ )
2133 {
2134 if ( i == 9 || i == 10 || i == 13 )
2135 {
2136 continue;
2137 }
2138 projectString.replace( QChar( i ), u"%1%2%1"_s.arg( FONTMARKER_CHR_FIX, QString::number( i ) ) );
2139 }
2140
2141 // location of problem associated with errorMsg
2142 int line, column;
2143 QString errorMsg;
2144 if ( !doc->setContent( projectString, &errorMsg, &line, &column ) )
2145 {
2146 const QString errorString = tr( "Project file read error in file %1: %2 at line %3 column %4" ).arg( projectFile.fileName(), errorMsg ).arg( line ).arg( column );
2147 QgsDebugError( errorString );
2148 setError( errorString );
2149
2150 return false;
2151 }
2152
2153 projectFile.close();
2154
2155 QgsDebugMsgLevel( "Opened document " + projectFile.fileName(), 2 );
2156
2157 // get project version string, if any
2158 const QgsProjectVersion fileVersion = getVersion( *doc );
2159 const QgsProjectVersion thisVersion( Qgis::version() );
2160
2161 profile.switchTask( tr( "Updating project file" ) );
2162 if ( thisVersion > fileVersion )
2163 {
2164 const bool isOlderMajorVersion = fileVersion.majorVersion() < thisVersion.majorVersion();
2165
2166 if ( isOlderMajorVersion )
2167 {
2169 "Loading a file that was saved with an older "
2170 "version of qgis (saved in "
2171 + fileVersion.text()
2172 + ", loaded in "
2173 + Qgis::version()
2174 + "). Problems may occur."
2175 );
2176 }
2177
2178 QgsProjectFileTransform projectFile( *doc, fileVersion );
2179
2180 // Shows a warning when an old project file is read.
2182 emit oldProjectVersionWarning( fileVersion.text() );
2184 emit readVersionMismatchOccurred( fileVersion.text() );
2185
2186 projectFile.updateRevision( thisVersion );
2187 }
2188 else if ( fileVersion > thisVersion )
2189 {
2191 "Loading a file that was saved with a newer "
2192 "version of qgis (saved in "
2193 + fileVersion.text()
2194 + ", loaded in "
2195 + Qgis::version()
2196 + "). Problems may occur."
2197 );
2198
2199 emit readVersionMismatchOccurred( fileVersion.text() );
2200 }
2201
2202 // start new project, just keep the file name and auxiliary storage
2203 profile.switchTask( tr( "Creating auxiliary storage" ) );
2204 const QString fileName = mFile.fileName();
2205
2206 const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
2207 const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
2208
2209 // NOTE [ND] -- I suspect this is wrong, as the archive may contain any number of non-auxiliary
2210 // storage related files from the previously loaded project.
2211 std::unique_ptr<QgsAuxiliaryStorage> aStorage = std::move( mAuxiliaryStorage );
2212 std::unique_ptr<QgsArchive> archive = std::move( mArchive );
2213
2214 // don't emit xxxChanged signals during the clear() call, as we'll be emitting
2215 // them again after reading the properties from the project file
2216 mBlockChangeSignalsDuringClear = true;
2217 clear();
2218 mBlockChangeSignalsDuringClear = false;
2219
2220 // this is ugly, but clear() will have created a new archive and started populating it. We
2221 // need to release handles to this archive now as the subsequent call to move will need
2222 // to delete it, and requires free access to do so.
2223 releaseHandlesToProjectArchive();
2224
2225 mAuxiliaryStorage = std::move( aStorage );
2226 mArchive = std::move( archive );
2227
2228 mFile.setFileName( fileName );
2229 mCachedHomePath.clear();
2230 mProjectScope.reset();
2231 mSaveVersion = fileVersion;
2232
2233 // now get any properties
2234 profile.switchTask( tr( "Reading properties" ) );
2235 _getProperties( *doc, mProperties );
2236
2237 // now get the data defined server properties
2238 mDataDefinedServerProperties = getDataDefinedServerProperties( *doc, dataDefinedServerPropertyDefinitions() );
2239
2240 QgsDebugMsgLevel( QString::number( mProperties.count() ) + " properties read", 2 );
2241
2242#if 0
2243 dump_( mProperties );
2244#endif
2245
2246 // get older style project title
2247 QString oldTitle;
2248 _getTitle( *doc, oldTitle );
2249
2250 readProjectFileMetadata( *doc, mSaveUser, mSaveUserFull, mSaveDateTime );
2251
2252 const QDomNodeList homePathNl = doc->elementsByTagName( u"homePath"_s );
2253 if ( homePathNl.count() > 0 )
2254 {
2255 const QDomElement homePathElement = homePathNl.at( 0 ).toElement();
2256 const QString homePath = homePathElement.attribute( u"path"_s );
2257 if ( !homePath.isEmpty() )
2259 }
2260 else
2261 {
2262 emit homePathChanged();
2263 }
2264
2265 const QColor backgroundColor( readNumEntry( u"Gui"_s, u"/CanvasColorRedPart"_s, 255 ), readNumEntry( u"Gui"_s, u"/CanvasColorGreenPart"_s, 255 ), readNumEntry( u"Gui"_s, u"/CanvasColorBluePart"_s, 255 ) );
2267 const QColor
2268 selectionColor( readNumEntry( u"Gui"_s, u"/SelectionColorRedPart"_s, 255 ), readNumEntry( u"Gui"_s, u"/SelectionColorGreenPart"_s, 255 ), readNumEntry( u"Gui"_s, u"/SelectionColorBluePart"_s, 255 ), readNumEntry( u"Gui"_s, u"/SelectionColorAlphaPart"_s, 255 ) );
2270
2271
2272 const QString distanceUnitString = readEntry( u"Measurement"_s, u"/DistanceUnits"_s, QString() );
2273 if ( !distanceUnitString.isEmpty() )
2274 setDistanceUnits( QgsUnitTypes::decodeDistanceUnit( distanceUnitString ) );
2275
2276 const QString areaUnitString = readEntry( u"Measurement"_s, u"/AreaUnits"_s, QString() );
2277 if ( !areaUnitString.isEmpty() )
2278 setAreaUnits( QgsUnitTypes::decodeAreaUnit( areaUnitString ) );
2279
2280 setScaleMethod( qgsEnumKeyToValue( readEntry( u"Measurement"_s, u"/ScaleMethod"_s, QString() ), Qgis::ScaleCalculationMethod::HorizontalMiddle ) );
2281
2282 QgsReadWriteContext context;
2283 context.setPathResolver( pathResolver() );
2284 context.setProjectTranslator( this );
2285
2286 //crs
2287 QgsCoordinateReferenceSystem projectCrs;
2288 if ( readNumEntry( u"SpatialRefSys"_s, u"/ProjectionsEnabled"_s, 0 ) )
2289 {
2290 // first preference - dedicated projectCrs node
2291 const QDomNode srsNode = doc->documentElement().namedItem( u"projectCrs"_s );
2292 if ( !srsNode.isNull() )
2293 {
2294 projectCrs.readXml( srsNode );
2295 }
2296
2297 if ( !projectCrs.isValid() )
2298 {
2299 const QString projCrsString = readEntry( u"SpatialRefSys"_s, u"/ProjectCRSProj4String"_s );
2300 const long currentCRS = readNumEntry( u"SpatialRefSys"_s, u"/ProjectCRSID"_s, -1 );
2301 const QString authid = readEntry( u"SpatialRefSys"_s, u"/ProjectCrs"_s );
2302
2303 // authid should be prioritized over all
2304 const bool isUserAuthId = authid.startsWith( "USER:"_L1, Qt::CaseInsensitive );
2305 if ( !authid.isEmpty() && !isUserAuthId )
2306 projectCrs = QgsCoordinateReferenceSystem( authid );
2307
2308 // try the CRS
2309 if ( !projectCrs.isValid() && currentCRS >= 0 )
2310 {
2311 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2312 }
2313
2314 // if that didn't produce a match, try the proj.4 string
2315 if ( !projCrsString.isEmpty() && ( authid.isEmpty() || isUserAuthId ) && ( !projectCrs.isValid() || projectCrs.toProj() != projCrsString ) )
2316 {
2317 projectCrs = QgsCoordinateReferenceSystem::fromProj( projCrsString );
2318 }
2319
2320 // last just take the given id
2321 if ( !projectCrs.isValid() )
2322 {
2323 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2324 }
2325 }
2326 }
2327 mCrs = projectCrs;
2328
2329 //vertical CRS
2330 {
2331 QgsCoordinateReferenceSystem verticalCrs;
2332 const QDomNode verticalCrsNode = doc->documentElement().namedItem( u"verticalCrs"_s );
2333 if ( !verticalCrsNode.isNull() )
2334 {
2335 verticalCrs.readXml( verticalCrsNode );
2336 }
2337 mVerticalCrs = verticalCrs;
2338 }
2339 rebuildCrs3D();
2340
2341 QStringList datumErrors;
2342 if ( !mTransformContext.readXml( doc->documentElement(), context, datumErrors ) && !datumErrors.empty() )
2343 {
2344 emit missingDatumTransforms( datumErrors );
2345 }
2347
2348 // map shading
2349 const QDomNode elevationShadingNode = doc->documentElement().namedItem( u"elevation-shading-renderer"_s );
2350 if ( !elevationShadingNode.isNull() )
2351 {
2352 mElevationShadingRenderer.readXml( elevationShadingNode.toElement(), context );
2353 }
2355
2356
2357 //add variables defined in project file - do this early in the reading cycle, as other components
2358 //(e.g. layouts) may depend on these variables
2359 const QStringList variableNames = readListEntry( u"Variables"_s, u"/variableNames"_s );
2360 const QStringList variableValues = readListEntry( u"Variables"_s, u"/variableValues"_s );
2361
2362 mCustomVariables.clear();
2363 if ( variableNames.length() == variableValues.length() )
2364 {
2365 for ( int i = 0; i < variableNames.length(); ++i )
2366 {
2367 mCustomVariables.insert( variableNames.at( i ), variableValues.at( i ) );
2368 }
2369 }
2370 else
2371 {
2372 QgsMessageLog::logMessage( tr( "Project Variables Invalid" ), tr( "The project contains invalid variable settings." ) );
2373 }
2374
2375 // Register expression functions stored in the project.
2376 // They might be using project variables and might be
2377 // in turn being used by other components (e.g., layouts).
2379
2380 QDomElement element = doc->documentElement().firstChildElement( u"projectMetadata"_s );
2381
2382 if ( !element.isNull() )
2383 {
2384 mMetadata.readMetadataXml( element, context );
2385 }
2386 else
2387 {
2388 // older project, no metadata => remove auto generated metadata which is populated on QgsProject::clear()
2389 mMetadata = QgsProjectMetadata();
2390 }
2391 if ( mMetadata.title().isEmpty() && !oldTitle.isEmpty() )
2392 {
2393 // upgrade older title storage to storing within project metadata.
2394 mMetadata.setTitle( oldTitle );
2395 }
2396 emit metadataChanged();
2397 emit titleChanged();
2398
2399 // Transaction mode
2400 element = doc->documentElement().firstChildElement( u"transaction"_s );
2401 if ( !element.isNull() )
2402 {
2403 mTransactionMode = qgsEnumKeyToValue( element.attribute( u"mode"_s ), Qgis::TransactionMode::Disabled );
2404 }
2405 else
2406 {
2407 // maybe older project => try read autotransaction
2408 element = doc->documentElement().firstChildElement( u"autotransaction"_s );
2409 if ( !element.isNull() )
2410 {
2411 mTransactionMode = static_cast<Qgis::TransactionMode>( element.attribute( u"active"_s, u"0"_s ).toInt() );
2412 }
2413 }
2414
2415 // read the layer tree from project file
2416 profile.switchTask( tr( "Loading layer tree" ) );
2417 mRootGroup->setCustomProperty( u"loading"_s, 1 );
2418
2419 QDomElement layerTreeElem = doc->documentElement().firstChildElement( u"layer-tree-group"_s );
2420 if ( !layerTreeElem.isNull() )
2421 {
2422 // Use a temporary tree to read the nodes to prevent signals being delivered to the models
2423 QgsLayerTree tempTree;
2424 tempTree.readChildrenFromXml( layerTreeElem, context );
2425 mRootGroup->insertChildNodes( -1, tempTree.abandonChildren() );
2426 }
2427 else
2428 {
2429 QgsLayerTreeUtils::readOldLegend( mRootGroup.get(), doc->documentElement().firstChildElement( u"legend"_s ) );
2430 }
2431
2432 mLayerTreeRegistryBridge->setEnabled( false );
2433
2434 // get the map layers
2435 profile.switchTask( tr( "Reading map layers" ) );
2436
2437 loadProjectFlags( doc.get() );
2438
2439 QList<QDomNode> brokenNodes;
2440 const bool clean = _getMapLayers( *doc, brokenNodes, flags );
2441
2442 // review the integrity of the retrieved map layers
2443 if ( !clean && !( flags & Qgis::ProjectReadFlag::DontResolveLayers ) )
2444 {
2445 QgsDebugError( u"Unable to get map layers from project file."_s );
2446
2447 if ( !brokenNodes.isEmpty() )
2448 {
2449 QgsDebugError( "there are " + QString::number( brokenNodes.size() ) + " broken layers" );
2450 }
2451
2452 // we let a custom handler decide what to do with missing layers
2453 // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
2454 mBadLayerHandler->handleBadLayers( brokenNodes );
2455 }
2456
2457 mMainAnnotationLayer->readLayerXml( doc->documentElement().firstChildElement( u"main-annotation-layer"_s ), context );
2458 mMainAnnotationLayer->setTransformContext( mTransformContext );
2459
2460 // load embedded groups and layers
2461 profile.switchTask( tr( "Loading embedded layers" ) );
2462 loadEmbeddedNodes( mRootGroup.get(), flags );
2463
2464 // Resolve references to other layers
2465 // Needs to be done here once all dependent layers are loaded
2466 profile.switchTask( tr( "Resolving layer references" ) );
2467 QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
2468 for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
2469 {
2470 it.value()->resolveReferences( this );
2471 }
2472 mMainAnnotationLayer->resolveReferences( this );
2473
2474 mLayerTreeRegistryBridge->setEnabled( true );
2475
2476 // now that layers are loaded, we can resolve layer tree's references to the layers
2477 profile.switchTask( tr( "Resolving references" ) );
2478 mRootGroup->resolveReferences( this );
2479
2480 // we need to migrate old fashion designed QgsSymbolLayerReference to new ones
2481 if ( QgsProjectVersion( 3, 28, 0 ) > mSaveVersion )
2482 {
2486 }
2487
2488 if ( !layerTreeElem.isNull() )
2489 {
2490 mRootGroup->readLayerOrderFromXml( layerTreeElem );
2491 }
2492
2493 // Load pre 3.0 configuration
2494 const QDomElement layerTreeCanvasElem = doc->documentElement().firstChildElement( u"layer-tree-canvas"_s );
2495 if ( !layerTreeCanvasElem.isNull() )
2496 {
2497 mRootGroup->readLayerOrderFromXml( layerTreeCanvasElem );
2498 }
2499
2500 // Convert pre 3.4 to create layers flags
2501 if ( QgsProjectVersion( 3, 4, 0 ) > mSaveVersion )
2502 {
2503 const QStringList requiredLayerIds = readListEntry( u"RequiredLayers"_s, u"Layers"_s );
2504 for ( const QString &layerId : requiredLayerIds )
2505 {
2506 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2507 {
2508 layer->setFlags( layer->flags() & ~QgsMapLayer::Removable );
2509 }
2510 }
2511 const QStringList disabledLayerIds = readListEntry( u"Identify"_s, u"/disabledLayers"_s );
2512 for ( const QString &layerId : disabledLayerIds )
2513 {
2514 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2515 {
2516 layer->setFlags( layer->flags() & ~QgsMapLayer::Identifiable );
2517 }
2518 }
2519 }
2520
2521 // Convert pre 3.26 default styles
2522 if ( QgsProjectVersion( 3, 26, 0 ) > mSaveVersion )
2523 {
2524 // Convert default symbols
2525 QString styleName = readEntry( u"DefaultStyles"_s, u"/Marker"_s );
2526 if ( !styleName.isEmpty() )
2527 {
2528 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2530 }
2531 styleName = readEntry( u"DefaultStyles"_s, u"/Line"_s );
2532 if ( !styleName.isEmpty() )
2533 {
2534 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2536 }
2537 styleName = readEntry( u"DefaultStyles"_s, u"/Fill"_s );
2538 if ( !styleName.isEmpty() )
2539 {
2540 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2542 }
2543 styleName = readEntry( u"DefaultStyles"_s, u"/ColorRamp"_s );
2544 if ( !styleName.isEmpty() )
2545 {
2546 std::unique_ptr<QgsColorRamp> colorRamp( QgsStyle::defaultStyle()->colorRamp( styleName ) );
2547 styleSettings()->setDefaultColorRamp( colorRamp.get() );
2548 }
2549
2550 // Convert randomize default symbol fill color
2551 styleSettings()->setRandomizeDefaultSymbolColor( readBoolEntry( u"DefaultStyles"_s, u"/RandomColors"_s, true ) );
2552
2553 // Convert default symbol opacity
2554 double opacity = 1.0;
2555 bool ok = false;
2556 // upgrade old setting
2557 double alpha = readDoubleEntry( u"DefaultStyles"_s, u"/AlphaInt"_s, 255, &ok );
2558 if ( ok )
2559 opacity = alpha / 255.0;
2560 double newOpacity = readDoubleEntry( u"DefaultStyles"_s, u"/Opacity"_s, 1.0, &ok );
2561 if ( ok )
2562 opacity = newOpacity;
2564
2565 // Cleanup
2566 removeEntry( u"DefaultStyles"_s, u"/Marker"_s );
2567 removeEntry( u"DefaultStyles"_s, u"/Line"_s );
2568 removeEntry( u"DefaultStyles"_s, u"/Fill"_s );
2569 removeEntry( u"DefaultStyles"_s, u"/ColorRamp"_s );
2570 removeEntry( u"DefaultStyles"_s, u"/RandomColors"_s );
2571 removeEntry( u"DefaultStyles"_s, u"/AlphaInt"_s );
2572 removeEntry( u"DefaultStyles"_s, u"/Opacity"_s );
2573 }
2574
2575 // After bad layer handling we might still have invalid layers,
2576 // store them in case the user wanted to handle them later
2577 // or wanted to pass them through when saving
2579 {
2580 profile.switchTask( tr( "Storing original layer properties" ) );
2581 QgsLayerTreeUtils::storeOriginalLayersProperties( mRootGroup.get(), doc.get() );
2582 }
2583
2584 mRootGroup->removeCustomProperty( u"loading"_s );
2585
2586 profile.switchTask( tr( "Loading map themes" ) );
2587 mMapThemeCollection = std::make_unique< QgsMapThemeCollection >( this );
2589 mMapThemeCollection->readXml( *doc );
2590
2591 profile.switchTask( tr( "Loading label settings" ) );
2592 mLabelingEngineSettings->readSettingsFromProject( this );
2593 {
2594 const QDomElement labelEngineSettingsElement = doc->documentElement().firstChildElement( u"labelEngineSettings"_s );
2595 mLabelingEngineSettings->readXml( labelEngineSettingsElement, context );
2596 }
2597 mLabelingEngineSettings->resolveReferences( this );
2598
2600
2601 profile.switchTask( tr( "Loading annotations" ) );
2603 {
2604 mAnnotationManager->readXml( doc->documentElement(), context );
2605 }
2606 else
2607 {
2608 mAnnotationManager->readXmlAndUpgradeToAnnotationLayerItems( doc->documentElement(), context, mMainAnnotationLayer, mTransformContext );
2609 }
2611 {
2612 profile.switchTask( tr( "Loading layouts" ) );
2613 mLayoutManager->readXml( doc->documentElement(), *doc );
2614 }
2615
2616 {
2617 profile.switchTask( tr( "Loading elevation profiles" ) );
2618 mElevationProfileManager->readXml( doc->documentElement(), *doc, context );
2619 mElevationProfileManager->resolveReferences( this );
2620 }
2621
2622 {
2623 profile.switchTask( tr( "Loading selective masking source sets" ) );
2624 mSelectiveMaskingSourceSetManager->readXml( doc->documentElement(), *doc, context );
2625 }
2626
2628 {
2629 profile.switchTask( tr( "Loading 3D Views" ) );
2630 m3DViewsManager->readXml( doc->documentElement(), *doc );
2631 }
2632
2633 profile.switchTask( tr( "Loading bookmarks" ) );
2634 mBookmarkManager->readXml( doc->documentElement(), *doc );
2635
2636 profile.switchTask( tr( "Loading sensors" ) );
2637 mSensorManager->readXml( doc->documentElement(), *doc );
2638
2639 // reassign change dependencies now that all layers are loaded
2640 QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2641 for ( QMap<QString, QgsMapLayer *>::iterator it = existingMaps.begin(); it != existingMaps.end(); ++it )
2642 {
2643 it.value()->setDependencies( it.value()->dependencies() );
2644 }
2645
2646 profile.switchTask( tr( "Loading snapping settings" ) );
2647 mSnappingConfig.readProject( *doc );
2648 mAvoidIntersectionsMode = static_cast<Qgis::AvoidIntersectionsMode>(
2649 readNumEntry( u"Digitizing"_s, u"/AvoidIntersectionsMode"_s, static_cast<int>( Qgis::AvoidIntersectionsMode::AvoidIntersectionsLayers ) )
2650 );
2651
2652 profile.switchTask( tr( "Loading view settings" ) );
2653 // restore older project scales settings
2654 mViewSettings->setUseProjectScales( readBoolEntry( u"Scales"_s, u"/useProjectScales"_s ) );
2655 const QStringList scales = readListEntry( u"Scales"_s, u"/ScalesList"_s );
2656 QVector<double> res;
2657 for ( const QString &scale : scales )
2658 {
2659 const QStringList parts = scale.split( ':' );
2660 if ( parts.size() != 2 )
2661 continue;
2662
2663 bool ok = false;
2664 const double denominator = QLocale().toDouble( parts[1], &ok );
2665 if ( ok )
2666 {
2667 res << denominator;
2668 }
2669 }
2670 mViewSettings->setMapScales( res );
2671 const QDomElement viewSettingsElement = doc->documentElement().firstChildElement( u"ProjectViewSettings"_s );
2672 if ( !viewSettingsElement.isNull() )
2673 mViewSettings->readXml( viewSettingsElement, context );
2674
2675 // restore style settings
2676 profile.switchTask( tr( "Loading style properties" ) );
2677 const QDomElement styleSettingsElement = doc->documentElement().firstChildElement( u"ProjectStyleSettings"_s );
2678 if ( !styleSettingsElement.isNull() )
2679 {
2680 mStyleSettings->removeProjectStyle();
2681 mStyleSettings->readXml( styleSettingsElement, context, flags );
2682 }
2683
2684 // restore time settings
2685 profile.switchTask( tr( "Loading temporal settings" ) );
2686 const QDomElement timeSettingsElement = doc->documentElement().firstChildElement( u"ProjectTimeSettings"_s );
2687 if ( !timeSettingsElement.isNull() )
2688 mTimeSettings->readXml( timeSettingsElement, context );
2689
2690
2691 profile.switchTask( tr( "Loading elevation properties" ) );
2692 const QDomElement elevationPropertiesElement = doc->documentElement().firstChildElement( u"ElevationProperties"_s );
2693 if ( !elevationPropertiesElement.isNull() )
2694 mElevationProperties->readXml( elevationPropertiesElement, context );
2695 mElevationProperties->resolveReferences( this );
2696
2697 profile.switchTask( tr( "Loading display settings" ) );
2698 {
2699 const QDomElement displaySettingsElement = doc->documentElement().firstChildElement( u"ProjectDisplaySettings"_s );
2700 if ( !displaySettingsElement.isNull() )
2701 mDisplaySettings->readXml( displaySettingsElement, context );
2702 }
2703
2704 profile.switchTask( tr( "Loading GPS settings" ) );
2705 {
2706 const QDomElement gpsSettingsElement = doc->documentElement().firstChildElement( u"ProjectGpsSettings"_s );
2707 if ( !gpsSettingsElement.isNull() )
2708 mGpsSettings->readXml( gpsSettingsElement, context );
2709 mGpsSettings->resolveReferences( this );
2710 }
2711
2712 profile.switchTask( tr( "Updating variables" ) );
2714 profile.switchTask( tr( "Updating CRS" ) );
2715 emit crsChanged();
2716 if ( verticalCrs() != oldVerticalCrs )
2717 emit verticalCrsChanged();
2718 if ( mCrs3D != oldCrs3D )
2719 emit crs3DChanged();
2720 emit ellipsoidChanged( ellipsoid() );
2721
2722 // read the project: used by map canvas and legend
2723 profile.switchTask( tr( "Reading external settings" ) );
2724 emit readProject( *doc );
2725 emit readProjectWithContext( *doc, context );
2726
2727 profile.switchTask( tr( "Updating interface" ) );
2728
2729 snapSignalBlock.release();
2730 if ( !mBlockSnappingUpdates )
2731 emit snappingConfigChanged( mSnappingConfig );
2732
2735 emit projectColorsChanged();
2736
2737 // if all went well, we're allegedly in pristine state
2738 if ( clean )
2739 setDirty( false );
2740
2741 QgsDebugMsgLevel( u"Project save user: %1"_s.arg( mSaveUser ), 2 );
2742 QgsDebugMsgLevel( u"Project save user: %1"_s.arg( mSaveUserFull ), 2 );
2743
2747
2748 if ( mTranslator )
2749 {
2750 //project possibly translated -> rename it with locale postfix
2751 const QString newFileName( u"%1/%2.qgs"_s.arg( QFileInfo( mFile ).absolutePath(), localeFileName ) );
2752 setFileName( newFileName );
2753
2754 if ( write() )
2755 {
2756 QgsMessageLog::logMessage( tr( "Translated project saved with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Success );
2757 }
2758 else
2759 {
2760 QgsMessageLog::logMessage( tr( "Error saving translated project with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Critical );
2761 }
2762 }
2763
2764 // lastly, make any previously editable layers editable
2765 const QMap<QString, QgsMapLayer *> loadedLayers = mapLayers();
2766 for ( auto it = loadedLayers.constBegin(); it != loadedLayers.constEnd(); ++it )
2767 {
2768 if ( it.value()->isValid() && it.value()->customProperty( u"_layer_was_editable"_s ).toBool() )
2769 {
2770 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( it.value() ) )
2771 vl->startEditing();
2772 it.value()->removeCustomProperty( u"_layer_was_editable"_s );
2773 }
2774 }
2775
2776 return true;
2777}
2778
2779bool QgsProject::loadEmbeddedNodes( QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
2780{
2782
2783 bool valid = true;
2784 const auto constChildren = group->children();
2785 for ( QgsLayerTreeNode *child : constChildren )
2786 {
2787 if ( QgsLayerTree::isGroup( child ) )
2788 {
2789 QgsLayerTreeGroup *childGroup = QgsLayerTree::toGroup( child );
2790 if ( childGroup->customProperty( u"embedded"_s ).toInt() )
2791 {
2792 // make sure to convert the path from relative to absolute
2793 const QString projectPath = readPath( childGroup->customProperty( u"embedded_project"_s ).toString() );
2794 childGroup->setCustomProperty( u"embedded_project"_s, projectPath );
2795 std::unique_ptr< QgsLayerTreeGroup > newGroup = createEmbeddedGroup( childGroup->name(), projectPath, childGroup->customProperty( u"embedded-invisible-layers"_s ).toStringList(), flags );
2796 if ( newGroup )
2797 {
2798 QList<QgsLayerTreeNode *> clonedChildren;
2799 const QList<QgsLayerTreeNode *> constChildren = newGroup->children();
2800 clonedChildren.reserve( constChildren.size() );
2801 for ( QgsLayerTreeNode *newGroupChild : constChildren )
2802 clonedChildren << newGroupChild->clone();
2803
2804 childGroup->insertChildNodes( 0, clonedChildren );
2805 }
2806 }
2807 else
2808 {
2809 loadEmbeddedNodes( childGroup, flags );
2810 }
2811 }
2812 else if ( QgsLayerTree::isLayer( child ) )
2813 {
2814 if ( child->customProperty( u"embedded"_s ).toInt() )
2815 {
2816 QList<QDomNode> brokenNodes;
2817 if ( !createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), readPath( child->customProperty( u"embedded_project"_s ).toString() ), brokenNodes, true, flags ) )
2818 {
2819 valid = valid && false;
2820 }
2821 }
2822 }
2823 }
2824
2825 return valid;
2826}
2827
2829{
2830 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
2832
2833 return mCustomVariables;
2834}
2835
2836void QgsProject::setCustomVariables( const QVariantMap &variables )
2837{
2839
2840 if ( variables == mCustomVariables )
2841 return;
2842
2843 //write variable to project
2844 QStringList variableNames;
2845 QStringList variableValues;
2846
2847 QVariantMap::const_iterator it = variables.constBegin();
2848 for ( ; it != variables.constEnd(); ++it )
2849 {
2850 variableNames << it.key();
2851 variableValues << it.value().toString();
2852 }
2853
2854 writeEntry( u"Variables"_s, u"/variableNames"_s, variableNames );
2855 writeEntry( u"Variables"_s, u"/variableValues"_s, variableValues );
2856
2857 mCustomVariables = variables;
2858 mProjectScope.reset();
2859
2861}
2862
2864{
2866
2867 *mLabelingEngineSettings = settings;
2869}
2870
2872{
2874
2875 return *mLabelingEngineSettings;
2876}
2877
2879{
2881
2882 mProjectScope.reset();
2883 return mLayerStore.get();
2884}
2885
2887{
2889
2890 return mLayerStore.get();
2891}
2892
2893QList<QgsVectorLayer *> QgsProject::avoidIntersectionsLayers() const
2894{
2896
2897 QList<QgsVectorLayer *> layers;
2898 const QStringList layerIds = readListEntry( u"Digitizing"_s, u"/AvoidIntersectionsList"_s, QStringList() );
2899 const auto constLayerIds = layerIds;
2900 for ( const QString &layerId : constLayerIds )
2901 {
2902 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer( layerId ) ) )
2903 layers << vlayer;
2904 }
2905 return layers;
2906}
2907
2908void QgsProject::setAvoidIntersectionsLayers( const QList<QgsVectorLayer *> &layers )
2909{
2911
2912 QStringList list;
2913 list.reserve( layers.size() );
2914
2915 for ( QgsVectorLayer *layer : layers )
2916 {
2917 if ( layer->geometryType() == Qgis::GeometryType::Polygon )
2918 list << layer->id();
2919 }
2920
2921 writeEntry( u"Digitizing"_s, u"/AvoidIntersectionsList"_s, list );
2923}
2924
2935
2937{
2938 // this method is called quite extensively using QgsProject::instance() skip-keyword-check
2940
2941 // MUCH cheaper to clone than build
2942 if ( mProjectScope )
2943 {
2944 auto projectScope = std::make_unique< QgsExpressionContextScope >( *mProjectScope );
2945
2946 // we can't cache these variables
2947 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_distance_units"_s, QgsUnitTypes::toString( distanceUnits() ), true, true ) );
2948 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_area_units"_s, QgsUnitTypes::toString( areaUnits() ), true, true ) );
2949
2950 // neither this function
2951 projectScope->addFunction( u"sensor_data"_s, new GetSensorData( sensorManager()->sensorsData() ) );
2952
2953 return projectScope.release();
2954 }
2955
2956 mProjectScope = std::make_unique< QgsExpressionContextScope >( QObject::tr( "Project" ) );
2957
2958 const QVariantMap vars = customVariables();
2959
2960 QVariantMap::const_iterator it = vars.constBegin();
2961
2962 for ( ; it != vars.constEnd(); ++it )
2963 {
2964 mProjectScope->setVariable( it.key(), it.value(), true );
2965 }
2966
2967 QString projectPath = projectStorage() ? fileName() : absoluteFilePath();
2968 if ( projectPath.isEmpty() )
2969 projectPath = mOriginalPath;
2970 const QString projectFolder = QFileInfo( projectPath ).path();
2971 const QString projectFilename = QFileInfo( projectPath ).fileName();
2972 const QString projectBasename = baseName();
2973
2974 //add other known project variables
2975 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_title"_s, title(), true, true ) );
2976 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_path"_s, QDir::toNativeSeparators( projectPath ), true, true ) );
2977 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_folder"_s, QDir::toNativeSeparators( projectFolder ), true, true ) );
2978 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_filename"_s, projectFilename, true, true ) );
2979 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_basename"_s, projectBasename, true, true ) );
2980 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_home"_s, QDir::toNativeSeparators( homePath() ), true, true ) );
2981 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_last_saved"_s, mSaveDateTime.isNull() ? QVariant() : QVariant( mSaveDateTime ), true, true ) );
2982
2983 const QgsCoordinateReferenceSystem projectCrs = crs();
2984 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs"_s, projectCrs.authid(), true, true ) );
2985 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_definition"_s, projectCrs.toProj(), true, true ) );
2986 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_description"_s, projectCrs.description(), true, true ) );
2987 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_acronym"_s, projectCrs.projectionAcronym(), true ) );
2988 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_ellipsoid"_s, projectCrs.ellipsoidAcronym(), true ) );
2989 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_proj4"_s, projectCrs.toProj(), true ) );
2990 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_wkt"_s, projectCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
2991
2992 const QgsCoordinateReferenceSystem projectVerticalCrs = QgsProject::verticalCrs();
2993 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs"_s, projectVerticalCrs.authid(), true, true ) );
2994 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs_definition"_s, projectVerticalCrs.toProj(), true, true ) );
2995 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs_description"_s, projectVerticalCrs.description(), true, true ) );
2996 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs_wkt"_s, projectVerticalCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
2997
2998 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_ellipsoid"_s, ellipsoid(), true, true ) );
2999 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"_project_transform_context"_s, QVariant::fromValue<QgsCoordinateTransformContext>( transformContext() ), true, true ) );
3000 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_units"_s, QgsUnitTypes::toString( projectCrs.mapUnits() ), true ) );
3001
3002 // metadata
3003 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_author"_s, metadata().author(), true, true ) );
3004 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_abstract"_s, metadata().abstract(), true, true ) );
3005 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_creation_date"_s, metadata().creationDateTime(), true, true ) );
3006 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_identifier"_s, metadata().identifier(), true, true ) );
3007
3008 // keywords
3009 QVariantMap keywords;
3010 const QgsAbstractMetadataBase::KeywordMap metadataKeywords = metadata().keywords();
3011 for ( auto it = metadataKeywords.constBegin(); it != metadataKeywords.constEnd(); ++it )
3012 {
3013 keywords.insert( it.key(), it.value() );
3014 }
3015 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_keywords"_s, keywords, true, true ) );
3016
3017 // layers
3018 QVariantList layersIds;
3019 QVariantList layers;
3020 const QMap<QString, QgsMapLayer *> layersInProject = mLayerStore->mapLayers();
3021 layersIds.reserve( layersInProject.count() );
3022 layers.reserve( layersInProject.count() );
3023 for ( auto it = layersInProject.constBegin(); it != layersInProject.constEnd(); ++it )
3024 {
3025 layersIds << it.value()->id();
3027 }
3028 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"layer_ids"_s, layersIds, true ) );
3029 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"layers"_s, layers, true ) );
3030
3031 mProjectScope->addFunction( u"project_color"_s, new GetNamedProjectColor( this ) );
3032 mProjectScope->addFunction( u"project_color_object"_s, new GetNamedProjectColorObject( this ) );
3033
3035}
3036
3037void QgsProject::onMapLayersAdded( const QList<QgsMapLayer *> &layers )
3038{
3040
3041 const QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
3042
3043 const auto constLayers = layers;
3044 for ( QgsMapLayer *layer : constLayers )
3045 {
3046 if ( !layer->isValid() )
3047 return;
3048
3049 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
3050 {
3051 vlayer->setReadExtentFromXml( mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics );
3052 if ( vlayer->dataProvider() )
3053 vlayer->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, ( bool ) ( mFlags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide ) );
3054 }
3055
3056 connect( layer, &QgsMapLayer::configChanged, this, [this] { setDirty(); } );
3057
3058 // check if we have to update connections for layers with dependencies
3059 for ( QMap<QString, QgsMapLayer *>::const_iterator it = existingMaps.cbegin(); it != existingMaps.cend(); ++it )
3060 {
3061 const QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
3062 if ( deps.contains( layer->id() ) )
3063 {
3064 // reconnect to change signals
3065 it.value()->setDependencies( deps );
3066 }
3067 }
3068 }
3069
3070 updateTransactionGroups();
3071
3072 if ( !mBlockSnappingUpdates && mSnappingConfig.addLayers( layers ) )
3073 emit snappingConfigChanged( mSnappingConfig );
3074}
3075
3076void QgsProject::onMapLayersRemoved( const QList<QgsMapLayer *> &layers )
3077{
3079
3080 if ( !mBlockSnappingUpdates && mSnappingConfig.removeLayers( layers ) )
3081 emit snappingConfigChanged( mSnappingConfig );
3082
3083 for ( QgsMapLayer *layer : layers )
3084 {
3085 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
3086 if ( !vlayer )
3087 continue;
3088
3089 mEditBufferGroup.removeLayer( vlayer );
3090 }
3091}
3092
3093void QgsProject::cleanTransactionGroups( bool force )
3094{
3096
3097 bool changed = false;
3098 for ( QMap< QPair< QString, QString>, QgsTransactionGroup *>::Iterator tg = mTransactionGroups.begin(); tg != mTransactionGroups.end(); )
3099 {
3100 if ( tg.value()->isEmpty() || force )
3101 {
3102 delete tg.value();
3103 tg = mTransactionGroups.erase( tg );
3104 changed = true;
3105 }
3106 else
3107 {
3108 ++tg;
3109 }
3110 }
3111 if ( changed )
3113}
3114
3115void QgsProject::updateTransactionGroups()
3116{
3118
3119 mEditBufferGroup.clear();
3120
3121 switch ( mTransactionMode )
3122 {
3124 {
3125 cleanTransactionGroups( true );
3126 return;
3127 }
3128 break;
3130 cleanTransactionGroups( true );
3131 break;
3133 cleanTransactionGroups( false );
3134 break;
3135 }
3136
3137 bool tgChanged = false;
3138 const auto constLayers = mapLayers().values();
3139 for ( QgsMapLayer *layer : constLayers )
3140 {
3141 if ( !layer->isValid() )
3142 continue;
3143
3144 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
3145 if ( !vlayer )
3146 continue;
3147
3148 switch ( mTransactionMode )
3149 {
3151 Q_ASSERT( false );
3152 break;
3154 {
3156 {
3157 const QString connString = QgsTransaction::connectionString( vlayer->source() );
3158 const QString key = vlayer->providerType();
3159
3160 QgsTransactionGroup *tg = mTransactionGroups.value( qMakePair( key, connString ) );
3161
3162 if ( !tg )
3163 {
3164 tg = new QgsTransactionGroup();
3165 mTransactionGroups.insert( qMakePair( key, connString ), tg );
3166 tgChanged = true;
3167 }
3168 tg->addLayer( vlayer );
3169 }
3170 }
3171 break;
3173 {
3174 if ( vlayer->supportsEditing() )
3175 mEditBufferGroup.addLayer( vlayer );
3176 }
3177 break;
3178 }
3179 }
3180
3181 if ( tgChanged )
3183}
3184
3185bool QgsProject::readLayer( const QDomNode &layerNode )
3186{
3188
3189 QgsReadWriteContext context;
3190 context.setPathResolver( pathResolver() );
3191 context.setProjectTranslator( this );
3192 context.setTransformContext( transformContext() );
3193 context.setCurrentLayerId( layerNode.toElement().firstChildElement( u"id"_s ).text() );
3194 QList<QDomNode> brokenNodes;
3195 if ( addLayer( layerNode.toElement(), brokenNodes, context ) )
3196 {
3197 // have to try to update joins for all layers now - a previously added layer may be dependent on this newly
3198 // added layer for joins
3199 const QVector<QgsVectorLayer *> vectorLayers = layers<QgsVectorLayer *>();
3200 for ( QgsVectorLayer *layer : vectorLayers )
3201 {
3202 // TODO: should be only done later - and with all layers (other layers may have referenced this layer)
3203 layer->resolveReferences( this );
3204
3205 if ( layer->isValid() && layer->customProperty( u"_layer_was_editable"_s ).toBool() )
3206 {
3207 layer->startEditing();
3208 layer->removeCustomProperty( u"_layer_was_editable"_s );
3209 }
3210 }
3211 return true;
3212 }
3213 return false;
3214}
3215
3216bool QgsProject::write( const QString &filename )
3217{
3219
3220 mFile.setFileName( filename );
3221 emit fileNameChanged();
3222 mCachedHomePath.clear();
3223 return write();
3224}
3225
3227{
3229
3230 mProjectScope.reset();
3231 if ( QgsProjectStorage *storage = projectStorage() )
3232 {
3233 QgsReadWriteContext context;
3234 // for projects stored in a custom storage, we have to check for the support
3235 // of relative paths since the storage most likely will not be in a file system
3236 const QString storageFilePath { storage->filePath( mFile.fileName() ) };
3237 if ( storageFilePath.isEmpty() )
3238 {
3240 }
3241 context.setPathResolver( pathResolver() );
3242
3243 const QString tempPath = QStandardPaths::standardLocations( QStandardPaths::TempLocation ).at( 0 );
3244 const QString tmpZipFilename( tempPath + QDir::separator() + QUuid::createUuid().toString() );
3245
3246 if ( !zip( tmpZipFilename ) )
3247 return false; // zip() already calls setError() when returning false
3248
3249 QFile tmpZipFile( tmpZipFilename );
3250 if ( !tmpZipFile.open( QIODevice::ReadOnly ) )
3251 {
3252 setError( tr( "Unable to read file %1" ).arg( tmpZipFilename ) );
3253 return false;
3254 }
3255
3257 if ( !storage->writeProject( mFile.fileName(), &tmpZipFile, context ) )
3258 {
3259 QString err = tr( "Unable to save project to storage %1" ).arg( mFile.fileName() );
3260 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
3261 if ( !messages.isEmpty() )
3262 err += u"\n\n"_s + messages.last().message();
3263 setError( err );
3264 return false;
3265 }
3266
3267 tmpZipFile.close();
3268 QFile::remove( tmpZipFilename );
3269
3270 return true;
3271 }
3272
3273 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
3274 {
3275 return zip( mFile.fileName() );
3276 }
3277 else
3278 {
3279 // write project file even if the auxiliary storage is not correctly
3280 // saved
3281 const bool asOk = saveAuxiliaryStorage();
3282 const bool writeOk = writeProjectFile( mFile.fileName() );
3283 bool attachmentsOk = true;
3284 if ( !mArchive->files().isEmpty() )
3285 {
3286 const QFileInfo finfo( mFile.fileName() );
3287 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( u"%1_attachments.zip"_s.arg( finfo.completeBaseName() ) );
3288 attachmentsOk = mArchive->zip( attachmentsZip );
3289 }
3290
3291 // errors raised during writing project file are more important
3292 if ( ( !asOk || !attachmentsOk ) && writeOk )
3293 {
3294 QStringList errorMessage;
3295 if ( !asOk )
3296 {
3297 const QString err = mAuxiliaryStorage->errorString();
3298 errorMessage.append( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
3299 }
3300 if ( !attachmentsOk )
3301 {
3302 errorMessage.append( tr( "Unable to save attachments archive" ) );
3303 }
3304 setError( errorMessage.join( '\n' ) );
3305 }
3306
3307 return asOk && writeOk && attachmentsOk;
3308 }
3309}
3310
3311bool QgsProject::writeProjectFile( const QString &filename )
3312{
3314
3315 QFile projectFile( filename );
3316 clearError();
3317
3318 // if we have problems creating or otherwise writing to the project file,
3319 // let's find out up front before we go through all the hand-waving
3320 // necessary to create all the Dom objects
3321 const QFileInfo myFileInfo( projectFile );
3322 if ( myFileInfo.exists() && !myFileInfo.isWritable() )
3323 {
3324 setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." ).arg( projectFile.fileName() ) );
3325 return false;
3326 }
3327
3328 QgsReadWriteContext context;
3329 context.setPathResolver( pathResolver() );
3331
3332 QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
3333
3334 const QDomDocumentType documentType = QDomImplementation().createDocumentType( u"qgis"_s, u"http://mrcc.com/qgis.dtd"_s, u"SYSTEM"_s );
3335 auto doc = std::make_unique<QDomDocument>( documentType );
3336
3337 QDomElement qgisNode = doc->createElement( u"qgis"_s );
3338 qgisNode.setAttribute( u"projectname"_s, title() );
3339 qgisNode.setAttribute( u"version"_s, Qgis::version() );
3340
3341 if ( !mSettings.value( u"projects/anonymize_saved_projects"_s, false, QgsSettings::Core ).toBool() )
3342 {
3343 const QString newSaveUser = QgsApplication::userLoginName();
3344 const QString newSaveUserFull = QgsApplication::userFullName();
3345 qgisNode.setAttribute( u"saveUser"_s, newSaveUser );
3346 qgisNode.setAttribute( u"saveUserFull"_s, newSaveUserFull );
3347 mSaveUser = newSaveUser;
3348 mSaveUserFull = newSaveUserFull;
3349 if ( mMetadata.author().isEmpty() )
3350 {
3351 mMetadata.setAuthor( QgsApplication::userFullName() );
3352 }
3353 if ( !mMetadata.creationDateTime().isValid() )
3354 {
3355 mMetadata.setCreationDateTime( QDateTime( QDateTime::currentDateTime() ) );
3356 }
3357 mSaveDateTime = QDateTime::currentDateTime();
3358 qgisNode.setAttribute( u"saveDateTime"_s, mSaveDateTime.toString( Qt::ISODate ) );
3359 }
3360 else
3361 {
3362 mSaveUser.clear();
3363 mSaveUserFull.clear();
3364 mMetadata.setAuthor( QString() );
3365 mMetadata.setCreationDateTime( QDateTime() );
3366 mSaveDateTime = QDateTime();
3367 }
3368 doc->appendChild( qgisNode );
3369 mSaveVersion = QgsProjectVersion( Qgis::version() );
3370
3371 QDomElement homePathNode = doc->createElement( u"homePath"_s );
3372 homePathNode.setAttribute( u"path"_s, mHomePath );
3373 qgisNode.appendChild( homePathNode );
3374
3375 // title
3376 QDomElement titleNode = doc->createElement( u"title"_s );
3377 qgisNode.appendChild( titleNode );
3378
3379 QDomElement transactionNode = doc->createElement( u"transaction"_s );
3380 transactionNode.setAttribute( u"mode"_s, qgsEnumValueToKey( mTransactionMode ) );
3381 qgisNode.appendChild( transactionNode );
3382
3383 QDomElement flagsNode = doc->createElement( u"projectFlags"_s );
3384 flagsNode.setAttribute( u"set"_s, qgsFlagValueToKeys( mFlags ) );
3385 qgisNode.appendChild( flagsNode );
3386
3387 const QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
3388 titleNode.appendChild( titleText );
3389
3390 // write project CRS
3391 {
3392 QDomElement srsNode = doc->createElement( u"projectCrs"_s );
3393 mCrs.writeXml( srsNode, *doc );
3394 qgisNode.appendChild( srsNode );
3395 }
3396 {
3397 QDomElement verticalSrsNode = doc->createElement( u"verticalCrs"_s );
3398 mVerticalCrs.writeXml( verticalSrsNode, *doc );
3399 qgisNode.appendChild( verticalSrsNode );
3400 }
3401
3402 QDomElement elevationShadingNode = doc->createElement( u"elevation-shading-renderer"_s );
3403 mElevationShadingRenderer.writeXml( elevationShadingNode, context );
3404 qgisNode.appendChild( elevationShadingNode );
3405
3406 // write layer tree - make sure it is without embedded subgroups
3407 std::unique_ptr< QgsLayerTreeNode > clonedRoot( mRootGroup->clone() );
3409 QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot.get() ), this ); // convert absolute paths to relative paths if required
3410
3411 clonedRoot->writeXml( qgisNode, context );
3412 clonedRoot.reset();
3413
3414 mSnappingConfig.writeProject( *doc );
3415 writeEntry( u"Digitizing"_s, u"/AvoidIntersectionsMode"_s, static_cast<int>( mAvoidIntersectionsMode ) );
3416
3417 // let map canvas and legend write their information
3418 emit writeProject( *doc );
3419
3420 // within top level node save list of layers
3421 const QMap<QString, QgsMapLayer *> layers = mapLayers();
3422
3423 QDomElement annotationLayerNode = doc->createElement( u"main-annotation-layer"_s );
3424 mMainAnnotationLayer->writeLayerXml( annotationLayerNode, *doc, context );
3425 qgisNode.appendChild( annotationLayerNode );
3426
3427 // Iterate over layers in zOrder
3428 // Call writeXml() on each
3429 QDomElement projectLayersNode = doc->createElement( u"projectlayers"_s );
3430
3431 QMap<QString, QgsMapLayer *>::ConstIterator li = layers.constBegin();
3432 while ( li != layers.end() )
3433 {
3434 QgsMapLayer *ml = li.value();
3435
3436 if ( ml )
3437 {
3438 const QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.constFind( ml->id() );
3439 if ( emIt == mEmbeddedLayers.constEnd() )
3440 {
3441 QDomElement maplayerElem;
3442 // If layer is not valid, prefer to restore saved properties from invalidLayerProperties. But if that's
3443 // not available, just write what we DO have
3444 if ( ml->isValid() || ml->originalXmlProperties().isEmpty() )
3445 {
3446 // general layer metadata
3447 maplayerElem = doc->createElement( u"maplayer"_s );
3448 ml->writeLayerXml( maplayerElem, *doc, context );
3449
3451 maplayerElem.setAttribute( u"editable"_s, u"1"_s );
3452 }
3453 else if ( !ml->originalXmlProperties().isEmpty() )
3454 {
3455 QDomDocument document;
3456 if ( document.setContent( ml->originalXmlProperties() ) )
3457 {
3458 maplayerElem = document.firstChildElement();
3459 }
3460 else
3461 {
3462 QgsDebugError( u"Could not restore layer properties for layer %1"_s.arg( ml->id() ) );
3463 }
3464 }
3465
3466 emit writeMapLayer( ml, maplayerElem, *doc );
3467
3468 projectLayersNode.appendChild( maplayerElem );
3469 }
3470 else
3471 {
3472 // layer defined in an external project file
3473 // only save embedded layer if not managed by a legend group
3474 if ( emIt.value().second )
3475 {
3476 QDomElement mapLayerElem = doc->createElement( u"maplayer"_s );
3477 mapLayerElem.setAttribute( u"embedded"_s, 1 );
3478 mapLayerElem.setAttribute( u"project"_s, writePath( emIt.value().first ) );
3479 mapLayerElem.setAttribute( u"id"_s, ml->id() );
3480 projectLayersNode.appendChild( mapLayerElem );
3481 }
3482 }
3483 }
3484 li++;
3485 }
3486
3487 qgisNode.appendChild( projectLayersNode );
3488
3489 QDomElement layerOrderNode = doc->createElement( u"layerorder"_s );
3490 const auto constCustomLayerOrder = mRootGroup->customLayerOrder();
3491 for ( QgsMapLayer *layer : constCustomLayerOrder )
3492 {
3493 QDomElement mapLayerElem = doc->createElement( u"layer"_s );
3494 mapLayerElem.setAttribute( u"id"_s, layer->id() );
3495 layerOrderNode.appendChild( mapLayerElem );
3496 }
3497 qgisNode.appendChild( layerOrderNode );
3498
3499 mLabelingEngineSettings->writeSettingsToProject( this );
3500 {
3501 QDomElement labelEngineSettingsElement = doc->createElement( u"labelEngineSettings"_s );
3502 mLabelingEngineSettings->writeXml( *doc, labelEngineSettingsElement, context );
3503 qgisNode.appendChild( labelEngineSettingsElement );
3504 }
3505
3506 writeEntry( u"Gui"_s, u"/CanvasColorRedPart"_s, mBackgroundColor.red() );
3507 writeEntry( u"Gui"_s, u"/CanvasColorGreenPart"_s, mBackgroundColor.green() );
3508 writeEntry( u"Gui"_s, u"/CanvasColorBluePart"_s, mBackgroundColor.blue() );
3509
3510 writeEntry( u"Gui"_s, u"/SelectionColorRedPart"_s, mSelectionColor.red() );
3511 writeEntry( u"Gui"_s, u"/SelectionColorGreenPart"_s, mSelectionColor.green() );
3512 writeEntry( u"Gui"_s, u"/SelectionColorBluePart"_s, mSelectionColor.blue() );
3513 writeEntry( u"Gui"_s, u"/SelectionColorAlphaPart"_s, mSelectionColor.alpha() );
3514
3515 writeEntry( u"Measurement"_s, u"/DistanceUnits"_s, QgsUnitTypes::encodeUnit( mDistanceUnits ) );
3516 writeEntry( u"Measurement"_s, u"/AreaUnits"_s, QgsUnitTypes::encodeUnit( mAreaUnits ) );
3517 writeEntry( u"Measurement"_s, u"/ScaleMethod"_s, qgsEnumValueToKey( mScaleMethod ) );
3518
3519 // now add the optional extra properties
3520#if 0
3521 dump_( mProperties );
3522#endif
3523
3524 QgsDebugMsgLevel( u"there are %1 property scopes"_s.arg( static_cast<int>( mProperties.count() ) ), 2 );
3525
3526 if ( !mProperties.isEmpty() ) // only worry about properties if we
3527 // actually have any properties
3528 {
3529 mProperties.writeXml( u"properties"_s, qgisNode, *doc );
3530 }
3531
3532 QDomElement ddElem = doc->createElement( u"dataDefinedServerProperties"_s );
3533 mDataDefinedServerProperties.writeXml( ddElem, dataDefinedServerPropertyDefinitions() );
3534 qgisNode.appendChild( ddElem );
3535
3536 mMapThemeCollection->writeXml( *doc );
3537
3538 mTransformContext.writeXml( qgisNode, context );
3539
3540 QDomElement metadataElem = doc->createElement( u"projectMetadata"_s );
3541 mMetadata.writeMetadataXml( metadataElem, *doc );
3542 qgisNode.appendChild( metadataElem );
3543
3544 {
3545 const QDomElement annotationsElem = mAnnotationManager->writeXml( *doc, context );
3546 qgisNode.appendChild( annotationsElem );
3547 }
3548
3549 {
3550 const QDomElement layoutElem = mLayoutManager->writeXml( *doc );
3551 qgisNode.appendChild( layoutElem );
3552 }
3553
3554 {
3555 const QDomElement elevationProfileElem = mElevationProfileManager->writeXml( *doc, context );
3556 qgisNode.appendChild( elevationProfileElem );
3557 }
3558
3559 {
3560 const QDomElement selectiveMaskingSourceSetElem = mSelectiveMaskingSourceSetManager->writeXml( *doc, context );
3561 qgisNode.appendChild( selectiveMaskingSourceSetElem );
3562 }
3563
3564 {
3565 const QDomElement views3DElem = m3DViewsManager->writeXml( *doc );
3566 qgisNode.appendChild( views3DElem );
3567 }
3568
3569 {
3570 const QDomElement bookmarkElem = mBookmarkManager->writeXml( *doc );
3571 qgisNode.appendChild( bookmarkElem );
3572 }
3573
3574 {
3575 const QDomElement sensorElem = mSensorManager->writeXml( *doc );
3576 qgisNode.appendChild( sensorElem );
3577 }
3578
3579 {
3580 const QDomElement viewSettingsElem = mViewSettings->writeXml( *doc, context );
3581 qgisNode.appendChild( viewSettingsElem );
3582 }
3583
3584 {
3585 const QDomElement styleSettingsElem = mStyleSettings->writeXml( *doc, context );
3586 qgisNode.appendChild( styleSettingsElem );
3587 }
3588
3589 {
3590 const QDomElement timeSettingsElement = mTimeSettings->writeXml( *doc, context );
3591 qgisNode.appendChild( timeSettingsElement );
3592 }
3593
3594 {
3595 const QDomElement elevationPropertiesElement = mElevationProperties->writeXml( *doc, context );
3596 qgisNode.appendChild( elevationPropertiesElement );
3597 }
3598
3599 {
3600 const QDomElement displaySettingsElem = mDisplaySettings->writeXml( *doc, context );
3601 qgisNode.appendChild( displaySettingsElem );
3602 }
3603
3604 {
3605 const QDomElement gpsSettingsElem = mGpsSettings->writeXml( *doc, context );
3606 qgisNode.appendChild( gpsSettingsElem );
3607 }
3608
3609 // now wrap it up and ship it to the project file
3610 doc->normalize(); // XXX I'm not entirely sure what this does
3611
3612 // Create backup file
3613 if ( QFile::exists( fileName() ) )
3614 {
3615 QFile backupFile( u"%1~"_s.arg( filename ) );
3616 bool ok = true;
3617 ok &= backupFile.open( QIODevice::WriteOnly | QIODevice::Truncate );
3618 ok &= projectFile.open( QIODevice::ReadOnly );
3619
3620 QByteArray ba;
3621 while ( ok && !projectFile.atEnd() )
3622 {
3623 ba = projectFile.read( 10240 );
3624 ok &= backupFile.write( ba ) == ba.size();
3625 }
3626
3627 projectFile.close();
3628 backupFile.close();
3629
3630 if ( !ok )
3631 {
3632 setError( tr( "Unable to create backup file %1" ).arg( backupFile.fileName() ) );
3633 return false;
3634 }
3635
3636 const QFileInfo fi( fileName() );
3637 struct utimbuf tb = { static_cast<time_t>( fi.lastRead().toSecsSinceEpoch() ), static_cast<time_t>( fi.lastModified().toSecsSinceEpoch() ) };
3638 utime( backupFile.fileName().toUtf8().constData(), &tb );
3639 }
3640
3641 if ( !projectFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
3642 {
3643 projectFile.close(); // even though we got an error, let's make
3644 // sure it's closed anyway
3645
3646 setError( tr( "Unable to save to file %1" ).arg( projectFile.fileName() ) );
3647 return false;
3648 }
3649
3650 QTemporaryFile tempFile;
3651 bool ok = tempFile.open();
3652 if ( ok )
3653 {
3654 QTextStream projectFileStream( &tempFile );
3655 doc->save( projectFileStream, 2 ); // save as utf-8
3656 ok &= projectFileStream.pos() > -1;
3657
3658 ok &= tempFile.seek( 0 );
3659
3660 QByteArray ba;
3661 while ( ok && !tempFile.atEnd() )
3662 {
3663 ba = tempFile.read( 10240 );
3664 ok &= projectFile.write( ba ) == ba.size();
3665 }
3666
3667 ok &= projectFile.error() == QFile::NoError;
3668
3669 projectFile.close();
3670 }
3671
3672 tempFile.close();
3673
3674 if ( !ok )
3675 {
3676 setError( tr(
3677 "Unable to save to file %1. Your project "
3678 "may be corrupted on disk. Try clearing some space on the volume and "
3679 "check file permissions before pressing save again."
3680 )
3681 .arg( projectFile.fileName() ) );
3682 return false;
3683 }
3684
3685 setDirty( false ); // reset to pristine state
3686
3687 emit projectSaved();
3688 return true;
3689}
3690
3691bool QgsProject::writeEntry( const QString &scope, QString const &key, bool value )
3692{
3694
3695 bool propertiesModified;
3696 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3697
3698 if ( propertiesModified )
3699 setDirty( true );
3700
3701 return success;
3702}
3703
3704bool QgsProject::writeEntry( const QString &scope, const QString &key, double value )
3705{
3707
3708 bool propertiesModified;
3709 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3710
3711 if ( propertiesModified )
3712 setDirty( true );
3713
3714 return success;
3715}
3716
3717bool QgsProject::writeEntry( const QString &scope, QString const &key, int value )
3718{
3720
3721 bool propertiesModified;
3722 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3723
3724 if ( propertiesModified )
3725 setDirty( true );
3726
3727 return success;
3728}
3729
3730bool QgsProject::writeEntry( const QString &scope, const QString &key, const QString &value )
3731{
3733
3734 bool propertiesModified;
3735 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3736
3737 if ( propertiesModified )
3738 setDirty( true );
3739
3740 return success;
3741}
3742
3743bool QgsProject::writeEntry( const QString &scope, const QString &key, const QStringList &value )
3744{
3746
3747 bool propertiesModified;
3748 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3749
3750 if ( propertiesModified )
3751 setDirty( true );
3752
3753 return success;
3754}
3755
3756QStringList QgsProject::readListEntry( const QString &scope, const QString &key, const QStringList &def, bool *ok ) const
3757{
3758 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
3760
3761 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3762
3763 QVariant value;
3764
3765 if ( property )
3766 {
3767 value = property->value();
3768
3769 const bool valid = QMetaType::Type::QStringList == value.userType();
3770 if ( ok )
3771 *ok = valid;
3772
3773 if ( valid )
3774 {
3775 return value.toStringList();
3776 }
3777 }
3778 else if ( ok )
3779 *ok = false;
3780
3781
3782 return def;
3783}
3784
3785QString QgsProject::readEntry( const QString &scope, const QString &key, const QString &def, bool *ok ) const
3786{
3788
3789 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3790
3791 QVariant value;
3792
3793 if ( property )
3794 {
3795 value = property->value();
3796
3797 const bool valid = value.canConvert( QMetaType::Type::QString );
3798 if ( ok )
3799 *ok = valid;
3800
3801 if ( valid )
3802 return value.toString();
3803 }
3804 else if ( ok )
3805 *ok = false;
3806
3807 return def;
3808}
3809
3810int QgsProject::readNumEntry( const QString &scope, const QString &key, int def, bool *ok ) const
3811{
3813
3814 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3815
3816 QVariant value;
3817
3818 if ( property )
3819 {
3820 value = property->value();
3821 }
3822
3823 const bool valid = value.canConvert( QMetaType::Type::Int );
3824
3825 if ( ok )
3826 {
3827 *ok = valid;
3828 }
3829
3830 if ( valid )
3831 {
3832 return value.toInt();
3833 }
3834
3835 return def;
3836}
3837
3838double QgsProject::readDoubleEntry( const QString &scope, const QString &key, double def, bool *ok ) const
3839{
3841
3842 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3843 if ( property )
3844 {
3845 const QVariant value = property->value();
3846
3847 const bool valid = value.canConvert( QMetaType::Type::Double );
3848 if ( ok )
3849 *ok = valid;
3850
3851 if ( valid )
3852 return value.toDouble();
3853 }
3854 else if ( ok )
3855 *ok = false;
3856
3857 return def;
3858}
3859
3860bool QgsProject::readBoolEntry( const QString &scope, const QString &key, bool def, bool *ok ) const
3861{
3863
3864 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3865
3866 if ( property )
3867 {
3868 const QVariant value = property->value();
3869
3870 const bool valid = value.canConvert( QMetaType::Type::Bool );
3871 if ( ok )
3872 *ok = valid;
3873
3874 if ( valid )
3875 return value.toBool();
3876 }
3877 else if ( ok )
3878 *ok = false;
3879
3880 return def;
3881}
3882
3883bool QgsProject::removeEntry( const QString &scope, const QString &key )
3884{
3886
3887 if ( findKey_( scope, key, mProperties ) )
3888 {
3889 removeKey_( scope, key, mProperties );
3890 setDirty( true );
3891 }
3892
3893 return !findKey_( scope, key, mProperties );
3894}
3895
3896QStringList QgsProject::entryList( const QString &scope, const QString &key ) const
3897{
3899
3900 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3901
3902 QStringList entries;
3903
3904 if ( foundProperty )
3905 {
3906 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3907
3908 if ( propertyKey )
3909 {
3910 propertyKey->entryList( entries );
3911 }
3912 }
3913
3914 return entries;
3915}
3916
3917QStringList QgsProject::subkeyList( const QString &scope, const QString &key ) const
3918{
3920
3921 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3922
3923 QStringList entries;
3924
3925 if ( foundProperty )
3926 {
3927 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3928
3929 if ( propertyKey )
3930 {
3931 propertyKey->subkeyList( entries );
3932 }
3933 }
3934
3935 return entries;
3936}
3937
3939{
3941
3942 dump_( mProperties );
3943}
3944
3946{
3948
3949 QString filePath;
3950 switch ( filePathStorage() )
3951 {
3953 break;
3954
3956 {
3957 // for projects stored in a custom storage, we need to ask to the
3958 // storage for the path, if the storage returns an empty path
3959 // relative paths are not supported
3960 if ( QgsProjectStorage *storage = projectStorage() )
3961 {
3962 filePath = storage->filePath( mFile.fileName() );
3963 }
3964 else
3965 {
3966 filePath = fileName();
3967 }
3968 break;
3969 }
3970 }
3971
3972 return QgsPathResolver( filePath, mArchive->dir() );
3973}
3974
3975QString QgsProject::readPath( const QString &src ) const
3976{
3978
3979 return pathResolver().readPath( src );
3980}
3981
3982QString QgsProject::writePath( const QString &src ) const
3983{
3985
3986 return pathResolver().writePath( src );
3987}
3988
3989void QgsProject::setError( const QString &errorMessage )
3990{
3992
3993 mErrorMessage = errorMessage;
3994}
3995
3996QString QgsProject::error() const
3997{
3999
4000 return mErrorMessage;
4001}
4002
4003void QgsProject::clearError()
4004{
4006
4007 setError( QString() );
4008}
4009
4011{
4013
4014 mBadLayerHandler.reset( handler );
4015}
4016
4017QString QgsProject::layerIsEmbedded( const QString &id ) const
4018{
4020
4021 const QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
4022 if ( it == mEmbeddedLayers.constEnd() )
4023 {
4024 return QString();
4025 }
4026 return it.value().first;
4027}
4028
4029bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &projectFilePath, QList<QDomNode> &brokenNodes, bool saveFlag, Qgis::ProjectReadFlags flags )
4030{
4032
4034
4035 static QString sPrevProjectFilePath;
4036 static QDateTime sPrevProjectFileTimestamp;
4037 static QDomDocument sProjectDocument;
4038
4039 QString qgsProjectFile = projectFilePath;
4040 QgsProjectArchive archive;
4041 if ( projectFilePath.endsWith( ".qgz"_L1, Qt::CaseInsensitive ) )
4042 {
4043 archive.unzip( projectFilePath );
4044 qgsProjectFile = archive.projectFile();
4045 }
4046
4047 const QDateTime projectFileTimestamp = QFileInfo( projectFilePath ).lastModified();
4048
4049 if ( projectFilePath != sPrevProjectFilePath || projectFileTimestamp != sPrevProjectFileTimestamp )
4050 {
4051 sPrevProjectFilePath.clear();
4052
4053 QFile projectFile( qgsProjectFile );
4054 if ( !projectFile.open( QIODevice::ReadOnly ) )
4055 {
4056 return false;
4057 }
4058
4059 if ( !sProjectDocument.setContent( &projectFile ) )
4060 {
4061 return false;
4062 }
4063
4064 sPrevProjectFilePath = projectFilePath;
4065 sPrevProjectFileTimestamp = projectFileTimestamp;
4066 }
4067
4068 // does project store paths absolute or relative?
4069 bool useAbsolutePaths = true;
4070
4071 const QDomElement propertiesElem = sProjectDocument.documentElement().firstChildElement( u"properties"_s );
4072 if ( !propertiesElem.isNull() )
4073 {
4074 QDomElement e = propertiesElem.firstChildElement( u"Paths"_s );
4075 if ( e.isNull() )
4076 {
4077 e = propertiesElem.firstChildElement( u"properties"_s );
4078 while ( !e.isNull() && e.attribute( u"name"_s ) != "Paths"_L1 )
4079 e = e.nextSiblingElement( u"properties"_s );
4080
4081 e = e.firstChildElement( u"properties"_s );
4082 while ( !e.isNull() && e.attribute( u"name"_s ) != "Absolute"_L1 )
4083 e = e.nextSiblingElement( u"properties"_s );
4084 }
4085 else
4086 {
4087 e = e.firstChildElement( u"Absolute"_s );
4088 }
4089
4090 if ( !e.isNull() )
4091 {
4092 useAbsolutePaths = e.text().compare( "true"_L1, Qt::CaseInsensitive ) == 0;
4093 }
4094 }
4095
4096 QgsReadWriteContext embeddedContext;
4097 if ( !useAbsolutePaths )
4098 embeddedContext.setPathResolver( QgsPathResolver( projectFilePath ) );
4099 embeddedContext.setProjectTranslator( this );
4100 embeddedContext.setTransformContext( transformContext() );
4101 embeddedContext.setCurrentLayerId( layerId );
4102
4103 const QDomElement projectLayersElem = sProjectDocument.documentElement().firstChildElement( u"projectlayers"_s );
4104 if ( projectLayersElem.isNull() )
4105 {
4106 return false;
4107 }
4108
4109 QDomElement mapLayerElem = projectLayersElem.firstChildElement( u"maplayer"_s );
4110 while ( !mapLayerElem.isNull() )
4111 {
4112 // get layer id
4113 const QString id = mapLayerElem.firstChildElement( u"id"_s ).text();
4114 if ( id == layerId )
4115 {
4116 // layer can be embedded only once
4117 if ( mapLayerElem.attribute( u"embedded"_s ) == "1"_L1 )
4118 {
4119 return false;
4120 }
4121
4122 mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
4123
4124 if ( addLayer( mapLayerElem, brokenNodes, embeddedContext, flags ) )
4125 {
4126 return true;
4127 }
4128 else
4129 {
4130 mEmbeddedLayers.remove( layerId );
4131 return false;
4132 }
4133 }
4134 mapLayerElem = mapLayerElem.nextSiblingElement( u"maplayer"_s );
4135 }
4136
4137 return false;
4138}
4139
4140std::unique_ptr<QgsLayerTreeGroup> QgsProject::createEmbeddedGroup( const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers, Qgis::ProjectReadFlags flags )
4141{
4143
4144 QString qgsProjectFile = projectFilePath;
4145 QgsProjectArchive archive;
4146 if ( projectFilePath.endsWith( ".qgz"_L1, Qt::CaseInsensitive ) )
4147 {
4148 archive.unzip( projectFilePath );
4149 qgsProjectFile = archive.projectFile();
4150 }
4151
4152 // open project file, get layer ids in group, add the layers
4153 QFile projectFile( qgsProjectFile );
4154 if ( !projectFile.open( QIODevice::ReadOnly ) )
4155 {
4156 return nullptr;
4157 }
4158
4159 QDomDocument projectDocument;
4160 if ( !projectDocument.setContent( &projectFile ) )
4161 {
4162 return nullptr;
4163 }
4164
4165 QgsReadWriteContext context;
4166 context.setPathResolver( pathResolver() );
4167 context.setProjectTranslator( this );
4169
4170 auto root = std::make_unique< QgsLayerTreeGroup >();
4171
4172 QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( u"layer-tree-group"_s );
4173 if ( !layerTreeElem.isNull() )
4174 {
4175 root->readChildrenFromXml( layerTreeElem, context );
4176 }
4177 else
4178 {
4179 QgsLayerTreeUtils::readOldLegend( root.get(), projectDocument.documentElement().firstChildElement( u"legend"_s ) );
4180 }
4181
4182 QgsLayerTreeGroup *group = root->findGroup( groupName );
4183 if ( !group || group->customProperty( u"embedded"_s ).toBool() )
4184 {
4185 // embedded groups cannot be embedded again
4186 return nullptr;
4187 }
4188
4189 // clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
4190 std::unique_ptr< QgsLayerTreeGroup > newGroup( QgsLayerTree::toGroup( group->clone() ) );
4191 root.reset();
4192
4193 newGroup->setCustomProperty( u"embedded"_s, 1 );
4194 newGroup->setCustomProperty( u"embedded_project"_s, projectFilePath );
4195
4196 // set "embedded" to all children + load embedded layers
4197 mLayerTreeRegistryBridge->setEnabled( false );
4198 initializeEmbeddedSubtree( projectFilePath, newGroup.get(), flags );
4199 mLayerTreeRegistryBridge->setEnabled( true );
4200
4201 // consider the layers might be identify disabled in its project
4202 const QStringList constFindLayerIds = newGroup->findLayerIds();
4203 for ( const QString &layerId : constFindLayerIds )
4204 {
4205 QgsLayerTreeLayer *layer = newGroup->findLayer( layerId );
4206 if ( layer )
4207 {
4208 layer->resolveReferences( this );
4209 layer->setItemVisibilityChecked( !invisibleLayers.contains( layerId ) );
4210 }
4211 }
4212
4213 return newGroup;
4214}
4215
4216void QgsProject::initializeEmbeddedSubtree( const QString &projectFilePath, QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
4217{
4219
4220 const auto constChildren = group->children();
4221 for ( QgsLayerTreeNode *child : constChildren )
4222 {
4223 // all nodes in the subtree will have "embedded" custom property set
4224 child->setCustomProperty( u"embedded"_s, 1 );
4225
4226 if ( QgsLayerTree::isGroup( child ) )
4227 {
4228 initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ), flags );
4229 }
4230 else if ( QgsLayerTree::isLayer( child ) )
4231 {
4232 // load the layer into our project
4233 QList<QDomNode> brokenNodes;
4234 createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, false, flags );
4235 }
4236 }
4237}
4238
4245
4252
4254{
4256
4257 writeEntry( u"Digitizing"_s, u"/TopologicalEditing"_s, ( enabled ? 1 : 0 ) );
4259}
4260
4262{
4264
4265 return readNumEntry( u"Digitizing"_s, u"/TopologicalEditing"_s, 0 );
4266}
4267
4269{
4271
4272 if ( mDistanceUnits == unit )
4273 return;
4274
4275 mDistanceUnits = unit;
4276
4277 emit distanceUnitsChanged();
4278}
4279
4281{
4283
4284 if ( mAreaUnits == unit )
4285 return;
4286
4287 mAreaUnits = unit;
4288
4289 emit areaUnitsChanged();
4290}
4291
4293{
4295
4296 if ( mScaleMethod == method )
4297 return;
4298
4299 mScaleMethod = method;
4300
4301 emit scaleMethodChanged();
4302}
4303
4305{
4306 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4308
4309 if ( !mCachedHomePath.isEmpty() )
4310 return mCachedHomePath;
4311
4312 const QFileInfo pfi( fileName() );
4313
4314 if ( !mHomePath.isEmpty() )
4315 {
4316 const QFileInfo homeInfo( mHomePath );
4317 if ( !homeInfo.isRelative() )
4318 {
4319 mCachedHomePath = mHomePath;
4320 return mHomePath;
4321 }
4322 }
4323 else if ( !fileName().isEmpty() )
4324 {
4325 // If it's not stored in the file system, try to get the path from the storage
4326 if ( QgsProjectStorage *storage = projectStorage() )
4327 {
4328 const QString storagePath { storage->filePath( fileName() ) };
4329 if ( !storagePath.isEmpty() && QFileInfo::exists( storagePath ) )
4330 {
4331 mCachedHomePath = QFileInfo( storagePath ).path();
4332 return mCachedHomePath;
4333 }
4334 }
4335
4336 mCachedHomePath = pfi.path();
4337 return mCachedHomePath;
4338 }
4339
4340 if ( !pfi.exists() )
4341 {
4342 mCachedHomePath = mHomePath;
4343 return mHomePath;
4344 }
4345
4346 if ( !mHomePath.isEmpty() )
4347 {
4348 // path is relative to project file
4349 mCachedHomePath = QDir::cleanPath( pfi.path() + '/' + mHomePath );
4350 }
4351 else
4352 {
4353 mCachedHomePath = pfi.canonicalPath();
4354 }
4355 return mCachedHomePath;
4356}
4357
4359{
4361
4362 return mHomePath;
4363}
4364
4366{
4367 // because relation aggregate functions are not thread safe
4369
4370 return mRelationManager.get();
4371}
4372
4374{
4376
4377 return mLayoutManager.get();
4378}
4379
4381{
4383
4384 return mLayoutManager.get();
4385}
4386
4388{
4390
4391 return mElevationProfileManager.get();
4392}
4393
4395{
4397
4398 return mElevationProfileManager.get();
4399}
4400
4402{
4404
4405 return mSelectiveMaskingSourceSetManager.get();
4406}
4407
4409{
4411
4412 return mSelectiveMaskingSourceSetManager.get();
4413}
4414
4416{
4418
4419 return m3DViewsManager.get();
4420}
4421
4423{
4425
4426 return m3DViewsManager.get();
4427}
4428
4430{
4432
4433 return mBookmarkManager;
4434}
4435
4437{
4439
4440 return mBookmarkManager;
4441}
4442
4444{
4446
4447 return mSensorManager;
4448}
4449
4451{
4453
4454 return mSensorManager;
4455}
4456
4458{
4460
4461 return mViewSettings;
4462}
4463
4470
4472{
4474
4475 return mStyleSettings;
4476}
4477
4479{
4480 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4482
4483 return mStyleSettings;
4484}
4485
4487{
4489
4490 return mTimeSettings;
4491}
4492
4499
4501{
4503
4504 return mElevationProperties;
4505}
4506
4513
4515{
4517
4518 return mDisplaySettings;
4519}
4520
4522{
4524
4525 return mDisplaySettings;
4526}
4527
4529{
4531
4532 return mGpsSettings;
4533}
4534
4541
4543{
4545
4546 return mRootGroup.get();
4547}
4548
4550{
4552
4553 return mMapThemeCollection.get();
4554}
4555
4557{
4559
4560 return mAnnotationManager.get();
4561}
4562
4564{
4566
4567 return mAnnotationManager.get();
4568}
4569
4570void QgsProject::setNonIdentifiableLayers( const QList<QgsMapLayer *> &layers )
4571{
4573
4574 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
4575 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
4576 {
4577 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4578 continue;
4579
4580 if ( layers.contains( it.value() ) )
4581 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Identifiable );
4582 else
4583 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Identifiable );
4584 }
4585
4589}
4590
4591void QgsProject::setNonIdentifiableLayers( const QStringList &layerIds )
4592{
4594
4595 QList<QgsMapLayer *> nonIdentifiableLayers;
4596 nonIdentifiableLayers.reserve( layerIds.count() );
4597 for ( const QString &layerId : layerIds )
4598 {
4599 QgsMapLayer *layer = mapLayer( layerId );
4600 if ( layer )
4601 nonIdentifiableLayers << layer;
4602 }
4606}
4607
4609{
4611
4612 QStringList nonIdentifiableLayers;
4613
4614 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
4615 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
4616 {
4617 if ( !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4618 {
4619 nonIdentifiableLayers.append( it.value()->id() );
4620 }
4621 }
4622 return nonIdentifiableLayers;
4623}
4624
4626{
4628
4629 return mTransactionMode == Qgis::TransactionMode::AutomaticGroups;
4630}
4631
4633{
4635
4636 if ( autoTransaction && mTransactionMode == Qgis::TransactionMode::AutomaticGroups )
4637 return;
4638
4639 if ( !autoTransaction && mTransactionMode == Qgis::TransactionMode::Disabled )
4640 return;
4641
4642 if ( autoTransaction )
4644 else
4646
4647 updateTransactionGroups();
4648}
4649
4651{
4653
4654 return mTransactionMode;
4655}
4656
4658{
4660
4661 if ( transactionMode == mTransactionMode )
4662 return true;
4663
4664 // Check that all layer are not in edit mode
4665 const auto constLayers = mapLayers().values();
4666 for ( QgsMapLayer *layer : constLayers )
4667 {
4668 if ( layer->isEditable() )
4669 {
4670 QgsLogger::warning( tr( "Transaction mode can be changed only if all layers are not editable." ) );
4671 return false;
4672 }
4673 }
4674
4675 mTransactionMode = transactionMode;
4676 updateTransactionGroups();
4678 return true;
4679}
4680
4681QMap<QPair<QString, QString>, QgsTransactionGroup *> QgsProject::transactionGroups()
4682{
4684
4685 return mTransactionGroups;
4686}
4687
4688
4689//
4690// QgsMapLayerStore methods
4691//
4692
4693
4695{
4697
4698 return mLayerStore->count();
4699}
4700
4702{
4704
4705 return mLayerStore->validCount();
4706}
4707
4708QgsMapLayer *QgsProject::mapLayer( const QString &layerId ) const
4709{
4710 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
4712
4713 if ( mMainAnnotationLayer && layerId == mMainAnnotationLayer->id() )
4714 return mMainAnnotationLayer;
4715
4716 return mLayerStore->mapLayer( layerId );
4717}
4718
4719QList<QgsMapLayer *> QgsProject::mapLayersByName( const QString &layerName ) const
4720{
4722
4723 return mLayerStore->mapLayersByName( layerName );
4724}
4725
4726QList<QgsMapLayer *> QgsProject::mapLayersByShortName( const QString &shortName ) const
4727{
4729
4730 QList<QgsMapLayer *> layers;
4731 const auto constMapLayers { mLayerStore->mapLayers() };
4732 for ( const auto &l : constMapLayers )
4733 {
4734 if ( !l->serverProperties()->shortName().isEmpty() )
4735 {
4736 if ( l->serverProperties()->shortName() == shortName )
4737 layers << l;
4738 }
4739 else if ( l->name() == shortName )
4740 {
4741 layers << l;
4742 }
4743 }
4744 return layers;
4745}
4746
4747bool QgsProject::unzip( const QString &filename, Qgis::ProjectReadFlags flags )
4748{
4750
4751 clearError();
4752 auto archive = std::make_unique<QgsProjectArchive>();
4753
4754 // unzip the archive
4755 if ( !archive->unzip( filename ) )
4756 {
4757 setError( tr( "Unable to unzip file '%1'" ).arg( filename ) );
4758 return false;
4759 }
4760
4761 // test if zip provides a .qgs file
4762 if ( archive->projectFile().isEmpty() )
4763 {
4764 setError( tr( "Zip archive does not provide a project file" ) );
4765 return false;
4766 }
4767
4768 // Keep the archive
4769 releaseHandlesToProjectArchive();
4770 mArchive = std::move( archive );
4771
4772 // load auxiliary storage
4773 if ( !static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile().isEmpty() )
4774 {
4775 // database file is already a copy as it's been unzipped. So we don't open
4776 // auxiliary storage in copy mode in this case
4777 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile(), false );
4778 }
4779 else
4780 {
4781 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( *this );
4782 }
4783
4784 // read the project file
4785 if ( !readProjectFile( static_cast<QgsProjectArchive *>( mArchive.get() )->projectFile(), flags ) )
4786 {
4787 setError( tr( "Cannot read unzipped qgs project file" ) + u": "_s + error() );
4788 return false;
4789 }
4790
4791 // Remove the temporary .qgs file
4792 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4793
4794 return true;
4795}
4796
4797bool QgsProject::zip( const QString &filename )
4798{
4800
4801 clearError();
4802
4803 // save the current project in a temporary .qgs file
4804 auto archive = std::make_unique<QgsProjectArchive>();
4805 const QString baseName = QFileInfo( filename ).baseName();
4806 const QString qgsFileName = u"%1.qgs"_s.arg( baseName );
4807 QFile qgsFile( QDir( archive->dir() ).filePath( qgsFileName ) );
4808
4809 bool writeOk = false;
4810 if ( qgsFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
4811 {
4812 writeOk = writeProjectFile( qgsFile.fileName() );
4813 qgsFile.close();
4814 }
4815
4816 // stop here with an error message
4817 if ( !writeOk )
4818 {
4819 setError( tr( "Unable to write temporary qgs file" ) );
4820 return false;
4821 }
4822
4823 // save auxiliary storage
4824 const QFileInfo info( qgsFile );
4825 const QString asExt = u".%1"_s.arg( QgsAuxiliaryStorage::extension() );
4826 const QString asFileName = info.path() + QDir::separator() + info.completeBaseName() + asExt;
4827
4828 bool auxiliaryStorageSavedOk = true;
4829 if ( !saveAuxiliaryStorage( asFileName ) )
4830 {
4831 const QString err = mAuxiliaryStorage->errorString();
4832 setError(
4833 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 )
4834 );
4835 auxiliaryStorageSavedOk = false;
4836
4837 // fixes the current archive and keep the previous version of qgd
4838 if ( !mArchive->exists() )
4839 {
4840 releaseHandlesToProjectArchive();
4841 mArchive = std::make_unique< QgsProjectArchive >();
4842 mArchive->unzip( mFile.fileName() );
4843 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4844
4845 const QString auxiliaryStorageFile = static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile();
4846 if ( !auxiliaryStorageFile.isEmpty() )
4847 {
4848 archive->addFile( auxiliaryStorageFile );
4849 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( auxiliaryStorageFile, false );
4850 }
4851 }
4852 }
4853 else
4854 {
4855 // in this case, an empty filename means that the auxiliary database is
4856 // empty, so we don't want to save it
4857 if ( QFile::exists( asFileName ) )
4858 {
4859 archive->addFile( asFileName );
4860 }
4861 }
4862
4863 // create the archive
4864 archive->addFile( qgsFile.fileName() );
4865
4866 // Add all other files
4867 const QStringList &files = mArchive->files();
4868 for ( const QString &file : files )
4869 {
4870 if ( !file.endsWith( ".qgs"_L1, Qt::CaseInsensitive ) && !file.endsWith( asExt, Qt::CaseInsensitive ) )
4871 {
4872 archive->addFile( file );
4873 }
4874 }
4875
4876 // zip
4877 bool zipOk = true;
4878 if ( !archive->zip( filename ) )
4879 {
4880 setError( tr( "Unable to perform zip" ) );
4881 zipOk = false;
4882 }
4883
4884 return auxiliaryStorageSavedOk && zipOk;
4885}
4886
4888{
4890
4891 return QgsZipUtils::isZipFile( mFile.fileName() );
4892}
4893
4894QList<QgsMapLayer *> QgsProject::addMapLayers( const QList<QgsMapLayer *> &layers, bool addToLegend, bool takeOwnership )
4895{
4897
4898 const QList<QgsMapLayer *> myResultList { mLayerStore->addMapLayers( layers, takeOwnership ) };
4899 if ( !myResultList.isEmpty() )
4900 {
4901 // Update transform context
4902 for ( auto &l : myResultList )
4903 {
4904 l->setTransformContext( transformContext() );
4905 }
4906 if ( addToLegend )
4907 {
4908 emit legendLayersAdded( myResultList );
4909 }
4910 else
4911 {
4912 emit layersAddedWithoutLegend( myResultList );
4913 }
4914 }
4915
4916 if ( mAuxiliaryStorage )
4917 {
4918 for ( QgsMapLayer *mlayer : myResultList )
4919 {
4920 if ( mlayer->type() != Qgis::LayerType::Vector )
4921 continue;
4922
4923 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mlayer );
4924 if ( vl )
4925 {
4926 vl->loadAuxiliaryLayer( *mAuxiliaryStorage );
4927 }
4928 }
4929 }
4930
4931 mProjectScope.reset();
4932
4933 return myResultList;
4934}
4935
4936QgsMapLayer *QgsProject::addMapLayer( QgsMapLayer *layer, bool addToLegend, bool takeOwnership )
4937{
4939
4940 QList<QgsMapLayer *> addedLayers;
4941 addedLayers = addMapLayers( QList<QgsMapLayer *>() << layer, addToLegend, takeOwnership );
4942 return addedLayers.isEmpty() ? nullptr : addedLayers[0];
4943}
4944
4945void QgsProject::removeAuxiliaryLayer( const QgsMapLayer *ml )
4946{
4948
4949 if ( !ml || ml->type() != Qgis::LayerType::Vector )
4950 return;
4951
4952 const QgsVectorLayer *vl = qobject_cast<const QgsVectorLayer *>( ml );
4953 if ( vl && vl->auxiliaryLayer() )
4954 {
4955 const QgsDataSourceUri uri( vl->auxiliaryLayer()->source() );
4957 }
4958}
4959
4960void QgsProject::removeMapLayers( const QStringList &layerIds )
4961{
4963
4964 for ( const auto &layerId : layerIds )
4965 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4966
4967 mProjectScope.reset();
4968 mLayerStore->removeMapLayers( layerIds );
4969}
4970
4971void QgsProject::removeMapLayers( const QList<QgsMapLayer *> &layers )
4972{
4974
4975 for ( const auto &layer : layers )
4976 removeAuxiliaryLayer( layer );
4977
4978 mProjectScope.reset();
4979 mLayerStore->removeMapLayers( layers );
4980}
4981
4982void QgsProject::removeMapLayer( const QString &layerId )
4983{
4985
4986 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4987 mProjectScope.reset();
4988 mLayerStore->removeMapLayer( layerId );
4989}
4990
4992{
4994
4995 removeAuxiliaryLayer( layer );
4996 mProjectScope.reset();
4997 mLayerStore->removeMapLayer( layer );
4998}
4999
5001{
5003
5004 mProjectScope.reset();
5005 return mLayerStore->takeMapLayer( layer );
5006}
5007
5009{
5011
5012 return mMainAnnotationLayer;
5013}
5014
5016{
5018
5019 if ( mLayerStore->count() == 0 )
5020 return;
5021
5022 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
5023 mProjectScope.reset();
5024 mLayerStore->removeAllMapLayers();
5025
5026 snapSingleBlocker.release();
5027 mSnappingConfig.clearIndividualLayerSettings();
5028 if ( !mBlockSnappingUpdates )
5029 emit snappingConfigChanged( mSnappingConfig );
5030}
5031
5033{
5035
5036 const QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
5037 QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin();
5038 for ( ; it != layers.constEnd(); ++it )
5039 {
5040 it.value()->reload();
5041 }
5042}
5043
5044QMap<QString, QgsMapLayer *> QgsProject::mapLayers( const bool validOnly ) const
5045{
5046 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
5048
5049 return validOnly ? mLayerStore->validMapLayers() : mLayerStore->mapLayers();
5050}
5051
5052QgsTransactionGroup *QgsProject::transactionGroup( const QString &providerKey, const QString &connString )
5053{
5055
5056 return mTransactionGroups.value( qMakePair( providerKey, connString ) );
5057}
5058
5065
5067{
5069
5071
5072 // TODO QGIS 5.0 -- remove this method, and place it somewhere in app (where it belongs)
5073 // 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)
5074 if ( mSettings.value( u"/projections/unknownCrsBehavior"_s, u"NoAction"_s, QgsSettings::App ).toString() == u"UseProjectCrs"_s
5075 || mSettings.value( u"/projections/unknownCrsBehavior"_s, 0, QgsSettings::App ).toString() == "2"_L1 )
5076 {
5077 // for new layers if the new layer crs method is set to either prompt or use project, then we use the project crs
5078 defaultCrs = crs();
5079 }
5080 else
5081 {
5082 // global crs
5083 const QString layerDefaultCrs = mSettings.value( u"/Projections/layerDefaultCrs"_s, u"EPSG:4326"_s ).toString();
5084 defaultCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( layerDefaultCrs );
5085 }
5086
5087 return defaultCrs;
5088}
5089
5096
5103
5104bool QgsProject::saveAuxiliaryStorage( const QString &filename )
5105{
5107
5108 const QMap<QString, QgsMapLayer *> layers = mapLayers();
5109 bool empty = true;
5110 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5111 {
5112 if ( it.value()->type() != Qgis::LayerType::Vector )
5113 continue;
5114
5115 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
5116 if ( vl && vl->auxiliaryLayer() )
5117 {
5118 vl->auxiliaryLayer()->save();
5119 empty &= vl->auxiliaryLayer()->auxiliaryFields().isEmpty();
5120 }
5121 }
5122
5123 if ( !mAuxiliaryStorage->exists( *this ) && empty )
5124 {
5125 return true; // it's not an error
5126 }
5127 else if ( !filename.isEmpty() )
5128 {
5129 return mAuxiliaryStorage->saveAs( filename );
5130 }
5131 else
5132 {
5133 return mAuxiliaryStorage->saveAs( *this );
5134 }
5135}
5136
5137QgsPropertiesDefinition &QgsProject::dataDefinedServerPropertyDefinitions()
5138{
5139 static QgsPropertiesDefinition sPropertyDefinitions {
5140 { static_cast< int >( QgsProject::DataDefinedServerProperty::WMSOnlineResource ), QgsPropertyDefinition( "WMSOnlineResource", QObject::tr( "WMS Online Resource" ), QgsPropertyDefinition::String ) },
5141 };
5142 return sPropertyDefinitions;
5143}
5144
5150
5152{
5154
5155 return mAuxiliaryStorage.get();
5156}
5157
5159{
5161
5162 return mAuxiliaryStorage.get();
5163}
5164
5165QString QgsProject::createAttachedFile( const QString &nameTemplate )
5166{
5168
5169 const QDir archiveDir( mArchive->dir() );
5170 QTemporaryFile tmpFile( archiveDir.filePath( "XXXXXX_" + nameTemplate ), this );
5171 tmpFile.setAutoRemove( false );
5172 if ( !tmpFile.open() )
5173 {
5174 setError( tr( "Unable to open %1" ).arg( tmpFile.fileName() ) );
5175 return QString();
5176 }
5177 mArchive->addFile( tmpFile.fileName() );
5178 return tmpFile.fileName();
5179}
5180
5181QStringList QgsProject::attachedFiles() const
5182{
5184
5185 QStringList attachments;
5186 const QString baseName = QFileInfo( fileName() ).baseName();
5187 const QStringList files = mArchive->files();
5188 attachments.reserve( files.size() );
5189 for ( const QString &file : files )
5190 {
5191 if ( QFileInfo( file ).baseName() != baseName )
5192 {
5193 attachments.append( file );
5194 }
5195 }
5196 return attachments;
5197}
5198
5199bool QgsProject::removeAttachedFile( const QString &path )
5200{
5202
5203 return mArchive->removeFile( path );
5204}
5205
5206QString QgsProject::attachmentIdentifier( const QString &attachedFile ) const
5207{
5209
5210 return u"attachment:///%1"_s.arg( QFileInfo( attachedFile ).fileName() );
5211}
5212
5213QString QgsProject::resolveAttachmentIdentifier( const QString &identifier ) const
5214{
5216
5217 if ( identifier.startsWith( "attachment:///"_L1 ) )
5218 {
5219 return QDir( mArchive->dir() ).absoluteFilePath( identifier.mid( 14 ) );
5220 }
5221 return QString();
5222}
5223
5225{
5226 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
5228
5229 return mMetadata;
5230}
5231
5233{
5235
5236 if ( metadata == mMetadata )
5237 return;
5238
5239 mMetadata = metadata;
5240 mProjectScope.reset();
5241
5242 emit metadataChanged();
5243 emit titleChanged();
5244
5245 setDirty( true );
5246}
5247
5248QSet<QgsMapLayer *> QgsProject::requiredLayers() const
5249{
5251
5252 QSet<QgsMapLayer *> requiredLayers;
5253
5254 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
5255 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
5256 {
5257 if ( !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
5258 {
5259 requiredLayers.insert( it.value() );
5260 }
5261 }
5262 return requiredLayers;
5263}
5264
5265void QgsProject::setRequiredLayers( const QSet<QgsMapLayer *> &layers )
5266{
5268
5269 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
5270 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
5271 {
5272 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
5273 continue;
5274
5275 if ( layers.contains( it.value() ) )
5276 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Removable );
5277 else
5278 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Removable );
5279 }
5280}
5281
5283{
5285
5286 // save colors to project
5287 QStringList customColors;
5288 QStringList customColorLabels;
5289
5290 QgsNamedColorList::const_iterator colorIt = colors.constBegin();
5291 for ( ; colorIt != colors.constEnd(); ++colorIt )
5292 {
5293 const QString color = QgsColorUtils::colorToString( ( *colorIt ).first );
5294 const QString label = ( *colorIt ).second;
5295 customColors.append( color );
5296 customColorLabels.append( label );
5297 }
5298 writeEntry( u"Palette"_s, u"/Colors"_s, customColors );
5299 writeEntry( u"Palette"_s, u"/Labels"_s, customColorLabels );
5300 mProjectScope.reset();
5301 emit projectColorsChanged();
5302}
5303
5304void QgsProject::setBackgroundColor( const QColor &color )
5305{
5307
5308 if ( mBackgroundColor == color )
5309 return;
5310
5311 mBackgroundColor = color;
5313}
5314
5316{
5318
5319 return mBackgroundColor;
5320}
5321
5322void QgsProject::setSelectionColor( const QColor &color )
5323{
5325
5326 if ( mSelectionColor == color )
5327 return;
5328
5329 mSelectionColor = color;
5330 emit selectionColorChanged();
5331}
5332
5334{
5336
5337 return mSelectionColor;
5338}
5339
5340void QgsProject::setMapScales( const QVector<double> &scales )
5341{
5343
5344 mViewSettings->setMapScales( scales );
5345}
5346
5347QVector<double> QgsProject::mapScales() const
5348{
5350
5351 return mViewSettings->mapScales();
5352}
5353
5355{
5357
5358 mViewSettings->setUseProjectScales( enabled );
5359}
5360
5362{
5364
5365 return mViewSettings->useProjectScales();
5366}
5367
5368void QgsProject::generateTsFile( const QString &locale )
5369{
5371
5372 QgsTranslationContext translationContext;
5373 translationContext.setProject( this );
5374 translationContext.setFileName( u"%1/%2.ts"_s.arg( absolutePath(), baseName() ) );
5375
5376 QgsApplication::instance()->collectTranslatableObjects( &translationContext );
5377
5378 translationContext.writeTsFile( locale );
5379}
5380
5381QString QgsProject::translate( const QString &context, const QString &sourceText, const char *disambiguation, int n ) const
5382{
5384
5385 if ( !mTranslator )
5386 {
5387 return sourceText;
5388 }
5389
5390 QString result = mTranslator->translate( context.toUtf8(), sourceText.toUtf8(), disambiguation, n );
5391
5392 if ( result.isEmpty() )
5393 {
5394 return sourceText;
5395 }
5396 return result;
5397}
5398
5400{
5402
5403 const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
5404 if ( !layers.empty() )
5405 {
5406 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5407 {
5408 // NOTE: if visitEnter returns false it means "don't visit this layer", not "abort all further visitations"
5409 if ( visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
5410 {
5411 if ( !( ( *it )->accept( visitor ) ) )
5412 return false;
5413
5414 if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
5415 return false;
5416 }
5417 }
5418 }
5419
5420 if ( !mLayoutManager->accept( visitor ) )
5421 return false;
5422
5423 if ( !mAnnotationManager->accept( visitor ) )
5424 return false;
5425
5426 return true;
5427}
5428
5430{
5432
5433 const QString macros = readEntry( u"Macros"_s, u"/pythonCode"_s, QString() );
5434 if ( !macros.isEmpty() )
5435 {
5436 QgsEmbeddedScriptEntity entity( Qgis::EmbeddedScriptType::Macro, tr( "Macros" ), macros );
5437 if ( !visitor->visitEmbeddedScript( entity, context ) )
5438 {
5439 return false;
5440 }
5441 }
5442
5443 const QString expressionFunctions = readEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s );
5444 if ( !expressionFunctions.isEmpty() )
5445 {
5446 QgsEmbeddedScriptEntity entity( Qgis::EmbeddedScriptType::ExpressionFunction, tr( "Expression functions" ), expressionFunctions );
5447 if ( !visitor->visitEmbeddedScript( entity, context ) )
5448 {
5449 return false;
5450 }
5451 }
5452
5453 const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
5454 if ( !layers.empty() )
5455 {
5456 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5457 {
5458 if ( !( ( *it )->accept( visitor, context ) ) )
5459 {
5460 return false;
5461 }
5462 }
5463 }
5464
5465 return true;
5466}
5467
5469{
5470 return mElevationShadingRenderer;
5471}
5472
5473void QgsProject::loadProjectFlags( const QDomDocument *doc )
5474{
5476
5477 QDomElement element = doc->documentElement().firstChildElement( u"projectFlags"_s );
5479 if ( !element.isNull() )
5480 {
5481 flags = qgsFlagKeysToValue( element.attribute( u"set"_s ), Qgis::ProjectFlags() );
5482 }
5483 else
5484 {
5485 // older project compatibility
5486 element = doc->documentElement().firstChildElement( u"evaluateDefaultValues"_s );
5487 if ( !element.isNull() )
5488 {
5489 if ( element.attribute( u"active"_s, u"0"_s ).toInt() == 1 )
5491 }
5492
5493 // Read trust layer metadata config in the project
5494 element = doc->documentElement().firstChildElement( u"trust"_s );
5495 if ( !element.isNull() )
5496 {
5497 if ( element.attribute( u"active"_s, u"0"_s ).toInt() == 1 )
5499 }
5500 }
5501
5502 setFlags( flags );
5503}
5504
5506{
5508 {
5510 {
5511 const QString projectFunctions = readEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s, QString() );
5512 if ( !projectFunctions.isEmpty() )
5513 {
5514 QgsPythonRunner::run( projectFunctions );
5515 return true;
5516 }
5517 }
5518 }
5519 return false;
5520}
5521
5523{
5525 {
5526 QgsPythonRunner::run( "qgis.utils.clean_project_expression_functions()" );
5527 }
5528}
5529
5531
5532QHash< QString, QColor > loadColorsFromProject( const QgsProject *project )
5533{
5534 QHash< QString, QColor > colors;
5535
5536 //build up color list from project. Do this in advance for speed
5537 QStringList colorStrings = project->readListEntry( u"Palette"_s, u"/Colors"_s );
5538 const QStringList colorLabels = project->readListEntry( u"Palette"_s, u"/Labels"_s );
5539
5540 //generate list from custom colors
5541 int colorIndex = 0;
5542 for ( QStringList::iterator it = colorStrings.begin(); it != colorStrings.end(); ++it )
5543 {
5544 const QColor color = QgsColorUtils::colorFromString( *it );
5545 QString label;
5546 if ( colorLabels.length() > colorIndex )
5547 {
5548 label = colorLabels.at( colorIndex );
5549 }
5550
5551 colors.insert( label.toLower(), color );
5552 colorIndex++;
5553 }
5554
5555 return colors;
5556}
5557
5558
5559GetNamedProjectColor::GetNamedProjectColor( const QgsProject *project )
5560 : QgsScopedExpressionFunction( u"project_color"_s, 1, u"Color"_s )
5561{
5562 if ( !project )
5563 return;
5564
5565 mColors = loadColorsFromProject( project );
5566}
5567
5568GetNamedProjectColor::GetNamedProjectColor( const QHash<QString, QColor> &colors )
5569 : QgsScopedExpressionFunction( u"project_color"_s, 1, u"Color"_s )
5570 , mColors( colors )
5571{}
5572
5573QVariant GetNamedProjectColor::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5574{
5575 const QString colorName = values.at( 0 ).toString().toLower();
5576 if ( mColors.contains( colorName ) )
5577 {
5578 return u"%1,%2,%3"_s.arg( mColors.value( colorName ).red() ).arg( mColors.value( colorName ).green() ).arg( mColors.value( colorName ).blue() );
5579 }
5580 else
5581 return QVariant();
5582}
5583
5584QgsScopedExpressionFunction *GetNamedProjectColor::clone() const
5585{
5586 return new GetNamedProjectColor( mColors );
5587}
5588
5589GetNamedProjectColorObject::GetNamedProjectColorObject( const QgsProject *project )
5590 : QgsScopedExpressionFunction( u"project_color_object"_s, 1, u"Color"_s )
5591{
5592 if ( !project )
5593 return;
5594
5595 mColors = loadColorsFromProject( project );
5596}
5597
5598GetNamedProjectColorObject::GetNamedProjectColorObject( const QHash<QString, QColor> &colors )
5599 : QgsScopedExpressionFunction( u"project_color_object"_s, 1, u"Color"_s )
5600 , mColors( colors )
5601{}
5602
5603QVariant GetNamedProjectColorObject::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5604{
5605 const QString colorName = values.at( 0 ).toString().toLower();
5606 if ( mColors.contains( colorName ) )
5607 {
5608 return mColors.value( colorName );
5609 }
5610 else
5611 return QVariant();
5612}
5613
5614QgsScopedExpressionFunction *GetNamedProjectColorObject::clone() const
5615{
5616 return new GetNamedProjectColorObject( mColors );
5617}
5618
5619// ----------------
5620
5621GetSensorData::GetSensorData( const QMap<QString, QgsAbstractSensor::SensorData> &sensorData )
5622 : QgsScopedExpressionFunction( u"sensor_data"_s, QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( u"name"_s ) << QgsExpressionFunction::Parameter( u"expiration"_s, true, 0 ), u"Sensors"_s )
5623 , mSensorData( sensorData )
5624{}
5625
5626QVariant GetSensorData::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5627{
5628 const QString sensorName = values.at( 0 ).toString();
5629 const int expiration = values.at( 1 ).toInt();
5630 const qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
5631 if ( mSensorData.contains( sensorName ) )
5632 {
5633 if ( expiration <= 0 || ( timestamp - mSensorData[sensorName].lastTimestamp.toMSecsSinceEpoch() ) < expiration )
5634 {
5635 return mSensorData[sensorName].lastValue;
5636 }
5637 }
5638
5639 return QVariant();
5640}
5641
5642QgsScopedExpressionFunction *GetSensorData::clone() const
5643{
5644 return new GetSensorData( mSensorData );
5645}
@ ExpressionFunction
Project macros.
Definition qgis.h:465
@ DontLoad3DViews
Skip loading 3D views.
Definition qgis.h:4524
@ DontStoreOriginalStyles
Skip the initial XML style storage for layers. Useful for minimising project load times in non-intera...
Definition qgis.h:4523
@ ForceReadOnlyLayers
Open layers in a read-only mode.
Definition qgis.h:4526
@ TrustLayerMetadata
Trust layer metadata. Improves project read time. Do not use it if layers' extent is not fixed during...
Definition qgis.h:4521
@ DontUpgradeAnnotations
Don't upgrade old annotation items to QgsAnnotationItem.
Definition qgis.h:4527
@ DontLoadLayouts
Don't load print layouts. Improves project read time if layouts are not required, and allows projects...
Definition qgis.h:4519
@ DontResolveLayers
Don't resolve layer paths (i.e. don't load any layer content). Dramatically improves project read tim...
Definition qgis.h:4517
static QString version()
Version string.
Definition qgis.cpp:682
@ Trusted
The project trust has not yet been determined by the user.
Definition qgis.h:478
QFlags< ProjectCapability > ProjectCapabilities
Flags which control project capabilities.
Definition qgis.h:4560
QFlags< ProjectReadFlag > ProjectReadFlags
Project load flags.
Definition qgis.h:4538
DistanceUnit
Units of distance.
Definition qgis.h:5269
@ Meters
Meters.
Definition qgis.h:5270
FilePathType
File path types.
Definition qgis.h:1795
@ Relative
Relative path.
Definition qgis.h:1797
@ Absolute
Absolute path.
Definition qgis.h:1796
TransactionMode
Transaction mode.
Definition qgis.h:4138
@ AutomaticGroups
Automatic transactional editing means that on supported datasources (postgres and geopackage database...
Definition qgis.h:4140
@ BufferedGroups
Buffered transactional editing means that all editable layers in the buffered transaction group are t...
Definition qgis.h:4141
@ Disabled
Edits are buffered locally and sent to the provider when toggling layer editing mode.
Definition qgis.h:4139
AreaUnit
Units of area.
Definition qgis.h:5346
@ SquareMeters
Square meters.
Definition qgis.h:5347
@ Critical
Critical/error message.
Definition qgis.h:163
@ Success
Used for reporting a successful operation.
Definition qgis.h:164
@ Vertical
Vertical CRS.
Definition qgis.h:2467
@ Temporal
Temporal CRS.
Definition qgis.h:2470
@ Compound
Compound (horizontal + vertical) CRS.
Definition qgis.h:2469
@ Projected
Projected CRS.
Definition qgis.h:2468
@ Other
Other type.
Definition qgis.h:2473
@ Bound
Bound CRS.
Definition qgis.h:2472
@ DerivedProjected
Derived projected CRS.
Definition qgis.h:2474
@ Unknown
Unknown type.
Definition qgis.h:2462
@ Engineering
Engineering CRS.
Definition qgis.h:2471
@ Geographic3d
3D geopraphic CRS
Definition qgis.h:2466
@ Geodetic
Geodetic CRS.
Definition qgis.h:2463
@ Geographic2d
2D geographic CRS
Definition qgis.h:2465
@ Geocentric
Geocentric CRS.
Definition qgis.h:2464
AvoidIntersectionsMode
Flags which control how intersections of pre-existing feature are handled when digitizing new feature...
Definition qgis.h:4487
@ AvoidIntersectionsLayers
Overlap with features from a specified list of layers when digitizing new features not allowed.
Definition qgis.h:4490
@ AllowIntersections
Overlap with any feature allowed when digitizing new features.
Definition qgis.h:4488
ProjectFlag
Flags which control the behavior of QgsProjects.
Definition qgis.h:4272
@ RememberLayerEditStatusBetweenSessions
If set, then any layers set to be editable will be stored in the project and immediately made editabl...
Definition qgis.h:4276
@ EvaluateDefaultValuesOnProviderSide
If set, default values for fields will be evaluated on the provider side when features from the proje...
Definition qgis.h:4273
@ TrustStoredLayerStatistics
If set, then layer statistics (such as the layer extent) will be read from values stored in the proje...
Definition qgis.h:4274
@ Polygon
Polygons.
Definition qgis.h:382
QFlags< DataProviderReadFlag > DataProviderReadFlags
Flags which control data provider construction.
Definition qgis.h:512
LayerType
Types of layers that can be added to a map.
Definition qgis.h:206
@ Group
Composite group layer. Added in QGIS 3.24.
Definition qgis.h:214
@ Plugin
Plugin based layer.
Definition qgis.h:209
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:215
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
Definition qgis.h:212
@ Vector
Vector layer.
Definition qgis.h:207
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:211
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:210
@ Raster
Raster layer.
Definition qgis.h:208
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Definition qgis.h:213
ScaleCalculationMethod
Scale calculation logic.
Definition qgis.h:5546
@ HorizontalMiddle
Calculate horizontally, across midle of map.
Definition qgis.h:5548
@ SkipCredentialsRequest
Skip credentials if the provided one are not valid, let the provider be invalid, avoiding to block th...
Definition qgis.h:499
@ ParallelThreadLoading
Provider is created in a parallel thread than the one where it will live.
Definition qgis.h:501
QFlags< ProjectFlag > ProjectFlags
Definition qgis.h:4280
@ Container
A container.
Definition qgis.h:5907
@ Marker
Marker symbol.
Definition qgis.h:637
@ Line
Line symbol.
Definition qgis.h:638
@ Fill
Fill symbol.
Definition qgis.h:639
static QString geoNone()
Constant that holds the string representation for "No ellipse/No CRS".
Definition qgis.h:6806
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
Definition qgis.h:2579
QMap< QString, QStringList > KeywordMap
Map of vocabulary string to keyword list.
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.
QList< QgsAction > actions(const QString &actionScope=QString()) const
Returns a list of actions that are available in the given action scope.
Utility class that encapsulates an action based on vector attributes.
Definition qgsaction.h:38
Represents a map layer containing a set of georeferenced annotations, e.g.
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.
Manages zip/unzip operations for an archive.
Definition qgsarchive.h:36
A container for attribute editors, used to group them visually in the attribute form if it is set to ...
QList< QgsAttributeEditorElement * > children() const
Gets a list of the children elements of this container.
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.
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.
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.
Represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QString toProj() const
Returns a Proj string representation of this CRS.
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
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.
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.
static QgsCoordinateReferenceSystem fromSrsId(long srsId)
Creates a CRS from a specified QGIS SRS ID.
Contains information about the context in which a coordinate transform is executed.
void readSettings()
Reads the context's state from application settings.
Abstract base class for spatial data provider implementations.
@ EvaluateDefaultValues
Evaluate default values on provider side when calling QgsVectorDataProvider::defaultValue( int index ...
Stores the component parts of a data source URI (e.g.
QgsAttributeEditorContainer * invisibleRootContainer()
Gets the invisible root container for the drag and drop designer form (EditorLayout::TabLayout).
Manages storage of a set of elevation profiles.
Renders elevation shading on an image with different methods (eye dome lighting, hillshading,...
A embedded script entity for QgsObjectEntityVisitorInterface.
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...
An abstract base class for defining QgsExpression functions.
An expression node for expression functions.
Handles parsing and evaluation of expressions (formerly called "search strings").
virtual QgsLegendSymbolList legendSymbolItems() const
Returns a list of symbology items for the legend.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Container of fields for a vector layer.
Definition qgsfields.h:46
bool isEmpty
Definition qgsfields.h:49
Stores global configuration for labeling engine.
Layer tree group node serves as a container for layers and further groups.
QgsLayerTreeGroup * findGroup(const QString &name)
Find group node with specified name.
void readChildrenFromXml(const QDomElement &element, const QgsReadWriteContext &context)
Read children from XML and append them to the group.
QString name() const override
Returns the group's name.
void insertChildNodes(int index, const QList< QgsLayerTreeNode * > &nodes)
Insert existing nodes at specified position.
QgsLayerTreeGroup * clone() const override
Returns a clone of the group.
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).
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.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
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).
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.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
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.
Manages storage of a set of layouts.
Stores information about one class/rule of a vector layer renderer in a unified way that can be used ...
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:83
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 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...
QString id
Definition qgsmaplayer.h:86
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:93
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.
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, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
An interface for classes which can visit various object entity (e.g.
virtual bool visitEmbeddedScript(const QgsEmbeddedScriptEntity &entity, const QgsObjectVisitorContext &context)
Called when the visitor will visit an embedded script entity.
A QgsObjectEntityVisitorInterface context object.
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.
Allows managing the zip/unzip actions on project files.
Definition qgsarchive.h:111
QString projectFile() const
Returns the current .qgs project 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 files.
Contains settings and properties relating to how a QgsProject should display values such as map coord...
Contains elevation properties for a QgsProject.
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.
A structured metadata store for a project.
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.
void subkeyList(QStringList &entries) const
Returns any sub-keys contained by this property which themselves contain other keys.
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.
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.
void setDefaultSymbol(Qgis::SymbolType symbolType, QgsSymbol *symbol)
Sets the project default symbol for a given type.
void setRandomizeDefaultSymbolColor(bool randomized)
Sets whether the default symbol fill color is randomized.
void setDefaultColorRamp(QgsColorRamp *colorRamp)
Sets the project default color ramp.
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...
static Qgis::ProjectTrustStatus checkUserTrust(QgsProject *project)
Returns the current trust status of the specified project.
Describes 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,...
void mapScalesChanged()
Emitted when the list of custom project map scales changes.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:113
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:124
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:131
void layersAddedWithoutLegend(const QList< QgsMapLayer * > &layers)
Emitted when layers were added to the registry without adding to the legend.
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:215
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:129
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:121
void fileNameChanged()
Emitted when the file name of the project changes.
void titleChanged()
Emitted when the title of the project changes.
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QString title
Definition qgsproject.h:116
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...
void setScaleMethod(Qgis::ScaleCalculationMethod method)
Sets the method to use for map scale calculations for the project.
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:132
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:123
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.
std::unique_ptr< 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.
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 scaleMethodChanged()
Emitted when the project's scale method is 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:126
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:122
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 specified scope and key.
Qgis::TransactionMode transactionMode
Definition qgsproject.h:134
QgsAnnotationManager * annotationManager()
Returns pointer to the project's annotation manager.
QgsProjectDisplaySettings * displaySettings
Definition qgsproject.h:133
QgsProjectMetadata metadata
Definition qgsproject.h:127
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:120
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:117
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:119
QgsMapLayer * addMapLayer(QgsMapLayer *mapLayer, bool addToLegend=true, bool takeOwnership=true)
Add a layer to the map of loaded layers.
QStringList nonIdentifiableLayers
Definition qgsproject.h:115
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...
Qgis::ProjectCapabilities capabilities() const
Returns the project's capabilities, which dictate optional functionality which can be selectively ena...
Definition qgsproject.h:205
bool loadFunctionsFromProject(bool force=false)
Loads python expression functions stored in the current 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...
const QgsElevationProfileManager * elevationProfileManager() const
Returns the project's elevation profile manager, which manages elevation profiles within the project.
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:128
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 specified scope and key.
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:130
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.
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.
const QgsSelectiveMaskingSourceSetManager * selectiveMaskingSourceSetManager() const
Returns the project's selective masking set manager, which manages storage of a set of selective mask...
void legendLayersAdded(const QList< QgsMapLayer * > &layers)
Emitted when layers were 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:118
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 an integer key value.
void clear() final
Removes all properties from the collection.
@ String
Any string value.
Definition qgsproperty.h:60
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).
A container for the context for various read/write operations on objects.
void setCurrentLayerId(const QString &layerId)
Sets the current layer id.
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.
Manages a set of relations between layers.
Represents a relationship between two vector layers.
Definition qgsrelation.h:42
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 storage of a set of selective masking source sets.
Manages sensors.
static const QgsSettingsEntryColor * settingsDefaultCanvasColor
Settings entry for default canvas background color.
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.
static const QgsSettingsEntryColor * settingsDefaultSelectionColor
Settings entry for default selection color.
static const QgsSettingsEntryString * settingsMeasureDisplayUnits
Settings entry for distance display units.
Stores configuration of snapping settings for the project.
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:164
static QString threadDescription(QThread *thread)
Returns a descriptive identifier for a thread.
Represents a transaction group.
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.
Represents a vector layer which manages a vector based dataset.
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...
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
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...
QgsActionManager * actions()
Returns all layer actions defined on this layer.
QgsEditFormConfig editFormConfig
static bool isZipFile(const QString &filename)
Returns true if the file name is a zipped file ( i.e with a '.qgz' extension, false otherwise.
QList< QPair< QColor, QString > > QgsNamedColorList
List of colors paired with a friendly display name identifying the color.
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:7278
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7621
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7259
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:7317
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:7346
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7620
#define QgsDebugCall
Definition qgslogger.h:55
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
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.
void dump_(const QgsProjectPropertyKey &topQgsPropertyKey)
QgsProjectProperty * findKey_(const QString &scope, const QString &key, QgsProjectPropertyKey &rootProperty)
Takes the given scope and key and convert them to a string list of key tokens that will be used to na...
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
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.
Contains information relating to a node (i.e.