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