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