QGIS API Documentation 4.3.0-Master (bf28115e945)
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 return projectScope.release();
2970 }
2971
2972 mProjectScope = std::make_unique< QgsExpressionContextScope >( QObject::tr( "Project" ) );
2973
2974 const QVariantMap vars = customVariables();
2975
2976 QVariantMap::const_iterator it = vars.constBegin();
2977
2978 for ( ; it != vars.constEnd(); ++it )
2979 {
2980 mProjectScope->setVariable( it.key(), it.value(), true );
2981 }
2982
2983 QString projectPath = projectStorage() ? fileName() : absoluteFilePath();
2984 if ( projectPath.isEmpty() )
2985 projectPath = mOriginalPath;
2986 const QString projectFolder = QFileInfo( projectPath ).path();
2987 const QString projectFilename = QFileInfo( projectPath ).fileName();
2988 const QString projectBasename = baseName();
2989
2990 //add other known project variables
2991 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_title"_s, title(), true, true ) );
2992 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_path"_s, QDir::toNativeSeparators( projectPath ), true, true ) );
2993 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_folder"_s, QDir::toNativeSeparators( projectFolder ), true, true ) );
2994 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_filename"_s, projectFilename, true, true ) );
2995 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_basename"_s, projectBasename, true, true ) );
2996 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_home"_s, QDir::toNativeSeparators( homePath() ), true, true ) );
2997 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_last_saved"_s, mSaveDateTime.isNull() ? QVariant() : QVariant( mSaveDateTime ), true, true ) );
2998
2999 const QgsCoordinateReferenceSystem projectCrs = crs();
3000 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs"_s, projectCrs.authid(), true, true ) );
3001 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_definition"_s, projectCrs.toProj(), true, true ) );
3002 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_description"_s, projectCrs.description(), true, true ) );
3003 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_acronym"_s, projectCrs.projectionAcronym(), true ) );
3004 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_ellipsoid"_s, projectCrs.ellipsoidAcronym(), true ) );
3005 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_proj4"_s, projectCrs.toProj(), true ) );
3006 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_wkt"_s, projectCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
3007
3008 const QgsCoordinateReferenceSystem projectVerticalCrs = QgsProject::verticalCrs();
3009 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs"_s, projectVerticalCrs.authid(), true, true ) );
3010 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs_definition"_s, projectVerticalCrs.toProj(), true, true ) );
3011 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs_description"_s, projectVerticalCrs.description(), true, true ) );
3012 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs_wkt"_s, projectVerticalCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
3013
3014 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_ellipsoid"_s, ellipsoid(), true, true ) );
3015 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"_project_transform_context"_s, QVariant::fromValue<QgsCoordinateTransformContext>( transformContext() ), true, true ) );
3016 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_units"_s, QgsUnitTypes::toString( projectCrs.mapUnits() ), true ) );
3017
3018 // metadata
3019 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_author"_s, metadata().author(), true, true ) );
3020 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_abstract"_s, metadata().abstract(), true, true ) );
3021 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_creation_date"_s, metadata().creationDateTime(), true, true ) );
3022 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_identifier"_s, metadata().identifier(), true, true ) );
3023
3024 // keywords
3025 QVariantMap keywords;
3026 const QgsAbstractMetadataBase::KeywordMap metadataKeywords = metadata().keywords();
3027 for ( auto it = metadataKeywords.constBegin(); it != metadataKeywords.constEnd(); ++it )
3028 {
3029 keywords.insert( it.key(), it.value() );
3030 }
3031 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_keywords"_s, keywords, true, true ) );
3032
3033 // layers
3034 QVariantList layersIds;
3035 QVariantList layers;
3036 const QMap<QString, QgsMapLayer *> layersInProject = mLayerStore->mapLayers();
3037 layersIds.reserve( layersInProject.count() );
3038 layers.reserve( layersInProject.count() );
3039 for ( auto it = layersInProject.constBegin(); it != layersInProject.constEnd(); ++it )
3040 {
3041 layersIds << it.value()->id();
3043 }
3044 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"layer_ids"_s, layersIds, true ) );
3045 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"layers"_s, layers, true ) );
3046
3047 mProjectScope->addFunction( u"project_color"_s, new GetNamedProjectColor( this ) );
3048 mProjectScope->addFunction( u"project_color_object"_s, new GetNamedProjectColorObject( this ) );
3049
3051}
3052
3053void QgsProject::onMapLayersAdded( const QList<QgsMapLayer *> &layers )
3054{
3056
3057 const QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
3058
3059 const auto constLayers = layers;
3060 for ( QgsMapLayer *layer : constLayers )
3061 {
3062 if ( !layer->isValid() )
3063 return;
3064
3065 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
3066 {
3067 vlayer->setReadExtentFromXml( mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics );
3068 if ( vlayer->dataProvider() )
3069 vlayer->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, ( bool ) ( mFlags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide ) );
3070 }
3071
3072 connect( layer, &QgsMapLayer::configChanged, this, [this] { setDirty(); } );
3073
3074 // check if we have to update connections for layers with dependencies
3075 for ( QMap<QString, QgsMapLayer *>::const_iterator it = existingMaps.cbegin(); it != existingMaps.cend(); ++it )
3076 {
3077 const QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
3078 if ( deps.contains( layer->id() ) )
3079 {
3080 // reconnect to change signals
3081 it.value()->setDependencies( deps );
3082 }
3083 }
3084 }
3085
3086 updateTransactionGroups();
3087
3088 if ( !mBlockSnappingUpdates && mSnappingConfig.addLayers( layers ) )
3089 emit snappingConfigChanged( mSnappingConfig );
3090}
3091
3092void QgsProject::onMapLayersRemoved( const QList<QgsMapLayer *> &layers )
3093{
3095
3096 if ( !mBlockSnappingUpdates && mSnappingConfig.removeLayers( layers ) )
3097 emit snappingConfigChanged( mSnappingConfig );
3098
3099 for ( QgsMapLayer *layer : layers )
3100 {
3101 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
3102 if ( !vlayer )
3103 continue;
3104
3105 mEditBufferGroup.removeLayer( vlayer );
3106 }
3107}
3108
3109void QgsProject::cleanTransactionGroups( bool force )
3110{
3112
3113 bool changed = false;
3114 for ( QMap< QPair< QString, QString>, QgsTransactionGroup *>::Iterator tg = mTransactionGroups.begin(); tg != mTransactionGroups.end(); )
3115 {
3116 if ( tg.value()->isEmpty() || force )
3117 {
3118 delete tg.value();
3119 tg = mTransactionGroups.erase( tg );
3120 changed = true;
3121 }
3122 else
3123 {
3124 ++tg;
3125 }
3126 }
3127 if ( changed )
3129}
3130
3131void QgsProject::updateTransactionGroups()
3132{
3134
3135 mEditBufferGroup.clear();
3136
3137 switch ( mTransactionMode )
3138 {
3140 {
3141 cleanTransactionGroups( true );
3142 return;
3143 }
3144 break;
3146 cleanTransactionGroups( true );
3147 break;
3149 cleanTransactionGroups( false );
3150 break;
3151 }
3152
3153 bool tgChanged = false;
3154 const auto constLayers = mapLayers().values();
3155 for ( QgsMapLayer *layer : constLayers )
3156 {
3157 if ( !layer->isValid() )
3158 continue;
3159
3160 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
3161 if ( !vlayer )
3162 continue;
3163
3164 switch ( mTransactionMode )
3165 {
3167 Q_ASSERT( false );
3168 break;
3170 {
3172 {
3173 const QString connString = QgsTransaction::connectionString( vlayer->source() );
3174 const QString key = vlayer->providerType();
3175
3176 QgsTransactionGroup *tg = mTransactionGroups.value( qMakePair( key, connString ) );
3177
3178 if ( !tg )
3179 {
3180 tg = new QgsTransactionGroup();
3181 mTransactionGroups.insert( qMakePair( key, connString ), tg );
3182 tgChanged = true;
3183 }
3184 tg->addLayer( vlayer );
3185 }
3186 }
3187 break;
3189 {
3190 if ( vlayer->supportsEditing() )
3191 mEditBufferGroup.addLayer( vlayer );
3192 }
3193 break;
3194 }
3195 }
3196
3197 if ( tgChanged )
3199}
3200
3201bool QgsProject::readLayer( const QDomNode &layerNode )
3202{
3204
3205 QgsReadWriteContext context;
3206 context.setPathResolver( pathResolver() );
3207 context.setProjectTranslator( this );
3208 context.setTransformContext( transformContext() );
3209 context.setCurrentLayerId( layerNode.toElement().firstChildElement( u"id"_s ).text() );
3210 QList<QDomNode> brokenNodes;
3211 if ( addLayer( layerNode.toElement(), brokenNodes, context ) )
3212 {
3213 // have to try to update joins for all layers now - a previously added layer may be dependent on this newly
3214 // added layer for joins
3215 const QVector<QgsVectorLayer *> vectorLayers = layers<QgsVectorLayer *>();
3216 for ( QgsVectorLayer *layer : vectorLayers )
3217 {
3218 // TODO: should be only done later - and with all layers (other layers may have referenced this layer)
3219 layer->resolveReferences( this );
3220
3221 if ( layer->isValid() && layer->customProperty( u"_layer_was_editable"_s ).toBool() )
3222 {
3223 layer->startEditing();
3224 layer->removeCustomProperty( u"_layer_was_editable"_s );
3225 }
3226 }
3227 return true;
3228 }
3229 return false;
3230}
3231
3232bool QgsProject::write( const QString &filename )
3233{
3235
3236 mFile.setFileName( filename );
3237 emit fileNameChanged();
3238 mCachedHomePath.clear();
3239 return write();
3240}
3241
3243{
3245
3246 mProjectScope.reset();
3247 if ( QgsProjectStorage *storage = projectStorage() )
3248 {
3249 QgsReadWriteContext context;
3250 // for projects stored in a custom storage, we have to check for the support
3251 // of relative paths since the storage most likely will not be in a file system
3252 const QString storageFilePath { storage->filePath( mFile.fileName() ) };
3253 if ( storageFilePath.isEmpty() )
3254 {
3256 }
3257 context.setPathResolver( pathResolver() );
3258
3259 const QString tempPath = QStandardPaths::standardLocations( QStandardPaths::TempLocation ).at( 0 );
3260 const QString tmpZipFilename( tempPath + QDir::separator() + QUuid::createUuid().toString() );
3261
3262 if ( !zip( tmpZipFilename ) )
3263 return false; // zip() already calls setError() when returning false
3264
3265 QFile tmpZipFile( tmpZipFilename );
3266 if ( !tmpZipFile.open( QIODevice::ReadOnly ) )
3267 {
3268 setError( tr( "Unable to read file %1" ).arg( tmpZipFilename ) );
3269 return false;
3270 }
3271
3273 if ( !storage->writeProject( mFile.fileName(), &tmpZipFile, context ) )
3274 {
3275 QString err = tr( "Unable to save project to storage %1" ).arg( mFile.fileName() );
3276 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
3277 if ( !messages.isEmpty() )
3278 err += u"\n\n"_s + messages.last().message();
3279 setError( err );
3280 return false;
3281 }
3282
3283 tmpZipFile.close();
3284 QFile::remove( tmpZipFilename );
3285
3286 return true;
3287 }
3288
3289 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
3290 {
3291 return zip( mFile.fileName() );
3292 }
3293 else
3294 {
3295 // write project file even if the auxiliary storage is not correctly
3296 // saved
3297 const bool asOk = saveAuxiliaryStorage();
3298 const bool writeOk = writeProjectFile( mFile.fileName() );
3299 bool attachmentsOk = true;
3300 if ( !mArchive->files().isEmpty() )
3301 {
3302 const QFileInfo finfo( mFile.fileName() );
3303 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( u"%1_attachments.zip"_s.arg( finfo.completeBaseName() ) );
3304 attachmentsOk = mArchive->zip( attachmentsZip );
3305 }
3306
3307 // errors raised during writing project file are more important
3308 if ( ( !asOk || !attachmentsOk ) && writeOk )
3309 {
3310 QStringList errorMessage;
3311 if ( !asOk )
3312 {
3313 const QString err = mAuxiliaryStorage->errorString();
3314 errorMessage.append( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
3315 }
3316 if ( !attachmentsOk )
3317 {
3318 errorMessage.append( tr( "Unable to save attachments archive" ) );
3319 }
3320 setError( errorMessage.join( '\n' ) );
3321 }
3322
3323 return asOk && writeOk && attachmentsOk;
3324 }
3325}
3326
3327bool QgsProject::writeProjectFile( const QString &filename )
3328{
3330
3331 QFile projectFile( filename );
3332 clearError();
3333
3334 // if we have problems creating or otherwise writing to the project file,
3335 // let's find out up front before we go through all the hand-waving
3336 // necessary to create all the Dom objects
3337 const QFileInfo myFileInfo( projectFile );
3338 if ( myFileInfo.exists() && !myFileInfo.isWritable() )
3339 {
3340 setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." ).arg( projectFile.fileName() ) );
3341 return false;
3342 }
3343
3344 QgsReadWriteContext context;
3345 context.setPathResolver( pathResolver() );
3347
3348 QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
3349
3350 const QDomDocumentType documentType = QDomImplementation().createDocumentType( u"qgis"_s, u"http://mrcc.com/qgis.dtd"_s, u"SYSTEM"_s );
3351 auto doc = std::make_unique<QDomDocument>( documentType );
3352
3353 QDomElement qgisNode = doc->createElement( u"qgis"_s );
3354 qgisNode.setAttribute( u"projectname"_s, title() );
3355 qgisNode.setAttribute( u"version"_s, Qgis::version() );
3356
3357 if ( !settingsAnonymizeSavedProjects->value() )
3358 {
3359 const QString newSaveUser = QgsApplication::userLoginName();
3360 const QString newSaveUserFull = QgsApplication::userFullName();
3361 qgisNode.setAttribute( u"saveUser"_s, newSaveUser );
3362 qgisNode.setAttribute( u"saveUserFull"_s, newSaveUserFull );
3363 mSaveUser = newSaveUser;
3364 mSaveUserFull = newSaveUserFull;
3365 if ( mMetadata.author().isEmpty() )
3366 {
3367 mMetadata.setAuthor( QgsApplication::userFullName() );
3368 }
3369 if ( !mMetadata.creationDateTime().isValid() )
3370 {
3371 mMetadata.setCreationDateTime( QDateTime( QDateTime::currentDateTime() ) );
3372 }
3373 mSaveDateTime = QDateTime::currentDateTime();
3374 qgisNode.setAttribute( u"saveDateTime"_s, mSaveDateTime.toString( Qt::ISODate ) );
3375 }
3376 else
3377 {
3378 mSaveUser.clear();
3379 mSaveUserFull.clear();
3380 mMetadata.setAuthor( QString() );
3381 mMetadata.setCreationDateTime( QDateTime() );
3382 mSaveDateTime = QDateTime();
3383 }
3384 doc->appendChild( qgisNode );
3385 mSaveVersion = QgsProjectVersion( Qgis::version() );
3386
3387 QDomElement homePathNode = doc->createElement( u"homePath"_s );
3388 homePathNode.setAttribute( u"path"_s, mHomePath );
3389 qgisNode.appendChild( homePathNode );
3390
3391 // title
3392 QDomElement titleNode = doc->createElement( u"title"_s );
3393 qgisNode.appendChild( titleNode );
3394
3395 QDomElement transactionNode = doc->createElement( u"transaction"_s );
3396 transactionNode.setAttribute( u"mode"_s, qgsEnumValueToKey( mTransactionMode ) );
3397 qgisNode.appendChild( transactionNode );
3398
3399 QDomElement flagsNode = doc->createElement( u"projectFlags"_s );
3400 flagsNode.setAttribute( u"set"_s, qgsFlagValueToKeys( mFlags ) );
3401 qgisNode.appendChild( flagsNode );
3402
3403 const QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
3404 titleNode.appendChild( titleText );
3405
3406 // write project CRS
3407 {
3408 QDomElement srsNode = doc->createElement( u"projectCrs"_s );
3409 mCrs.writeXml( srsNode, *doc );
3410 qgisNode.appendChild( srsNode );
3411 }
3412 {
3413 QDomElement verticalSrsNode = doc->createElement( u"verticalCrs"_s );
3414 mVerticalCrs.writeXml( verticalSrsNode, *doc );
3415 qgisNode.appendChild( verticalSrsNode );
3416 }
3417 QDomElement elevationShadingNode = doc->createElement( u"elevation-shading-renderer"_s );
3418 mElevationShadingRenderer.writeXml( elevationShadingNode, context );
3419 qgisNode.appendChild( elevationShadingNode );
3420
3421 // write layer tree - make sure it is without embedded subgroups
3422 std::unique_ptr< QgsLayerTreeNode > clonedRoot( mRootGroup->clone() );
3424 QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot.get() ), this ); // convert absolute paths to relative paths if required
3425
3426 clonedRoot->writeXml( qgisNode, context );
3427 clonedRoot.reset();
3428
3429 mSnappingConfig.writeProject( *doc );
3430 writeEntry( u"Digitizing"_s, u"/AvoidIntersectionsMode"_s, static_cast<int>( mAvoidIntersectionsMode ) );
3431
3432 // let map canvas and legend write their information
3433 emit writeProject( *doc );
3434
3435 // within top level node save list of layers
3436 const QMap<QString, QgsMapLayer *> layers = mapLayers();
3437
3438 QDomElement annotationLayerNode = doc->createElement( u"main-annotation-layer"_s );
3439 mMainAnnotationLayer->writeLayerXml( annotationLayerNode, *doc, context );
3440 qgisNode.appendChild( annotationLayerNode );
3441
3442 // Iterate over layers in zOrder
3443 // Call writeXml() on each
3444 QDomElement projectLayersNode = doc->createElement( u"projectlayers"_s );
3445
3446 QMap<QString, QgsMapLayer *>::ConstIterator li = layers.constBegin();
3447 while ( li != layers.end() )
3448 {
3449 QgsMapLayer *ml = li.value();
3450
3451 if ( ml )
3452 {
3453 const QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.constFind( ml->id() );
3454 if ( emIt == mEmbeddedLayers.constEnd() )
3455 {
3456 QDomElement maplayerElem;
3457 // If layer is not valid, prefer to restore saved properties from invalidLayerProperties. But if that's
3458 // not available, just write what we DO have
3459 if ( ml->isValid() || ml->originalXmlProperties().isEmpty() )
3460 {
3461 // general layer metadata
3462 maplayerElem = doc->createElement( u"maplayer"_s );
3463 ml->writeLayerXml( maplayerElem, *doc, context );
3464
3466 maplayerElem.setAttribute( u"editable"_s, u"1"_s );
3467 }
3468 else if ( !ml->originalXmlProperties().isEmpty() )
3469 {
3470 QDomDocument document;
3471 if ( document.setContent( ml->originalXmlProperties() ) )
3472 {
3473 maplayerElem = document.firstChildElement();
3474 }
3475 else
3476 {
3477 QgsDebugError( u"Could not restore layer properties for layer %1"_s.arg( ml->id() ) );
3478 }
3479 }
3480
3481 emit writeMapLayer( ml, maplayerElem, *doc );
3482
3483 projectLayersNode.appendChild( maplayerElem );
3484 }
3485 else
3486 {
3487 // layer defined in an external project file
3488 // only save embedded layer if not managed by a legend group
3489 if ( emIt.value().second )
3490 {
3491 QDomElement mapLayerElem = doc->createElement( u"maplayer"_s );
3492 mapLayerElem.setAttribute( u"embedded"_s, 1 );
3493 mapLayerElem.setAttribute( u"project"_s, writePath( emIt.value().first ) );
3494 mapLayerElem.setAttribute( u"id"_s, ml->id() );
3495 projectLayersNode.appendChild( mapLayerElem );
3496 }
3497 }
3498 }
3499 li++;
3500 }
3501
3502 qgisNode.appendChild( projectLayersNode );
3503
3504 QDomElement layerOrderNode = doc->createElement( u"layerorder"_s );
3505 const auto constCustomLayerOrder = mRootGroup->customLayerOrder();
3506 for ( QgsMapLayer *layer : constCustomLayerOrder )
3507 {
3508 QDomElement mapLayerElem = doc->createElement( u"layer"_s );
3509 mapLayerElem.setAttribute( u"id"_s, layer->id() );
3510 layerOrderNode.appendChild( mapLayerElem );
3511 }
3512 qgisNode.appendChild( layerOrderNode );
3513
3514 mLabelingEngineSettings->writeSettingsToProject( this );
3515 {
3516 QDomElement labelEngineSettingsElement = doc->createElement( u"labelEngineSettings"_s );
3517 mLabelingEngineSettings->writeXml( *doc, labelEngineSettingsElement, context );
3518 qgisNode.appendChild( labelEngineSettingsElement );
3519 }
3520
3521 writeEntry( u"Gui"_s, u"/CanvasColorRedPart"_s, mBackgroundColor.red() );
3522 writeEntry( u"Gui"_s, u"/CanvasColorGreenPart"_s, mBackgroundColor.green() );
3523 writeEntry( u"Gui"_s, u"/CanvasColorBluePart"_s, mBackgroundColor.blue() );
3524
3525 writeEntry( u"Gui"_s, u"/SelectionColorRedPart"_s, mSelectionColor.red() );
3526 writeEntry( u"Gui"_s, u"/SelectionColorGreenPart"_s, mSelectionColor.green() );
3527 writeEntry( u"Gui"_s, u"/SelectionColorBluePart"_s, mSelectionColor.blue() );
3528 writeEntry( u"Gui"_s, u"/SelectionColorAlphaPart"_s, mSelectionColor.alpha() );
3529
3530 writeEntry( u"Measurement"_s, u"/DistanceUnits"_s, QgsUnitTypes::encodeUnit( mDistanceUnits ) );
3531 writeEntry( u"Measurement"_s, u"/AreaUnits"_s, QgsUnitTypes::encodeUnit( mAreaUnits ) );
3532 writeEntry( u"Measurement"_s, u"/ScaleMethod"_s, qgsEnumValueToKey( mScaleMethod ) );
3533
3534 // now add the optional extra properties
3535#if 0
3536 dump_( mProperties );
3537#endif
3538
3539 QgsDebugMsgLevel( u"there are %1 property scopes"_s.arg( static_cast<int>( mProperties.count() ) ), 2 );
3540
3541 if ( !mProperties.isEmpty() ) // only worry about properties if we
3542 // actually have any properties
3543 {
3544 mProperties.writeXml( u"properties"_s, qgisNode, *doc );
3545 }
3546
3547 QDomElement ddElem = doc->createElement( u"dataDefinedServerProperties"_s );
3548 mDataDefinedServerProperties.writeXml( ddElem, dataDefinedServerPropertyDefinitions() );
3549 qgisNode.appendChild( ddElem );
3550
3551 mMapThemeCollection->writeXml( *doc );
3552
3553 mTransformContext.writeXml( qgisNode, context );
3554
3555 QDomElement metadataElem = doc->createElement( u"projectMetadata"_s );
3556 mMetadata.writeMetadataXml( metadataElem, *doc );
3557 qgisNode.appendChild( metadataElem );
3558
3559 {
3560 const QDomElement annotationsElem = mAnnotationManager->writeXml( *doc, context );
3561 qgisNode.appendChild( annotationsElem );
3562 }
3563
3564 {
3565 const QDomElement layoutElem = mLayoutManager->writeXml( *doc );
3566 qgisNode.appendChild( layoutElem );
3567 }
3568
3569 {
3570 const QDomElement elevationProfileElem = mElevationProfileManager->writeXml( *doc, context );
3571 qgisNode.appendChild( elevationProfileElem );
3572 }
3573
3574 {
3575 const QDomElement selectiveMaskingSourceSetElem = mSelectiveMaskingSourceSetManager->writeXml( *doc, context );
3576 qgisNode.appendChild( selectiveMaskingSourceSetElem );
3577 }
3578
3579 {
3580 const QDomElement views3DElem = m3DViewsManager->writeXml( *doc );
3581 qgisNode.appendChild( views3DElem );
3582 }
3583
3584 {
3585 const QDomElement bookmarkElem = mBookmarkManager->writeXml( *doc );
3586 qgisNode.appendChild( bookmarkElem );
3587 }
3588
3589 {
3590 const QDomElement sensorElem = mSensorManager->writeXml( *doc );
3591 qgisNode.appendChild( sensorElem );
3592 }
3593
3594 {
3595 const QDomElement viewSettingsElem = mViewSettings->writeXml( *doc, context );
3596 qgisNode.appendChild( viewSettingsElem );
3597 }
3598
3599 {
3600 const QDomElement styleSettingsElem = mStyleSettings->writeXml( *doc, context );
3601 qgisNode.appendChild( styleSettingsElem );
3602 }
3603
3604 {
3605 const QDomElement timeSettingsElement = mTimeSettings->writeXml( *doc, context );
3606 qgisNode.appendChild( timeSettingsElement );
3607 }
3608
3609 {
3610 const QDomElement elevationPropertiesElement = mElevationProperties->writeXml( *doc, context );
3611 qgisNode.appendChild( elevationPropertiesElement );
3612 }
3613
3614 {
3615 const QDomElement displaySettingsElem = mDisplaySettings->writeXml( *doc, context );
3616 qgisNode.appendChild( displaySettingsElem );
3617 }
3618
3619 {
3620 const QDomElement gpsSettingsElem = mGpsSettings->writeXml( *doc, context );
3621 qgisNode.appendChild( gpsSettingsElem );
3622 }
3623
3624 // now wrap it up and ship it to the project file
3625 doc->normalize(); // XXX I'm not entirely sure what this does
3626
3627 // Create backup file
3628 if ( QFile::exists( fileName() ) )
3629 {
3630 QFile backupFile( u"%1~"_s.arg( filename ) );
3631 bool ok = true;
3632 ok &= backupFile.open( QIODevice::WriteOnly | QIODevice::Truncate );
3633 ok &= projectFile.open( QIODevice::ReadOnly );
3634
3635 QByteArray ba;
3636 while ( ok && !projectFile.atEnd() )
3637 {
3638 ba = projectFile.read( 10240 );
3639 ok &= backupFile.write( ba ) == ba.size();
3640 }
3641
3642 projectFile.close();
3643 backupFile.close();
3644
3645 if ( !ok )
3646 {
3647 setError( tr( "Unable to create backup file %1" ).arg( backupFile.fileName() ) );
3648 return false;
3649 }
3650
3651 const QFileInfo fi( fileName() );
3652 struct utimbuf tb = { static_cast<time_t>( fi.lastRead().toSecsSinceEpoch() ), static_cast<time_t>( fi.lastModified().toSecsSinceEpoch() ) };
3653 utime( backupFile.fileName().toUtf8().constData(), &tb );
3654 }
3655
3656 if ( !projectFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
3657 {
3658 projectFile.close(); // even though we got an error, let's make
3659 // sure it's closed anyway
3660
3661 setError( tr( "Unable to save to file %1" ).arg( projectFile.fileName() ) );
3662 return false;
3663 }
3664
3665 QTemporaryFile tempFile;
3666 bool ok = tempFile.open();
3667 if ( ok )
3668 {
3669 QTextStream projectFileStream( &tempFile );
3670 doc->save( projectFileStream, 2 ); // save as utf-8
3671 ok &= projectFileStream.pos() > -1;
3672
3673 ok &= tempFile.seek( 0 );
3674
3675 QByteArray ba;
3676 while ( ok && !tempFile.atEnd() )
3677 {
3678 ba = tempFile.read( 10240 );
3679 ok &= projectFile.write( ba ) == ba.size();
3680 }
3681
3682 ok &= projectFile.error() == QFile::NoError;
3683
3684 projectFile.close();
3685 }
3686
3687 tempFile.close();
3688
3689 if ( !ok )
3690 {
3691 setError( tr(
3692 "Unable to save to file %1. Your project "
3693 "may be corrupted on disk. Try clearing some space on the volume and "
3694 "check file permissions before pressing save again."
3695 )
3696 .arg( projectFile.fileName() ) );
3697 return false;
3698 }
3699
3700 setDirty( false ); // reset to pristine state
3701
3702 emit projectSaved();
3703 return true;
3704}
3705
3706bool QgsProject::writeEntry( const QString &scope, QString const &key, bool value )
3707{
3709
3710 bool propertiesModified;
3711 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3712
3713 if ( propertiesModified )
3714 setDirty( true );
3715
3716 return success;
3717}
3718
3719bool QgsProject::writeEntry( const QString &scope, const QString &key, double value )
3720{
3722
3723 bool propertiesModified;
3724 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3725
3726 if ( propertiesModified )
3727 setDirty( true );
3728
3729 return success;
3730}
3731
3732bool QgsProject::writeEntry( const QString &scope, QString const &key, int value )
3733{
3735
3736 bool propertiesModified;
3737 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3738
3739 if ( propertiesModified )
3740 setDirty( true );
3741
3742 return success;
3743}
3744
3745bool QgsProject::writeEntry( const QString &scope, const QString &key, const QString &value )
3746{
3748
3749 bool propertiesModified;
3750 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3751
3752 if ( propertiesModified )
3753 setDirty( true );
3754
3755 return success;
3756}
3757
3758bool QgsProject::writeEntry( const QString &scope, const QString &key, const QStringList &value )
3759{
3761
3762 bool propertiesModified;
3763 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3764
3765 if ( propertiesModified )
3766 setDirty( true );
3767
3768 return success;
3769}
3770
3771QStringList QgsProject::readListEntry( const QString &scope, const QString &key, const QStringList &def, bool *ok ) const
3772{
3773 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
3775
3776 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3777
3778 QVariant value;
3779
3780 if ( property )
3781 {
3782 value = property->value();
3783
3784 const bool valid = QMetaType::Type::QStringList == value.userType();
3785 if ( ok )
3786 *ok = valid;
3787
3788 if ( valid )
3789 {
3790 return value.toStringList();
3791 }
3792 }
3793 else if ( ok )
3794 *ok = false;
3795
3796
3797 return def;
3798}
3799
3800QString QgsProject::readEntry( const QString &scope, const QString &key, const QString &def, bool *ok ) const
3801{
3803
3804 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3805
3806 QVariant value;
3807
3808 if ( property )
3809 {
3810 value = property->value();
3811
3812 const bool valid = value.canConvert( QMetaType::Type::QString );
3813 if ( ok )
3814 *ok = valid;
3815
3816 if ( valid )
3817 return value.toString();
3818 }
3819 else if ( ok )
3820 *ok = false;
3821
3822 return def;
3823}
3824
3825int QgsProject::readNumEntry( const QString &scope, const QString &key, int def, bool *ok ) const
3826{
3828
3829 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3830
3831 QVariant value;
3832
3833 if ( property )
3834 {
3835 value = property->value();
3836 }
3837
3838 const bool valid = value.canConvert( QMetaType::Type::Int );
3839
3840 if ( ok )
3841 {
3842 *ok = valid;
3843 }
3844
3845 if ( valid )
3846 {
3847 return value.toInt();
3848 }
3849
3850 return def;
3851}
3852
3853double QgsProject::readDoubleEntry( const QString &scope, const QString &key, double def, bool *ok ) const
3854{
3856
3857 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3858 if ( property )
3859 {
3860 const QVariant value = property->value();
3861
3862 const bool valid = value.canConvert( QMetaType::Type::Double );
3863 if ( ok )
3864 *ok = valid;
3865
3866 if ( valid )
3867 return value.toDouble();
3868 }
3869 else if ( ok )
3870 *ok = false;
3871
3872 return def;
3873}
3874
3875bool QgsProject::readBoolEntry( const QString &scope, const QString &key, bool def, bool *ok ) const
3876{
3878
3879 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3880
3881 if ( property )
3882 {
3883 const QVariant value = property->value();
3884
3885 const bool valid = value.canConvert( QMetaType::Type::Bool );
3886 if ( ok )
3887 *ok = valid;
3888
3889 if ( valid )
3890 return value.toBool();
3891 }
3892 else if ( ok )
3893 *ok = false;
3894
3895 return def;
3896}
3897
3898bool QgsProject::removeEntry( const QString &scope, const QString &key )
3899{
3901
3902 if ( findKey_( scope, key, mProperties ) )
3903 {
3904 removeKey_( scope, key, mProperties );
3905 setDirty( true );
3906 }
3907
3908 return !findKey_( scope, key, mProperties );
3909}
3910
3911QStringList QgsProject::entryList( const QString &scope, const QString &key ) const
3912{
3914
3915 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3916
3917 QStringList entries;
3918
3919 if ( foundProperty )
3920 {
3921 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3922
3923 if ( propertyKey )
3924 {
3925 propertyKey->entryList( entries );
3926 }
3927 }
3928
3929 return entries;
3930}
3931
3932QStringList QgsProject::subkeyList( const QString &scope, const QString &key ) const
3933{
3935
3936 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3937
3938 QStringList entries;
3939
3940 if ( foundProperty )
3941 {
3942 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3943
3944 if ( propertyKey )
3945 {
3946 propertyKey->subkeyList( entries );
3947 }
3948 }
3949
3950 return entries;
3951}
3952
3954{
3956
3957 dump_( mProperties );
3958}
3959
3961{
3963
3964 QString filePath;
3965 switch ( filePathStorage() )
3966 {
3968 break;
3969
3971 {
3972 // for projects stored in a custom storage, we need to ask to the
3973 // storage for the path, if the storage returns an empty path
3974 // relative paths are not supported
3975 if ( QgsProjectStorage *storage = projectStorage() )
3976 {
3977 filePath = storage->filePath( mFile.fileName() );
3978 }
3979 else
3980 {
3981 filePath = fileName();
3982 }
3983 break;
3984 }
3985 }
3986
3987 return QgsPathResolver( filePath, mArchive->dir() );
3988}
3989
3990QString QgsProject::readPath( const QString &src ) const
3991{
3993
3994 return pathResolver().readPath( src );
3995}
3996
3997QString QgsProject::writePath( const QString &src ) const
3998{
4000
4001 return pathResolver().writePath( src );
4002}
4003
4004void QgsProject::setError( const QString &errorMessage )
4005{
4007
4008 mErrorMessage = errorMessage;
4009}
4010
4011QString QgsProject::error() const
4012{
4014
4015 return mErrorMessage;
4016}
4017
4018void QgsProject::clearError()
4019{
4021
4022 setError( QString() );
4023}
4024
4026{
4028
4029 mBadLayerHandler.reset( handler );
4030}
4031
4032QString QgsProject::layerIsEmbedded( const QString &id ) const
4033{
4035
4036 const QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
4037 if ( it == mEmbeddedLayers.constEnd() )
4038 {
4039 return QString();
4040 }
4041 return it.value().first;
4042}
4043
4044bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &projectFilePath, QList<QDomNode> &brokenNodes, bool saveFlag, Qgis::ProjectReadFlags flags )
4045{
4047
4049
4050 static QString sPrevProjectFilePath;
4051 static QDateTime sPrevProjectFileTimestamp;
4052 static QDomDocument sProjectDocument;
4053
4054 QString qgsProjectFile = projectFilePath;
4055 QgsProjectArchive archive;
4056 if ( projectFilePath.endsWith( ".qgz"_L1, Qt::CaseInsensitive ) )
4057 {
4058 archive.unzip( projectFilePath );
4059 qgsProjectFile = archive.projectFile();
4060 }
4061
4062 const QDateTime projectFileTimestamp = QFileInfo( projectFilePath ).lastModified();
4063
4064 if ( projectFilePath != sPrevProjectFilePath || projectFileTimestamp != sPrevProjectFileTimestamp )
4065 {
4066 sPrevProjectFilePath.clear();
4067
4068 QFile projectFile( qgsProjectFile );
4069 if ( !projectFile.open( QIODevice::ReadOnly ) )
4070 {
4071 return false;
4072 }
4073
4074 if ( !sProjectDocument.setContent( &projectFile ) )
4075 {
4076 return false;
4077 }
4078
4079 sPrevProjectFilePath = projectFilePath;
4080 sPrevProjectFileTimestamp = projectFileTimestamp;
4081 }
4082
4083 // does project store paths absolute or relative?
4084 bool useAbsolutePaths = true;
4085
4086 const QDomElement propertiesElem = sProjectDocument.documentElement().firstChildElement( u"properties"_s );
4087 if ( !propertiesElem.isNull() )
4088 {
4089 QDomElement e = propertiesElem.firstChildElement( u"Paths"_s );
4090 if ( e.isNull() )
4091 {
4092 e = propertiesElem.firstChildElement( u"properties"_s );
4093 while ( !e.isNull() && e.attribute( u"name"_s ) != "Paths"_L1 )
4094 e = e.nextSiblingElement( u"properties"_s );
4095
4096 e = e.firstChildElement( u"properties"_s );
4097 while ( !e.isNull() && e.attribute( u"name"_s ) != "Absolute"_L1 )
4098 e = e.nextSiblingElement( u"properties"_s );
4099 }
4100 else
4101 {
4102 e = e.firstChildElement( u"Absolute"_s );
4103 }
4104
4105 if ( !e.isNull() )
4106 {
4107 useAbsolutePaths = e.text().compare( "true"_L1, Qt::CaseInsensitive ) == 0;
4108 }
4109 }
4110
4111 QgsReadWriteContext embeddedContext;
4112 if ( !useAbsolutePaths )
4113 embeddedContext.setPathResolver( QgsPathResolver( projectFilePath ) );
4114 embeddedContext.setProjectTranslator( this );
4115 embeddedContext.setTransformContext( transformContext() );
4116 embeddedContext.setCurrentLayerId( layerId );
4117
4118 const QDomElement projectLayersElem = sProjectDocument.documentElement().firstChildElement( u"projectlayers"_s );
4119 if ( projectLayersElem.isNull() )
4120 {
4121 return false;
4122 }
4123
4124 QDomElement mapLayerElem = projectLayersElem.firstChildElement( u"maplayer"_s );
4125 while ( !mapLayerElem.isNull() )
4126 {
4127 // get layer id
4128 const QString id = mapLayerElem.firstChildElement( u"id"_s ).text();
4129 if ( id == layerId )
4130 {
4131 // layer can be embedded only once
4132 if ( mapLayerElem.attribute( u"embedded"_s ) == "1"_L1 )
4133 {
4134 return false;
4135 }
4136
4137 mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
4138
4139 if ( addLayer( mapLayerElem, brokenNodes, embeddedContext, flags ) )
4140 {
4141 return true;
4142 }
4143 else
4144 {
4145 mEmbeddedLayers.remove( layerId );
4146 return false;
4147 }
4148 }
4149 mapLayerElem = mapLayerElem.nextSiblingElement( u"maplayer"_s );
4150 }
4151
4152 return false;
4153}
4154
4155std::unique_ptr<QgsLayerTreeGroup> QgsProject::createEmbeddedGroup( const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers, Qgis::ProjectReadFlags flags )
4156{
4158
4159 QString qgsProjectFile = projectFilePath;
4160 QgsProjectArchive archive;
4161 if ( projectFilePath.endsWith( ".qgz"_L1, Qt::CaseInsensitive ) )
4162 {
4163 archive.unzip( projectFilePath );
4164 qgsProjectFile = archive.projectFile();
4165 }
4166
4167 // open project file, get layer ids in group, add the layers
4168 QFile projectFile( qgsProjectFile );
4169 if ( !projectFile.open( QIODevice::ReadOnly ) )
4170 {
4171 return nullptr;
4172 }
4173
4174 QDomDocument projectDocument;
4175 if ( !projectDocument.setContent( &projectFile ) )
4176 {
4177 return nullptr;
4178 }
4179
4180 QgsReadWriteContext context;
4181 context.setPathResolver( pathResolver() );
4182 context.setProjectTranslator( this );
4184
4185 auto root = std::make_unique< QgsLayerTreeGroup >();
4186
4187 QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( u"layer-tree-group"_s );
4188 if ( !layerTreeElem.isNull() )
4189 {
4190 root->readChildrenFromXml( layerTreeElem, context );
4191 }
4192 else
4193 {
4194 QgsLayerTreeUtils::readOldLegend( root.get(), projectDocument.documentElement().firstChildElement( u"legend"_s ) );
4195 }
4196
4197 QgsLayerTreeGroup *group = root->findGroup( groupName );
4198 if ( !group || group->customProperty( u"embedded"_s ).toBool() )
4199 {
4200 // embedded groups cannot be embedded again
4201 return nullptr;
4202 }
4203
4204 // clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
4205 std::unique_ptr< QgsLayerTreeGroup > newGroup( QgsLayerTree::toGroup( group->clone() ) );
4206 root.reset();
4207
4208 newGroup->setCustomProperty( u"embedded"_s, 1 );
4209 newGroup->setCustomProperty( u"embedded_project"_s, projectFilePath );
4210
4211 // set "embedded" to all children + load embedded layers
4212 mLayerTreeRegistryBridge->setEnabled( false );
4213 initializeEmbeddedSubtree( projectFilePath, newGroup.get(), flags );
4214 mLayerTreeRegistryBridge->setEnabled( true );
4215
4216 // consider the layers might be identify disabled in its project
4217 const QStringList constFindLayerIds = newGroup->findLayerIds();
4218 for ( const QString &layerId : constFindLayerIds )
4219 {
4220 QgsLayerTreeLayer *layer = newGroup->findLayer( layerId );
4221 if ( layer )
4222 {
4223 layer->resolveReferences( this );
4224 layer->setItemVisibilityChecked( !invisibleLayers.contains( layerId ) );
4225 }
4226 }
4227
4228 return newGroup;
4229}
4230
4231void QgsProject::initializeEmbeddedSubtree( const QString &projectFilePath, QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
4232{
4234
4235 const auto constChildren = group->children();
4236 for ( QgsLayerTreeNode *child : constChildren )
4237 {
4238 // all nodes in the subtree will have "embedded" custom property set
4239 child->setCustomProperty( u"embedded"_s, 1 );
4240
4241 if ( QgsLayerTree::isGroup( child ) )
4242 {
4243 initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ), flags );
4244 }
4245 else if ( QgsLayerTree::isLayer( child ) )
4246 {
4247 // load the layer into our project
4248 QList<QDomNode> brokenNodes;
4249 createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, false, flags );
4250 }
4251 }
4252}
4253
4260
4267
4269{
4271
4272 writeEntry( u"Digitizing"_s, u"/TopologicalEditing"_s, ( enabled ? 1 : 0 ) );
4274}
4275
4277{
4279
4280 return readNumEntry( u"Digitizing"_s, u"/TopologicalEditing"_s, 0 );
4281}
4282
4284{
4286
4287 if ( mDistanceUnits == unit )
4288 return;
4289
4290 mDistanceUnits = unit;
4291
4292 emit distanceUnitsChanged();
4293}
4294
4296{
4298
4299 if ( mAreaUnits == unit )
4300 return;
4301
4302 mAreaUnits = unit;
4303
4304 emit areaUnitsChanged();
4305}
4306
4308{
4310
4311 if ( mScaleMethod == method )
4312 return;
4313
4314 mScaleMethod = method;
4315
4316 emit scaleMethodChanged();
4317}
4318
4320{
4321 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4323
4324 if ( !mCachedHomePath.isEmpty() )
4325 return mCachedHomePath;
4326
4327 const QFileInfo pfi( fileName() );
4328
4329 if ( !mHomePath.isEmpty() )
4330 {
4331 const QFileInfo homeInfo( mHomePath );
4332 if ( !homeInfo.isRelative() )
4333 {
4334 mCachedHomePath = mHomePath;
4335 return mHomePath;
4336 }
4337 }
4338 else if ( !fileName().isEmpty() )
4339 {
4340 // If it's not stored in the file system, try to get the path from the storage
4341 if ( QgsProjectStorage *storage = projectStorage() )
4342 {
4343 const QString storagePath { storage->filePath( fileName() ) };
4344 if ( !storagePath.isEmpty() && QFileInfo::exists( storagePath ) )
4345 {
4346 mCachedHomePath = QFileInfo( storagePath ).path();
4347 return mCachedHomePath;
4348 }
4349 }
4350
4351 mCachedHomePath = pfi.path();
4352 return mCachedHomePath;
4353 }
4354
4355 if ( !pfi.exists() )
4356 {
4357 mCachedHomePath = mHomePath;
4358 return mHomePath;
4359 }
4360
4361 if ( !mHomePath.isEmpty() )
4362 {
4363 // path is relative to project file
4364 mCachedHomePath = QDir::cleanPath( pfi.path() + '/' + mHomePath );
4365 }
4366 else
4367 {
4368 mCachedHomePath = pfi.canonicalPath();
4369 }
4370 return mCachedHomePath;
4371}
4372
4374{
4376
4377 return mHomePath;
4378}
4379
4381{
4382 // because relation aggregate functions are not thread safe
4384
4385 return mRelationManager.get();
4386}
4387
4389{
4391
4392 return mLayoutManager.get();
4393}
4394
4396{
4398
4399 return mLayoutManager.get();
4400}
4401
4403{
4405
4406 return mElevationProfileManager.get();
4407}
4408
4410{
4412
4413 return mElevationProfileManager.get();
4414}
4415
4417{
4419
4420 return mSelectiveMaskingSourceSetManager.get();
4421}
4422
4424{
4426
4427 return mSelectiveMaskingSourceSetManager.get();
4428}
4429
4431{
4433
4434 return m3DViewsManager.get();
4435}
4436
4438{
4440
4441 return m3DViewsManager.get();
4442}
4443
4445{
4447
4448 return mBookmarkManager;
4449}
4450
4452{
4454
4455 return mBookmarkManager;
4456}
4457
4459{
4461
4462 return mSensorManager;
4463}
4464
4466{
4468
4469 return mSensorManager;
4470}
4471
4473{
4475
4476 return mViewSettings;
4477}
4478
4485
4487{
4489
4490 return mStyleSettings;
4491}
4492
4494{
4495 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4497
4498 return mStyleSettings;
4499}
4500
4502{
4504
4505 return mTimeSettings;
4506}
4507
4514
4516{
4518
4519 return mElevationProperties;
4520}
4521
4528
4530{
4532
4533 return mDisplaySettings;
4534}
4535
4537{
4539
4540 return mDisplaySettings;
4541}
4542
4544{
4546
4547 return mGpsSettings;
4548}
4549
4556
4558{
4560
4561 return mRootGroup.get();
4562}
4563
4565{
4567
4568 return mMapThemeCollection.get();
4569}
4570
4572{
4574
4575 return mAnnotationManager.get();
4576}
4577
4579{
4581
4582 return mAnnotationManager.get();
4583}
4584
4585void QgsProject::setNonIdentifiableLayers( const QList<QgsMapLayer *> &layers )
4586{
4588
4589 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
4590 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
4591 {
4592 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4593 continue;
4594
4595 if ( layers.contains( it.value() ) )
4596 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Identifiable );
4597 else
4598 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Identifiable );
4599 }
4600
4604}
4605
4606void QgsProject::setNonIdentifiableLayers( const QStringList &layerIds )
4607{
4609
4610 QList<QgsMapLayer *> nonIdentifiableLayers;
4611 nonIdentifiableLayers.reserve( layerIds.count() );
4612 for ( const QString &layerId : layerIds )
4613 {
4614 QgsMapLayer *layer = mapLayer( layerId );
4615 if ( layer )
4616 nonIdentifiableLayers << layer;
4617 }
4621}
4622
4624{
4626
4627 QStringList nonIdentifiableLayers;
4628
4629 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
4630 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
4631 {
4632 if ( !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4633 {
4634 nonIdentifiableLayers.append( it.value()->id() );
4635 }
4636 }
4637 return nonIdentifiableLayers;
4638}
4639
4641{
4643
4644 return mTransactionMode == Qgis::TransactionMode::AutomaticGroups;
4645}
4646
4648{
4650
4651 if ( autoTransaction && mTransactionMode == Qgis::TransactionMode::AutomaticGroups )
4652 return;
4653
4654 if ( !autoTransaction && mTransactionMode == Qgis::TransactionMode::Disabled )
4655 return;
4656
4657 if ( autoTransaction )
4659 else
4661
4662 updateTransactionGroups();
4663}
4664
4666{
4668
4669 return mTransactionMode;
4670}
4671
4673{
4675
4676 if ( transactionMode == mTransactionMode )
4677 return true;
4678
4679 // Check that all layer are not in edit mode
4680 const auto constLayers = mapLayers().values();
4681 for ( QgsMapLayer *layer : constLayers )
4682 {
4683 if ( layer->isEditable() )
4684 {
4685 QgsLogger::warning( tr( "Transaction mode can be changed only if all layers are not editable." ) );
4686 return false;
4687 }
4688 }
4689
4690 mTransactionMode = transactionMode;
4691 updateTransactionGroups();
4693 return true;
4694}
4695
4696QMap<QPair<QString, QString>, QgsTransactionGroup *> QgsProject::transactionGroups()
4697{
4699
4700 return mTransactionGroups;
4701}
4702
4703
4704//
4705// QgsMapLayerStore methods
4706//
4707
4708
4710{
4712
4713 return mLayerStore->count();
4714}
4715
4717{
4719
4720 return mLayerStore->validCount();
4721}
4722
4723QgsMapLayer *QgsProject::mapLayer( const QString &layerId ) const
4724{
4725 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
4727
4728 if ( mMainAnnotationLayer && layerId == mMainAnnotationLayer->id() )
4729 return mMainAnnotationLayer;
4730
4731 return mLayerStore->mapLayer( layerId );
4732}
4733
4734QList<QgsMapLayer *> QgsProject::mapLayersByName( const QString &layerName ) const
4735{
4737
4738 return mLayerStore->mapLayersByName( layerName );
4739}
4740
4741QList<QgsMapLayer *> QgsProject::mapLayersByShortName( const QString &shortName ) const
4742{
4744
4745 QList<QgsMapLayer *> layers;
4746 const auto constMapLayers { mLayerStore->mapLayers() };
4747 for ( const auto &l : constMapLayers )
4748 {
4749 if ( !l->serverProperties()->shortName().isEmpty() )
4750 {
4751 if ( l->serverProperties()->shortName() == shortName )
4752 layers << l;
4753 }
4754 else if ( l->name() == shortName )
4755 {
4756 layers << l;
4757 }
4758 }
4759 return layers;
4760}
4761
4762bool QgsProject::unzip( const QString &filename, Qgis::ProjectReadFlags flags )
4763{
4765
4766 clearError();
4767 auto archive = std::make_unique<QgsProjectArchive>();
4768
4769 // unzip the archive
4770 if ( !archive->unzip( filename ) )
4771 {
4772 setError( tr( "Unable to unzip file '%1'" ).arg( filename ) );
4773 return false;
4774 }
4775
4776 // test if zip provides a .qgs file
4777 if ( archive->projectFile().isEmpty() )
4778 {
4779 setError( tr( "Zip archive does not provide a project file" ) );
4780 return false;
4781 }
4782
4783 // Keep the archive
4784 releaseHandlesToProjectArchive();
4785 mArchive = std::move( archive );
4786
4787 // load auxiliary storage
4788 if ( !static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile().isEmpty() )
4789 {
4790 // database file is already a copy as it's been unzipped. So we don't open
4791 // auxiliary storage in copy mode in this case
4792 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile(), false );
4793 }
4794 else
4795 {
4796 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( *this );
4797 }
4798
4799 // read the project file
4800 if ( !readProjectFile( static_cast<QgsProjectArchive *>( mArchive.get() )->projectFile(), flags ) )
4801 {
4802 setError( tr( "Cannot read unzipped qgs project file" ) + u": "_s + error() );
4803 return false;
4804 }
4805
4806 // Remove the temporary .qgs file
4807 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4808
4809 return true;
4810}
4811
4812bool QgsProject::zip( const QString &filename )
4813{
4815
4816 clearError();
4817
4818 // save the current project in a temporary .qgs file
4819 auto archive = std::make_unique<QgsProjectArchive>();
4820 const QString baseName = QFileInfo( filename ).baseName();
4821 const QString qgsFileName = u"%1.qgs"_s.arg( baseName );
4822 QFile qgsFile( QDir( archive->dir() ).filePath( qgsFileName ) );
4823
4824 bool writeOk = false;
4825 if ( qgsFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
4826 {
4827 writeOk = writeProjectFile( qgsFile.fileName() );
4828 qgsFile.close();
4829 }
4830
4831 // stop here with an error message
4832 if ( !writeOk )
4833 {
4834 setError( tr( "Unable to write temporary qgs file" ) );
4835 return false;
4836 }
4837
4838 // save auxiliary storage
4839 const QFileInfo info( qgsFile );
4840 const QString asExt = u".%1"_s.arg( QgsAuxiliaryStorage::extension() );
4841 const QString asFileName = info.path() + QDir::separator() + info.completeBaseName() + asExt;
4842
4843 bool auxiliaryStorageSavedOk = true;
4844 if ( !saveAuxiliaryStorage( asFileName ) )
4845 {
4846 const QString err = mAuxiliaryStorage->errorString();
4847 setError(
4848 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 )
4849 );
4850 auxiliaryStorageSavedOk = false;
4851
4852 // fixes the current archive and keep the previous version of qgd
4853 if ( !mArchive->exists() )
4854 {
4855 releaseHandlesToProjectArchive();
4856 mArchive = std::make_unique< QgsProjectArchive >();
4857 mArchive->unzip( mFile.fileName() );
4858 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4859
4860 const QString auxiliaryStorageFile = static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile();
4861 if ( !auxiliaryStorageFile.isEmpty() )
4862 {
4863 archive->addFile( auxiliaryStorageFile );
4864 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( auxiliaryStorageFile, false );
4865 }
4866 }
4867 }
4868 else
4869 {
4870 // in this case, an empty filename means that the auxiliary database is
4871 // empty, so we don't want to save it
4872 if ( QFile::exists( asFileName ) )
4873 {
4874 archive->addFile( asFileName );
4875 }
4876 }
4877
4878 // create the archive
4879 archive->addFile( qgsFile.fileName() );
4880
4881 // Add all other files
4882 const QStringList &files = mArchive->files();
4883 for ( const QString &file : files )
4884 {
4885 if ( !file.endsWith( ".qgs"_L1, Qt::CaseInsensitive ) && !file.endsWith( asExt, Qt::CaseInsensitive ) )
4886 {
4887 archive->addFile( file );
4888 }
4889 }
4890
4891 // zip
4892 bool zipOk = true;
4893 if ( !archive->zip( filename ) )
4894 {
4895 setError( tr( "Unable to perform zip" ) );
4896 zipOk = false;
4897 }
4898
4899 return auxiliaryStorageSavedOk && zipOk;
4900}
4901
4903{
4905
4906 return QgsZipUtils::isZipFile( mFile.fileName() );
4907}
4908
4909QList<QgsMapLayer *> QgsProject::addMapLayers( const QList<QgsMapLayer *> &layers, bool addToLegend, bool takeOwnership )
4910{
4912
4913 const QList<QgsMapLayer *> myResultList { mLayerStore->addMapLayers( layers, takeOwnership ) };
4914 if ( !myResultList.isEmpty() )
4915 {
4916 // Update transform context
4917 for ( auto &l : myResultList )
4918 {
4919 l->setTransformContext( transformContext() );
4920 }
4921 if ( addToLegend )
4922 {
4923 emit legendLayersAdded( myResultList );
4924 }
4925 else
4926 {
4927 emit layersAddedWithoutLegend( myResultList );
4928 }
4929 }
4930
4931 if ( mAuxiliaryStorage )
4932 {
4933 for ( QgsMapLayer *mlayer : myResultList )
4934 {
4935 if ( mlayer->type() != Qgis::LayerType::Vector )
4936 continue;
4937
4938 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mlayer );
4939 if ( vl )
4940 {
4941 vl->loadAuxiliaryLayer( *mAuxiliaryStorage );
4942 }
4943 }
4944 }
4945
4946 mProjectScope.reset();
4947
4948 return myResultList;
4949}
4950
4951QgsMapLayer *QgsProject::addMapLayer( QgsMapLayer *layer, bool addToLegend, bool takeOwnership )
4952{
4954
4955 QList<QgsMapLayer *> addedLayers;
4956 addedLayers = addMapLayers( QList<QgsMapLayer *>() << layer, addToLegend, takeOwnership );
4957 return addedLayers.isEmpty() ? nullptr : addedLayers[0];
4958}
4959
4960void QgsProject::removeAuxiliaryLayer( const QgsMapLayer *ml )
4961{
4963
4964 if ( !ml || ml->type() != Qgis::LayerType::Vector )
4965 return;
4966
4967 const QgsVectorLayer *vl = qobject_cast<const QgsVectorLayer *>( ml );
4968 if ( vl && vl->auxiliaryLayer() )
4969 {
4970 const QgsDataSourceUri uri( vl->auxiliaryLayer()->source() );
4972 }
4973}
4974
4975void QgsProject::removeMapLayers( const QStringList &layerIds )
4976{
4978
4979 for ( const auto &layerId : layerIds )
4980 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4981
4982 mProjectScope.reset();
4983 mLayerStore->removeMapLayers( layerIds );
4984}
4985
4986void QgsProject::removeMapLayers( const QList<QgsMapLayer *> &layers )
4987{
4989
4990 for ( const auto &layer : layers )
4991 removeAuxiliaryLayer( layer );
4992
4993 mProjectScope.reset();
4994 mLayerStore->removeMapLayers( layers );
4995}
4996
4997void QgsProject::removeMapLayer( const QString &layerId )
4998{
5000
5001 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
5002 mProjectScope.reset();
5003 mLayerStore->removeMapLayer( layerId );
5004}
5005
5007{
5009
5010 removeAuxiliaryLayer( layer );
5011 mProjectScope.reset();
5012 mLayerStore->removeMapLayer( layer );
5013}
5014
5016{
5018
5019 mProjectScope.reset();
5020 return mLayerStore->takeMapLayer( layer );
5021}
5022
5024{
5026
5027 return mMainAnnotationLayer;
5028}
5029
5031{
5033
5034 if ( mLayerStore->count() == 0 )
5035 return;
5036
5037 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
5038 mProjectScope.reset();
5039 mLayerStore->removeAllMapLayers();
5040
5041 snapSingleBlocker.release();
5042 mSnappingConfig.clearIndividualLayerSettings();
5043 if ( !mBlockSnappingUpdates )
5044 emit snappingConfigChanged( mSnappingConfig );
5045}
5046
5048{
5050
5051 const QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
5052 QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin();
5053 for ( ; it != layers.constEnd(); ++it )
5054 {
5055 it.value()->reload();
5056 }
5057}
5058
5059QMap<QString, QgsMapLayer *> QgsProject::mapLayers( const bool validOnly ) const
5060{
5061 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
5063
5064 return validOnly ? mLayerStore->validMapLayers() : mLayerStore->mapLayers();
5065}
5066
5067QgsTransactionGroup *QgsProject::transactionGroup( const QString &providerKey, const QString &connString )
5068{
5070
5071 return mTransactionGroups.value( qMakePair( providerKey, connString ) );
5072}
5073
5080
5082{
5084
5086
5087 // TODO QGIS 5.0 -- remove this method, and place it somewhere in app (where it belongs)
5089 {
5090 // for new layers if the new layer crs method is set to either prompt or use project, then we use the project crs
5091 defaultCrs = crs();
5092 }
5093 else
5094 {
5095 // global crs
5096 const QString layerDefaultCrs = QgsSettingsRegistryCore::settingsLayerDefaultCrs->value();
5097 defaultCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( layerDefaultCrs );
5098 }
5099
5100 return defaultCrs;
5101}
5102
5109
5116
5117bool QgsProject::saveAuxiliaryStorage( const QString &filename )
5118{
5120
5121 const QMap<QString, QgsMapLayer *> layers = mapLayers();
5122 bool empty = true;
5123 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5124 {
5125 if ( it.value()->type() != Qgis::LayerType::Vector )
5126 continue;
5127
5128 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
5129 if ( vl && vl->auxiliaryLayer() )
5130 {
5131 vl->auxiliaryLayer()->save();
5132 empty &= vl->auxiliaryLayer()->auxiliaryFields().isEmpty();
5133 }
5134 }
5135
5136 if ( !mAuxiliaryStorage->exists( *this ) && empty )
5137 {
5138 return true; // it's not an error
5139 }
5140 else if ( !filename.isEmpty() )
5141 {
5142 return mAuxiliaryStorage->saveAs( filename );
5143 }
5144 else
5145 {
5146 return mAuxiliaryStorage->saveAs( *this );
5147 }
5148}
5149
5150QgsPropertiesDefinition &QgsProject::dataDefinedServerPropertyDefinitions()
5151{
5152 static QgsPropertiesDefinition sPropertyDefinitions {
5153 { static_cast< int >( QgsProject::DataDefinedServerProperty::WMSOnlineResource ), QgsPropertyDefinition( "WMSOnlineResource", QObject::tr( "WMS Online Resource" ), QgsPropertyDefinition::String ) },
5154 };
5155 return sPropertyDefinitions;
5156}
5157
5163
5165{
5167
5168 return mAuxiliaryStorage.get();
5169}
5170
5172{
5174
5175 return mAuxiliaryStorage.get();
5176}
5177
5178QString QgsProject::createAttachedFile( const QString &nameTemplate )
5179{
5181
5182 const QDir archiveDir( mArchive->dir() );
5183 QTemporaryFile tmpFile( archiveDir.filePath( "XXXXXX_" + nameTemplate ), this );
5184 tmpFile.setAutoRemove( false );
5185 if ( !tmpFile.open() )
5186 {
5187 setError( tr( "Unable to open %1" ).arg( tmpFile.fileName() ) );
5188 return QString();
5189 }
5190 mArchive->addFile( tmpFile.fileName() );
5191 return tmpFile.fileName();
5192}
5193
5194QStringList QgsProject::attachedFiles() const
5195{
5197
5198 QStringList attachments;
5199 const QString baseName = QFileInfo( fileName() ).baseName();
5200 const QStringList files = mArchive->files();
5201 attachments.reserve( files.size() );
5202 for ( const QString &file : files )
5203 {
5204 if ( QFileInfo( file ).baseName() != baseName )
5205 {
5206 attachments.append( file );
5207 }
5208 }
5209 return attachments;
5210}
5211
5212bool QgsProject::removeAttachedFile( const QString &path )
5213{
5215
5216 return mArchive->removeFile( path );
5217}
5218
5219QString QgsProject::attachmentIdentifier( const QString &attachedFile ) const
5220{
5222
5223 return u"attachment:///%1"_s.arg( QFileInfo( attachedFile ).fileName() );
5224}
5225
5226QString QgsProject::resolveAttachmentIdentifier( const QString &identifier ) const
5227{
5229
5230 if ( identifier.startsWith( "attachment:///"_L1 ) )
5231 {
5232 return QDir( mArchive->dir() ).absoluteFilePath( identifier.mid( 14 ) );
5233 }
5234 return QString();
5235}
5236
5238{
5239 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
5241
5242 return mMetadata;
5243}
5244
5246{
5248
5249 if ( metadata == mMetadata )
5250 return;
5251
5252 mMetadata = metadata;
5253 mProjectScope.reset();
5254
5255 emit metadataChanged();
5256 emit titleChanged();
5257
5258 setDirty( true );
5259}
5260
5261QSet<QgsMapLayer *> QgsProject::requiredLayers() const
5262{
5264
5265 QSet<QgsMapLayer *> requiredLayers;
5266
5267 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
5268 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
5269 {
5270 if ( !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
5271 {
5272 requiredLayers.insert( it.value() );
5273 }
5274 }
5275 return requiredLayers;
5276}
5277
5278void QgsProject::setRequiredLayers( const QSet<QgsMapLayer *> &layers )
5279{
5281
5282 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
5283 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
5284 {
5285 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
5286 continue;
5287
5288 if ( layers.contains( it.value() ) )
5289 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Removable );
5290 else
5291 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Removable );
5292 }
5293}
5294
5296{
5298
5299 // save colors to project
5300 QStringList customColors;
5301 QStringList customColorLabels;
5302
5303 QgsNamedColorList::const_iterator colorIt = colors.constBegin();
5304 for ( ; colorIt != colors.constEnd(); ++colorIt )
5305 {
5306 const QString color = QgsColorUtils::colorToString( ( *colorIt ).first );
5307 const QString label = ( *colorIt ).second;
5308 customColors.append( color );
5309 customColorLabels.append( label );
5310 }
5311 writeEntry( u"Palette"_s, u"/Colors"_s, customColors );
5312 writeEntry( u"Palette"_s, u"/Labels"_s, customColorLabels );
5313 mProjectScope.reset();
5314 emit projectColorsChanged();
5315}
5316
5317void QgsProject::setBackgroundColor( const QColor &color )
5318{
5320
5321 if ( mBackgroundColor == color )
5322 return;
5323
5324 mBackgroundColor = color;
5326}
5327
5329{
5331
5332 return mBackgroundColor;
5333}
5334
5335void QgsProject::setSelectionColor( const QColor &color )
5336{
5338
5339 if ( mSelectionColor == color )
5340 return;
5341
5342 mSelectionColor = color;
5343 emit selectionColorChanged();
5344}
5345
5347{
5349
5350 return mSelectionColor;
5351}
5352
5353void QgsProject::setMapScales( const QVector<double> &scales )
5354{
5356
5357 mViewSettings->setMapScales( scales );
5358}
5359
5360QVector<double> QgsProject::mapScales() const
5361{
5363
5364 return mViewSettings->mapScales();
5365}
5366
5368{
5370
5371 mViewSettings->setUseProjectScales( enabled );
5372}
5373
5375{
5377
5378 return mViewSettings->useProjectScales();
5379}
5380
5381void QgsProject::generateTsFile( const QString &locale )
5382{
5384
5385 QgsTranslationContext translationContext;
5386 translationContext.setProject( this );
5387 translationContext.setFileName( u"%1/%2.ts"_s.arg( absolutePath(), baseName() ) );
5388
5389 QgsApplication::instance()->collectTranslatableObjects( &translationContext );
5390
5391 translationContext.writeTsFile( locale );
5392}
5393
5394QString QgsProject::translate( const QString &context, const QString &sourceText, const char *disambiguation, int n ) const
5395{
5397
5398 if ( !mTranslator )
5399 {
5400 return sourceText;
5401 }
5402
5403 QString result = mTranslator->translate( context.toUtf8(), sourceText.toUtf8(), disambiguation, n );
5404
5405 if ( result.isEmpty() )
5406 {
5407 return sourceText;
5408 }
5409 return result;
5410}
5411
5413{
5415
5416 const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
5417 if ( !layers.empty() )
5418 {
5419 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5420 {
5421 // NOTE: if visitEnter returns false it means "don't visit this layer", not "abort all further visitations"
5422 if ( visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
5423 {
5424 if ( !( ( *it )->accept( visitor ) ) )
5425 return false;
5426
5427 if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
5428 return false;
5429 }
5430 }
5431 }
5432
5433 if ( !mLayoutManager->accept( visitor ) )
5434 return false;
5435
5436 if ( !mAnnotationManager->accept( visitor ) )
5437 return false;
5438
5439 return true;
5440}
5441
5443{
5445
5446 const QString macros = readEntry( u"Macros"_s, u"/pythonCode"_s, QString() );
5447 if ( !macros.isEmpty() )
5448 {
5449 QgsEmbeddedScriptEntity entity( Qgis::EmbeddedScriptType::Macro, tr( "Macros" ), macros );
5450 if ( !visitor->visitEmbeddedScript( entity, context ) )
5451 {
5452 return false;
5453 }
5454 }
5455
5456 const QString expressionFunctions = readEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s );
5457 if ( !expressionFunctions.isEmpty() )
5458 {
5459 QgsEmbeddedScriptEntity entity( Qgis::EmbeddedScriptType::ExpressionFunction, tr( "Expression functions" ), expressionFunctions );
5460 if ( !visitor->visitEmbeddedScript( entity, context ) )
5461 {
5462 return false;
5463 }
5464 }
5465
5466 const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
5467 if ( !layers.empty() )
5468 {
5469 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5470 {
5471 if ( !( ( *it )->accept( visitor, context ) ) )
5472 {
5473 return false;
5474 }
5475 }
5476 }
5477
5478 return true;
5479}
5480
5482{
5483 return mElevationShadingRenderer;
5484}
5485
5486void QgsProject::loadProjectFlags( const QDomDocument *doc )
5487{
5489
5490 QDomElement element = doc->documentElement().firstChildElement( u"projectFlags"_s );
5492 if ( !element.isNull() )
5493 {
5494 flags = qgsFlagKeysToValue( element.attribute( u"set"_s ), Qgis::ProjectFlags() );
5495 }
5496 else
5497 {
5498 // older project compatibility
5499 element = doc->documentElement().firstChildElement( u"evaluateDefaultValues"_s );
5500 if ( !element.isNull() )
5501 {
5502 if ( element.attribute( u"active"_s, u"0"_s ).toInt() == 1 )
5504 }
5505
5506 // Read trust layer metadata config in the project
5507 element = doc->documentElement().firstChildElement( u"trust"_s );
5508 if ( !element.isNull() )
5509 {
5510 if ( element.attribute( u"active"_s, u"0"_s ).toInt() == 1 )
5512 }
5513 }
5514
5515 setFlags( flags );
5516}
5517
5519{
5521 {
5523 {
5524 const QString projectFunctions = readEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s, QString() );
5525 if ( !projectFunctions.isEmpty() )
5526 {
5527 QgsPythonRunner::run( projectFunctions );
5528 return true;
5529 }
5530 }
5531 }
5532 return false;
5533}
5534
5536{
5538 {
5539 QgsPythonRunner::run( "qgis.utils.clean_project_expression_functions()" );
5540 }
5541}
5542
5544
5545QHash< QString, QColor > loadColorsFromProject( const QgsProject *project )
5546{
5547 QHash< QString, QColor > colors;
5548
5549 //build up color list from project. Do this in advance for speed
5550 QStringList colorStrings = project->readListEntry( u"Palette"_s, u"/Colors"_s );
5551 const QStringList colorLabels = project->readListEntry( u"Palette"_s, u"/Labels"_s );
5552
5553 //generate list from custom colors
5554 int colorIndex = 0;
5555 for ( QStringList::iterator it = colorStrings.begin(); it != colorStrings.end(); ++it )
5556 {
5557 const QColor color = QgsColorUtils::colorFromString( *it );
5558 QString label;
5559 if ( colorLabels.length() > colorIndex )
5560 {
5561 label = colorLabels.at( colorIndex );
5562 }
5563
5564 colors.insert( label.toLower(), color );
5565 colorIndex++;
5566 }
5567
5568 return colors;
5569}
5570
5571
5572GetNamedProjectColor::GetNamedProjectColor( const QgsProject *project )
5573 : QgsScopedExpressionFunction( u"project_color"_s, 1, u"Color"_s )
5574{
5575 if ( !project )
5576 return;
5577
5578 mColors = loadColorsFromProject( project );
5579}
5580
5581GetNamedProjectColor::GetNamedProjectColor( const QHash<QString, QColor> &colors )
5582 : QgsScopedExpressionFunction( u"project_color"_s, 1, u"Color"_s )
5583 , mColors( colors )
5584{}
5585
5586QVariant GetNamedProjectColor::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5587{
5588 const QString colorName = values.at( 0 ).toString().toLower();
5589 if ( mColors.contains( colorName ) )
5590 {
5591 return u"%1,%2,%3"_s.arg( mColors.value( colorName ).red() ).arg( mColors.value( colorName ).green() ).arg( mColors.value( colorName ).blue() );
5592 }
5593 else
5594 return QVariant();
5595}
5596
5597QgsScopedExpressionFunction *GetNamedProjectColor::clone() const
5598{
5599 return new GetNamedProjectColor( mColors );
5600}
5601
5602GetNamedProjectColorObject::GetNamedProjectColorObject( const QgsProject *project )
5603 : QgsScopedExpressionFunction( u"project_color_object"_s, 1, u"Color"_s )
5604{
5605 if ( !project )
5606 return;
5607
5608 mColors = loadColorsFromProject( project );
5609}
5610
5611GetNamedProjectColorObject::GetNamedProjectColorObject( const QHash<QString, QColor> &colors )
5612 : QgsScopedExpressionFunction( u"project_color_object"_s, 1, u"Color"_s )
5613 , mColors( colors )
5614{}
5615
5616QVariant GetNamedProjectColorObject::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5617{
5618 const QString colorName = values.at( 0 ).toString().toLower();
5619 if ( mColors.contains( colorName ) )
5620 {
5621 return mColors.value( colorName );
5622 }
5623 else
5624 return QVariant();
5625}
5626
5627QgsScopedExpressionFunction *GetNamedProjectColorObject::clone() const
5628{
5629 return new GetNamedProjectColorObject( mColors );
5630}
5631
5632// ----------------
5633
5634GetSensorData::GetSensorData( const QMap<QString, QgsAbstractSensor::SensorData> &sensorData )
5635 : QgsScopedExpressionFunction( u"sensor_data"_s, QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( u"name"_s ) << QgsExpressionFunction::Parameter( u"expiration"_s, true, 0 ), u"Sensors"_s )
5636 , mSensorData( sensorData )
5637{}
5638
5639QVariant GetSensorData::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5640{
5641 const QString sensorName = values.at( 0 ).toString();
5642 const int expiration = values.at( 1 ).toInt();
5643 const qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
5644 if ( mSensorData.contains( sensorName ) )
5645 {
5646 if ( expiration <= 0 || ( timestamp - mSensorData[sensorName].lastTimestamp.toMSecsSinceEpoch() ) < expiration )
5647 {
5648 return mSensorData[sensorName].lastValue;
5649 }
5650 }
5651
5652 return QVariant();
5653}
5654
5655QgsScopedExpressionFunction *GetSensorData::clone() const
5656{
5657 return new GetSensorData( mSensorData );
5658}
@ Macro
Project macros.
Definition qgis.h:464
@ ExpressionFunction
Expression functions.
Definition qgis.h:465
@ DontLoad3DViews
Skip loading 3D views.
Definition qgis.h:4690
@ DontStoreOriginalStyles
Skip the initial XML style storage for layers. Useful for minimising project load times in non-intera...
Definition qgis.h:4689
@ ForceReadOnlyLayers
Open layers in a read-only mode.
Definition qgis.h:4692
@ TrustLayerMetadata
Trust layer metadata. Improves project read time. Do not use it if layers' extent is not fixed during...
Definition qgis.h:4687
@ DontUpgradeAnnotations
Don't upgrade old annotation items to QgsAnnotationItem.
Definition qgis.h:4693
@ DontLoadLayouts
Don't load print layouts. Improves project read time if layouts are not required, and allows projects...
Definition qgis.h:4685
@ DontResolveLayers
Don't resolve layer paths (i.e. don't load any layer content). Dramatically improves project read tim...
Definition qgis.h:4683
static QString version()
Version string.
Definition qgis.cpp:682
@ Trusted
The project has been determined by the user as trusted.
Definition qgis.h:478
QFlags< ProjectCapability > ProjectCapabilities
Flags which control project capabilities.
Definition qgis.h:4726
QFlags< ProjectReadFlag > ProjectReadFlags
Project load flags.
Definition qgis.h:4704
DistanceUnit
Units of distance.
Definition qgis.h:5464
@ Meters
Meters.
Definition qgis.h:5465
FilePathType
File path types.
Definition qgis.h:1810
@ Relative
Relative path.
Definition qgis.h:1812
@ Absolute
Absolute path.
Definition qgis.h:1811
TransactionMode
Transaction mode.
Definition qgis.h:4167
@ AutomaticGroups
Automatic transactional editing means that on supported datasources (postgres and geopackage database...
Definition qgis.h:4169
@ BufferedGroups
Buffered transactional editing means that all editable layers in the buffered transaction group are t...
Definition qgis.h:4170
@ Disabled
Edits are buffered locally and sent to the provider when toggling layer editing mode.
Definition qgis.h:4168
AreaUnit
Units of area.
Definition qgis.h:5541
@ SquareMeters
Square meters.
Definition qgis.h:5542
@ 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:2482
@ Temporal
Temporal CRS.
Definition qgis.h:2485
@ Compound
Compound (horizontal + vertical) CRS.
Definition qgis.h:2484
@ Projected
Projected CRS.
Definition qgis.h:2483
@ Other
Other type.
Definition qgis.h:2488
@ Bound
Bound CRS.
Definition qgis.h:2487
@ DerivedProjected
Derived projected CRS.
Definition qgis.h:2489
@ Unknown
Unknown type.
Definition qgis.h:2477
@ Engineering
Engineering CRS.
Definition qgis.h:2486
@ Geographic3d
3D geopraphic CRS
Definition qgis.h:2481
@ Geodetic
Geodetic CRS.
Definition qgis.h:2478
@ Geographic2d
2D geographic CRS
Definition qgis.h:2480
@ Geocentric
Geocentric CRS.
Definition qgis.h:2479
AvoidIntersectionsMode
Flags which control how intersections of pre-existing feature are handled when digitizing new feature...
Definition qgis.h:4653
@ AvoidIntersectionsLayers
Overlap with features from a specified list of layers when digitizing new features not allowed.
Definition qgis.h:4656
@ AllowIntersections
Overlap with any feature allowed when digitizing new features.
Definition qgis.h:4654
ProjectFlag
Flags which control the behavior of QgsProjects.
Definition qgis.h:4301
@ RememberLayerEditStatusBetweenSessions
If set, then any layers set to be editable will be stored in the project and immediately made editabl...
Definition qgis.h:4305
@ EvaluateDefaultValuesOnProviderSide
If set, default values for fields will be evaluated on the provider side when features from the proje...
Definition qgis.h:4302
@ TrustStoredLayerStatistics
If set, then layer statistics (such as the layer extent) will be read from values stored in the proje...
Definition qgis.h:4303
@ Polygon
Polygons.
Definition qgis.h:382
QFlags< DataProviderReadFlag > DataProviderReadFlags
Flags which control data provider construction.
Definition qgis.h:512
LayerType
Types of layers that can be added to a map.
Definition qgis.h:206
@ Group
Composite group layer. Added in QGIS 3.24.
Definition qgis.h:214
@ Plugin
Plugin based layer.
Definition qgis.h:209
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:215
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
Definition qgis.h:212
@ Vector
Vector layer.
Definition qgis.h:207
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:211
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:210
@ Raster
Raster layer.
Definition qgis.h:208
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Definition qgis.h:213
ScaleCalculationMethod
Scale calculation logic.
Definition qgis.h:5741
@ HorizontalMiddle
Calculate horizontally, across midle of map.
Definition qgis.h:5743
@ SkipCredentialsRequest
Skip credentials if the provided one are not valid, let the provider be invalid, avoiding to block th...
Definition qgis.h:499
@ ParallelThreadLoading
Provider is created in a parallel thread than the one where it will live.
Definition qgis.h:501
@ UseProjectCrs
Copy the current project's CRS.
Definition qgis.h:2610
QFlags< ProjectFlag > ProjectFlags
Definition qgis.h:4309
@ Container
A container.
Definition qgis.h:6102
@ Marker
Marker symbol.
Definition qgis.h:652
@ Line
Line symbol.
Definition qgis.h:653
@ Fill
Fill symbol.
Definition qgis.h:654
static QString geoNone()
Constant that holds the string representation for "No ellipse/No CRS".
Definition qgis.h:7095
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
Definition qgis.h:2594
QMap< QString, QStringList > KeywordMap
Map of vocabulary string to keyword list.
QgsAbstractMetadataBase::KeywordMap keywords() const
Returns the keywords map, which is a set of descriptive keywords associated with the resource.
virtual bool readXml(const QDomElement &collectionElem, const QgsPropertiesDefinition &definitions)
Reads property collection state from an XML element.
QList< QgsAction > actions(const QString &actionScope=QString()) const
Returns a list of actions that are available in the given action scope.
Utility class that encapsulates an action based on vector attributes.
Definition qgsaction.h:38
Represents a map layer containing a set of georeferenced annotations, e.g.
Manages storage of a set of QgsAnnotation annotation objects.
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
static QgsProjectStorageRegistry * projectStorageRegistry()
Returns registry of available project storage implementations.
static const QgsSettingsEntryString * settingsLocaleUserLocale
Settings entry locale user locale.
static QgsRuntimeProfiler * profiler()
Returns the application runtime profiler.
void collectTranslatableObjects(QgsTranslationContext *translationContext)
Emits the signal to collect all the strings of .qgs to be included in ts file.
static QgsPluginLayerRegistry * pluginLayerRegistry()
Returns the application's plugin layer registry, used for managing plugin layer types.
void requestForTranslatableObjects(QgsTranslationContext *translationContext)
Emitted when project strings which require translation are being collected for inclusion in a ....
static QString userFullName()
Returns the user's operating system login account full display name.
static QString userLoginName()
Returns the user's operating system login account name.
Manages zip/unzip operations for an archive.
Definition qgsarchive.h:36
A container for attribute editors, used to group them visually in the attribute form if it is set to ...
QList< QgsAttributeEditorElement * > children() const
Gets a list of the children elements of this container.
An abstract base class for any elements of a drag and drop form.
QString name() const
Returns the name of this element.
QgsFields auxiliaryFields() const
Returns a list of all auxiliary fields currently managed by the layer.
bool save()
Commits changes and starts editing then.
Providing some utility methods to manage auxiliary storage.
static QString extension()
Returns the extension used for auxiliary databases.
static bool deleteTable(const QgsDataSourceUri &uri)
Removes a table from the auxiliary storage.
Manages storage of a set of bookmarks.
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QString toProj() const
Returns a Proj string representation of this CRS.
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
static QgsCoordinateReferenceSystem createCompoundCrs(const QgsCoordinateReferenceSystem &horizontalCrs, const QgsCoordinateReferenceSystem &verticalCrs, QString &error)
Given a horizontal and vertical CRS, attempts to create a compound CRS from them.
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
QString projectionAcronym() const
Returns the projection acronym for the projection used by the CRS.
static QgsCoordinateReferenceSystem fromProj(const QString &proj)
Creates a CRS from a proj style formatted string.
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
static QgsCoordinateReferenceSystem fromSrsId(long srsId)
Creates a CRS from a specified QGIS SRS ID.
Contains information about the context in which a coordinate transform is executed.
void readSettings()
Reads the context's state from application settings.
Abstract base class for spatial data provider implementations.
@ EvaluateDefaultValues
Evaluate default values on provider side when calling QgsVectorDataProvider::defaultValue( int index ...
Stores the component parts of a data source URI (e.g.
QgsAttributeEditorContainer * invisibleRootContainer()
Gets the invisible root container for the drag and drop designer form (EditorLayout::TabLayout).
Manages storage of a set of elevation profiles.
Renders elevation shading on an image with different methods (eye dome lighting, hillshading,...
A embedded script entity for QgsObjectEntityVisitorInterface.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
An abstract base class for defining QgsExpression functions.
An expression node for expression functions.
Handles parsing and evaluation of expressions (formerly called "search strings").
virtual QgsLegendSymbolList legendSymbolItems() const
Returns a list of symbology items for the legend.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Container of fields for a vector layer.
Definition qgsfields.h:46
bool isEmpty
Definition qgsfields.h:49
Stores global configuration for labeling engine.
Layer tree group node serves as a container for layers and further groups.
QgsLayerTreeGroup * findGroup(const QString &name)
Find group node with specified name.
void readChildrenFromXml(const QDomElement &element, const QgsReadWriteContext &context)
Read children from XML and append them to the group.
QString name() const override
Returns the group's name.
void insertChildNodes(int index, const QList< QgsLayerTreeNode * > &nodes)
Insert existing nodes at specified position.
QgsLayerTreeGroup * clone() const override
Returns a clone of the group.
Layer tree node points to a map layer.
void resolveReferences(const QgsProject *project, bool looseMatching=false) override
Resolves reference to layer from stored layer ID (if it has not been resolved already).
Base class for nodes in a layer tree.
QList< QgsLayerTreeNode * > abandonChildren()
Removes the children, disconnect all the forwarded and external signals and sets their parent to null...
void setCustomProperty(const QString &key, const QVariant &value)
Sets a custom property for the node. Properties are stored in a map and saved in project file.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file.
void setItemVisibilityChecked(bool checked)
Check or uncheck a node (independently of its ancestors or children).
static void replaceChildrenOfEmbeddedGroups(QgsLayerTreeGroup *group)
Remove subtree of embedded groups and replaces it with a custom property embedded-visible-layers.
static void storeOriginalLayersProperties(QgsLayerTreeGroup *group, const QDomDocument *doc)
Stores in a layer's originalXmlProperties the layer properties information.
static void updateEmbeddedGroupsProjectPath(QgsLayerTreeGroup *group, const QgsProject *project)
Updates an embedded group from a project.
static bool readOldLegend(QgsLayerTreeGroup *root, const QDomElement &legendElem)
Try to load layer tree from.
Namespace with helper functions for layer tree operations.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
Manages storage of a set of layouts.
Stores information about one class/rule of a vector layer renderer in a unified way that can be used ...
static void warning(const QString &msg)
Goes to qWarning.
static Qgis::LayerType typeFromString(const QString &string, bool &ok)
Returns the map layer type corresponding a string value.
A storage object for map layers, in which the layers are owned by the store and have their lifetime b...
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the store.
void layerWillBeRemoved(const QString &layerId)
Emitted when a layer is about to be removed from the store.
void layersRemoved(const QStringList &layerIds)
Emitted after one or more layers were removed from the store.
void allLayersRemoved()
Emitted when all layers are removed, before layersWillBeRemoved() and layerWillBeRemoved() signals ar...
void layerRemoved(const QString &layerId)
Emitted after a layer was removed from the store.
void layerWasAdded(QgsMapLayer *layer)
Emitted when a layer was added to the store.
QgsMapLayer * mapLayer(const QString &id) const
Retrieve a pointer to a layer by layer id.
void layersAdded(const QList< QgsMapLayer * > &layers)
Emitted when one or more layers were added to the store.
Base class for all map layer types.
Definition qgsmaplayer.h:83
QFlags< ReadFlag > ReadFlags
QString source() const
Returns the source for the layer.
QString providerType() const
Returns the provider type (provider key) for this layer.
void configChanged()
Emitted whenever the configuration is changed.
static Qgis::DataProviderReadFlags providerReadFlags(const QDomNode &layerNode, QgsMapLayer::ReadFlags layerReadFlags)
Returns provider read flag deduced from layer read flags layerReadFlags and a dom node layerNode that...
QString id
Definition qgsmaplayer.h:86
QString originalXmlProperties() const
Returns the XML properties of the original layer as they were when the layer was first read from the ...
Qgis::LayerType type
Definition qgsmaplayer.h:93
virtual bool isEditable() const
Returns true if the layer can be edited.
bool writeLayerXml(QDomElement &layerElement, QDomDocument &document, const QgsReadWriteContext &context) const
Stores state in DOM node.
@ Identifiable
If the layer is identifiable using the identify map tool and as a WMS layer.
@ Removable
If the layer can be removed from the project. The layer will not be removable from the legend menu en...
@ FlagReadExtentFromXml
Read extent from xml and skip get extent from provider.
@ FlagTrustLayerMetadata
Trust layer metadata. Improves layer load time by skipping expensive checks like primary key unicity,...
@ FlagForceReadOnly
Force open as read only.
@ FlagDontResolveLayers
Don't resolve layer paths or create data providers for layers.
Container class that allows storage of map themes consisting of visible map layers and layer styles.
Manages storage of a set of views.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
An interface for classes which can visit various object entity (e.g.
virtual bool visitEmbeddedScript(const QgsEmbeddedScriptEntity &entity, const QgsObjectVisitorContext &context)
Called when the visitor will visit an embedded script entity.
A QgsObjectEntityVisitorInterface context object.
Resolves relative paths into absolute paths and vice versa.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
Allows managing the zip/unzip actions on project files.
Definition qgsarchive.h:111
QString projectFile() const
Returns the current .qgs project file or an empty string if there's none.
bool unzip(const QString &zipFilename) override
Clear the current content of this archive and unzip.
Interface for classes that handle missing layer files when reading project files.
Contains settings and properties relating to how a QgsProject should display values such as map coord...
Contains elevation properties for a QgsProject.
static Q_DECL_DEPRECATED void fixOldSymbolLayerReferences(const QMap< QString, QgsMapLayer * > &mapLayers)
QgsSymbolLayerReference uses QgsSymbolLayer unique uuid identifier since QGIS 3.30,...
Contains settings and properties relating to how a QgsProject should interact with a GPS device.
A structured metadata store for a project.
Project property key node.
QString name() const
The name of the property is used as identifier.
QgsProjectProperty * find(const QString &propertyName) const
Attempts to find a property with a matching sub-key name.
void removeKey(const QString &keyName)
Removes the specified key.
void dump(int tabs=0) const override
Dumps out the keys and values.
void subkeyList(QStringList &entries) const
Returns any sub-keys contained by this property which themselves contain other keys.
QgsProjectPropertyKey * addKey(const QString &keyName)
Adds the specified property key as a sub-key.
QVariant value() const override
If this key has a value, it will be stored by its name in its properties.
QgsProjectPropertyValue * setValue(const QString &name, const QVariant &value)
Sets the value associated with this key.
void entryList(QStringList &entries) const
Returns any sub-keys contained by this property that do not contain other keys.
bool readXml(const QDomNode &keyNode) override
Restores the property hierarchy from a specified DOM node.
An abstract base class for QGIS project property hierarchys.
virtual bool isKey() const =0
Returns true if the property is a QgsProjectPropertyKey.
virtual bool isValue() const =0
Returns true if the property is a QgsProjectPropertyValue.
QgsProjectStorage * projectStorageFromUri(const QString &uri)
Returns storage implementation if the URI matches one. Returns nullptr otherwise (it is a normal file...
Metadata associated with a project.
Abstract interface for project storage - to be implemented by various backends and registered in QgsP...
Contains settings and properties relating to how a QgsProject should handle styling.
void setDefaultSymbol(Qgis::SymbolType symbolType, QgsSymbol *symbol)
Sets the project default symbol for a given type.
void setRandomizeDefaultSymbolColor(bool randomized)
Sets whether the default symbol fill color is randomized.
void setDefaultColorRamp(QgsColorRamp *colorRamp)
Sets the project default color ramp.
void setDefaultSymbolOpacity(double opacity)
Sets the default symbol opacity.
Contains temporal settings and properties for the project, this may be used when animating maps or sh...
static Qgis::ProjectTrustStatus checkUserTrust(QgsProject *project)
Returns the current trust status of the specified project.
Describes the version of a project.
QString text() const
Returns a string representation of the version.
int majorVersion() const
Returns the major version number.
Contains settings and properties relating to how a QgsProject should be displayed inside map canvas,...
void mapScalesChanged()
Emitted when the list of custom project map scales changes.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h: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:7636
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7979
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7617
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:7675
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:7704
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7978
#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.