QGIS API Documentation 4.3.0-Master (4ecdf0f3f59)
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 if ( !doc->setContent( &projectFile ) )
2145 {
2146 projectFile.seek( 0 );
2147 QTextStream textStream( &projectFile );
2148 QString projectString;
2149 while ( true )
2150 {
2151 QString part = textStream.read( 65536 );
2152 if ( part.isEmpty() )
2153 break;
2154
2155 for ( int i = 0; i < 32; i++ )
2156 {
2157 if ( i == 9 || i == 10 || i == 13 )
2158 {
2159 continue;
2160 }
2161 part.replace( QChar( i ), u"%1%2%1"_s.arg( FONTMARKER_CHR_FIX, QString::number( i ) ) );
2162 }
2163 projectString.append( part );
2164 }
2165 projectFile.close();
2166
2167 int line, column;
2168 QString errorMsg;
2169 if ( !doc->setContent( projectString, &errorMsg, &line, &column ) )
2170 {
2171 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 );
2172 QgsDebugError( errorString );
2173 setError( errorString );
2174
2175 return false;
2176 }
2177 }
2178 else
2179 {
2180 projectFile.close();
2181 }
2182
2183 QgsDebugMsgLevel( "Opened document " + projectFile.fileName(), 2 );
2184
2185 // get project version string, if any
2186 const QgsProjectVersion fileVersion = getVersion( *doc );
2187 const QgsProjectVersion thisVersion( Qgis::version() );
2188
2189 profile.switchTask( tr( "Updating project file" ) );
2190 if ( thisVersion > fileVersion )
2191 {
2192 const bool isOlderMajorVersion = fileVersion.majorVersion() < thisVersion.majorVersion();
2193
2194 if ( isOlderMajorVersion )
2195 {
2197 "Loading a file that was saved with an older "
2198 "version of qgis (saved in "
2199 + fileVersion.text()
2200 + ", loaded in "
2201 + Qgis::version()
2202 + "). Problems may occur."
2203 );
2204 }
2205
2206 QgsProjectFileTransform projectFile( *doc, fileVersion );
2207
2208 // Shows a warning when an old project file is read.
2210 emit oldProjectVersionWarning( fileVersion.text() );
2212 emit readVersionMismatchOccurred( fileVersion.text() );
2213
2214 projectFile.updateRevision( thisVersion );
2215 }
2216 else if ( fileVersion > thisVersion )
2217 {
2219 "Loading a file that was saved with a newer "
2220 "version of qgis (saved in "
2221 + fileVersion.text()
2222 + ", loaded in "
2223 + Qgis::version()
2224 + "). Problems may occur."
2225 );
2226
2227 emit readVersionMismatchOccurred( fileVersion.text() );
2228 }
2229
2230 // start new project, just keep the file name and auxiliary storage
2231 profile.switchTask( tr( "Creating auxiliary storage" ) );
2232 const QString fileName = mFile.fileName();
2233
2234 const QgsCoordinateReferenceSystem oldVerticalCrs = verticalCrs();
2235 const QgsCoordinateReferenceSystem oldCrs3D = mCrs3D;
2236
2237 // NOTE [ND] -- I suspect this is wrong, as the archive may contain any number of non-auxiliary
2238 // storage related files from the previously loaded project.
2239 std::unique_ptr<QgsAuxiliaryStorage> aStorage = std::move( mAuxiliaryStorage );
2240 std::unique_ptr<QgsArchive> archive = std::move( mArchive );
2241
2242 // don't emit xxxChanged signals during the clear() call, as we'll be emitting
2243 // them again after reading the properties from the project file
2244 mBlockChangeSignalsDuringClear = true;
2245 clear();
2246 mBlockChangeSignalsDuringClear = false;
2247
2248 // this is ugly, but clear() will have created a new archive and started populating it. We
2249 // need to release handles to this archive now as the subsequent call to move will need
2250 // to delete it, and requires free access to do so.
2251 releaseHandlesToProjectArchive();
2252
2253 mAuxiliaryStorage = std::move( aStorage );
2254 mArchive = std::move( archive );
2255
2256 mFile.setFileName( fileName );
2257 mCachedHomePath.clear();
2258 mProjectScope.reset();
2259 mSaveVersion = fileVersion;
2260
2261 // now get any properties
2262 profile.switchTask( tr( "Reading properties" ) );
2263 _getProperties( *doc, mProperties );
2264
2265 // now get the data defined server properties
2266 mDataDefinedServerProperties = getDataDefinedServerProperties( *doc, dataDefinedServerPropertyDefinitions() );
2267
2268 QgsDebugMsgLevel( QString::number( mProperties.count() ) + " properties read", 2 );
2269
2270#if 0
2271 dump_( mProperties );
2272#endif
2273
2274 // get older style project title
2275 QString oldTitle;
2276 _getTitle( *doc, oldTitle );
2277
2278 readProjectFileMetadata( *doc, mSaveUser, mSaveUserFull, mSaveDateTime );
2279
2280 const QDomNodeList homePathNl = doc->elementsByTagName( u"homePath"_s );
2281 if ( homePathNl.count() > 0 )
2282 {
2283 const QDomElement homePathElement = homePathNl.at( 0 ).toElement();
2284 const QString homePath = homePathElement.attribute( u"path"_s );
2285 if ( !homePath.isEmpty() )
2287 }
2288 else
2289 {
2290 emit homePathChanged();
2291 }
2292
2293 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 ) );
2295 const QColor
2296 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 ) );
2298
2299
2300 const QString distanceUnitString = readEntry( u"Measurement"_s, u"/DistanceUnits"_s, QString() );
2301 if ( !distanceUnitString.isEmpty() )
2302 setDistanceUnits( QgsUnitTypes::decodeDistanceUnit( distanceUnitString ) );
2303
2304 const QString areaUnitString = readEntry( u"Measurement"_s, u"/AreaUnits"_s, QString() );
2305 if ( !areaUnitString.isEmpty() )
2306 setAreaUnits( QgsUnitTypes::decodeAreaUnit( areaUnitString ) );
2307
2308 setScaleMethod( qgsEnumKeyToValue( readEntry( u"Measurement"_s, u"/ScaleMethod"_s, QString() ), Qgis::ScaleCalculationMethod::HorizontalMiddle ) );
2309
2310 QgsReadWriteContext context;
2311 context.setPathResolver( pathResolver() );
2312 context.setProjectTranslator( this );
2313
2314 //crs
2315 QgsCoordinateReferenceSystem projectCrs;
2316 if ( readNumEntry( u"SpatialRefSys"_s, u"/ProjectionsEnabled"_s, 0 ) )
2317 {
2318 // first preference - dedicated projectCrs node
2319 const QDomNode srsNode = doc->documentElement().namedItem( u"projectCrs"_s );
2320 if ( !srsNode.isNull() )
2321 {
2322 projectCrs.readXml( srsNode );
2323 }
2324
2325 if ( !projectCrs.isValid() )
2326 {
2327 const QString projCrsString = readEntry( u"SpatialRefSys"_s, u"/ProjectCRSProj4String"_s );
2328 const long currentCRS = readNumEntry( u"SpatialRefSys"_s, u"/ProjectCRSID"_s, -1 );
2329 const QString authid = readEntry( u"SpatialRefSys"_s, u"/ProjectCrs"_s );
2330
2331 // authid should be prioritized over all
2332 const bool isUserAuthId = authid.startsWith( "USER:"_L1, Qt::CaseInsensitive );
2333 if ( !authid.isEmpty() && !isUserAuthId )
2334 projectCrs = QgsCoordinateReferenceSystem( authid );
2335
2336 // try the CRS
2337 if ( !projectCrs.isValid() && currentCRS >= 0 )
2338 {
2339 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2340 }
2341
2342 // if that didn't produce a match, try the proj.4 string
2343 if ( !projCrsString.isEmpty() && ( authid.isEmpty() || isUserAuthId ) && ( !projectCrs.isValid() || projectCrs.toProj() != projCrsString ) )
2344 {
2345 projectCrs = QgsCoordinateReferenceSystem::fromProj( projCrsString );
2346 }
2347
2348 // last just take the given id
2349 if ( !projectCrs.isValid() )
2350 {
2351 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
2352 }
2353 }
2354 }
2355 mCrs = projectCrs;
2356
2357 //vertical CRS
2358 {
2359 QgsCoordinateReferenceSystem verticalCrs;
2360 const QDomNode verticalCrsNode = doc->documentElement().namedItem( u"verticalCrs"_s );
2361 if ( !verticalCrsNode.isNull() )
2362 {
2363 verticalCrs.readXml( verticalCrsNode );
2364 }
2365 mVerticalCrs = verticalCrs;
2366 }
2367 rebuildCrs3D();
2368
2369 QStringList datumErrors;
2370 if ( !mTransformContext.readXml( doc->documentElement(), context, datumErrors ) && !datumErrors.empty() )
2371 {
2372 emit missingDatumTransforms( datumErrors );
2373 }
2375
2376 // map shading
2377 const QDomNode elevationShadingNode = doc->documentElement().namedItem( u"elevation-shading-renderer"_s );
2378 if ( !elevationShadingNode.isNull() )
2379 {
2380 mElevationShadingRenderer.readXml( elevationShadingNode.toElement(), context );
2381 }
2383
2384
2385 //add variables defined in project file - do this early in the reading cycle, as other components
2386 //(e.g. layouts) may depend on these variables
2387 const QStringList variableNames = readListEntry( u"Variables"_s, u"/variableNames"_s );
2388 const QStringList variableValues = readListEntry( u"Variables"_s, u"/variableValues"_s );
2389
2390 mCustomVariables.clear();
2391 if ( variableNames.length() == variableValues.length() )
2392 {
2393 for ( int i = 0; i < variableNames.length(); ++i )
2394 {
2395 mCustomVariables.insert( variableNames.at( i ), variableValues.at( i ) );
2396 }
2397 }
2398 else
2399 {
2400 QgsMessageLog::logMessage( tr( "Project Variables Invalid" ), tr( "The project contains invalid variable settings." ) );
2401 }
2402
2403 // Register expression functions stored in the project.
2404 // They might be using project variables and might be
2405 // in turn being used by other components (e.g., layouts).
2407
2408 QDomElement element = doc->documentElement().firstChildElement( u"projectMetadata"_s );
2409
2410 if ( !element.isNull() )
2411 {
2412 mMetadata.readMetadataXml( element, context );
2413 }
2414 else
2415 {
2416 // older project, no metadata => remove auto generated metadata which is populated on QgsProject::clear()
2417 mMetadata = QgsProjectMetadata();
2418 }
2419 if ( mMetadata.title().isEmpty() && !oldTitle.isEmpty() )
2420 {
2421 // upgrade older title storage to storing within project metadata.
2422 mMetadata.setTitle( oldTitle );
2423 }
2424 emit metadataChanged();
2425 emit titleChanged();
2426
2427 // Transaction mode
2428 element = doc->documentElement().firstChildElement( u"transaction"_s );
2429 if ( !element.isNull() )
2430 {
2431 mTransactionMode = qgsEnumKeyToValue( element.attribute( u"mode"_s ), Qgis::TransactionMode::Disabled );
2432 }
2433 else
2434 {
2435 // maybe older project => try read autotransaction
2436 element = doc->documentElement().firstChildElement( u"autotransaction"_s );
2437 if ( !element.isNull() )
2438 {
2439 mTransactionMode = static_cast<Qgis::TransactionMode>( element.attribute( u"active"_s, u"0"_s ).toInt() );
2440 }
2441 }
2442
2443 // read the layer tree from project file
2444 profile.switchTask( tr( "Loading layer tree" ) );
2445 mRootGroup->setCustomProperty( u"loading"_s, 1 );
2446
2447 QDomElement layerTreeElem = doc->documentElement().firstChildElement( u"layer-tree-group"_s );
2448 if ( !layerTreeElem.isNull() )
2449 {
2450 // Use a temporary tree to read the nodes to prevent signals being delivered to the models
2451 QgsLayerTree tempTree;
2452 tempTree.readChildrenFromXml( layerTreeElem, context );
2453 mRootGroup->insertChildNodes( -1, tempTree.abandonChildren() );
2454 }
2455 else
2456 {
2457 QgsLayerTreeUtils::readOldLegend( mRootGroup.get(), doc->documentElement().firstChildElement( u"legend"_s ) );
2458 }
2459
2460 mLayerTreeRegistryBridge->setEnabled( false );
2461
2462 // get the map layers
2463 profile.switchTask( tr( "Reading map layers" ) );
2464
2465 loadProjectFlags( doc.get() );
2466
2467 QList<QDomNode> brokenNodes;
2468 const bool clean = _getMapLayers( *doc, brokenNodes, flags );
2469
2470 // review the integrity of the retrieved map layers
2471 if ( !clean && !( flags & Qgis::ProjectReadFlag::DontResolveLayers ) )
2472 {
2473 QgsDebugError( u"Unable to get map layers from project file."_s );
2474
2475 if ( !brokenNodes.isEmpty() )
2476 {
2477 QgsDebugError( "there are " + QString::number( brokenNodes.size() ) + " broken layers" );
2478 }
2479
2480 // we let a custom handler decide what to do with missing layers
2481 // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
2482 mBadLayerHandler->handleBadLayers( brokenNodes );
2483 }
2484
2485 mMainAnnotationLayer->readLayerXml( doc->documentElement().firstChildElement( u"main-annotation-layer"_s ), context );
2486 mMainAnnotationLayer->setTransformContext( mTransformContext );
2487
2488 // load embedded groups and layers
2489 profile.switchTask( tr( "Loading embedded layers" ) );
2490 loadEmbeddedNodes( mRootGroup.get(), flags );
2491
2492 // Resolve references to other layers
2493 // Needs to be done here once all dependent layers are loaded
2494 profile.switchTask( tr( "Resolving layer references" ) );
2495 QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
2496 for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
2497 {
2498 it.value()->resolveReferences( this );
2499 }
2500 mMainAnnotationLayer->resolveReferences( this );
2501
2502 mLayerTreeRegistryBridge->setEnabled( true );
2503
2504 // now that layers are loaded, we can resolve layer tree's references to the layers
2505 profile.switchTask( tr( "Resolving references" ) );
2506 mRootGroup->resolveReferences( this );
2507
2508 // we need to migrate old fashion designed QgsSymbolLayerReference to new ones
2509 if ( QgsProjectVersion( 3, 28, 0 ) > mSaveVersion )
2510 {
2514 }
2515
2516 if ( !layerTreeElem.isNull() )
2517 {
2518 mRootGroup->readLayerOrderFromXml( layerTreeElem );
2519 }
2520
2521 // Load pre 3.0 configuration
2522 const QDomElement layerTreeCanvasElem = doc->documentElement().firstChildElement( u"layer-tree-canvas"_s );
2523 if ( !layerTreeCanvasElem.isNull() )
2524 {
2525 mRootGroup->readLayerOrderFromXml( layerTreeCanvasElem );
2526 }
2527
2528 // Convert pre 3.4 to create layers flags
2529 if ( QgsProjectVersion( 3, 4, 0 ) > mSaveVersion )
2530 {
2531 const QStringList requiredLayerIds = readListEntry( u"RequiredLayers"_s, u"Layers"_s );
2532 for ( const QString &layerId : requiredLayerIds )
2533 {
2534 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2535 {
2536 layer->setFlags( layer->flags() & ~QgsMapLayer::Removable );
2537 }
2538 }
2539 const QStringList disabledLayerIds = readListEntry( u"Identify"_s, u"/disabledLayers"_s );
2540 for ( const QString &layerId : disabledLayerIds )
2541 {
2542 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2543 {
2544 layer->setFlags( layer->flags() & ~QgsMapLayer::Identifiable );
2545 }
2546 }
2547 }
2548
2549 // Convert pre 3.26 default styles
2550 if ( QgsProjectVersion( 3, 26, 0 ) > mSaveVersion )
2551 {
2552 // Convert default symbols
2553 QString styleName = readEntry( u"DefaultStyles"_s, u"/Marker"_s );
2554 if ( !styleName.isEmpty() )
2555 {
2556 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2558 }
2559 styleName = readEntry( u"DefaultStyles"_s, u"/Line"_s );
2560 if ( !styleName.isEmpty() )
2561 {
2562 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2564 }
2565 styleName = readEntry( u"DefaultStyles"_s, u"/Fill"_s );
2566 if ( !styleName.isEmpty() )
2567 {
2568 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2570 }
2571 styleName = readEntry( u"DefaultStyles"_s, u"/ColorRamp"_s );
2572 if ( !styleName.isEmpty() )
2573 {
2574 std::unique_ptr<QgsColorRamp> colorRamp( QgsStyle::defaultStyle()->colorRamp( styleName ) );
2575 styleSettings()->setDefaultColorRamp( colorRamp.get() );
2576 }
2577
2578 // Convert randomize default symbol fill color
2579 styleSettings()->setRandomizeDefaultSymbolColor( readBoolEntry( u"DefaultStyles"_s, u"/RandomColors"_s, true ) );
2580
2581 // Convert default symbol opacity
2582 double opacity = 1.0;
2583 bool ok = false;
2584 // upgrade old setting
2585 double alpha = readDoubleEntry( u"DefaultStyles"_s, u"/AlphaInt"_s, 255, &ok );
2586 if ( ok )
2587 opacity = alpha / 255.0;
2588 double newOpacity = readDoubleEntry( u"DefaultStyles"_s, u"/Opacity"_s, 1.0, &ok );
2589 if ( ok )
2590 opacity = newOpacity;
2592
2593 // Cleanup
2594 removeEntry( u"DefaultStyles"_s, u"/Marker"_s );
2595 removeEntry( u"DefaultStyles"_s, u"/Line"_s );
2596 removeEntry( u"DefaultStyles"_s, u"/Fill"_s );
2597 removeEntry( u"DefaultStyles"_s, u"/ColorRamp"_s );
2598 removeEntry( u"DefaultStyles"_s, u"/RandomColors"_s );
2599 removeEntry( u"DefaultStyles"_s, u"/AlphaInt"_s );
2600 removeEntry( u"DefaultStyles"_s, u"/Opacity"_s );
2601 }
2602
2603 // After bad layer handling we might still have invalid layers,
2604 // store them in case the user wanted to handle them later
2605 // or wanted to pass them through when saving
2607 {
2608 profile.switchTask( tr( "Storing original layer properties" ) );
2609 QgsLayerTreeUtils::storeOriginalLayersProperties( mRootGroup.get(), doc.get() );
2610 }
2611
2612 mRootGroup->removeCustomProperty( u"loading"_s );
2613
2614 profile.switchTask( tr( "Loading map themes" ) );
2615 mMapThemeCollection = std::make_unique< QgsMapThemeCollection >( this );
2617 mMapThemeCollection->readXml( *doc );
2618
2619 profile.switchTask( tr( "Loading label settings" ) );
2620 mLabelingEngineSettings->readSettingsFromProject( this );
2621 {
2622 const QDomElement labelEngineSettingsElement = doc->documentElement().firstChildElement( u"labelEngineSettings"_s );
2623 mLabelingEngineSettings->readXml( labelEngineSettingsElement, context );
2624 }
2625 mLabelingEngineSettings->resolveReferences( this );
2626
2628
2629 profile.switchTask( tr( "Loading annotations" ) );
2631 {
2632 mAnnotationManager->readXml( doc->documentElement(), context );
2633 }
2634 else
2635 {
2636 mAnnotationManager->readXmlAndUpgradeToAnnotationLayerItems( doc->documentElement(), context, mMainAnnotationLayer, mTransformContext );
2637 }
2639 {
2640 profile.switchTask( tr( "Loading layouts" ) );
2641 mLayoutManager->readXml( doc->documentElement(), *doc );
2642 }
2643
2644 {
2645 profile.switchTask( tr( "Loading elevation profiles" ) );
2646 mElevationProfileManager->readXml( doc->documentElement(), *doc, context );
2647 mElevationProfileManager->resolveReferences( this );
2648 }
2649
2650 {
2651 profile.switchTask( tr( "Loading selective masking source sets" ) );
2652 mSelectiveMaskingSourceSetManager->readXml( doc->documentElement(), *doc, context );
2653 }
2654
2656 {
2657 profile.switchTask( tr( "Loading 3D Views" ) );
2658 m3DViewsManager->readXml( doc->documentElement(), *doc );
2659 }
2660
2661 profile.switchTask( tr( "Loading bookmarks" ) );
2662 mBookmarkManager->readXml( doc->documentElement(), *doc );
2663
2664 profile.switchTask( tr( "Loading sensors" ) );
2665 mSensorManager->readXml( doc->documentElement(), *doc );
2666
2667 // reassign change dependencies now that all layers are loaded
2668 QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2669 for ( QMap<QString, QgsMapLayer *>::iterator it = existingMaps.begin(); it != existingMaps.end(); ++it )
2670 {
2671 it.value()->setDependencies( it.value()->dependencies() );
2672 }
2673
2674 profile.switchTask( tr( "Loading snapping settings" ) );
2675 mSnappingConfig.readProject( *doc );
2676 mAvoidIntersectionsMode = static_cast<Qgis::AvoidIntersectionsMode>(
2677 readNumEntry( u"Digitizing"_s, u"/AvoidIntersectionsMode"_s, static_cast<int>( Qgis::AvoidIntersectionsMode::AvoidIntersectionsLayers ) )
2678 );
2679
2680 profile.switchTask( tr( "Loading view settings" ) );
2681 // restore older project scales settings
2682 mViewSettings->setUseProjectScales( readBoolEntry( u"Scales"_s, u"/useProjectScales"_s ) );
2683 const QStringList scales = readListEntry( u"Scales"_s, u"/ScalesList"_s );
2684 QVector<double> res;
2685 for ( const QString &scale : scales )
2686 {
2687 const QStringList parts = scale.split( ':' );
2688 if ( parts.size() != 2 )
2689 continue;
2690
2691 bool ok = false;
2692 const double denominator = QLocale().toDouble( parts[1], &ok );
2693 if ( ok )
2694 {
2695 res << denominator;
2696 }
2697 }
2698 mViewSettings->setMapScales( res );
2699 const QDomElement viewSettingsElement = doc->documentElement().firstChildElement( u"ProjectViewSettings"_s );
2700 if ( !viewSettingsElement.isNull() )
2701 mViewSettings->readXml( viewSettingsElement, context );
2702
2703 // restore style settings
2704 profile.switchTask( tr( "Loading style properties" ) );
2705 const QDomElement styleSettingsElement = doc->documentElement().firstChildElement( u"ProjectStyleSettings"_s );
2706 if ( !styleSettingsElement.isNull() )
2707 {
2708 mStyleSettings->removeProjectStyle();
2709 mStyleSettings->readXml( styleSettingsElement, context, flags );
2710 }
2711
2712 // restore time settings
2713 profile.switchTask( tr( "Loading temporal settings" ) );
2714 const QDomElement timeSettingsElement = doc->documentElement().firstChildElement( u"ProjectTimeSettings"_s );
2715 if ( !timeSettingsElement.isNull() )
2716 mTimeSettings->readXml( timeSettingsElement, context );
2717
2718
2719 profile.switchTask( tr( "Loading elevation properties" ) );
2720 const QDomElement elevationPropertiesElement = doc->documentElement().firstChildElement( u"ElevationProperties"_s );
2721 if ( !elevationPropertiesElement.isNull() )
2722 mElevationProperties->readXml( elevationPropertiesElement, context );
2723 mElevationProperties->resolveReferences( this );
2724
2725 profile.switchTask( tr( "Loading display settings" ) );
2726 {
2727 const QDomElement displaySettingsElement = doc->documentElement().firstChildElement( u"ProjectDisplaySettings"_s );
2728 if ( !displaySettingsElement.isNull() )
2729 mDisplaySettings->readXml( displaySettingsElement, context );
2730 }
2731
2732 profile.switchTask( tr( "Loading GPS settings" ) );
2733 {
2734 const QDomElement gpsSettingsElement = doc->documentElement().firstChildElement( u"ProjectGpsSettings"_s );
2735 if ( !gpsSettingsElement.isNull() )
2736 mGpsSettings->readXml( gpsSettingsElement, context );
2737 mGpsSettings->resolveReferences( this );
2738 }
2739
2740 profile.switchTask( tr( "Updating variables" ) );
2742 profile.switchTask( tr( "Updating CRS" ) );
2743 emit crsChanged();
2744 if ( verticalCrs() != oldVerticalCrs )
2745 emit verticalCrsChanged();
2746 if ( mCrs3D != oldCrs3D )
2747 emit crs3DChanged();
2748 emit ellipsoidChanged( ellipsoid() );
2749
2750 // read the project: used by map canvas and legend
2751 profile.switchTask( tr( "Reading external settings" ) );
2752 emit readProject( *doc );
2753 emit readProjectWithContext( *doc, context );
2754
2755 profile.switchTask( tr( "Updating interface" ) );
2756
2757 snapSignalBlock.release();
2758 if ( !mBlockSnappingUpdates )
2759 emit snappingConfigChanged( mSnappingConfig );
2760
2763 emit projectColorsChanged();
2764
2765 // if all went well, we're allegedly in pristine state
2766 if ( clean )
2767 setDirty( false );
2768
2769 QgsDebugMsgLevel( u"Project save user: %1"_s.arg( mSaveUser ), 2 );
2770 QgsDebugMsgLevel( u"Project save user: %1"_s.arg( mSaveUserFull ), 2 );
2771
2775
2776 if ( mTranslator )
2777 {
2778 //project possibly translated -> rename it with locale postfix
2779 const QString newFileName( u"%1/%2.qgs"_s.arg( QFileInfo( mFile ).absolutePath(), localeFileName ) );
2780 setFileName( newFileName );
2781
2782 if ( write() )
2783 {
2784 QgsMessageLog::logMessage( tr( "Translated project saved with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Success );
2785 }
2786 else
2787 {
2788 QgsMessageLog::logMessage( tr( "Error saving translated project with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Critical );
2789 }
2790 }
2791
2792 // lastly, make any previously editable layers editable
2793 const QMap<QString, QgsMapLayer *> loadedLayers = mapLayers();
2794 for ( auto it = loadedLayers.constBegin(); it != loadedLayers.constEnd(); ++it )
2795 {
2796 if ( it.value()->isValid() && it.value()->customProperty( u"_layer_was_editable"_s ).toBool() )
2797 {
2798 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( it.value() ) )
2799 vl->startEditing();
2800 it.value()->removeCustomProperty( u"_layer_was_editable"_s );
2801 }
2802 }
2803
2804 return true;
2805}
2806
2807bool QgsProject::loadEmbeddedNodes( QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
2808{
2810
2811 bool valid = true;
2812 const auto constChildren = group->children();
2813 for ( QgsLayerTreeNode *child : constChildren )
2814 {
2815 if ( QgsLayerTree::isGroup( child ) )
2816 {
2817 QgsLayerTreeGroup *childGroup = QgsLayerTree::toGroup( child );
2818 if ( childGroup->customProperty( u"embedded"_s ).toInt() )
2819 {
2820 // make sure to convert the path from relative to absolute
2821 const QString projectPath = readPath( childGroup->customProperty( u"embedded_project"_s ).toString() );
2822 childGroup->setCustomProperty( u"embedded_project"_s, projectPath );
2823 std::unique_ptr< QgsLayerTreeGroup > newGroup = createEmbeddedGroup( childGroup->name(), projectPath, childGroup->customProperty( u"embedded-invisible-layers"_s ).toStringList(), flags );
2824 if ( newGroup )
2825 {
2826 QList<QgsLayerTreeNode *> clonedChildren;
2827 const QList<QgsLayerTreeNode *> constChildren = newGroup->children();
2828 clonedChildren.reserve( constChildren.size() );
2829 for ( QgsLayerTreeNode *newGroupChild : constChildren )
2830 clonedChildren << newGroupChild->clone();
2831
2832 childGroup->insertChildNodes( 0, clonedChildren );
2833 }
2834 }
2835 else
2836 {
2837 loadEmbeddedNodes( childGroup, flags );
2838 }
2839 }
2840 else if ( QgsLayerTree::isLayer( child ) )
2841 {
2842 if ( child->customProperty( u"embedded"_s ).toInt() )
2843 {
2844 QList<QDomNode> brokenNodes;
2845 if ( !createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), readPath( child->customProperty( u"embedded_project"_s ).toString() ), brokenNodes, true, flags ) )
2846 {
2847 valid = valid && false;
2848 }
2849 }
2850 }
2851 }
2852
2853 return valid;
2854}
2855
2857{
2858 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
2860
2861 return mCustomVariables;
2862}
2863
2864void QgsProject::setCustomVariables( const QVariantMap &variables )
2865{
2867
2868 if ( variables == mCustomVariables )
2869 return;
2870
2871 //write variable to project
2872 QStringList variableNames;
2873 QStringList variableValues;
2874
2875 QVariantMap::const_iterator it = variables.constBegin();
2876 for ( ; it != variables.constEnd(); ++it )
2877 {
2878 variableNames << it.key();
2879 variableValues << it.value().toString();
2880 }
2881
2882 writeEntry( u"Variables"_s, u"/variableNames"_s, variableNames );
2883 writeEntry( u"Variables"_s, u"/variableValues"_s, variableValues );
2884
2885 mCustomVariables = variables;
2886 mProjectScope.reset();
2887
2889}
2890
2892{
2894
2895 *mLabelingEngineSettings = settings;
2897}
2898
2900{
2902
2903 return *mLabelingEngineSettings;
2904}
2905
2907{
2909
2910 mProjectScope.reset();
2911 return mLayerStore.get();
2912}
2913
2915{
2917
2918 return mLayerStore.get();
2919}
2920
2921QList<QgsVectorLayer *> QgsProject::avoidIntersectionsLayers() const
2922{
2924
2925 QList<QgsVectorLayer *> layers;
2926 const QStringList layerIds = readListEntry( u"Digitizing"_s, u"/AvoidIntersectionsList"_s, QStringList() );
2927 const auto constLayerIds = layerIds;
2928 for ( const QString &layerId : constLayerIds )
2929 {
2930 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer( layerId ) ) )
2931 layers << vlayer;
2932 }
2933 return layers;
2934}
2935
2936void QgsProject::setAvoidIntersectionsLayers( const QList<QgsVectorLayer *> &layers )
2937{
2939
2940 QStringList list;
2941 list.reserve( layers.size() );
2942
2943 for ( QgsVectorLayer *layer : layers )
2944 {
2945 if ( layer->geometryType() == Qgis::GeometryType::Polygon )
2946 list << layer->id();
2947 }
2948
2949 writeEntry( u"Digitizing"_s, u"/AvoidIntersectionsList"_s, list );
2951}
2952
2963
2965{
2966 // this method is called quite extensively using QgsProject::instance() skip-keyword-check
2968
2969 // MUCH cheaper to clone than build
2970 if ( mProjectScope )
2971 {
2972 auto projectScope = std::make_unique< QgsExpressionContextScope >( *mProjectScope );
2973
2974 // we can't cache these variables
2975 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_distance_units"_s, QgsUnitTypes::toString( distanceUnits() ), true, true ) );
2976 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_area_units"_s, QgsUnitTypes::toString( areaUnits() ), true, true ) );
2977
2978 // neither this function
2979 projectScope->addFunction( u"sensor_data"_s, new GetSensorData( sensorManager()->sensorsData() ) );
2980
2981 projectScope->addLayerStore( mLayerStore.get() );
2982
2983 return projectScope.release();
2984 }
2985
2986 mProjectScope = std::make_unique< QgsExpressionContextScope >( QObject::tr( "Project" ) );
2987
2988 const QVariantMap vars = customVariables();
2989
2990 QVariantMap::const_iterator it = vars.constBegin();
2991
2992 for ( ; it != vars.constEnd(); ++it )
2993 {
2994 mProjectScope->setVariable( it.key(), it.value(), true );
2995 }
2996
2997 QString projectPath = projectStorage() ? fileName() : absoluteFilePath();
2998 if ( projectPath.isEmpty() )
2999 projectPath = mOriginalPath;
3000 const QString projectFolder = QFileInfo( projectPath ).path();
3001 const QString projectFilename = QFileInfo( projectPath ).fileName();
3002 const QString projectBasename = baseName();
3003
3004 //add other known project variables
3005 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_title"_s, title(), true, true ) );
3006 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_path"_s, QDir::toNativeSeparators( projectPath ), true, true ) );
3007 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_folder"_s, QDir::toNativeSeparators( projectFolder ), true, true ) );
3008 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_filename"_s, projectFilename, true, true ) );
3009 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_basename"_s, projectBasename, true, true ) );
3010 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_home"_s, QDir::toNativeSeparators( homePath() ), true, true ) );
3011 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_last_saved"_s, mSaveDateTime.isNull() ? QVariant() : QVariant( mSaveDateTime ), true, true ) );
3012
3013 const QgsCoordinateReferenceSystem projectCrs = crs();
3014 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs"_s, projectCrs.authid(), true, true ) );
3015 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_definition"_s, projectCrs.toProj(), true, true ) );
3016 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_description"_s, projectCrs.description(), true, true ) );
3017 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_acronym"_s, projectCrs.projectionAcronym(), true ) );
3018 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_ellipsoid"_s, projectCrs.ellipsoidAcronym(), true ) );
3019 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_proj4"_s, projectCrs.toProj(), true ) );
3020 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_crs_wkt"_s, projectCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
3021
3022 const QgsCoordinateReferenceSystem projectVerticalCrs = QgsProject::verticalCrs();
3023 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs"_s, projectVerticalCrs.authid(), true, true ) );
3024 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs_definition"_s, projectVerticalCrs.toProj(), true, true ) );
3025 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs_description"_s, projectVerticalCrs.description(), true, true ) );
3026 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_vertical_crs_wkt"_s, projectVerticalCrs.toWkt( Qgis::CrsWktVariant::Preferred ), true ) );
3027
3028 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_ellipsoid"_s, ellipsoid(), true, true ) );
3029 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"_project_transform_context"_s, QVariant::fromValue<QgsCoordinateTransformContext>( transformContext() ), true, true ) );
3030 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_units"_s, QgsUnitTypes::toString( projectCrs.mapUnits() ), true ) );
3031
3032 // metadata
3033 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_author"_s, metadata().author(), true, true ) );
3034 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_abstract"_s, metadata().abstract(), true, true ) );
3035 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_creation_date"_s, metadata().creationDateTime(), true, true ) );
3036 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_identifier"_s, metadata().identifier(), true, true ) );
3037
3038 // keywords
3039 QVariantMap keywords;
3040 const QgsAbstractMetadataBase::KeywordMap metadataKeywords = metadata().keywords();
3041 for ( auto it = metadataKeywords.constBegin(); it != metadataKeywords.constEnd(); ++it )
3042 {
3043 keywords.insert( it.key(), it.value() );
3044 }
3045 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"project_keywords"_s, keywords, true, true ) );
3046
3047 // layers
3048 QVariantList layersIds;
3049 QVariantList layers;
3050 const QMap<QString, QgsMapLayer *> layersInProject = mLayerStore->mapLayers();
3051 layersIds.reserve( layersInProject.count() );
3052 layers.reserve( layersInProject.count() );
3053 for ( auto it = layersInProject.constBegin(); it != layersInProject.constEnd(); ++it )
3054 {
3055 layersIds << it.value()->id();
3057 }
3058 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"layer_ids"_s, layersIds, true ) );
3059 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( u"layers"_s, layers, true ) );
3060
3061 mProjectScope->addFunction( u"project_color"_s, new GetNamedProjectColor( this ) );
3062 mProjectScope->addFunction( u"project_color_object"_s, new GetNamedProjectColorObject( this ) );
3063
3065}
3066
3067void QgsProject::onMapLayersAdded( const QList<QgsMapLayer *> &layers )
3068{
3070
3071 const QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
3072
3073 const auto constLayers = layers;
3074 for ( QgsMapLayer *layer : constLayers )
3075 {
3076 if ( !layer->isValid() )
3077 return;
3078
3079 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer ) )
3080 {
3081 vlayer->setReadExtentFromXml( mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics );
3082 if ( vlayer->dataProvider() )
3083 vlayer->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, ( bool ) ( mFlags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide ) );
3084 }
3085
3086 connect( layer, &QgsMapLayer::configChanged, this, [this] { setDirty(); } );
3087
3088 // check if we have to update connections for layers with dependencies
3089 for ( QMap<QString, QgsMapLayer *>::const_iterator it = existingMaps.cbegin(); it != existingMaps.cend(); ++it )
3090 {
3091 const QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
3092 if ( deps.contains( layer->id() ) )
3093 {
3094 // reconnect to change signals
3095 it.value()->setDependencies( deps );
3096 }
3097 }
3098 }
3099
3100 updateTransactionGroups();
3101
3102 if ( !mBlockSnappingUpdates && mSnappingConfig.addLayers( layers ) )
3103 emit snappingConfigChanged( mSnappingConfig );
3104}
3105
3106void QgsProject::onMapLayersRemoved( const QList<QgsMapLayer *> &layers )
3107{
3109
3110 if ( !mBlockSnappingUpdates && mSnappingConfig.removeLayers( layers ) )
3111 emit snappingConfigChanged( mSnappingConfig );
3112
3113 for ( QgsMapLayer *layer : layers )
3114 {
3115 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
3116 if ( !vlayer )
3117 continue;
3118
3119 mEditBufferGroup.removeLayer( vlayer );
3120 }
3121}
3122
3123void QgsProject::cleanTransactionGroups( bool force )
3124{
3126
3127 bool changed = false;
3128 for ( QMap< QPair< QString, QString>, QgsTransactionGroup *>::Iterator tg = mTransactionGroups.begin(); tg != mTransactionGroups.end(); )
3129 {
3130 if ( tg.value()->isEmpty() || force )
3131 {
3132 delete tg.value();
3133 tg = mTransactionGroups.erase( tg );
3134 changed = true;
3135 }
3136 else
3137 {
3138 ++tg;
3139 }
3140 }
3141 if ( changed )
3143}
3144
3145void QgsProject::updateTransactionGroups()
3146{
3148
3149 mEditBufferGroup.clear();
3150
3151 switch ( mTransactionMode )
3152 {
3154 {
3155 cleanTransactionGroups( true );
3156 return;
3157 }
3158 break;
3160 cleanTransactionGroups( true );
3161 break;
3163 cleanTransactionGroups( false );
3164 break;
3165 }
3166
3167 bool tgChanged = false;
3168 const auto constLayers = mapLayers().values();
3169 for ( QgsMapLayer *layer : constLayers )
3170 {
3171 if ( !layer->isValid() )
3172 continue;
3173
3174 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
3175 if ( !vlayer )
3176 continue;
3177
3178 switch ( mTransactionMode )
3179 {
3181 Q_ASSERT( false );
3182 break;
3184 {
3186 {
3187 const QString connString = QgsTransaction::connectionString( vlayer->source() );
3188 const QString key = vlayer->providerType();
3189
3190 QgsTransactionGroup *tg = mTransactionGroups.value( qMakePair( key, connString ) );
3191
3192 if ( !tg )
3193 {
3194 tg = new QgsTransactionGroup();
3195 mTransactionGroups.insert( qMakePair( key, connString ), tg );
3196 tgChanged = true;
3197 }
3198 tg->addLayer( vlayer );
3199 }
3200 }
3201 break;
3203 {
3204 if ( vlayer->supportsEditing() )
3205 mEditBufferGroup.addLayer( vlayer );
3206 }
3207 break;
3208 }
3209 }
3210
3211 if ( tgChanged )
3213}
3214
3215bool QgsProject::readLayer( const QDomNode &layerNode )
3216{
3218
3219 QgsReadWriteContext context;
3220 context.setPathResolver( pathResolver() );
3221 context.setProjectTranslator( this );
3222 context.setTransformContext( transformContext() );
3223 context.setCurrentLayerId( layerNode.toElement().firstChildElement( u"id"_s ).text() );
3224 QList<QDomNode> brokenNodes;
3225 if ( addLayer( layerNode.toElement(), brokenNodes, context ) )
3226 {
3227 // have to try to update joins for all layers now - a previously added layer may be dependent on this newly
3228 // added layer for joins
3229 const QVector<QgsVectorLayer *> vectorLayers = layers<QgsVectorLayer *>();
3230 for ( QgsVectorLayer *layer : vectorLayers )
3231 {
3232 // TODO: should be only done later - and with all layers (other layers may have referenced this layer)
3233 layer->resolveReferences( this );
3234
3235 if ( layer->isValid() && layer->customProperty( u"_layer_was_editable"_s ).toBool() )
3236 {
3237 layer->startEditing();
3238 layer->removeCustomProperty( u"_layer_was_editable"_s );
3239 }
3240 }
3241 return true;
3242 }
3243 return false;
3244}
3245
3246bool QgsProject::write( const QString &filename )
3247{
3249
3250 mFile.setFileName( filename );
3251 emit fileNameChanged();
3252 mCachedHomePath.clear();
3253 return write();
3254}
3255
3257{
3259
3260 mProjectScope.reset();
3261 if ( QgsProjectStorage *storage = projectStorage() )
3262 {
3263 QgsReadWriteContext context;
3264 // for projects stored in a custom storage, we have to check for the support
3265 // of relative paths since the storage most likely will not be in a file system
3266 const QString storageFilePath { storage->filePath( mFile.fileName() ) };
3267 if ( storageFilePath.isEmpty() )
3268 {
3270 }
3271 context.setPathResolver( pathResolver() );
3272
3273 const QString tempPath = QStandardPaths::standardLocations( QStandardPaths::TempLocation ).at( 0 );
3274 const QString tmpZipFilename( tempPath + QDir::separator() + QUuid::createUuid().toString() );
3275
3276 if ( !zip( tmpZipFilename ) )
3277 return false; // zip() already calls setError() when returning false
3278
3279 QFile tmpZipFile( tmpZipFilename );
3280 if ( !tmpZipFile.open( QIODevice::ReadOnly ) )
3281 {
3282 setError( tr( "Unable to read file %1" ).arg( tmpZipFilename ) );
3283 return false;
3284 }
3285
3287 if ( !storage->writeProject( mFile.fileName(), &tmpZipFile, context ) )
3288 {
3289 QString err = tr( "Unable to save project to storage %1" ).arg( mFile.fileName() );
3290 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
3291 if ( !messages.isEmpty() )
3292 err += u"\n\n"_s + messages.last().message();
3293 setError( err );
3294 return false;
3295 }
3296
3297 tmpZipFile.close();
3298 QFile::remove( tmpZipFilename );
3299
3300 return true;
3301 }
3302
3303 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
3304 {
3305 return zip( mFile.fileName() );
3306 }
3307 else
3308 {
3309 // write project file even if the auxiliary storage is not correctly
3310 // saved
3311 const bool asOk = saveAuxiliaryStorage();
3312 const bool writeOk = writeProjectFile( mFile.fileName() );
3313 bool attachmentsOk = true;
3314 if ( !mArchive->files().isEmpty() )
3315 {
3316 const QFileInfo finfo( mFile.fileName() );
3317 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( u"%1_attachments.zip"_s.arg( finfo.completeBaseName() ) );
3318 attachmentsOk = mArchive->zip( attachmentsZip );
3319 }
3320
3321 // errors raised during writing project file are more important
3322 if ( ( !asOk || !attachmentsOk ) && writeOk )
3323 {
3324 QStringList errorMessage;
3325 if ( !asOk )
3326 {
3327 const QString err = mAuxiliaryStorage->errorString();
3328 errorMessage.append( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
3329 }
3330 if ( !attachmentsOk )
3331 {
3332 errorMessage.append( tr( "Unable to save attachments archive" ) );
3333 }
3334 setError( errorMessage.join( '\n' ) );
3335 }
3336
3337 return asOk && writeOk && attachmentsOk;
3338 }
3339}
3340
3341bool QgsProject::writeProjectFile( const QString &filename )
3342{
3344
3345 QFile projectFile( filename );
3346 clearError();
3347
3348 // if we have problems creating or otherwise writing to the project file,
3349 // let's find out up front before we go through all the hand-waving
3350 // necessary to create all the Dom objects
3351 const QFileInfo myFileInfo( projectFile );
3352 if ( myFileInfo.exists() && !myFileInfo.isWritable() )
3353 {
3354 setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." ).arg( projectFile.fileName() ) );
3355 return false;
3356 }
3357
3358 QgsReadWriteContext context;
3359 context.setPathResolver( pathResolver() );
3361
3362 QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
3363
3364 const QDomDocumentType documentType = QDomImplementation().createDocumentType( u"qgis"_s, u"http://mrcc.com/qgis.dtd"_s, u"SYSTEM"_s );
3365 auto doc = std::make_unique<QDomDocument>( documentType );
3366
3367 QDomElement qgisNode = doc->createElement( u"qgis"_s );
3368 qgisNode.setAttribute( u"projectname"_s, title() );
3369 qgisNode.setAttribute( u"version"_s, Qgis::version() );
3370
3371 if ( !settingsAnonymizeSavedProjects->value() )
3372 {
3373 const QString newSaveUser = QgsApplication::userLoginName();
3374 const QString newSaveUserFull = QgsApplication::userFullName();
3375 qgisNode.setAttribute( u"saveUser"_s, newSaveUser );
3376 qgisNode.setAttribute( u"saveUserFull"_s, newSaveUserFull );
3377 mSaveUser = newSaveUser;
3378 mSaveUserFull = newSaveUserFull;
3379 if ( mMetadata.author().isEmpty() )
3380 {
3381 mMetadata.setAuthor( QgsApplication::userFullName() );
3382 }
3383 if ( !mMetadata.creationDateTime().isValid() )
3384 {
3385 mMetadata.setCreationDateTime( QDateTime( QDateTime::currentDateTime() ) );
3386 }
3387 mSaveDateTime = QDateTime::currentDateTime();
3388 qgisNode.setAttribute( u"saveDateTime"_s, mSaveDateTime.toString( Qt::ISODate ) );
3389 }
3390 else
3391 {
3392 mSaveUser.clear();
3393 mSaveUserFull.clear();
3394 mMetadata.setAuthor( QString() );
3395 mMetadata.setCreationDateTime( QDateTime() );
3396 mSaveDateTime = QDateTime();
3397 }
3398 doc->appendChild( qgisNode );
3399 mSaveVersion = QgsProjectVersion( Qgis::version() );
3400
3401 QDomElement homePathNode = doc->createElement( u"homePath"_s );
3402 homePathNode.setAttribute( u"path"_s, mHomePath );
3403 qgisNode.appendChild( homePathNode );
3404
3405 // title
3406 QDomElement titleNode = doc->createElement( u"title"_s );
3407 qgisNode.appendChild( titleNode );
3408
3409 QDomElement transactionNode = doc->createElement( u"transaction"_s );
3410 transactionNode.setAttribute( u"mode"_s, qgsEnumValueToKey( mTransactionMode ) );
3411 qgisNode.appendChild( transactionNode );
3412
3413 QDomElement flagsNode = doc->createElement( u"projectFlags"_s );
3414 flagsNode.setAttribute( u"set"_s, qgsFlagValueToKeys( mFlags ) );
3415 qgisNode.appendChild( flagsNode );
3416
3417 const QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
3418 titleNode.appendChild( titleText );
3419
3420 // write project CRS
3421 {
3422 QDomElement srsNode = doc->createElement( u"projectCrs"_s );
3423 mCrs.writeXml( srsNode, *doc );
3424 qgisNode.appendChild( srsNode );
3425 }
3426 {
3427 QDomElement verticalSrsNode = doc->createElement( u"verticalCrs"_s );
3428 mVerticalCrs.writeXml( verticalSrsNode, *doc );
3429 qgisNode.appendChild( verticalSrsNode );
3430 }
3431 QDomElement elevationShadingNode = doc->createElement( u"elevation-shading-renderer"_s );
3432 mElevationShadingRenderer.writeXml( elevationShadingNode, context );
3433 qgisNode.appendChild( elevationShadingNode );
3434
3435 // write layer tree - make sure it is without embedded subgroups
3436 std::unique_ptr< QgsLayerTreeNode > clonedRoot( mRootGroup->clone() );
3438 QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot.get() ), this ); // convert absolute paths to relative paths if required
3439
3440 clonedRoot->writeXml( qgisNode, context );
3441 clonedRoot.reset();
3442
3443 mSnappingConfig.writeProject( *doc );
3444 writeEntry( u"Digitizing"_s, u"/AvoidIntersectionsMode"_s, static_cast<int>( mAvoidIntersectionsMode ) );
3445
3446 // let map canvas and legend write their information
3447 emit writeProject( *doc );
3448
3449 // within top level node save list of layers
3450 const QMap<QString, QgsMapLayer *> layers = mapLayers();
3451
3452 QDomElement annotationLayerNode = doc->createElement( u"main-annotation-layer"_s );
3453 mMainAnnotationLayer->writeLayerXml( annotationLayerNode, *doc, context );
3454 qgisNode.appendChild( annotationLayerNode );
3455
3456 // Iterate over layers in zOrder
3457 // Call writeXml() on each
3458 QDomElement projectLayersNode = doc->createElement( u"projectlayers"_s );
3459
3460 QMap<QString, QgsMapLayer *>::ConstIterator li = layers.constBegin();
3461 while ( li != layers.end() )
3462 {
3463 QgsMapLayer *ml = li.value();
3464
3465 if ( ml )
3466 {
3467 const QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.constFind( ml->id() );
3468 if ( emIt == mEmbeddedLayers.constEnd() )
3469 {
3470 QDomElement maplayerElem;
3471 // If layer is not valid, prefer to restore saved properties from invalidLayerProperties. But if that's
3472 // not available, just write what we DO have
3473 if ( ml->isValid() || ml->originalXmlProperties().isEmpty() )
3474 {
3475 // general layer metadata
3476 maplayerElem = doc->createElement( u"maplayer"_s );
3477 ml->writeLayerXml( maplayerElem, *doc, context );
3478
3480 maplayerElem.setAttribute( u"editable"_s, u"1"_s );
3481 }
3482 else if ( !ml->originalXmlProperties().isEmpty() )
3483 {
3484 QDomDocument document;
3485 if ( document.setContent( ml->originalXmlProperties() ) )
3486 {
3487 maplayerElem = document.firstChildElement();
3488 }
3489 else
3490 {
3491 QgsDebugError( u"Could not restore layer properties for layer %1"_s.arg( ml->id() ) );
3492 }
3493 }
3494
3495 emit writeMapLayer( ml, maplayerElem, *doc );
3496
3497 projectLayersNode.appendChild( maplayerElem );
3498 }
3499 else
3500 {
3501 // layer defined in an external project file
3502 // only save embedded layer if not managed by a legend group
3503 if ( emIt.value().second )
3504 {
3505 QDomElement mapLayerElem = doc->createElement( u"maplayer"_s );
3506 mapLayerElem.setAttribute( u"embedded"_s, 1 );
3507 mapLayerElem.setAttribute( u"project"_s, writePath( emIt.value().first ) );
3508 mapLayerElem.setAttribute( u"id"_s, ml->id() );
3509 projectLayersNode.appendChild( mapLayerElem );
3510 }
3511 }
3512 }
3513 li++;
3514 }
3515
3516 qgisNode.appendChild( projectLayersNode );
3517
3518 QDomElement layerOrderNode = doc->createElement( u"layerorder"_s );
3519 const auto constCustomLayerOrder = mRootGroup->customLayerOrder();
3520 for ( QgsMapLayer *layer : constCustomLayerOrder )
3521 {
3522 QDomElement mapLayerElem = doc->createElement( u"layer"_s );
3523 mapLayerElem.setAttribute( u"id"_s, layer->id() );
3524 layerOrderNode.appendChild( mapLayerElem );
3525 }
3526 qgisNode.appendChild( layerOrderNode );
3527
3528 mLabelingEngineSettings->writeSettingsToProject( this );
3529 {
3530 QDomElement labelEngineSettingsElement = doc->createElement( u"labelEngineSettings"_s );
3531 mLabelingEngineSettings->writeXml( *doc, labelEngineSettingsElement, context );
3532 qgisNode.appendChild( labelEngineSettingsElement );
3533 }
3534
3535 writeEntry( u"Gui"_s, u"/CanvasColorRedPart"_s, mBackgroundColor.red() );
3536 writeEntry( u"Gui"_s, u"/CanvasColorGreenPart"_s, mBackgroundColor.green() );
3537 writeEntry( u"Gui"_s, u"/CanvasColorBluePart"_s, mBackgroundColor.blue() );
3538
3539 writeEntry( u"Gui"_s, u"/SelectionColorRedPart"_s, mSelectionColor.red() );
3540 writeEntry( u"Gui"_s, u"/SelectionColorGreenPart"_s, mSelectionColor.green() );
3541 writeEntry( u"Gui"_s, u"/SelectionColorBluePart"_s, mSelectionColor.blue() );
3542 writeEntry( u"Gui"_s, u"/SelectionColorAlphaPart"_s, mSelectionColor.alpha() );
3543
3544 writeEntry( u"Measurement"_s, u"/DistanceUnits"_s, QgsUnitTypes::encodeUnit( mDistanceUnits ) );
3545 writeEntry( u"Measurement"_s, u"/AreaUnits"_s, QgsUnitTypes::encodeUnit( mAreaUnits ) );
3546 writeEntry( u"Measurement"_s, u"/ScaleMethod"_s, qgsEnumValueToKey( mScaleMethod ) );
3547
3548 // now add the optional extra properties
3549#if 0
3550 dump_( mProperties );
3551#endif
3552
3553 QgsDebugMsgLevel( u"there are %1 property scopes"_s.arg( static_cast<int>( mProperties.count() ) ), 2 );
3554
3555 if ( !mProperties.isEmpty() ) // only worry about properties if we
3556 // actually have any properties
3557 {
3558 mProperties.writeXml( u"properties"_s, qgisNode, *doc );
3559 }
3560
3561 QDomElement ddElem = doc->createElement( u"dataDefinedServerProperties"_s );
3562 mDataDefinedServerProperties.writeXml( ddElem, dataDefinedServerPropertyDefinitions() );
3563 qgisNode.appendChild( ddElem );
3564
3565 mMapThemeCollection->writeXml( *doc );
3566
3567 mTransformContext.writeXml( qgisNode, context );
3568
3569 QDomElement metadataElem = doc->createElement( u"projectMetadata"_s );
3570 mMetadata.writeMetadataXml( metadataElem, *doc );
3571 qgisNode.appendChild( metadataElem );
3572
3573 {
3574 const QDomElement annotationsElem = mAnnotationManager->writeXml( *doc, context );
3575 qgisNode.appendChild( annotationsElem );
3576 }
3577
3578 {
3579 const QDomElement layoutElem = mLayoutManager->writeXml( *doc );
3580 qgisNode.appendChild( layoutElem );
3581 }
3582
3583 {
3584 const QDomElement elevationProfileElem = mElevationProfileManager->writeXml( *doc, context );
3585 qgisNode.appendChild( elevationProfileElem );
3586 }
3587
3588 {
3589 const QDomElement selectiveMaskingSourceSetElem = mSelectiveMaskingSourceSetManager->writeXml( *doc, context );
3590 qgisNode.appendChild( selectiveMaskingSourceSetElem );
3591 }
3592
3593 {
3594 const QDomElement views3DElem = m3DViewsManager->writeXml( *doc );
3595 qgisNode.appendChild( views3DElem );
3596 }
3597
3598 {
3599 const QDomElement bookmarkElem = mBookmarkManager->writeXml( *doc );
3600 qgisNode.appendChild( bookmarkElem );
3601 }
3602
3603 {
3604 const QDomElement sensorElem = mSensorManager->writeXml( *doc );
3605 qgisNode.appendChild( sensorElem );
3606 }
3607
3608 {
3609 const QDomElement viewSettingsElem = mViewSettings->writeXml( *doc, context );
3610 qgisNode.appendChild( viewSettingsElem );
3611 }
3612
3613 {
3614 const QDomElement styleSettingsElem = mStyleSettings->writeXml( *doc, context );
3615 qgisNode.appendChild( styleSettingsElem );
3616 }
3617
3618 {
3619 const QDomElement timeSettingsElement = mTimeSettings->writeXml( *doc, context );
3620 qgisNode.appendChild( timeSettingsElement );
3621 }
3622
3623 {
3624 const QDomElement elevationPropertiesElement = mElevationProperties->writeXml( *doc, context );
3625 qgisNode.appendChild( elevationPropertiesElement );
3626 }
3627
3628 {
3629 const QDomElement displaySettingsElem = mDisplaySettings->writeXml( *doc, context );
3630 qgisNode.appendChild( displaySettingsElem );
3631 }
3632
3633 {
3634 const QDomElement gpsSettingsElem = mGpsSettings->writeXml( *doc, context );
3635 qgisNode.appendChild( gpsSettingsElem );
3636 }
3637
3638 // now wrap it up and ship it to the project file
3639 doc->normalize(); // XXX I'm not entirely sure what this does
3640
3641 // Create backup file
3642 if ( QFile::exists( fileName() ) )
3643 {
3644 QFile backupFile( u"%1~"_s.arg( filename ) );
3645 bool ok = true;
3646 ok &= backupFile.open( QIODevice::WriteOnly | QIODevice::Truncate );
3647 ok &= projectFile.open( QIODevice::ReadOnly );
3648
3649 QByteArray ba;
3650 while ( ok && !projectFile.atEnd() )
3651 {
3652 ba = projectFile.read( 10240 );
3653 ok &= backupFile.write( ba ) == ba.size();
3654 }
3655
3656 projectFile.close();
3657 backupFile.close();
3658
3659 if ( !ok )
3660 {
3661 setError( tr( "Unable to create backup file %1" ).arg( backupFile.fileName() ) );
3662 return false;
3663 }
3664
3665 const QFileInfo fi( fileName() );
3666 struct utimbuf tb = { static_cast<time_t>( fi.lastRead().toSecsSinceEpoch() ), static_cast<time_t>( fi.lastModified().toSecsSinceEpoch() ) };
3667 utime( backupFile.fileName().toUtf8().constData(), &tb );
3668 }
3669
3670 if ( !projectFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
3671 {
3672 projectFile.close(); // even though we got an error, let's make
3673 // sure it's closed anyway
3674
3675 setError( tr( "Unable to save to file %1" ).arg( projectFile.fileName() ) );
3676 return false;
3677 }
3678
3679 QTemporaryFile tempFile;
3680 bool ok = tempFile.open();
3681 if ( ok )
3682 {
3683 QTextStream projectFileStream( &tempFile );
3684 doc->save( projectFileStream, 2 ); // save as utf-8
3685 ok &= projectFileStream.pos() > -1;
3686
3687 ok &= tempFile.seek( 0 );
3688
3689 QByteArray ba;
3690 while ( ok && !tempFile.atEnd() )
3691 {
3692 ba = tempFile.read( 10240 );
3693 ok &= projectFile.write( ba ) == ba.size();
3694 }
3695
3696 ok &= projectFile.error() == QFile::NoError;
3697
3698 projectFile.close();
3699 }
3700
3701 tempFile.close();
3702
3703 if ( !ok )
3704 {
3705 setError( tr(
3706 "Unable to save to file %1. Your project "
3707 "may be corrupted on disk. Try clearing some space on the volume and "
3708 "check file permissions before pressing save again."
3709 )
3710 .arg( projectFile.fileName() ) );
3711 return false;
3712 }
3713
3714 setDirty( false ); // reset to pristine state
3715
3716 emit projectSaved();
3717 return true;
3718}
3719
3720bool QgsProject::writeEntry( const QString &scope, QString const &key, bool value )
3721{
3723
3724 bool propertiesModified;
3725 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3726
3727 if ( propertiesModified )
3728 setDirty( true );
3729
3730 return success;
3731}
3732
3733bool QgsProject::writeEntry( const QString &scope, const QString &key, double value )
3734{
3736
3737 bool propertiesModified;
3738 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3739
3740 if ( propertiesModified )
3741 setDirty( true );
3742
3743 return success;
3744}
3745
3746bool QgsProject::writeEntry( const QString &scope, QString const &key, int value )
3747{
3749
3750 bool propertiesModified;
3751 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3752
3753 if ( propertiesModified )
3754 setDirty( true );
3755
3756 return success;
3757}
3758
3759bool QgsProject::writeEntry( const QString &scope, const QString &key, const QString &value )
3760{
3762
3763 bool propertiesModified;
3764 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3765
3766 if ( propertiesModified )
3767 setDirty( true );
3768
3769 return success;
3770}
3771
3772bool QgsProject::writeEntry( const QString &scope, const QString &key, const QStringList &value )
3773{
3775
3776 bool propertiesModified;
3777 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3778
3779 if ( propertiesModified )
3780 setDirty( true );
3781
3782 return success;
3783}
3784
3785QStringList QgsProject::readListEntry( const QString &scope, const QString &key, const QStringList &def, bool *ok ) const
3786{
3787 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
3789
3790 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3791
3792 QVariant value;
3793
3794 if ( property )
3795 {
3796 value = property->value();
3797
3798 const bool valid = QMetaType::Type::QStringList == value.userType();
3799 if ( ok )
3800 *ok = valid;
3801
3802 if ( valid )
3803 {
3804 return value.toStringList();
3805 }
3806 }
3807 else if ( ok )
3808 *ok = false;
3809
3810
3811 return def;
3812}
3813
3814QString QgsProject::readEntry( const QString &scope, const QString &key, const QString &def, bool *ok ) const
3815{
3817
3818 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3819
3820 QVariant value;
3821
3822 if ( property )
3823 {
3824 value = property->value();
3825
3826 const bool valid = value.canConvert( QMetaType::Type::QString );
3827 if ( ok )
3828 *ok = valid;
3829
3830 if ( valid )
3831 return value.toString();
3832 }
3833 else if ( ok )
3834 *ok = false;
3835
3836 return def;
3837}
3838
3839int QgsProject::readNumEntry( const QString &scope, const QString &key, int def, bool *ok ) const
3840{
3842
3843 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3844
3845 QVariant value;
3846
3847 if ( property )
3848 {
3849 value = property->value();
3850 }
3851
3852 const bool valid = value.canConvert( QMetaType::Type::Int );
3853
3854 if ( ok )
3855 {
3856 *ok = valid;
3857 }
3858
3859 if ( valid )
3860 {
3861 return value.toInt();
3862 }
3863
3864 return def;
3865}
3866
3867double QgsProject::readDoubleEntry( const QString &scope, const QString &key, double def, bool *ok ) const
3868{
3870
3871 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3872 if ( property )
3873 {
3874 const QVariant value = property->value();
3875
3876 const bool valid = value.canConvert( QMetaType::Type::Double );
3877 if ( ok )
3878 *ok = valid;
3879
3880 if ( valid )
3881 return value.toDouble();
3882 }
3883 else if ( ok )
3884 *ok = false;
3885
3886 return def;
3887}
3888
3889bool QgsProject::readBoolEntry( const QString &scope, const QString &key, bool def, bool *ok ) const
3890{
3892
3893 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3894
3895 if ( property )
3896 {
3897 const QVariant value = property->value();
3898
3899 const bool valid = value.canConvert( QMetaType::Type::Bool );
3900 if ( ok )
3901 *ok = valid;
3902
3903 if ( valid )
3904 return value.toBool();
3905 }
3906 else if ( ok )
3907 *ok = false;
3908
3909 return def;
3910}
3911
3912bool QgsProject::removeEntry( const QString &scope, const QString &key )
3913{
3915
3916 if ( findKey_( scope, key, mProperties ) )
3917 {
3918 removeKey_( scope, key, mProperties );
3919 setDirty( true );
3920 }
3921
3922 return !findKey_( scope, key, mProperties );
3923}
3924
3925QStringList QgsProject::entryList( const QString &scope, const QString &key ) const
3926{
3928
3929 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3930
3931 QStringList entries;
3932
3933 if ( foundProperty )
3934 {
3935 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3936
3937 if ( propertyKey )
3938 {
3939 propertyKey->entryList( entries );
3940 }
3941 }
3942
3943 return entries;
3944}
3945
3946QStringList QgsProject::subkeyList( const QString &scope, const QString &key ) const
3947{
3949
3950 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3951
3952 QStringList entries;
3953
3954 if ( foundProperty )
3955 {
3956 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3957
3958 if ( propertyKey )
3959 {
3960 propertyKey->subkeyList( entries );
3961 }
3962 }
3963
3964 return entries;
3965}
3966
3968{
3970
3971 dump_( mProperties );
3972}
3973
3975{
3977
3978 QString filePath;
3979 switch ( filePathStorage() )
3980 {
3982 break;
3983
3985 {
3986 // for projects stored in a custom storage, we need to ask to the
3987 // storage for the path, if the storage returns an empty path
3988 // relative paths are not supported
3989 if ( QgsProjectStorage *storage = projectStorage() )
3990 {
3991 filePath = storage->filePath( mFile.fileName() );
3992 }
3993 else
3994 {
3995 filePath = fileName();
3996 }
3997 break;
3998 }
3999 }
4000
4001 return QgsPathResolver( filePath, mArchive->dir() );
4002}
4003
4004QString QgsProject::readPath( const QString &src ) const
4005{
4007
4008 return pathResolver().readPath( src );
4009}
4010
4011QString QgsProject::writePath( const QString &src ) const
4012{
4014
4015 return pathResolver().writePath( src );
4016}
4017
4018void QgsProject::setError( const QString &errorMessage )
4019{
4021
4022 mErrorMessage = errorMessage;
4023}
4024
4025QString QgsProject::error() const
4026{
4028
4029 return mErrorMessage;
4030}
4031
4032void QgsProject::clearError()
4033{
4035
4036 setError( QString() );
4037}
4038
4040{
4042
4043 mBadLayerHandler.reset( handler );
4044}
4045
4046QString QgsProject::layerIsEmbedded( const QString &id ) const
4047{
4049
4050 const QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
4051 if ( it == mEmbeddedLayers.constEnd() )
4052 {
4053 return QString();
4054 }
4055 return it.value().first;
4056}
4057
4058bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &projectFilePath, QList<QDomNode> &brokenNodes, bool saveFlag, Qgis::ProjectReadFlags flags )
4059{
4061
4063
4064 static QString sPrevProjectFilePath;
4065 static QDateTime sPrevProjectFileTimestamp;
4066 static QDomDocument sProjectDocument;
4067
4068 QString qgsProjectFile = projectFilePath;
4069 QgsProjectArchive archive;
4070 if ( projectFilePath.endsWith( ".qgz"_L1, Qt::CaseInsensitive ) )
4071 {
4072 archive.unzip( projectFilePath );
4073 qgsProjectFile = archive.projectFile();
4074 }
4075
4076 const QDateTime projectFileTimestamp = QFileInfo( projectFilePath ).lastModified();
4077
4078 if ( projectFilePath != sPrevProjectFilePath || projectFileTimestamp != sPrevProjectFileTimestamp )
4079 {
4080 sPrevProjectFilePath.clear();
4081
4082 QFile projectFile( qgsProjectFile );
4083 if ( !projectFile.open( QIODevice::ReadOnly ) )
4084 {
4085 return false;
4086 }
4087
4088 if ( !sProjectDocument.setContent( &projectFile ) )
4089 {
4090 return false;
4091 }
4092
4093 sPrevProjectFilePath = projectFilePath;
4094 sPrevProjectFileTimestamp = projectFileTimestamp;
4095 }
4096
4097 // does project store paths absolute or relative?
4098 bool useAbsolutePaths = true;
4099
4100 const QDomElement propertiesElem = sProjectDocument.documentElement().firstChildElement( u"properties"_s );
4101 if ( !propertiesElem.isNull() )
4102 {
4103 QDomElement e = propertiesElem.firstChildElement( u"Paths"_s );
4104 if ( e.isNull() )
4105 {
4106 e = propertiesElem.firstChildElement( u"properties"_s );
4107 while ( !e.isNull() && e.attribute( u"name"_s ) != "Paths"_L1 )
4108 e = e.nextSiblingElement( u"properties"_s );
4109
4110 e = e.firstChildElement( u"properties"_s );
4111 while ( !e.isNull() && e.attribute( u"name"_s ) != "Absolute"_L1 )
4112 e = e.nextSiblingElement( u"properties"_s );
4113 }
4114 else
4115 {
4116 e = e.firstChildElement( u"Absolute"_s );
4117 }
4118
4119 if ( !e.isNull() )
4120 {
4121 useAbsolutePaths = e.text().compare( "true"_L1, Qt::CaseInsensitive ) == 0;
4122 }
4123 }
4124
4125 QgsReadWriteContext embeddedContext;
4126 if ( !useAbsolutePaths )
4127 embeddedContext.setPathResolver( QgsPathResolver( projectFilePath ) );
4128 embeddedContext.setProjectTranslator( this );
4129 embeddedContext.setTransformContext( transformContext() );
4130 embeddedContext.setCurrentLayerId( layerId );
4131
4132 const QDomElement projectLayersElem = sProjectDocument.documentElement().firstChildElement( u"projectlayers"_s );
4133 if ( projectLayersElem.isNull() )
4134 {
4135 return false;
4136 }
4137
4138 QDomElement mapLayerElem = projectLayersElem.firstChildElement( u"maplayer"_s );
4139 while ( !mapLayerElem.isNull() )
4140 {
4141 // get layer id
4142 const QString id = mapLayerElem.firstChildElement( u"id"_s ).text();
4143 if ( id == layerId )
4144 {
4145 // layer can be embedded only once
4146 if ( mapLayerElem.attribute( u"embedded"_s ) == "1"_L1 )
4147 {
4148 return false;
4149 }
4150
4151 mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
4152
4153 if ( addLayer( mapLayerElem, brokenNodes, embeddedContext, flags ) )
4154 {
4155 return true;
4156 }
4157 else
4158 {
4159 mEmbeddedLayers.remove( layerId );
4160 return false;
4161 }
4162 }
4163 mapLayerElem = mapLayerElem.nextSiblingElement( u"maplayer"_s );
4164 }
4165
4166 return false;
4167}
4168
4169std::unique_ptr<QgsLayerTreeGroup> QgsProject::createEmbeddedGroup( const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers, Qgis::ProjectReadFlags flags )
4170{
4172
4173 QString qgsProjectFile = projectFilePath;
4174 QgsProjectArchive archive;
4175 if ( projectFilePath.endsWith( ".qgz"_L1, Qt::CaseInsensitive ) )
4176 {
4177 archive.unzip( projectFilePath );
4178 qgsProjectFile = archive.projectFile();
4179 }
4180
4181 // open project file, get layer ids in group, add the layers
4182 QFile projectFile( qgsProjectFile );
4183 if ( !projectFile.open( QIODevice::ReadOnly ) )
4184 {
4185 return nullptr;
4186 }
4187
4188 QDomDocument projectDocument;
4189 if ( !projectDocument.setContent( &projectFile ) )
4190 {
4191 return nullptr;
4192 }
4193
4194 QgsReadWriteContext context;
4195 context.setPathResolver( pathResolver() );
4196 context.setProjectTranslator( this );
4198
4199 auto root = std::make_unique< QgsLayerTreeGroup >();
4200
4201 QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( u"layer-tree-group"_s );
4202 if ( !layerTreeElem.isNull() )
4203 {
4204 root->readChildrenFromXml( layerTreeElem, context );
4205 }
4206 else
4207 {
4208 QgsLayerTreeUtils::readOldLegend( root.get(), projectDocument.documentElement().firstChildElement( u"legend"_s ) );
4209 }
4210
4211 QgsLayerTreeGroup *group = root->findGroup( groupName );
4212 if ( !group || group->customProperty( u"embedded"_s ).toBool() )
4213 {
4214 // embedded groups cannot be embedded again
4215 return nullptr;
4216 }
4217
4218 // clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
4219 std::unique_ptr< QgsLayerTreeGroup > newGroup( QgsLayerTree::toGroup( group->clone() ) );
4220 root.reset();
4221
4222 newGroup->setCustomProperty( u"embedded"_s, 1 );
4223 newGroup->setCustomProperty( u"embedded_project"_s, projectFilePath );
4224
4225 // set "embedded" to all children + load embedded layers
4226 mLayerTreeRegistryBridge->setEnabled( false );
4227 initializeEmbeddedSubtree( projectFilePath, newGroup.get(), flags );
4228 mLayerTreeRegistryBridge->setEnabled( true );
4229
4230 // consider the layers might be identify disabled in its project
4231 const QStringList constFindLayerIds = newGroup->findLayerIds();
4232 for ( const QString &layerId : constFindLayerIds )
4233 {
4234 QgsLayerTreeLayer *layer = newGroup->findLayer( layerId );
4235 if ( layer )
4236 {
4237 layer->resolveReferences( this );
4238 layer->setItemVisibilityChecked( !invisibleLayers.contains( layerId ) );
4239 }
4240 }
4241
4242 return newGroup;
4243}
4244
4245void QgsProject::initializeEmbeddedSubtree( const QString &projectFilePath, QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
4246{
4248
4249 const auto constChildren = group->children();
4250 for ( QgsLayerTreeNode *child : constChildren )
4251 {
4252 // all nodes in the subtree will have "embedded" custom property set
4253 child->setCustomProperty( u"embedded"_s, 1 );
4254
4255 if ( QgsLayerTree::isGroup( child ) )
4256 {
4257 initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ), flags );
4258 }
4259 else if ( QgsLayerTree::isLayer( child ) )
4260 {
4261 // load the layer into our project
4262 QList<QDomNode> brokenNodes;
4263 createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, false, flags );
4264 }
4265 }
4266}
4267
4274
4281
4283{
4285
4286 writeEntry( u"Digitizing"_s, u"/TopologicalEditing"_s, ( enabled ? 1 : 0 ) );
4288}
4289
4291{
4293
4294 return readNumEntry( u"Digitizing"_s, u"/TopologicalEditing"_s, 0 );
4295}
4296
4298{
4300
4301 if ( mDistanceUnits == unit )
4302 return;
4303
4304 mDistanceUnits = unit;
4305
4306 emit distanceUnitsChanged();
4307}
4308
4310{
4312
4313 if ( mAreaUnits == unit )
4314 return;
4315
4316 mAreaUnits = unit;
4317
4318 emit areaUnitsChanged();
4319}
4320
4322{
4324
4325 if ( mScaleMethod == method )
4326 return;
4327
4328 mScaleMethod = method;
4329
4330 emit scaleMethodChanged();
4331}
4332
4334{
4335 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4337
4338 if ( !mCachedHomePath.isEmpty() )
4339 return mCachedHomePath;
4340
4341 const QFileInfo pfi( fileName() );
4342
4343 if ( !mHomePath.isEmpty() )
4344 {
4345 const QFileInfo homeInfo( mHomePath );
4346 if ( !homeInfo.isRelative() )
4347 {
4348 mCachedHomePath = mHomePath;
4349 return mHomePath;
4350 }
4351 }
4352 else if ( !fileName().isEmpty() )
4353 {
4354 // If it's not stored in the file system, try to get the path from the storage
4355 if ( QgsProjectStorage *storage = projectStorage() )
4356 {
4357 const QString storagePath { storage->filePath( fileName() ) };
4358 if ( !storagePath.isEmpty() && QFileInfo::exists( storagePath ) )
4359 {
4360 mCachedHomePath = QFileInfo( storagePath ).path();
4361 return mCachedHomePath;
4362 }
4363 }
4364
4365 mCachedHomePath = pfi.path();
4366 return mCachedHomePath;
4367 }
4368
4369 if ( !pfi.exists() )
4370 {
4371 mCachedHomePath = mHomePath;
4372 return mHomePath;
4373 }
4374
4375 if ( !mHomePath.isEmpty() )
4376 {
4377 // path is relative to project file
4378 mCachedHomePath = QDir::cleanPath( pfi.path() + '/' + mHomePath );
4379 }
4380 else
4381 {
4382 mCachedHomePath = pfi.canonicalPath();
4383 }
4384 return mCachedHomePath;
4385}
4386
4388{
4390
4391 return mHomePath;
4392}
4393
4395{
4396 // because relation aggregate functions are not thread safe
4398
4399 return mRelationManager.get();
4400}
4401
4403{
4405
4406 return mLayoutManager.get();
4407}
4408
4410{
4412
4413 return mLayoutManager.get();
4414}
4415
4417{
4419
4420 return mElevationProfileManager.get();
4421}
4422
4424{
4426
4427 return mElevationProfileManager.get();
4428}
4429
4431{
4433
4434 return mSelectiveMaskingSourceSetManager.get();
4435}
4436
4438{
4440
4441 return mSelectiveMaskingSourceSetManager.get();
4442}
4443
4445{
4447
4448 return m3DViewsManager.get();
4449}
4450
4452{
4454
4455 return m3DViewsManager.get();
4456}
4457
4459{
4461
4462 return mBookmarkManager;
4463}
4464
4466{
4468
4469 return mBookmarkManager;
4470}
4471
4473{
4475
4476 return mSensorManager;
4477}
4478
4480{
4482
4483 return mSensorManager;
4484}
4485
4487{
4489
4490 return mViewSettings;
4491}
4492
4499
4501{
4503
4504 return mStyleSettings;
4505}
4506
4508{
4509 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4511
4512 return mStyleSettings;
4513}
4514
4516{
4518
4519 return mTimeSettings;
4520}
4521
4528
4530{
4532
4533 return mElevationProperties;
4534}
4535
4542
4544{
4546
4547 return mDisplaySettings;
4548}
4549
4551{
4553
4554 return mDisplaySettings;
4555}
4556
4558{
4560
4561 return mGpsSettings;
4562}
4563
4570
4572{
4574
4575 return mRootGroup.get();
4576}
4577
4579{
4581
4582 return mMapThemeCollection.get();
4583}
4584
4586{
4588
4589 return mAnnotationManager.get();
4590}
4591
4593{
4595
4596 return mAnnotationManager.get();
4597}
4598
4599void QgsProject::setNonIdentifiableLayers( const QList<QgsMapLayer *> &layers )
4600{
4602
4603 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
4604 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
4605 {
4606 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4607 continue;
4608
4609 if ( layers.contains( it.value() ) )
4610 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Identifiable );
4611 else
4612 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Identifiable );
4613 }
4614
4618}
4619
4620void QgsProject::setNonIdentifiableLayers( const QStringList &layerIds )
4621{
4623
4624 QList<QgsMapLayer *> nonIdentifiableLayers;
4625 nonIdentifiableLayers.reserve( layerIds.count() );
4626 for ( const QString &layerId : layerIds )
4627 {
4628 QgsMapLayer *layer = mapLayer( layerId );
4629 if ( layer )
4630 nonIdentifiableLayers << layer;
4631 }
4635}
4636
4638{
4640
4641 QStringList nonIdentifiableLayers;
4642
4643 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
4644 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
4645 {
4646 if ( !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4647 {
4648 nonIdentifiableLayers.append( it.value()->id() );
4649 }
4650 }
4651 return nonIdentifiableLayers;
4652}
4653
4655{
4657
4658 return mTransactionMode == Qgis::TransactionMode::AutomaticGroups;
4659}
4660
4662{
4664
4665 if ( autoTransaction && mTransactionMode == Qgis::TransactionMode::AutomaticGroups )
4666 return;
4667
4668 if ( !autoTransaction && mTransactionMode == Qgis::TransactionMode::Disabled )
4669 return;
4670
4671 if ( autoTransaction )
4673 else
4675
4676 updateTransactionGroups();
4677}
4678
4680{
4682
4683 return mTransactionMode;
4684}
4685
4687{
4689
4690 if ( transactionMode == mTransactionMode )
4691 return true;
4692
4693 // Check that all layer are not in edit mode
4694 const auto constLayers = mapLayers().values();
4695 for ( QgsMapLayer *layer : constLayers )
4696 {
4697 if ( layer->isEditable() )
4698 {
4699 QgsLogger::warning( tr( "Transaction mode can be changed only if all layers are not editable." ) );
4700 return false;
4701 }
4702 }
4703
4704 mTransactionMode = transactionMode;
4705 updateTransactionGroups();
4707 return true;
4708}
4709
4710QMap<QPair<QString, QString>, QgsTransactionGroup *> QgsProject::transactionGroups()
4711{
4713
4714 return mTransactionGroups;
4715}
4716
4717
4718//
4719// QgsMapLayerStore methods
4720//
4721
4722
4724{
4726
4727 return mLayerStore->count();
4728}
4729
4731{
4733
4734 return mLayerStore->validCount();
4735}
4736
4737QgsMapLayer *QgsProject::mapLayer( const QString &layerId ) const
4738{
4739 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
4741
4742 if ( mMainAnnotationLayer && layerId == mMainAnnotationLayer->id() )
4743 return mMainAnnotationLayer;
4744
4745 return mLayerStore->mapLayer( layerId );
4746}
4747
4748QList<QgsMapLayer *> QgsProject::mapLayersByName( const QString &layerName ) const
4749{
4751
4752 return mLayerStore->mapLayersByName( layerName );
4753}
4754
4755QList<QgsMapLayer *> QgsProject::mapLayersByShortName( const QString &shortName ) const
4756{
4758
4759 QList<QgsMapLayer *> layers;
4760 const auto constMapLayers { mLayerStore->mapLayers() };
4761 for ( const auto &l : constMapLayers )
4762 {
4763 if ( !l->serverProperties()->shortName().isEmpty() )
4764 {
4765 if ( l->serverProperties()->shortName() == shortName )
4766 layers << l;
4767 }
4768 else if ( l->name() == shortName )
4769 {
4770 layers << l;
4771 }
4772 }
4773 return layers;
4774}
4775
4776bool QgsProject::unzip( const QString &filename, Qgis::ProjectReadFlags flags )
4777{
4779
4780 clearError();
4781 auto archive = std::make_unique<QgsProjectArchive>();
4782
4783 // unzip the archive
4784 if ( !archive->unzip( filename ) )
4785 {
4786 setError( tr( "Unable to unzip file '%1'" ).arg( filename ) );
4787 return false;
4788 }
4789
4790 // test if zip provides a .qgs file
4791 if ( archive->projectFile().isEmpty() )
4792 {
4793 setError( tr( "Zip archive does not provide a project file" ) );
4794 return false;
4795 }
4796
4797 // Keep the archive
4798 releaseHandlesToProjectArchive();
4799 mArchive = std::move( archive );
4800
4801 // load auxiliary storage
4802 if ( !static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile().isEmpty() )
4803 {
4804 // database file is already a copy as it's been unzipped. So we don't open
4805 // auxiliary storage in copy mode in this case
4806 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile(), false );
4807 }
4808 else
4809 {
4810 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( *this );
4811 }
4812
4813 // read the project file
4814 if ( !readProjectFile( static_cast<QgsProjectArchive *>( mArchive.get() )->projectFile(), flags ) )
4815 {
4816 setError( tr( "Cannot read unzipped qgs project file" ) + u": "_s + error() );
4817 return false;
4818 }
4819
4820 // Remove the temporary .qgs file
4821 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4822
4823 return true;
4824}
4825
4826bool QgsProject::zip( const QString &filename )
4827{
4829
4830 clearError();
4831
4832 // save the current project in a temporary .qgs file
4833 auto archive = std::make_unique<QgsProjectArchive>();
4834 const QString baseName = QFileInfo( filename ).baseName();
4835 const QString qgsFileName = u"%1.qgs"_s.arg( baseName );
4836 QFile qgsFile( QDir( archive->dir() ).filePath( qgsFileName ) );
4837
4838 bool writeOk = false;
4839 if ( qgsFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
4840 {
4841 writeOk = writeProjectFile( qgsFile.fileName() );
4842 qgsFile.close();
4843 }
4844
4845 // stop here with an error message
4846 if ( !writeOk )
4847 {
4848 setError( tr( "Unable to write temporary qgs file" ) );
4849 return false;
4850 }
4851
4852 // save auxiliary storage
4853 const QFileInfo info( qgsFile );
4854 const QString asExt = u".%1"_s.arg( QgsAuxiliaryStorage::extension() );
4855 const QString asFileName = info.path() + QDir::separator() + info.completeBaseName() + asExt;
4856
4857 bool auxiliaryStorageSavedOk = true;
4858 if ( !saveAuxiliaryStorage( asFileName ) )
4859 {
4860 const QString err = mAuxiliaryStorage->errorString();
4861 setError(
4862 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 )
4863 );
4864 auxiliaryStorageSavedOk = false;
4865
4866 // fixes the current archive and keep the previous version of qgd
4867 if ( !mArchive->exists() )
4868 {
4869 releaseHandlesToProjectArchive();
4870 mArchive = std::make_unique< QgsProjectArchive >();
4871 mArchive->unzip( mFile.fileName() );
4872 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4873
4874 const QString auxiliaryStorageFile = static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile();
4875 if ( !auxiliaryStorageFile.isEmpty() )
4876 {
4877 archive->addFile( auxiliaryStorageFile );
4878 mAuxiliaryStorage = std::make_unique< QgsAuxiliaryStorage >( auxiliaryStorageFile, false );
4879 }
4880 }
4881 }
4882 else
4883 {
4884 // in this case, an empty filename means that the auxiliary database is
4885 // empty, so we don't want to save it
4886 if ( QFile::exists( asFileName ) )
4887 {
4888 archive->addFile( asFileName );
4889 }
4890 }
4891
4892 // create the archive
4893 archive->addFile( qgsFile.fileName() );
4894
4895 // Add all other files
4896 const QStringList &files = mArchive->files();
4897 for ( const QString &file : files )
4898 {
4899 if ( !file.endsWith( ".qgs"_L1, Qt::CaseInsensitive ) && !file.endsWith( asExt, Qt::CaseInsensitive ) )
4900 {
4901 archive->addFile( file );
4902 }
4903 }
4904
4905 // zip
4906 bool zipOk = true;
4907 if ( !archive->zip( filename ) )
4908 {
4909 setError( tr( "Unable to perform zip" ) );
4910 zipOk = false;
4911 }
4912
4913 return auxiliaryStorageSavedOk && zipOk;
4914}
4915
4917{
4919
4920 return QgsZipUtils::isZipFile( mFile.fileName() );
4921}
4922
4923QList<QgsMapLayer *> QgsProject::addMapLayers( const QList<QgsMapLayer *> &layers, bool addToLegend, bool takeOwnership )
4924{
4926
4927 const QList<QgsMapLayer *> myResultList { mLayerStore->addMapLayers( layers, takeOwnership ) };
4928 if ( !myResultList.isEmpty() )
4929 {
4930 // Update transform context
4931 for ( auto &l : myResultList )
4932 {
4933 l->setTransformContext( transformContext() );
4934 }
4935 if ( addToLegend )
4936 {
4937 emit legendLayersAdded( myResultList );
4938 }
4939 else
4940 {
4941 emit layersAddedWithoutLegend( myResultList );
4942 }
4943 }
4944
4945 if ( mAuxiliaryStorage )
4946 {
4947 for ( QgsMapLayer *mlayer : myResultList )
4948 {
4949 if ( mlayer->type() != Qgis::LayerType::Vector )
4950 continue;
4951
4952 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mlayer );
4953 if ( vl )
4954 {
4955 vl->loadAuxiliaryLayer( *mAuxiliaryStorage );
4956 }
4957 }
4958 }
4959
4960 mProjectScope.reset();
4961
4962 return myResultList;
4963}
4964
4965QgsMapLayer *QgsProject::addMapLayer( QgsMapLayer *layer, bool addToLegend, bool takeOwnership )
4966{
4968
4969 QList<QgsMapLayer *> addedLayers;
4970 addedLayers = addMapLayers( QList<QgsMapLayer *>() << layer, addToLegend, takeOwnership );
4971 return addedLayers.isEmpty() ? nullptr : addedLayers[0];
4972}
4973
4974void QgsProject::removeAuxiliaryLayer( const QgsMapLayer *ml )
4975{
4977
4978 if ( !ml || ml->type() != Qgis::LayerType::Vector )
4979 return;
4980
4981 const QgsVectorLayer *vl = qobject_cast<const QgsVectorLayer *>( ml );
4982 if ( vl && vl->auxiliaryLayer() )
4983 {
4984 const QgsDataSourceUri uri( vl->auxiliaryLayer()->source() );
4986 }
4987}
4988
4989void QgsProject::removeMapLayers( const QStringList &layerIds )
4990{
4992
4993 for ( const auto &layerId : layerIds )
4994 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4995
4996 mProjectScope.reset();
4997 mLayerStore->removeMapLayers( layerIds );
4998}
4999
5000void QgsProject::removeMapLayers( const QList<QgsMapLayer *> &layers )
5001{
5003
5004 for ( const auto &layer : layers )
5005 removeAuxiliaryLayer( layer );
5006
5007 mProjectScope.reset();
5008 mLayerStore->removeMapLayers( layers );
5009}
5010
5011void QgsProject::removeMapLayer( const QString &layerId )
5012{
5014
5015 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
5016 mProjectScope.reset();
5017 mLayerStore->removeMapLayer( layerId );
5018}
5019
5021{
5023
5024 removeAuxiliaryLayer( layer );
5025 mProjectScope.reset();
5026 mLayerStore->removeMapLayer( layer );
5027}
5028
5030{
5032
5033 mProjectScope.reset();
5034 return mLayerStore->takeMapLayer( layer );
5035}
5036
5038{
5040
5041 return mMainAnnotationLayer;
5042}
5043
5045{
5047
5048 if ( mLayerStore->count() == 0 )
5049 return;
5050
5051 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
5052 mProjectScope.reset();
5053 mLayerStore->removeAllMapLayers();
5054
5055 snapSingleBlocker.release();
5056 mSnappingConfig.clearIndividualLayerSettings();
5057 if ( !mBlockSnappingUpdates )
5058 emit snappingConfigChanged( mSnappingConfig );
5059}
5060
5062{
5064
5065 const QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
5066 QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin();
5067 for ( ; it != layers.constEnd(); ++it )
5068 {
5069 it.value()->reload();
5070 }
5071}
5072
5073QMap<QString, QgsMapLayer *> QgsProject::mapLayers( const bool validOnly ) const
5074{
5075 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
5077
5078 return validOnly ? mLayerStore->validMapLayers() : mLayerStore->mapLayers();
5079}
5080
5081QgsTransactionGroup *QgsProject::transactionGroup( const QString &providerKey, const QString &connString )
5082{
5084
5085 return mTransactionGroups.value( qMakePair( providerKey, connString ) );
5086}
5087
5094
5096{
5098
5100
5101 // TODO QGIS 5.0 -- remove this method, and place it somewhere in app (where it belongs)
5103 {
5104 // for new layers if the new layer crs method is set to either prompt or use project, then we use the project crs
5105 defaultCrs = crs();
5106 }
5107 else
5108 {
5109 // global crs
5110 const QString layerDefaultCrs = QgsSettingsRegistryCore::settingsLayerDefaultCrs->value();
5111 defaultCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( layerDefaultCrs );
5112 }
5113
5114 return defaultCrs;
5115}
5116
5123
5130
5131bool QgsProject::saveAuxiliaryStorage( const QString &filename )
5132{
5134
5135 const QMap<QString, QgsMapLayer *> layers = mapLayers();
5136 bool empty = true;
5137 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5138 {
5139 if ( it.value()->type() != Qgis::LayerType::Vector )
5140 continue;
5141
5142 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
5143 if ( vl && vl->auxiliaryLayer() )
5144 {
5145 vl->auxiliaryLayer()->save();
5146 empty &= vl->auxiliaryLayer()->auxiliaryFields().isEmpty();
5147 }
5148 }
5149
5150 if ( !mAuxiliaryStorage->exists( *this ) && empty )
5151 {
5152 return true; // it's not an error
5153 }
5154 else if ( !filename.isEmpty() )
5155 {
5156 return mAuxiliaryStorage->saveAs( filename );
5157 }
5158 else
5159 {
5160 return mAuxiliaryStorage->saveAs( *this );
5161 }
5162}
5163
5164QgsPropertiesDefinition &QgsProject::dataDefinedServerPropertyDefinitions()
5165{
5166 static QgsPropertiesDefinition sPropertyDefinitions {
5167 { static_cast< int >( QgsProject::DataDefinedServerProperty::WMSOnlineResource ), QgsPropertyDefinition( "WMSOnlineResource", QObject::tr( "WMS Online Resource" ), QgsPropertyDefinition::String ) },
5168 };
5169 return sPropertyDefinitions;
5170}
5171
5177
5179{
5181
5182 return mAuxiliaryStorage.get();
5183}
5184
5186{
5188
5189 return mAuxiliaryStorage.get();
5190}
5191
5192QString QgsProject::createAttachedFile( const QString &nameTemplate )
5193{
5195
5196 const QDir archiveDir( mArchive->dir() );
5197 QTemporaryFile tmpFile( archiveDir.filePath( "XXXXXX_" + nameTemplate ), this );
5198 tmpFile.setAutoRemove( false );
5199 if ( !tmpFile.open() )
5200 {
5201 setError( tr( "Unable to open %1" ).arg( tmpFile.fileName() ) );
5202 return QString();
5203 }
5204 mArchive->addFile( tmpFile.fileName() );
5205 return tmpFile.fileName();
5206}
5207
5208QStringList QgsProject::attachedFiles() const
5209{
5211
5212 QStringList attachments;
5213 const QString baseName = QFileInfo( fileName() ).baseName();
5214 const QStringList files = mArchive->files();
5215 attachments.reserve( files.size() );
5216 for ( const QString &file : files )
5217 {
5218 if ( QFileInfo( file ).baseName() != baseName )
5219 {
5220 attachments.append( file );
5221 }
5222 }
5223 return attachments;
5224}
5225
5226bool QgsProject::removeAttachedFile( const QString &path )
5227{
5229
5230 return mArchive->removeFile( path );
5231}
5232
5233QString QgsProject::attachmentIdentifier( const QString &attachedFile ) const
5234{
5236
5237 return u"attachment:///%1"_s.arg( QFileInfo( attachedFile ).fileName() );
5238}
5239
5240QString QgsProject::resolveAttachmentIdentifier( const QString &identifier ) const
5241{
5243
5244 if ( identifier.startsWith( "attachment:///"_L1 ) )
5245 {
5246 return QDir( mArchive->dir() ).absoluteFilePath( identifier.mid( 14 ) );
5247 }
5248 return QString();
5249}
5250
5252{
5253 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
5255
5256 return mMetadata;
5257}
5258
5260{
5262
5263 if ( metadata == mMetadata )
5264 return;
5265
5266 mMetadata = metadata;
5267 mProjectScope.reset();
5268
5269 emit metadataChanged();
5270 emit titleChanged();
5271
5272 setDirty( true );
5273}
5274
5275QSet<QgsMapLayer *> QgsProject::requiredLayers() const
5276{
5278
5279 QSet<QgsMapLayer *> requiredLayers;
5280
5281 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
5282 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
5283 {
5284 if ( !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
5285 {
5286 requiredLayers.insert( it.value() );
5287 }
5288 }
5289 return requiredLayers;
5290}
5291
5292void QgsProject::setRequiredLayers( const QSet<QgsMapLayer *> &layers )
5293{
5295
5296 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
5297 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
5298 {
5299 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
5300 continue;
5301
5302 if ( layers.contains( it.value() ) )
5303 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Removable );
5304 else
5305 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Removable );
5306 }
5307}
5308
5310{
5312
5313 // save colors to project
5314 QStringList customColors;
5315 QStringList customColorLabels;
5316
5317 QgsNamedColorList::const_iterator colorIt = colors.constBegin();
5318 for ( ; colorIt != colors.constEnd(); ++colorIt )
5319 {
5320 const QString color = QgsColorUtils::colorToString( ( *colorIt ).first );
5321 const QString label = ( *colorIt ).second;
5322 customColors.append( color );
5323 customColorLabels.append( label );
5324 }
5325 writeEntry( u"Palette"_s, u"/Colors"_s, customColors );
5326 writeEntry( u"Palette"_s, u"/Labels"_s, customColorLabels );
5327 mProjectScope.reset();
5328 emit projectColorsChanged();
5329}
5330
5331void QgsProject::setBackgroundColor( const QColor &color )
5332{
5334
5335 if ( mBackgroundColor == color )
5336 return;
5337
5338 mBackgroundColor = color;
5340}
5341
5343{
5345
5346 return mBackgroundColor;
5347}
5348
5349void QgsProject::setSelectionColor( const QColor &color )
5350{
5352
5353 if ( mSelectionColor == color )
5354 return;
5355
5356 mSelectionColor = color;
5357 emit selectionColorChanged();
5358}
5359
5361{
5363
5364 return mSelectionColor;
5365}
5366
5367void QgsProject::setMapScales( const QVector<double> &scales )
5368{
5370
5371 mViewSettings->setMapScales( scales );
5372}
5373
5374QVector<double> QgsProject::mapScales() const
5375{
5377
5378 return mViewSettings->mapScales();
5379}
5380
5382{
5384
5385 mViewSettings->setUseProjectScales( enabled );
5386}
5387
5389{
5391
5392 return mViewSettings->useProjectScales();
5393}
5394
5395void QgsProject::generateTsFile( const QString &locale )
5396{
5398
5399 QgsTranslationContext translationContext;
5400 translationContext.setProject( this );
5401 translationContext.setFileName( u"%1/%2.ts"_s.arg( absolutePath(), baseName() ) );
5402
5403 QgsApplication::instance()->collectTranslatableObjects( &translationContext );
5404
5405 translationContext.writeTsFile( locale );
5406}
5407
5408QString QgsProject::translate( const QString &context, const QString &sourceText, const char *disambiguation, int n ) const
5409{
5411
5412 if ( !mTranslator )
5413 {
5414 return sourceText;
5415 }
5416
5417 QString result = mTranslator->translate( context.toUtf8(), sourceText.toUtf8(), disambiguation, n );
5418
5419 if ( result.isEmpty() )
5420 {
5421 return sourceText;
5422 }
5423 return result;
5424}
5425
5427{
5429
5430 const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
5431 if ( !layers.empty() )
5432 {
5433 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5434 {
5435 // NOTE: if visitEnter returns false it means "don't visit this layer", not "abort all further visitations"
5436 if ( visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
5437 {
5438 if ( !( ( *it )->accept( visitor ) ) )
5439 return false;
5440
5441 if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
5442 return false;
5443 }
5444 }
5445 }
5446
5447 if ( !mLayoutManager->accept( visitor ) )
5448 return false;
5449
5450 if ( !mAnnotationManager->accept( visitor ) )
5451 return false;
5452
5453 return true;
5454}
5455
5457{
5459
5460 const QString macros = readEntry( u"Macros"_s, u"/pythonCode"_s, QString() );
5461 if ( !macros.isEmpty() )
5462 {
5463 QgsEmbeddedScriptEntity entity( Qgis::EmbeddedScriptType::Macro, tr( "Macros" ), macros );
5464 if ( !visitor->visitEmbeddedScript( entity, context ) )
5465 {
5466 return false;
5467 }
5468 }
5469
5470 const QString expressionFunctions = readEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s );
5471 if ( !expressionFunctions.isEmpty() )
5472 {
5473 QgsEmbeddedScriptEntity entity( Qgis::EmbeddedScriptType::ExpressionFunction, tr( "Expression functions" ), expressionFunctions );
5474 if ( !visitor->visitEmbeddedScript( entity, context ) )
5475 {
5476 return false;
5477 }
5478 }
5479
5480 const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
5481 if ( !layers.empty() )
5482 {
5483 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
5484 {
5485 if ( !( ( *it )->accept( visitor, context ) ) )
5486 {
5487 return false;
5488 }
5489 }
5490 }
5491
5492 return true;
5493}
5494
5496{
5497 return mElevationShadingRenderer;
5498}
5499
5500void QgsProject::loadProjectFlags( const QDomDocument *doc )
5501{
5503
5504 QDomElement element = doc->documentElement().firstChildElement( u"projectFlags"_s );
5506 if ( !element.isNull() )
5507 {
5508 flags = qgsFlagKeysToValue( element.attribute( u"set"_s ), Qgis::ProjectFlags() );
5509 }
5510 else
5511 {
5512 // older project compatibility
5513 element = doc->documentElement().firstChildElement( u"evaluateDefaultValues"_s );
5514 if ( !element.isNull() )
5515 {
5516 if ( element.attribute( u"active"_s, u"0"_s ).toInt() == 1 )
5518 }
5519
5520 // Read trust layer metadata config in the project
5521 element = doc->documentElement().firstChildElement( u"trust"_s );
5522 if ( !element.isNull() )
5523 {
5524 if ( element.attribute( u"active"_s, u"0"_s ).toInt() == 1 )
5526 }
5527 }
5528
5529 setFlags( flags );
5530}
5531
5533{
5535 {
5537 {
5538 const QString projectFunctions = readEntry( u"ExpressionFunctions"_s, u"/pythonCode"_s, QString() );
5539 if ( !projectFunctions.isEmpty() )
5540 {
5541 QgsPythonRunner::run( projectFunctions );
5542 return true;
5543 }
5544 }
5545 }
5546 return false;
5547}
5548
5550{
5552 {
5553 QgsPythonRunner::run( "qgis.utils.clean_project_expression_functions()" );
5554 }
5555}
5556
5558
5559QHash< QString, QColor > loadColorsFromProject( const QgsProject *project )
5560{
5561 QHash< QString, QColor > colors;
5562
5563 //build up color list from project. Do this in advance for speed
5564 QStringList colorStrings = project->readListEntry( u"Palette"_s, u"/Colors"_s );
5565 const QStringList colorLabels = project->readListEntry( u"Palette"_s, u"/Labels"_s );
5566
5567 //generate list from custom colors
5568 int colorIndex = 0;
5569 for ( QStringList::iterator it = colorStrings.begin(); it != colorStrings.end(); ++it )
5570 {
5571 const QColor color = QgsColorUtils::colorFromString( *it );
5572 QString label;
5573 if ( colorLabels.length() > colorIndex )
5574 {
5575 label = colorLabels.at( colorIndex );
5576 }
5577
5578 colors.insert( label.toLower(), color );
5579 colorIndex++;
5580 }
5581
5582 return colors;
5583}
5584
5585
5586GetNamedProjectColor::GetNamedProjectColor( const QgsProject *project )
5587 : QgsScopedExpressionFunction( u"project_color"_s, 1, u"Color"_s )
5588{
5589 if ( !project )
5590 return;
5591
5592 mColors = loadColorsFromProject( project );
5593}
5594
5595GetNamedProjectColor::GetNamedProjectColor( const QHash<QString, QColor> &colors )
5596 : QgsScopedExpressionFunction( u"project_color"_s, 1, u"Color"_s )
5597 , mColors( colors )
5598{}
5599
5600QVariant GetNamedProjectColor::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5601{
5602 const QString colorName = values.at( 0 ).toString().toLower();
5603 if ( mColors.contains( colorName ) )
5604 {
5605 return u"%1,%2,%3"_s.arg( mColors.value( colorName ).red() ).arg( mColors.value( colorName ).green() ).arg( mColors.value( colorName ).blue() );
5606 }
5607 else
5608 return QVariant();
5609}
5610
5611QgsScopedExpressionFunction *GetNamedProjectColor::clone() const
5612{
5613 return new GetNamedProjectColor( mColors );
5614}
5615
5616GetNamedProjectColorObject::GetNamedProjectColorObject( const QgsProject *project )
5617 : QgsScopedExpressionFunction( u"project_color_object"_s, 1, u"Color"_s )
5618{
5619 if ( !project )
5620 return;
5621
5622 mColors = loadColorsFromProject( project );
5623}
5624
5625GetNamedProjectColorObject::GetNamedProjectColorObject( const QHash<QString, QColor> &colors )
5626 : QgsScopedExpressionFunction( u"project_color_object"_s, 1, u"Color"_s )
5627 , mColors( colors )
5628{}
5629
5630QVariant GetNamedProjectColorObject::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5631{
5632 const QString colorName = values.at( 0 ).toString().toLower();
5633 if ( mColors.contains( colorName ) )
5634 {
5635 return mColors.value( colorName );
5636 }
5637 else
5638 return QVariant();
5639}
5640
5641QgsScopedExpressionFunction *GetNamedProjectColorObject::clone() const
5642{
5643 return new GetNamedProjectColorObject( mColors );
5644}
5645
5646// ----------------
5647
5648GetSensorData::GetSensorData( const QMap<QString, QgsAbstractSensor::SensorData> &sensorData )
5649 : QgsScopedExpressionFunction( u"sensor_data"_s, QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( u"name"_s ) << QgsExpressionFunction::Parameter( u"expiration"_s, true, 0 ), u"Sensors"_s )
5650 , mSensorData( sensorData )
5651{}
5652
5653QVariant GetSensorData::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
5654{
5655 const QString sensorName = values.at( 0 ).toString();
5656 const int expiration = values.at( 1 ).toInt();
5657 const qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
5658 if ( mSensorData.contains( sensorName ) )
5659 {
5660 if ( expiration <= 0 || ( timestamp - mSensorData[sensorName].lastTimestamp.toMSecsSinceEpoch() ) < expiration )
5661 {
5662 return mSensorData[sensorName].lastValue;
5663 }
5664 }
5665
5666 return QVariant();
5667}
5668
5669QgsScopedExpressionFunction *GetSensorData::clone() const
5670{
5671 return new GetSensorData( mSensorData );
5672}
@ Macro
Project macros.
Definition qgis.h:484
@ ExpressionFunction
Expression functions.
Definition qgis.h:485
@ DontLoad3DViews
Skip loading 3D views.
Definition qgis.h:4779
@ DontStoreOriginalStyles
Skip the initial XML style storage for layers. Useful for minimising project load times in non-intera...
Definition qgis.h:4778
@ ForceReadOnlyLayers
Open layers in a read-only mode.
Definition qgis.h:4781
@ TrustLayerMetadata
Trust layer metadata. Improves project read time. Do not use it if layers' extent is not fixed during...
Definition qgis.h:4776
@ DontUpgradeAnnotations
Don't upgrade old annotation items to QgsAnnotationItem.
Definition qgis.h:4782
@ DontLoadLayouts
Don't load print layouts. Improves project read time if layouts are not required, and allows projects...
Definition qgis.h:4774
@ DontResolveLayers
Don't resolve layer paths (i.e. don't load any layer content). Dramatically improves project read tim...
Definition qgis.h:4772
static QString version()
Version string.
Definition qgis.cpp:682
@ Trusted
The project has been determined by the user as trusted.
Definition qgis.h:498
QFlags< ProjectCapability > ProjectCapabilities
Flags which control project capabilities.
Definition qgis.h:4815
QFlags< ProjectReadFlag > ProjectReadFlags
Project load flags.
Definition qgis.h:4793
DistanceUnit
Units of distance.
Definition qgis.h:5568
@ Meters
Meters.
Definition qgis.h:5569
FilePathType
File path types.
Definition qgis.h:1846
@ Relative
Relative path.
Definition qgis.h:1848
@ Absolute
Absolute path.
Definition qgis.h:1847
TransactionMode
Transaction mode.
Definition qgis.h:4232
@ AutomaticGroups
Automatic transactional editing means that on supported datasources (postgres and geopackage database...
Definition qgis.h:4234
@ BufferedGroups
Buffered transactional editing means that all editable layers in the buffered transaction group are t...
Definition qgis.h:4235
@ Disabled
Edits are buffered locally and sent to the provider when toggling layer editing mode.
Definition qgis.h:4233
AreaUnit
Units of area.
Definition qgis.h:5645
@ SquareMeters
Square meters.
Definition qgis.h:5646
@ Critical
Critical/error message.
Definition qgis.h:163
@ Success
Used for reporting a successful operation.
Definition qgis.h:164
@ Vertical
Vertical CRS.
Definition qgis.h:2519
@ Temporal
Temporal CRS.
Definition qgis.h:2522
@ Compound
Compound (horizontal + vertical) CRS.
Definition qgis.h:2521
@ Projected
Projected CRS.
Definition qgis.h:2520
@ Other
Other type.
Definition qgis.h:2525
@ Bound
Bound CRS.
Definition qgis.h:2524
@ DerivedProjected
Derived projected CRS.
Definition qgis.h:2526
@ Unknown
Unknown type.
Definition qgis.h:2514
@ Engineering
Engineering CRS.
Definition qgis.h:2523
@ Geographic3d
3D geopraphic CRS
Definition qgis.h:2518
@ Geodetic
Geodetic CRS.
Definition qgis.h:2515
@ Geographic2d
2D geographic CRS
Definition qgis.h:2517
@ Geocentric
Geocentric CRS.
Definition qgis.h:2516
AvoidIntersectionsMode
Flags which control how intersections of pre-existing feature are handled when digitizing new feature...
Definition qgis.h:4742
@ AvoidIntersectionsLayers
Overlap with features from a specified list of layers when digitizing new features not allowed.
Definition qgis.h:4745
@ AllowIntersections
Overlap with any feature allowed when digitizing new features.
Definition qgis.h:4743
ProjectFlag
Flags which control the behavior of QgsProjects.
Definition qgis.h:4366
@ RememberLayerEditStatusBetweenSessions
If set, then any layers set to be editable will be stored in the project and immediately made editabl...
Definition qgis.h:4370
@ EvaluateDefaultValuesOnProviderSide
If set, default values for fields will be evaluated on the provider side when features from the proje...
Definition qgis.h:4367
@ TrustStoredLayerStatistics
If set, then layer statistics (such as the layer extent) will be read from values stored in the proje...
Definition qgis.h:4368
@ Polygon
Polygons.
Definition qgis.h:402
QFlags< DataProviderReadFlag > DataProviderReadFlags
Flags which control data provider construction.
Definition qgis.h:532
LayerType
Types of layers that can be added to a map.
Definition qgis.h:206
@ Group
Composite group layer. Added in QGIS 3.24.
Definition qgis.h:214
@ Plugin
Plugin based layer.
Definition qgis.h:209
@ TiledScene
Tiled scene layer. Added in QGIS 3.34.
Definition qgis.h:215
@ Annotation
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
Definition qgis.h:212
@ Vector
Vector layer.
Definition qgis.h:207
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:211
@ Mesh
Mesh layer. Added in QGIS 3.2.
Definition qgis.h:210
@ Raster
Raster layer.
Definition qgis.h:208
@ PointCloud
Point cloud layer. Added in QGIS 3.18.
Definition qgis.h:213
ScaleCalculationMethod
Scale calculation logic.
Definition qgis.h:5865
@ HorizontalMiddle
Calculate horizontally, across midle of map.
Definition qgis.h:5867
@ SkipCredentialsRequest
Skip credentials if the provided one are not valid, let the provider be invalid, avoiding to block th...
Definition qgis.h:519
@ ParallelThreadLoading
Provider is created in a parallel thread than the one where it will live.
Definition qgis.h:521
@ UseProjectCrs
Copy the current project's CRS.
Definition qgis.h:2647
QFlags< ProjectFlag > ProjectFlags
Definition qgis.h:4374
@ Container
A container.
Definition qgis.h:6272
@ Marker
Marker symbol.
Definition qgis.h:672
@ Line
Line symbol.
Definition qgis.h:673
@ Fill
Fill symbol.
Definition qgis.h:674
static QString geoNone()
Constant that holds the string representation for "No ellipse/No CRS".
Definition qgis.h:7482
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
Definition qgis.h:2631
QMap< QString, QStringList > KeywordMap
Map of vocabulary string to keyword list.
QgsAbstractMetadataBase::KeywordMap keywords() const
Returns the keywords map, which is a set of descriptive keywords associated with the resource.
virtual bool readXml(const QDomElement &collectionElem, const QgsPropertiesDefinition &definitions)
Reads property collection state from an XML element.
QList< QgsAction > actions(const QString &actionScope=QString()) const
Returns a list of actions that are available in the given action scope.
Utility class that encapsulates an action based on vector attributes.
Definition qgsaction.h:38
Represents a map layer containing a set of georeferenced annotations, e.g.
Manages storage of a set of QgsAnnotation annotation objects.
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
static QgsProjectStorageRegistry * projectStorageRegistry()
Returns registry of available project storage implementations.
static const QgsSettingsEntryString * settingsLocaleUserLocale
Settings entry locale user locale.
static QgsRuntimeProfiler * profiler()
Returns the application runtime profiler.
void collectTranslatableObjects(QgsTranslationContext *translationContext)
Emits the signal to collect all the strings of .qgs to be included in ts file.
static QgsPluginLayerRegistry * pluginLayerRegistry()
Returns the application's plugin layer registry, used for managing plugin layer types.
void requestForTranslatableObjects(QgsTranslationContext *translationContext)
Emitted when project strings which require translation are being collected for inclusion in a ....
static QString userFullName()
Returns the user's operating system login account full display name.
static QString userLoginName()
Returns the user's operating system login account name.
Manages zip/unzip operations for an archive.
Definition qgsarchive.h:36
A container for attribute editors, used to group them visually in the attribute form if it is set to ...
QList< QgsAttributeEditorElement * > children() const
Gets a list of the children elements of this container.
An abstract base class for any elements of a drag and drop form.
QString name() const
Returns the name of this element.
QgsFields auxiliaryFields() const
Returns a list of all auxiliary fields currently managed by the layer.
bool save()
Commits changes and starts editing then.
Providing some utility methods to manage auxiliary storage.
static QString extension()
Returns the extension used for auxiliary databases.
static bool deleteTable(const QgsDataSourceUri &uri)
Removes a table from the auxiliary storage.
Manages storage of a set of bookmarks.
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
Represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QString toProj() const
Returns a Proj string representation of this CRS.
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
static QgsCoordinateReferenceSystem createCompoundCrs(const QgsCoordinateReferenceSystem &horizontalCrs, const QgsCoordinateReferenceSystem &verticalCrs, QString &error)
Given a horizontal and vertical CRS, attempts to create a compound CRS from them.
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
QString projectionAcronym() const
Returns the projection acronym for the projection used by the CRS.
static QgsCoordinateReferenceSystem fromProj(const QString &proj)
Creates a CRS from a proj style formatted string.
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
static QgsCoordinateReferenceSystem fromSrsId(long srsId)
Creates a CRS from a specified QGIS SRS ID.
Contains information about the context in which a coordinate transform is executed.
void readSettings()
Reads the context's state from application settings.
Abstract base class for spatial data provider implementations.
@ EvaluateDefaultValues
Evaluate default values on provider side when calling QgsVectorDataProvider::defaultValue( int index ...
Stores the component parts of a data source URI (e.g.
QgsAttributeEditorContainer * invisibleRootContainer()
Gets the invisible root container for the drag and drop designer form (EditorLayout::TabLayout).
Manages storage of a set of elevation profiles.
Renders elevation shading on an image with different methods (eye dome lighting, hillshading,...
A embedded script entity for QgsObjectEntityVisitorInterface.
Single scope for storing variables and functions for use within a QgsExpressionContext.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
An abstract base class for defining QgsExpression functions.
An expression node for expression functions.
Handles parsing and evaluation of expressions (formerly called "search strings").
virtual QgsLegendSymbolList legendSymbolItems() const
Returns a list of symbology items for the legend.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Container of fields for a vector layer.
Definition qgsfields.h:45
bool isEmpty
Definition qgsfields.h:48
Stores global configuration for labeling engine.
Layer tree group node serves as a container for layers and further groups.
QgsLayerTreeGroup * findGroup(const QString &name)
Find group node with specified name.
void readChildrenFromXml(const QDomElement &element, const QgsReadWriteContext &context)
Read children from XML and append them to the group.
QString name() const override
Returns the group's name.
void insertChildNodes(int index, const QList< QgsLayerTreeNode * > &nodes)
Insert existing nodes at specified position.
QgsLayerTreeGroup * clone() const override
Returns a clone of the group.
Layer tree node points to a map layer.
void resolveReferences(const QgsProject *project, bool looseMatching=false) override
Resolves reference to layer from stored layer ID (if it has not been resolved already).
Base class for nodes in a layer tree.
QList< QgsLayerTreeNode * > abandonChildren()
Removes the children, disconnect all the forwarded and external signals and sets their parent to null...
void setCustomProperty(const QString &key, const QVariant &value)
Sets a custom property for the node. Properties are stored in a map and saved in project file.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file.
void setItemVisibilityChecked(bool checked)
Check or uncheck a node (independently of its ancestors or children).
static void replaceChildrenOfEmbeddedGroups(QgsLayerTreeGroup *group)
Remove subtree of embedded groups and replaces it with a custom property embedded-visible-layers.
static void storeOriginalLayersProperties(QgsLayerTreeGroup *group, const QDomDocument *doc)
Stores in a layer's originalXmlProperties the layer properties information.
static void updateEmbeddedGroupsProjectPath(QgsLayerTreeGroup *group, const QgsProject *project)
Updates an embedded group from a project.
static bool readOldLegend(QgsLayerTreeGroup *root, const QDomElement &legendElem)
Try to load layer tree from.
Namespace with helper functions for layer tree operations.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
Manages storage of a set of layouts.
Stores information about one class/rule of a vector layer renderer in a unified way that can be used ...
static void warning(const QString &msg)
Goes to qWarning.
static Qgis::LayerType typeFromString(const QString &string, bool &ok)
Returns the map layer type corresponding a string value.
A storage object for map layers, in which the layers are owned by the store and have their lifetime b...
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the store.
void layerWillBeRemoved(const QString &layerId)
Emitted when a layer is about to be removed from the store.
void layersRemoved(const QStringList &layerIds)
Emitted after one or more layers were removed from the store.
void allLayersRemoved()
Emitted when all layers are removed, before layersWillBeRemoved() and layerWillBeRemoved() signals ar...
void layerRemoved(const QString &layerId)
Emitted after a layer was removed from the store.
void layerWasAdded(QgsMapLayer *layer)
Emitted when a layer was added to the store.
QgsMapLayer * mapLayer(const QString &id) const
Retrieve a pointer to a layer by layer id.
void layersAdded(const QList< QgsMapLayer * > &layers)
Emitted when one or more layers were added to the store.
Base class for all map layer types.
Definition qgsmaplayer.h:83
QFlags< ReadFlag > ReadFlags
QString source() const
Returns the source for the layer.
QString providerType() const
Returns the provider type (provider key) for this layer.
void configChanged()
Emitted whenever the configuration is changed.
static Qgis::DataProviderReadFlags providerReadFlags(const QDomNode &layerNode, QgsMapLayer::ReadFlags layerReadFlags)
Returns provider read flag deduced from layer read flags layerReadFlags and a dom node layerNode that...
QString id
Definition qgsmaplayer.h:86
QString originalXmlProperties() const
Returns the XML properties of the original layer as they were when the layer was first read from the ...
Qgis::LayerType type
Definition qgsmaplayer.h:93
virtual bool isEditable() const
Returns true if the layer can be edited.
bool writeLayerXml(QDomElement &layerElement, QDomDocument &document, const QgsReadWriteContext &context) const
Stores state in DOM node.
@ Identifiable
If the layer is identifiable using the identify map tool and as a WMS layer.
@ Removable
If the layer can be removed from the project. The layer will not be removable from the legend menu en...
@ FlagReadExtentFromXml
Read extent from xml and skip get extent from provider.
@ FlagTrustLayerMetadata
Trust layer metadata. Improves layer load time by skipping expensive checks like primary key unicity,...
@ FlagForceReadOnly
Force open as read only.
@ FlagDontResolveLayers
Don't resolve layer paths or create data providers for layers.
Container class that allows storage of map themes consisting of visible map layers and layer styles.
Manages storage of a set of views.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
An interface for classes which can visit various object entity (e.g.
virtual bool visitEmbeddedScript(const QgsEmbeddedScriptEntity &entity, const QgsObjectVisitorContext &context)
Called when the visitor will visit an embedded script entity.
A QgsObjectEntityVisitorInterface context object.
Resolves relative paths into absolute paths and vice versa.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
Allows managing the zip/unzip actions on project files.
Definition qgsarchive.h:111
QString projectFile() const
Returns the current .qgs project file or an empty string if there's none.
bool unzip(const QString &zipFilename) override
Clear the current content of this archive and unzip.
Interface for classes that handle missing layer files when reading project files.
Contains settings and properties relating to how a QgsProject should display values such as map coord...
Contains elevation properties for a QgsProject.
static Q_DECL_DEPRECATED void fixOldSymbolLayerReferences(const QMap< QString, QgsMapLayer * > &mapLayers)
QgsSymbolLayerReference uses QgsSymbolLayer unique uuid identifier since QGIS 3.30,...
Contains settings and properties relating to how a QgsProject should interact with a GPS device.
A structured metadata store for a project.
Project property key node.
QString name() const
The name of the property is used as identifier.
QgsProjectProperty * find(const QString &propertyName) const
Attempts to find a property with a matching sub-key name.
void removeKey(const QString &keyName)
Removes the specified key.
void dump(int tabs=0) const override
Dumps out the keys and values.
void subkeyList(QStringList &entries) const
Returns any sub-keys contained by this property which themselves contain other keys.
QgsProjectPropertyKey * addKey(const QString &keyName)
Adds the specified property key as a sub-key.
QVariant value() const override
If this key has a value, it will be stored by its name in its properties.
QgsProjectPropertyValue * setValue(const QString &name, const QVariant &value)
Sets the value associated with this key.
void entryList(QStringList &entries) const
Returns any sub-keys contained by this property that do not contain other keys.
bool readXml(const QDomNode &keyNode) override
Restores the property hierarchy from a specified DOM node.
An abstract base class for QGIS project property hierarchys.
virtual bool isKey() const =0
Returns true if the property is a QgsProjectPropertyKey.
virtual bool isValue() const =0
Returns true if the property is a QgsProjectPropertyValue.
QgsProjectStorage * projectStorageFromUri(const QString &uri)
Returns storage implementation if the URI matches one. Returns nullptr otherwise (it is a normal file...
Metadata associated with a project.
Abstract interface for project storage - to be implemented by various backends and registered in QgsP...
Contains settings and properties relating to how a QgsProject should handle styling.
void setDefaultSymbol(Qgis::SymbolType symbolType, QgsSymbol *symbol)
Sets the project default symbol for a given type.
void setRandomizeDefaultSymbolColor(bool randomized)
Sets whether the default symbol fill color is randomized.
void setDefaultColorRamp(QgsColorRamp *colorRamp)
Sets the project default color ramp.
void setDefaultSymbolOpacity(double opacity)
Sets the default symbol opacity.
Contains temporal settings and properties for the project, this may be used when animating maps or sh...
static Qgis::ProjectTrustStatus checkUserTrust(QgsProject *project)
Returns the current trust status of the specified project.
Describes the version of a project.
QString text() const
Returns a string representation of the version.
int majorVersion() const
Returns the major version number.
Contains settings and properties relating to how a QgsProject should be displayed inside map canvas,...
void mapScalesChanged()
Emitted when the list of custom project map scales changes.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:114
bool isZipped() const
Returns true if the project comes from a zip archive, false otherwise.
bool removeAttachedFile(const QString &path)
Removes the attached file.
QgsRelationManager * relationManager
Definition qgsproject.h:125
bool write()
Writes the project to its current associated file (see fileName() ).
QgsProject(QObject *parent=nullptr, Qgis::ProjectCapabilities capabilities=Qgis::ProjectCapability::ProjectStyles)
Create a new QgsProject.
void removeMapLayer(const QString &layerId)
Remove a layer from the registry by layer ID.
Q_DECL_DEPRECATED void oldProjectVersionWarning(const QString &warning)
Emitted when an old project file is read.
Q_DECL_DEPRECATED bool evaluateDefaultValues() const
Should default values be evaluated on provider side when requested and not when committed.
Qgis::DistanceUnit distanceUnits
Definition qgsproject.h:132
void layersAddedWithoutLegend(const QList< QgsMapLayer * > &layers)
Emitted when layers were added to the registry without adding to the legend.
void layersRemoved(const QStringList &layerIds)
Emitted after one or more layers were removed from the registry.
void clear()
Clears the project, removing all settings and resetting it back to an empty, default state.
~QgsProject() override
QString error() const
Returns error message from previous read/write.
Q_DECL_DEPRECATED void setUseProjectScales(bool enabled)
Sets whether project mapScales() are enabled.
void readProjectWithContext(const QDomDocument &document, QgsReadWriteContext &context)
Emitted when a project is being read.
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
Reads an integer from the specified scope and key.
Q_DECL_DEPRECATED void setNonIdentifiableLayers(const QList< QgsMapLayer * > &layers)
Set a list of layers which should not be taken into account on map identification.
QList< QgsMapLayer * > addMapLayers(const QList< QgsMapLayer * > &mapLayers, bool addToLegend=true, bool takeOwnership=true)
Add a list of layers to the map of loaded layers.
Qgis::ProjectFlags flags() const
Returns the project's flags, which dictate the behavior of the project.
Definition qgsproject.h:220
Q_DECL_DEPRECATED QFileInfo fileInfo() const
Returns QFileInfo object for the project's associated file.
QString presetHomePath() const
Returns any manual project home path setting, or an empty string if not set.
void setBackgroundColor(const QColor &color)
Sets the default background color used by default map canvases.
void setCrs(const QgsCoordinateReferenceSystem &crs, bool adjustEllipsoid=false)
Sets the project's native coordinate reference system.
QColor selectionColor
Definition qgsproject.h:130
bool commitChanges(QStringList &commitErrors, bool stopEditing=true, QgsVectorLayer *vectorLayer=nullptr)
Attempts to commit to the underlying data provider any buffered changes made since the last to call t...
void mapThemeCollectionChanged()
Emitted when the map theme collection changes.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Qgis::FilePathType filePathStorage() const
Returns the type of paths used when storing file paths in a QGS/QGZ project file.
QString createAttachedFile(const QString &nameTemplate)
Attaches a file to the project.
Q_DECL_DEPRECATED void mapScalesChanged()
Emitted when the list of custom project map scales changes.
void readVersionMismatchOccurred(const QString &fileVersion)
Emitted when a project is read and the version of QGIS used to save the project differs from the curr...
QString ellipsoid
Definition qgsproject.h:122
void fileNameChanged()
Emitted when the file name of the project changes.
void titleChanged()
Emitted when the title of the project changes.
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QString title
Definition qgsproject.h:117
void writeMapLayer(QgsMapLayer *mapLayer, QDomElement &layerElem, QDomDocument &doc)
Emitted when a layer is being saved.
const QgsSensorManager * sensorManager() const
Returns the project's sensor manager, which manages sensors within the project.
void setSnappingConfig(const QgsSnappingConfig &snappingConfig)
The snapping configuration for this project.
void areaUnitsChanged()
Emitted when the default area units changes.
QgsPropertyCollection dataDefinedServerProperties() const
Returns the data defined properties used for overrides in user defined server parameters.
Q_DECL_DEPRECATED void nonIdentifiableLayersChanged(QStringList nonIdentifiableLayers)
Emitted when the list of layer which are excluded from map identification changes.
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the registry.
QString attachmentIdentifier(const QString &attachedFile) const
Returns an identifier for an attachment file path An attachment identifier is a string which does not...
void setScaleMethod(Qgis::ScaleCalculationMethod method)
Sets the method to use for map scale calculations for the project.
QgsVectorLayerEditBufferGroup * editBufferGroup()
Returns the edit buffer group.
void setSelectionColor(const QColor &color)
Sets the color used to highlight selected features.
bool rollBack(QStringList &rollbackErrors, bool stopEditing=true, QgsVectorLayer *vectorLayer=nullptr)
Stops a current editing operation on vectorLayer and discards any uncommitted edits.
void snappingConfigChanged(const QgsSnappingConfig &config)
Emitted whenever the configuration for snapping has changed.
QgsPathResolver pathResolver() const
Returns path resolver object with considering whether the project uses absolute or relative paths and...
void setBadLayerHandler(QgsProjectBadLayerHandler *handler)
Change handler for missing layers.
Q_DECL_DEPRECATED void setEvaluateDefaultValues(bool evaluateDefaultValues)
Defines if default values should be evaluated on provider side when requested and not when committed.
Qgis::AreaUnit areaUnits
Definition qgsproject.h:133
void crsChanged()
Emitted when the crs() of the project has changed.
QString translate(const QString &context, const QString &sourceText, const char *disambiguation=nullptr, int n=-1) const override
Translates a string using the Qt QTranslator mechanism.
const QgsProjectStyleSettings * styleSettings() const
Returns the project's style settings, which contains settings and properties relating to how a QgsPro...
QgsSnappingConfig snappingConfig
Definition qgsproject.h:124
const QgsProjectGpsSettings * gpsSettings() const
Returns the project's GPS settings, which contains settings and properties relating to how a QgsProje...
void setFileName(const QString &name)
Sets the file name associated with the project.
void avoidIntersectionsLayersChanged()
Emitted whenever avoidIntersectionsLayers has changed.
void setDataDefinedServerProperties(const QgsPropertyCollection &properties)
Sets the data defined properties used for overrides in user defined server parameters to properties.
void registerTranslatableObjects(QgsTranslationContext *translationContext)
Registers the objects that require translation into the translationContext.
void distanceUnitsChanged()
Emitted when the default distance units changes.
QgsAnnotationLayer * mainAnnotationLayer()
Returns the main annotation layer associated with the project.
const QgsBookmarkManager * bookmarkManager() const
Returns the project's bookmark manager, which manages bookmarks within the project.
void readMapLayer(QgsMapLayer *mapLayer, const QDomElement &layerNode)
Emitted after the basic initialization of a layer from the project file is done.
std::unique_ptr< QgsLayerTreeGroup > createEmbeddedGroup(const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers, Qgis::ProjectReadFlags flags=Qgis::ProjectReadFlags())
Create layer group instance defined in an arbitrary project file.
Q_DECL_DEPRECATED void setAutoTransaction(bool autoTransaction)
Transactional editing means that on supported datasources (postgres databases) the edit state of all ...
bool startEditing(QgsVectorLayer *vectorLayer=nullptr)
Makes the layer editable.
void aboutToBeCleared()
Emitted when the project is about to be cleared.
Q_DECL_DEPRECATED void setTrustLayerMetadata(bool trust)
Sets the trust option allowing to indicate if the extent has to be read from the XML document when da...
void cleared()
Emitted when the project is cleared (and additionally when an open project is cleared just before a n...
bool setVerticalCrs(const QgsCoordinateReferenceSystem &crs, QString *errorMessage=nullptr)
Sets the project's vertical coordinate reference system.
void setLabelingEngineSettings(const QgsLabelingEngineSettings &settings)
Sets project's global labeling engine settings.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void metadataChanged()
Emitted when the project's metadata is changed.
QString resolveAttachmentIdentifier(const QString &identifier) const
Resolves an attachment identifier to a attachment file path.
const QgsProjectElevationProperties * elevationProperties() const
Returns the project's elevation properties, which contains the project's elevation related settings.
QString absolutePath() const
Returns full absolute path to the project folder if the project is stored in a file system - derived ...
void crs3DChanged()
Emitted when the crs3D() of the project has changed.
void scaleMethodChanged()
Emitted when the project's scale method is changed.
void removeMapLayers(const QStringList &layerIds)
Remove a set of layers from the registry by layer ID.
Q_DECL_DEPRECATED void setRequiredLayers(const QSet< QgsMapLayer * > &layers)
Configures a set of map layers that are required in the project and therefore they should not get rem...
bool createEmbeddedLayer(const QString &layerId, const QString &projectFilePath, QList< QDomNode > &brokenNodes, bool saveFlag=true, Qgis::ProjectReadFlags flags=Qgis::ProjectReadFlags())
Creates a maplayer instance defined in an arbitrary project file.
QList< QgsVectorLayer * > avoidIntersectionsLayers
Definition qgsproject.h:127
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
Reads a string from the specified scope and key.
QgsExpressionContextScope * createExpressionContextScope() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QString baseName() const
Returns the base name of the project file without the path and without extension - derived from fileN...
void ellipsoidChanged(const QString &ellipsoid)
Emitted when the project ellipsoid is changed.
QgsMapThemeCollection * mapThemeCollection
Definition qgsproject.h:123
void generateTsFile(const QString &locale)
Triggers the collection strings of .qgs to be included in ts file and calls writeTsFile().
QStringList entryList(const QString &scope, const QString &key) const
Returns a list of child keys with values which exist within the specified scope and key.
Qgis::TransactionMode transactionMode
Definition qgsproject.h:135
QgsAnnotationManager * annotationManager()
Returns pointer to the project's annotation manager.
QgsProjectDisplaySettings * displaySettings
Definition qgsproject.h:134
QgsProjectMetadata metadata
Definition qgsproject.h:128
void projectColorsChanged()
Emitted whenever the project's color scheme has been changed.
QString saveUser() const
Returns the user name that did the last save.
QVector< T > layers() const
Returns a list of registered map layers with a specified layer type.
void setProjectColors(const QgsNamedColorList &colors)
Sets the colors for the project's color scheme (see QgsProjectColorScheme).
bool setTransactionMode(Qgis::TransactionMode transactionMode)
Set transaction mode.
QgsCoordinateTransformContext transformContext
Definition qgsproject.h:121
void transactionModeChanged()
Emitted when the transaction mode has changed.
void labelingEngineSettingsChanged()
Emitted when global configuration of the labeling engine changes.
void customVariablesChanged()
Emitted whenever the expression variables stored in the project have been changed.
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
bool readBoolEntry(const QString &scope, const QString &key, bool def=false, bool *ok=nullptr) const
Reads a boolean from the specified scope and key.
QgsMapLayerStore * layerStore()
Returns a pointer to the project's internal layer store.
QString originalPath() const
Returns the original path associated with the project.
void setOriginalPath(const QString &path)
Sets the original path associated with the project.
void dumpProperties() const
Dump out current project properties to stderr.
QgsElevationShadingRenderer elevationShadingRenderer() const
Returns the elevation shading renderer used for map shading.
const QgsMapViewsManager * viewsManager() const
Returns the project's views manager, which manages map views (including 3d maps) in the project.
static void setInstance(QgsProject *project)
Set the current project singleton instance to project.
int validCount() const
Returns the number of registered valid layers.
const QgsLayoutManager * layoutManager() const
Returns the project's layout manager, which manages print layouts, atlases and reports within the pro...
void elevationShadingRendererChanged()
Emitted when the map shading renderer changes.
Q_INVOKABLE QList< QgsMapLayer * > mapLayersByName(const QString &layerName) const
Retrieve a list of matching registered layers by layer name.
QString fileName
Definition qgsproject.h:118
QgsCoordinateReferenceSystem crs3D() const
Returns the CRS to use for the project when transforming 3D data, or when z/elevation value handling ...
Q_DECL_DEPRECATED bool autoTransaction() const
Transactional editing means that on supported datasources (postgres databases) the edit state of all ...
bool accept(QgsStyleEntityVisitorInterface *visitor) const
Accepts the specified style entity visitor, causing it to visit all style entities associated with th...
QStringList attachedFiles() const
Returns a map of all attached files with identifier and real paths.
void setMetadata(const QgsProjectMetadata &metadata)
Sets the project's metadata store.
void missingDatumTransforms(const QStringList &missingTransforms)
Emitted when datum transforms stored in the project are not available locally.
QgsTransactionGroup * transactionGroup(const QString &providerKey, const QString &connString)
Returns the matching transaction group from a provider key and connection string.
QgsCoordinateReferenceSystem crs
Definition qgsproject.h:120
QgsMapLayer * addMapLayer(QgsMapLayer *mapLayer, bool addToLegend=true, bool takeOwnership=true)
Add a layer to the map of loaded layers.
QStringList nonIdentifiableLayers
Definition qgsproject.h:116
void setAvoidIntersectionsMode(const Qgis::AvoidIntersectionsMode mode)
Sets the avoid intersections mode.
void transactionGroupsChanged()
Emitted whenever a new transaction group has been created or a transaction group has been removed.
const QgsAuxiliaryStorage * auxiliaryStorage() const
Returns the current const auxiliary storage.
void reloadAllLayers()
Reload all registered layer's provider data caches, synchronising the layer with any changes in the d...
int count() const
Returns the number of registered layers.
void loadingLayerMessageReceived(const QString &layerName, const QList< QgsReadWriteContext::ReadWriteMessage > &messages)
Emitted when loading layers has produced some messages.
void setAreaUnits(Qgis::AreaUnit unit)
Sets the default area measurement units for the project.
void setTitle(const QString &title)
Sets the project's title.
QMap< QPair< QString, QString >, QgsTransactionGroup * > transactionGroups()
Map of transaction groups.
void setFlag(Qgis::ProjectFlag flag, bool enabled=true)
Sets whether a project flag is enabled.
QDateTime lastModified() const
Returns last modified time of the project file as returned by the file system (or other project stora...
static const QgsSettingsEntryBool * settingsAnonymizeSavedProjects
Definition qgsproject.h:140
Qgis::ProjectCapabilities capabilities() const
Returns the project's capabilities, which dictate optional functionality which can be selectively ena...
Definition qgsproject.h:210
bool loadFunctionsFromProject(bool force=false)
Loads python expression functions stored in the current project.
bool readLayer(const QDomNode &layerNode)
Reads the layer described in the associated DOM node.
double readDoubleEntry(const QString &scope, const QString &key, double def=0, bool *ok=nullptr) const
Reads a double from the specified scope and key.
bool writeEntry(const QString &scope, const QString &key, bool value)
Write a boolean value to the project file.
QString absoluteFilePath() const
Returns full absolute path to the project file if the project is stored in a file system - derived fr...
const QgsElevationProfileManager * elevationProfileManager() const
Returns the project's elevation profile manager, which manages elevation profiles within the project.
QDateTime lastSaveDateTime() const
Returns the date and time when the project was last saved.
void projectSaved()
Emitted when the project file has been written and closed.
Q_DECL_DEPRECATED bool trustLayerMetadata() const
Returns true if the trust option is activated, false otherwise.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
void setEllipsoid(const QString &ellipsoid)
Sets the project's ellipsoid from a proj string representation, e.g., "WGS84".
void readProject(const QDomDocument &document)
Emitted when a project is being read.
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the project's coordinate transform context, which stores various information regarding which dat...
QColor backgroundColor
Definition qgsproject.h:129
void layerLoaded(int i, int n)
Emitted when a layer from a projects was read.
QStringList subkeyList(const QString &scope, const QString &key) const
Returns a list of child keys which contain other keys that exist within the specified scope and key.
static const QgsSettingsEntryBool * settingsAnonymizeNewProjects
Definition qgsproject.h:139
bool read(const QString &filename, Qgis::ProjectReadFlags flags=Qgis::ProjectReadFlags())
Reads given project file from the given file.
QStringList readListEntry(const QString &scope, const QString &key, const QStringList &def=QStringList(), bool *ok=nullptr) const
Reads a string list from the specified scope and key.
void selectionColorChanged()
Emitted whenever the project's selection color has been changed.
bool topologicalEditing
Definition qgsproject.h:131
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns project's global labeling engine settings.
void removeAllMapLayers()
Removes all registered layers.
Q_DECL_DEPRECATED QVector< double > mapScales() const
Returns the list of custom project map scales.
void setDirty(bool b=true)
Flag the project as dirty (modified).
void backgroundColorChanged()
Emitted whenever the project's canvas background color has been changed.
const QgsProjectViewSettings * viewSettings() const
Returns the project's view settings, which contains settings and properties relating to how a QgsProj...
void cleanFunctionsFromProject()
Unloads python expression functions stored in the current project and reloads local functions from th...
QgsCoordinateReferenceSystem verticalCrs() const
Returns the project's vertical coordinate reference system.
QString readPath(const QString &filename) const
Transforms a filename read from the project file to an absolute path.
void registerTranslatableContainers(QgsTranslationContext *translationContext, QgsAttributeEditorContainer *parent, const QString &layerId)
Registers the containers that require translation into the translationContext.
void setElevationShadingRenderer(const QgsElevationShadingRenderer &elevationShadingRenderer)
Sets the elevation shading renderer used for global map shading.
void setFilePathStorage(Qgis::FilePathType type)
Sets the type of paths used when storing file paths in a QGS/QGZ project file.
Q_DECL_DEPRECATED QSet< QgsMapLayer * > requiredLayers() const
Returns a set of map layers that are required in the project and therefore they should not get remove...
void transformContextChanged()
Emitted when the project transformContext() is changed.
void setTopologicalEditing(bool enabled)
Convenience function to set topological editing.
const QgsSelectiveMaskingSourceSetManager * selectiveMaskingSourceSetManager() const
Returns the project's selective masking set manager, which manages storage of a set of selective mask...
void legendLayersAdded(const QList< QgsMapLayer * > &layers)
Emitted when layers were added to the registry and the legend.
QVariantMap customVariables() const
A map of custom project variables.
void setAvoidIntersectionsLayers(const QList< QgsVectorLayer * > &layers)
Sets the list of layers with which intersections should be avoided.
void homePathChanged()
Emitted when the home path of the project changes.
void dirtySet()
Emitted when setDirty(true) is called.
void setCustomVariables(const QVariantMap &customVariables)
A map of custom project variables.
void writeProject(QDomDocument &document)
Emitted when the project is being written.
QgsCoordinateReferenceSystem defaultCrsForNewLayers() const
Returns the default CRS for new layers based on the settings and the current project CRS.
QString saveUserFullName() const
Returns the full user name that did the last save.
void layersAdded(const QList< QgsMapLayer * > &layers)
Emitted when one or more layers were added to the registry.
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
QString homePath
Definition qgsproject.h:119
bool isDirty() const
Returns true if the project has been modified since the last write().
QgsMapLayer * takeMapLayer(QgsMapLayer *layer)
Takes a layer from the registry.
void isDirtyChanged(bool dirty)
Emitted when the project dirty status changes.
void setDistanceUnits(Qgis::DistanceUnit unit)
Sets the default distance measurement units for the project.
Q_DECL_DEPRECATED bool useProjectScales() const
Returns true if project mapScales() are enabled.
Q_DECL_DEPRECATED void setMapScales(const QVector< double > &scales)
Sets the list of custom project map scales.
void setPresetHomePath(const QString &path)
Sets the project's home path.
void setFlags(Qgis::ProjectFlags flags)
Sets the project's flags, which dictate the behavior of the project.
QList< QgsMapLayer * > mapLayersByShortName(const QString &shortName) const
Retrieves a list of matching registered layers by layer shortName.
QgsProjectStorage * projectStorage() const
Returns pointer to project storage implementation that handles read/write of the project file.
QString layerIsEmbedded(const QString &id) const
Returns the source project file path if the layer with matching id is embedded from other project fil...
const QgsProjectTimeSettings * timeSettings() const
Returns the project's time settings, which contains the project's temporal range and other time based...
void verticalCrsChanged()
Emitted when the verticalCrs() of the project has changed.
void topologicalEditingChanged()
Emitted when the topological editing flag has changed.
bool removeEntry(const QString &scope, const QString &key)
Remove the given key from the specified scope.
static const QgsSettingsEntryBool * settingsDefaultProjectPathsRelative
Definition qgsproject.h:141
QgsProjectVersion lastSaveVersion() const
Returns the QGIS version which the project was last saved using.
void avoidIntersectionsModeChanged()
Emitted whenever the avoid intersections mode has changed.
void loadingLayer(const QString &layerName)
Emitted when a layer is loaded.
A grouped map of multiple QgsProperty objects, each referenced by an integer key value.
void clear() final
Removes all properties from the collection.
@ String
Any string value.
Definition qgsproperty.h:60
virtual QgsProviderMetadata::ProviderCapabilities providerCapabilities() const
Returns the provider's capabilities.
@ ParallelCreateProvider
Indicates that the provider supports parallel creation, that is, can be created on another thread tha...
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
QString relativeToAbsoluteUri(const QString &providerKey, const QString &uri, const QgsReadWriteContext &context) const
Converts relative path(s) to absolute path(s) in the given provider-specific URI.
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
static bool run(const QString &command, const QString &messageOnError=QString())
Execute a Python statement.
static bool isValid()
Returns true if the runner has an instance (and thus is able to run commands).
A container for the context for various read/write operations on objects.
void setCurrentLayerId(const QString &layerId)
Sets the current layer id.
void setTransformContext(const QgsCoordinateTransformContext &transformContext)
Sets data coordinate transform context to transformContext.
QgsCoordinateTransformContext transformContext() const
Returns data provider coordinate transform context.
QList< QgsReadWriteContext::ReadWriteMessage > takeMessages()
Returns the stored messages and remove them.
void setProjectTranslator(QgsProjectTranslator *projectTranslator)
Sets the project translator.
void setPathResolver(const QgsPathResolver &resolver)
Sets up path resolver for conversion between relative and absolute paths.
Manages a set of relations between layers.
Represents a relationship between two vector layers.
Definition qgsrelation.h:42
void providerCreated(bool isValid, const QString &layerId)
Emitted when a provider is created with isValid set to True when the provider is valid.
QgsDataProvider * dataProvider()
Returns the created data provider.
void clear(const QString &group="startup")
clear Clear all profile data.
Expression function for use within a QgsExpressionContextScope.
Scoped object for logging of the runtime for a single operation or group of operations.
Manages storage of a set of selective masking source sets.
Manages sensors.
A boolean settings entry.
static const QgsSettingsEntryColor * settingsDefaultCanvasColor
Settings entry for default canvas background color.
static const QgsSettingsEntryEnumFlag< Qgis::UnknownLayerCrsBehavior > * settingsUnknownCrsBehavior
Settings entry for behavior when encountering a layer with an unknown CRS (NoAction,...
static const QgsSettingsEntryInteger * settingsLayerParallelLoadingMaxCount
Settings entry maximum thread count used to load layer in parallel.
static const QgsSettingsEntryBool * settingsLayerParallelLoading
Settings entry whether layer are loading in parallel.
static const QgsSettingsEntryString * settingsMeasureAreaUnits
Settings entry for area display units.
static const QgsSettingsEntryColor * settingsDefaultSelectionColor
Settings entry for default selection color.
static const QgsSettingsEntryString * settingsLayerDefaultCrs
Settings entry for the default CRS used for layers with unknown CRS.
static const QgsSettingsEntryString * settingsMeasureDisplayUnits
Settings entry for distance display units.
static QgsSettingsTreeNode * sTreeProject
static QgsSettingsTreeNode * sTreeCore
Stores configuration of snapping settings for the project.
An interface for classes which can visit style entity (e.g.
virtual bool visitExit(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor stops visiting a node.
virtual bool visitEnter(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor starts visiting a node.
void triggerIconRebuild()
Triggers emission of the rebuildIconPreviews() signal.
static QgsStyle * defaultStyle(bool initialize=true)
Returns the default application-wide style.
Definition qgsstyle.cpp:164
static QString threadDescription(QThread *thread)
Returns a descriptive identifier for a thread.
Represents a transaction group.
bool addLayer(QgsVectorLayer *layer)
Add a layer to this transaction group.
static bool supportsTransaction(const QgsVectorLayer *layer)
Checks if the provider of a given layer supports transactions.
QString connectionString() const
Returns the connection string of the transaction.
Used for the collecting of strings from projects for translation and creation of ts files.
void registerTranslation(const QString &context, const QString &source)
Registers the source to be translated.
void setProject(QgsProject *project)
Sets the project being translated.
static Q_INVOKABLE QString toString(Qgis::DistanceUnit unit)
Returns a translated string representing a distance unit.
static Q_INVOKABLE Qgis::AreaUnit decodeAreaUnit(const QString &string, bool *ok=nullptr)
Decodes an areal unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to a string.
static Q_INVOKABLE Qgis::DistanceUnit decodeDistanceUnit(const QString &string, bool *ok=nullptr)
Decodes a distance unit from a string.
The edit buffer group manages a group of edit buffers.
Represents a vector layer which manages a vector based dataset.
Q_INVOKABLE bool startEditing()
Makes the layer editable.
bool loadAuxiliaryLayer(const QgsAuxiliaryStorage &storage, const QString &key=QString())
Loads the auxiliary layer for this vector layer.
QgsAuxiliaryLayer * auxiliaryLayer()
Returns the current auxiliary layer.
QStringList commitErrors() const
Returns a list containing any error messages generated when attempting to commit changes to the layer...
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
Q_INVOKABLE bool rollBack(bool deleteBuffer=true)
Stops a current editing operation and discards any uncommitted edits.
Q_INVOKABLE bool commitChanges(bool stopEditing=true)
Attempts to commit to the underlying data provider any buffered changes made since the last to call t...
QgsActionManager * actions()
Returns all layer actions defined on this layer.
QgsEditFormConfig editFormConfig
static bool isZipFile(const QString &filename)
Returns true if the file name is a zipped file ( i.e with a '.qgz' extension, false otherwise.
QList< QPair< QColor, QString > > QgsNamedColorList
List of colors paired with a friendly display name identifying the color.
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition qgis.h:8064
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:8407
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:8045
QString qgsFlagValueToKeys(const T &value, bool *returnOk=nullptr)
Returns the value for the given keys of a flag.
Definition qgis.h:8103
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:8132
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:8406
#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.