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