QGIS API Documentation 3.31.0-Master (9f23a2c1dc)
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 "qgsdatasourceuri.h"
22#include "qgslayertree.h"
23#include "qgslayertreeutils.h"
25#include "qgslogger.h"
26#include "qgsmessagelog.h"
27#include "qgsmaplayerfactory.h"
28#include "qgspluginlayer.h"
31#include "qgssnappingconfig.h"
32#include "qgspathresolver.h"
33#include "qgsprojectstorage.h"
35#include "qgsprojectversion.h"
36#include "qgsrasterlayer.h"
37#include "qgsreadwritecontext.h"
38#include "qgsrelationmanager.h"
42#include "qgslayerdefinition.h"
43#include "qgsunittypes.h"
44#include "qgstransaction.h"
45#include "qgstransactiongroup.h"
48#include "qgsmeshlayer.h"
49#include "qgslayoutmanager.h"
50#include "qgsbookmarkmanager.h"
51#include "qgsmaplayerstore.h"
52#include "qgsziputils.h"
53#include "qgsauxiliarystorage.h"
54#include "qgssymbollayerutils.h"
55#include "qgsapplication.h"
61#include "qgsvectortilelayer.h"
62#include "qgsruntimeprofiler.h"
63#include "qgsannotationlayer.h"
64#include "qgspointcloudlayer.h"
66#include "qgsgrouplayer.h"
67#include "qgsmapviewsmanager.h"
71#include "qgsthreadingutils.h"
72#include "qgssensormanager.h"
73#include "qgsproviderregistry.h"
76
77#include <algorithm>
78#include <QApplication>
79#include <QFileInfo>
80#include <QDomNode>
81#include <QObject>
82#include <QTextStream>
83#include <QTemporaryFile>
84#include <QDir>
85#include <QUrl>
86#include <QStandardPaths>
87#include <QUuid>
88#include <QRegularExpression>
89#include <QThreadPool>
90
91#ifdef _MSC_VER
92#include <sys/utime.h>
93#else
94#include <utime.h>
95#endif
96
97// canonical project instance
98QgsProject *QgsProject::sProject = nullptr;
99
108QStringList makeKeyTokens_( const QString &scope, const QString &key )
109{
110 QStringList keyTokens = QStringList( scope );
111#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
112 keyTokens += key.split( '/', QString::SkipEmptyParts );
113#else
114 keyTokens += key.split( '/', Qt::SkipEmptyParts );
115#endif
116
117 // be sure to include the canonical root node
118 keyTokens.push_front( QStringLiteral( "properties" ) );
119
120 //check validy of keys since an invalid xml name will will be dropped upon saving the xml file. If not valid, we print a message to the console.
121 for ( int i = 0; i < keyTokens.size(); ++i )
122 {
123 const QString keyToken = keyTokens.at( i );
124
125 //invalid chars in XML are found at http://www.w3.org/TR/REC-xml/#NT-NameChar
126 //note : it seems \x10000-\xEFFFF is valid, but it when added to the regexp, a lot of unwanted characters remain
127 const thread_local QRegularExpression sInvalidRegexp = QRegularExpression( QStringLiteral( "([^:A-Z_a-z\\x{C0}-\\x{D6}\\x{D8}-\\x{F6}\\x{F8}-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}\\-\\.0-9\\x{B7}\\x{0300}-\\x{036F}\\x{203F}-\\x{2040}]|^[^:A-Z_a-z\\x{C0}-\\x{D6}\\x{D8}-\\x{F6}\\x{F8}-\\x{2FF}\\x{370}-\\x{37D}\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}])" ) );
128 if ( keyToken.contains( sInvalidRegexp ) )
129 {
130 const QString errorString = QObject::tr( "Entry token invalid : '%1'. The token will not be saved to file." ).arg( keyToken );
131 QgsMessageLog::logMessage( errorString, QString(), Qgis::MessageLevel::Critical );
132 }
133 }
134
135 return keyTokens;
136}
137
138
139
149QgsProjectProperty *findKey_( const QString &scope,
150 const QString &key,
151 QgsProjectPropertyKey &rootProperty )
152{
153 QgsProjectPropertyKey *currentProperty = &rootProperty;
154 QgsProjectProperty *nextProperty; // link to next property down hierarchy
155
156 QStringList keySequence = makeKeyTokens_( scope, key );
157
158 while ( !keySequence.isEmpty() )
159 {
160 // if the current head of the sequence list matches the property name,
161 // then traverse down the property hierarchy
162 if ( keySequence.first() == currentProperty->name() )
163 {
164 // remove front key since we're traversing down a level
165 keySequence.pop_front();
166
167 if ( 1 == keySequence.count() )
168 {
169 // if we have only one key name left, then return the key found
170 return currentProperty->find( keySequence.front() );
171 }
172 else if ( keySequence.isEmpty() )
173 {
174 // if we're out of keys then the current property is the one we
175 // want; i.e., we're in the rate case of being at the top-most
176 // property node
177 return currentProperty;
178 }
179 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
180 {
181 if ( nextProperty->isKey() )
182 {
183 currentProperty = static_cast<QgsProjectPropertyKey *>( nextProperty );
184 }
185 else if ( nextProperty->isValue() && 1 == keySequence.count() )
186 {
187 // it may be that this may be one of several property value
188 // nodes keyed by QDict string; if this is the last remaining
189 // key token and the next property is a value node, then
190 // that's the situation, so return the currentProperty
191 return currentProperty;
192 }
193 else
194 {
195 // QgsProjectPropertyValue not Key, so return null
196 return nullptr;
197 }
198 }
199 else
200 {
201 // if the next key down isn't found
202 // then the overall key sequence doesn't exist
203 return nullptr;
204 }
205 }
206 else
207 {
208 return nullptr;
209 }
210 }
211
212 return nullptr;
213}
214
215
216
226QgsProjectProperty *addKey_( const QString &scope,
227 const QString &key,
228 QgsProjectPropertyKey *rootProperty,
229 const QVariant &value,
230 bool &propertiesModified )
231{
232 QStringList keySequence = makeKeyTokens_( scope, key );
233
234 // cursor through property key/value hierarchy
235 QgsProjectPropertyKey *currentProperty = rootProperty;
236 QgsProjectProperty *nextProperty; // link to next property down hierarchy
237 QgsProjectPropertyKey *newPropertyKey = nullptr;
238
239 propertiesModified = false;
240 while ( ! keySequence.isEmpty() )
241 {
242 // if the current head of the sequence list matches the property name,
243 // then traverse down the property hierarchy
244 if ( keySequence.first() == currentProperty->name() )
245 {
246 // remove front key since we're traversing down a level
247 keySequence.pop_front();
248
249 // if key sequence has one last element, then we use that as the
250 // name to store the value
251 if ( 1 == keySequence.count() )
252 {
253 QgsProjectProperty *property = currentProperty->find( keySequence.front() );
254 if ( !property || property->value() != value )
255 {
256 currentProperty->setValue( keySequence.front(), value );
257 propertiesModified = true;
258 }
259
260 return currentProperty;
261 }
262 // we're at the top element if popping the keySequence element
263 // will leave it empty; in that case, just add the key
264 else if ( keySequence.isEmpty() )
265 {
266 if ( currentProperty->value() != value )
267 {
268 currentProperty->setValue( value );
269 propertiesModified = true;
270 }
271
272 return currentProperty;
273 }
274 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
275 {
276 currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
277
278 if ( currentProperty )
279 {
280 continue;
281 }
282 else // QgsProjectPropertyValue not Key, so return null
283 {
284 return nullptr;
285 }
286 }
287 else // the next subkey doesn't exist, so add it
288 {
289 if ( ( newPropertyKey = currentProperty->addKey( keySequence.first() ) ) )
290 {
291 currentProperty = newPropertyKey;
292 }
293 continue;
294 }
295 }
296 else
297 {
298 return nullptr;
299 }
300 }
301
302 return nullptr;
303}
304
312void removeKey_( const QString &scope,
313 const QString &key,
314 QgsProjectPropertyKey &rootProperty )
315{
316 QgsProjectPropertyKey *currentProperty = &rootProperty;
317
318 QgsProjectProperty *nextProperty = nullptr; // link to next property down hierarchy
319 QgsProjectPropertyKey *previousQgsPropertyKey = nullptr; // link to previous property up hierarchy
320
321 QStringList keySequence = makeKeyTokens_( scope, key );
322
323 while ( ! keySequence.isEmpty() )
324 {
325 // if the current head of the sequence list matches the property name,
326 // then traverse down the property hierarchy
327 if ( keySequence.first() == currentProperty->name() )
328 {
329 // remove front key since we're traversing down a level
330 keySequence.pop_front();
331
332 // if we have only one key name left, then try to remove the key
333 // with that name
334 if ( 1 == keySequence.count() )
335 {
336 currentProperty->removeKey( keySequence.front() );
337 }
338 // if we're out of keys then the current property is the one we
339 // want to remove, but we can't delete it directly; we need to
340 // delete it from the parent property key container
341 else if ( keySequence.isEmpty() )
342 {
343 previousQgsPropertyKey->removeKey( currentProperty->name() );
344 }
345 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
346 {
347 previousQgsPropertyKey = currentProperty;
348 currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
349
350 if ( currentProperty )
351 {
352 continue;
353 }
354 else // QgsProjectPropertyValue not Key, so return null
355 {
356 return;
357 }
358 }
359 else // if the next key down isn't found
360 {
361 // then the overall key sequence doesn't exist
362 return;
363 }
364 }
365 else
366 {
367 return;
368 }
369 }
370}
371
372QgsProject::QgsProject( QObject *parent, Qgis::ProjectCapabilities capabilities )
373 : QObject( parent )
374 , mCapabilities( capabilities )
375 , mLayerStore( new QgsMapLayerStore( this ) )
376 , mBadLayerHandler( new QgsProjectBadLayerHandler() )
377 , mSnappingConfig( this )
378 , mRelationManager( new QgsRelationManager( this ) )
379 , mAnnotationManager( new QgsAnnotationManager( this ) )
380 , mLayoutManager( new QgsLayoutManager( this ) )
381 , m3DViewsManager( new QgsMapViewsManager( this ) )
382 , mBookmarkManager( QgsBookmarkManager::createProjectBasedManager( this ) )
383 , mSensorManager( new QgsSensorManager( this ) )
384 , mViewSettings( new QgsProjectViewSettings( this ) )
385 , mStyleSettings( new QgsProjectStyleSettings( this ) )
386 , mTimeSettings( new QgsProjectTimeSettings( this ) )
387 , mElevationProperties( new QgsProjectElevationProperties( this ) )
388 , mDisplaySettings( new QgsProjectDisplaySettings( this ) )
389 , mGpsSettings( new QgsProjectGpsSettings( this ) )
390 , mRootGroup( new QgsLayerTree )
391 , mLabelingEngineSettings( new QgsLabelingEngineSettings )
392 , mArchive( new QgsArchive() )
393 , mAuxiliaryStorage( new QgsAuxiliaryStorage() )
394{
395 mProperties.setName( QStringLiteral( "properties" ) );
396
397 mMainAnnotationLayer = new QgsAnnotationLayer( QObject::tr( "Annotations" ), QgsAnnotationLayer::LayerOptions( mTransformContext ) );
398 mMainAnnotationLayer->setParent( this );
399
400 clear();
401
402 // bind the layer tree to the map layer registry.
403 // whenever layers are added to or removed from the registry,
404 // layer tree will be updated
405 mLayerTreeRegistryBridge = new QgsLayerTreeRegistryBridge( mRootGroup, this, this );
406 connect( this, &QgsProject::layersAdded, this, &QgsProject::onMapLayersAdded );
407 connect( this, &QgsProject::layersRemoved, this, [ = ] { cleanTransactionGroups(); } );
408 connect( this, qOverload< const QList<QgsMapLayer *> & >( &QgsProject::layersWillBeRemoved ), this, &QgsProject::onMapLayersRemoved );
409
410 // proxy map layer store signals to this
411 connect( mLayerStore.get(), qOverload<const QStringList &>( &QgsMapLayerStore::layersWillBeRemoved ),
412 this, [ = ]( const QStringList & layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
413 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersWillBeRemoved ),
414 this, [ = ]( const QList<QgsMapLayer *> &layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
415 connect( mLayerStore.get(), qOverload< const QString & >( &QgsMapLayerStore::layerWillBeRemoved ),
416 this, [ = ]( const QString & layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
417 connect( mLayerStore.get(), qOverload< QgsMapLayer * >( &QgsMapLayerStore::layerWillBeRemoved ),
418 this, [ = ]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
419 connect( mLayerStore.get(), qOverload<const QStringList & >( &QgsMapLayerStore::layersRemoved ), this,
420 [ = ]( const QStringList & layers ) { mProjectScope.reset(); emit layersRemoved( layers ); } );
421 connect( mLayerStore.get(), &QgsMapLayerStore::layerRemoved, this,
422 [ = ]( const QString & layer ) { mProjectScope.reset(); emit layerRemoved( layer ); } );
423 connect( mLayerStore.get(), &QgsMapLayerStore::allLayersRemoved, this,
424 [ = ]() { mProjectScope.reset(); emit removeAll(); } );
425 connect( mLayerStore.get(), &QgsMapLayerStore::layersAdded, this,
426 [ = ]( const QList< QgsMapLayer * > &layers ) { mProjectScope.reset(); emit layersAdded( layers ); } );
427 connect( mLayerStore.get(), &QgsMapLayerStore::layerWasAdded, this,
428 [ = ]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWasAdded( layer ); } );
429
431 {
433 }
434
435 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersWillBeRemoved ), this,
436 [ = ]( const QList<QgsMapLayer *> &layers )
437 {
438 for ( const auto &layer : layers )
439 {
440 disconnect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager, &QgsRelationManager::updateRelationsStatus );
441 }
442 }
443 );
444 connect( mLayerStore.get(), qOverload< const QList<QgsMapLayer *> & >( &QgsMapLayerStore::layersAdded ), this,
445 [ = ]( const QList<QgsMapLayer *> &layers )
446 {
447 for ( const auto &layer : layers )
448 {
449 connect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager, &QgsRelationManager::updateRelationsStatus );
450 }
451 }
452 );
453
457
458 mStyleSettings->combinedStyleModel()->addDefaultStyle();
459}
460
461
463{
464 mIsBeingDeleted = true;
465
466 clear();
467 delete mBadLayerHandler;
468 delete mRelationManager;
469 delete mLayerTreeRegistryBridge;
470 delete mRootGroup;
471 if ( this == sProject )
472 {
473 sProject = nullptr;
474 }
475}
476
478{
479 sProject = project;
480}
481
482
484{
485 if ( !sProject )
486 {
487 sProject = new QgsProject;
488
490 }
491 return sProject;
492}
493
494void QgsProject::setTitle( const QString &title )
495{
497
498 if ( title == mMetadata.title() )
499 return;
500
501 mMetadata.setTitle( title );
502 mProjectScope.reset();
503 emit metadataChanged();
504
505 setDirty( true );
506}
507
508QString QgsProject::title() const
509{
510 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
512
513 return mMetadata.title();
514}
515
516void QgsProject::setFlags( Qgis::ProjectFlags flags )
517{
519
520 const bool oldEvaluateDefaultValues = mFlags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide;
521 const bool newEvaluateDefaultValues = flags & Qgis::ProjectFlag::EvaluateDefaultValuesOnProviderSide;
522 if ( oldEvaluateDefaultValues != newEvaluateDefaultValues )
523 {
524 const QMap<QString, QgsMapLayer *> layers = mapLayers();
525 for ( auto layerIt = layers.constBegin(); layerIt != layers.constEnd(); ++layerIt )
526 {
527 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() ) )
528 {
529 vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, newEvaluateDefaultValues );
530 }
531 }
532 }
533
534 const bool oldTrustLayerMetadata = mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics;
535 const bool newTrustLayerMetadata = flags & Qgis::ProjectFlag::TrustStoredLayerStatistics;
536 if ( oldTrustLayerMetadata != newTrustLayerMetadata )
537 {
538 const QMap<QString, QgsMapLayer *> layers = mapLayers();
539 for ( auto layerIt = layers.constBegin(); layerIt != layers.constEnd(); ++layerIt )
540 {
541 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() ) )
542 {
543 vl->setReadExtentFromXml( newTrustLayerMetadata );
544 }
545 }
546 }
547
548 if ( mFlags != flags )
549 {
550 mFlags = flags;
551 setDirty( true );
552 }
553}
554
555void QgsProject::setFlag( Qgis::ProjectFlag flag, bool enabled )
556{
558
559 Qgis::ProjectFlags newFlags = mFlags;
560 if ( enabled )
561 newFlags |= flag;
562 else
563 newFlags &= ~( static_cast< int >( flag ) );
564 setFlags( newFlags );
565}
566
567QString QgsProject::saveUser() const
568{
570
571 return mSaveUser;
572}
573
575{
577
578 return mSaveUserFull;
579}
580
582{
584
585 return mSaveDateTime;
586}
587
589{
591
592 return mSaveVersion;
593}
594
596{
598
599 return mDirty;
600}
601
602void QgsProject::setDirty( const bool dirty )
603{
605
606 if ( dirty && mDirtyBlockCount > 0 )
607 return;
608
609 if ( dirty )
610 emit dirtySet();
611
612 if ( mDirty == dirty )
613 return;
614
615 mDirty = dirty;
616 emit isDirtyChanged( mDirty );
617}
618
619void QgsProject::setPresetHomePath( const QString &path )
620{
622
623 if ( path == mHomePath )
624 return;
625
626 mHomePath = path;
627 mCachedHomePath.clear();
628 mProjectScope.reset();
629
630 emit homePathChanged();
631
632 setDirty( true );
633}
634
635void QgsProject::registerTranslatableContainers( QgsTranslationContext *translationContext, QgsAttributeEditorContainer *parent, const QString &layerId )
636{
638
639 const QList<QgsAttributeEditorElement *> elements = parent->children();
640
641 for ( QgsAttributeEditorElement *element : elements )
642 {
643 if ( element->type() == Qgis::AttributeEditorType::Container )
644 {
645 QgsAttributeEditorContainer *container = dynamic_cast<QgsAttributeEditorContainer *>( element );
646
647 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:formcontainers" ).arg( layerId ), container->name() );
648
649 if ( !container->children().empty() )
650 registerTranslatableContainers( translationContext, container, layerId );
651 }
652 }
653}
654
656{
658
659 //register layers
660 const QList<QgsLayerTreeLayer *> layers = mRootGroup->findLayers();
661
662 for ( const QgsLayerTreeLayer *layer : layers )
663 {
664 translationContext->registerTranslation( QStringLiteral( "project:layers:%1" ).arg( layer->layerId() ), layer->name() );
665
666 QgsMapLayer *mapLayer = layer->layer();
667 if ( mapLayer && mapLayer->type() == Qgis::LayerType::Vector )
668 {
669 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer );
670
671 //register aliases and fields
672 const QgsFields fields = vlayer->fields();
673 for ( const QgsField &field : fields )
674 {
675 QString fieldName;
676 if ( field.alias().isEmpty() )
677 fieldName = field.name();
678 else
679 fieldName = field.alias();
680
681 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:fieldaliases" ).arg( vlayer->id() ), fieldName );
682
683 if ( field.editorWidgetSetup().type() == QLatin1String( "ValueRelation" ) )
684 {
685 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:fields:%2:valuerelationvalue" ).arg( vlayer->id(), field.name() ), field.editorWidgetSetup().config().value( QStringLiteral( "Value" ) ).toString() );
686 }
687 }
688
689 //register formcontainers
690 registerTranslatableContainers( translationContext, vlayer->editFormConfig().invisibleRootContainer(), vlayer->id() );
691
692 }
693 }
694
695 //register layergroups
696 const QList<QgsLayerTreeGroup *> groupLayers = mRootGroup->findGroups();
697 for ( const QgsLayerTreeGroup *groupLayer : groupLayers )
698 {
699 translationContext->registerTranslation( QStringLiteral( "project:layergroups" ), groupLayer->name() );
700 }
701
702 //register relations
703 const QList<QgsRelation> &relations = mRelationManager->relations().values();
704 for ( const QgsRelation &relation : relations )
705 {
706 translationContext->registerTranslation( QStringLiteral( "project:relations" ), relation.name() );
707 }
708}
709
711{
713
714 mDataDefinedServerProperties = properties;
715}
716
718{
720
721 return mDataDefinedServerProperties;
722}
723
725{
727
728 switch ( mTransactionMode )
729 {
732 {
733 if ( ! vectorLayer )
734 return false;
735 return vectorLayer->startEditing();
736 }
737
739 return mEditBufferGroup.startEditing();
740 }
741
742 return false;
743}
744
745bool QgsProject::commitChanges( QStringList &commitErrors, bool stopEditing, QgsVectorLayer *vectorLayer )
746{
748
749 switch ( mTransactionMode )
750 {
753 {
754 if ( ! vectorLayer )
755 {
756 commitErrors.append( tr( "Trying to commit changes without a layer specified. This only works if the transaction mode is buffered" ) );
757 return false;
758 }
759 bool success = vectorLayer->commitChanges( stopEditing );
760 commitErrors = vectorLayer->commitErrors();
761 return success;
762 }
763
765 return mEditBufferGroup.commitChanges( commitErrors, stopEditing );
766 }
767
768 return false;
769}
770
771bool QgsProject::rollBack( QStringList &rollbackErrors, bool stopEditing, QgsVectorLayer *vectorLayer )
772{
774
775 switch ( mTransactionMode )
776 {
779 {
780 if ( ! vectorLayer )
781 {
782 rollbackErrors.append( tr( "Trying to roll back changes without a layer specified. This only works if the transaction mode is buffered" ) );
783 return false;
784 }
785 bool success = vectorLayer->rollBack( stopEditing );
786 rollbackErrors = vectorLayer->commitErrors();
787 return success;
788 }
789
791 return mEditBufferGroup.rollBack( rollbackErrors, stopEditing );
792 }
793
794 return false;
795}
796
797void QgsProject::setFileName( const QString &name )
798{
800
801 if ( name == mFile.fileName() )
802 return;
803
804 const QString oldHomePath = homePath();
805
806 mFile.setFileName( name );
807 mCachedHomePath.clear();
808 mProjectScope.reset();
809
810 emit fileNameChanged();
811
812 const QString newHomePath = homePath();
813 if ( newHomePath != oldHomePath )
814 emit homePathChanged();
815
816 setDirty( true );
817}
818
819QString QgsProject::fileName() const
820{
821 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
823
824 return mFile.fileName();
825}
826
827void QgsProject::setOriginalPath( const QString &path )
828{
830
831 mOriginalPath = path;
832}
833
835{
837
838 return mOriginalPath;
839}
840
841QFileInfo QgsProject::fileInfo() const
842{
844
845 return QFileInfo( mFile );
846}
847
849{
850 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
852
854}
855
857{
859
860 if ( QgsProjectStorage *storage = projectStorage() )
861 {
863 storage->readProjectStorageMetadata( mFile.fileName(), metadata );
864 return metadata.lastModified;
865 }
866 else
867 {
868 return QFileInfo( mFile.fileName() ).lastModified();
869 }
870}
871
873{
875
876 if ( projectStorage() )
877 return QString();
878
879 if ( mFile.fileName().isEmpty() )
880 return QString(); // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
881
882 return QFileInfo( mFile.fileName() ).absolutePath();
883}
884
886{
887 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
889
890 if ( projectStorage() )
891 return QString();
892
893 if ( mFile.fileName().isEmpty() )
894 return QString(); // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
895
896 return QFileInfo( mFile.fileName() ).absoluteFilePath();
897}
898
899QString QgsProject::baseName() const
900{
901 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
903
904 if ( QgsProjectStorage *storage = projectStorage() )
905 {
907 storage->readProjectStorageMetadata( mFile.fileName(), metadata );
908 return metadata.name;
909 }
910 else
911 {
912 return QFileInfo( mFile.fileName() ).completeBaseName();
913 }
914}
915
917{
919
920 const bool absolutePaths = readBoolEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), false );
922}
923
925{
927
928 switch ( type )
929 {
931 writeEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), true );
932 break;
934 writeEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), false );
935 break;
936 }
937}
938
940{
941 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
943
944 return mCrs;
945}
946
947void QgsProject::setCrs( const QgsCoordinateReferenceSystem &crs, bool adjustEllipsoid )
948{
950
951 if ( crs != mCrs )
952 {
953 mCrs = crs;
954 writeEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), crs.isValid() ? 1 : 0 );
955 mProjectScope.reset();
956
957 // if annotation layer doesn't have a crs (i.e. in a newly created project), it should
958 // initially inherit the project CRS
959 if ( !mMainAnnotationLayer->crs().isValid() || mMainAnnotationLayer->isEmpty() )
960 mMainAnnotationLayer->setCrs( crs );
961
962 setDirty( true );
963 emit crsChanged();
964 }
965
966 if ( adjustEllipsoid )
968}
969
971{
972 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
974
975 if ( !crs().isValid() )
976 return geoNone();
977
978 return readEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ), geoNone() );
979}
980
981void QgsProject::setEllipsoid( const QString &ellipsoid )
982{
984
985 if ( ellipsoid == readEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ) ) )
986 return;
987
988 mProjectScope.reset();
989 writeEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ), ellipsoid );
991}
992
994{
995 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
997
998 return mTransformContext;
999}
1000
1002{
1004
1005 if ( context == mTransformContext )
1006 return;
1007
1008 mTransformContext = context;
1009 mProjectScope.reset();
1010
1011 mMainAnnotationLayer->setTransformContext( context );
1012 for ( auto &layer : mLayerStore.get()->mapLayers() )
1013 {
1014 layer->setTransformContext( context );
1015 }
1017}
1018
1020{
1022
1023 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
1024
1025 mProjectScope.reset();
1026 mFile.setFileName( QString() );
1027 mProperties.clearKeys();
1028 mSaveUser.clear();
1029 mSaveUserFull.clear();
1030 mSaveDateTime = QDateTime();
1031 mSaveVersion = QgsProjectVersion();
1032 mHomePath.clear();
1033 mCachedHomePath.clear();
1034 mTransactionMode = Qgis::TransactionMode::Disabled;
1035 mFlags = Qgis::ProjectFlags();
1036 mDirty = false;
1037 mCustomVariables.clear();
1039 mMetadata = QgsProjectMetadata();
1040 mElevationShadingRenderer = QgsElevationShadingRenderer();
1041 if ( !mSettings.value( QStringLiteral( "projects/anonymize_new_projects" ), false, QgsSettings::Core ).toBool() )
1042 {
1043 mMetadata.setCreationDateTime( QDateTime::currentDateTime() );
1045 }
1046 emit metadataChanged();
1047
1049 context.readSettings();
1050 setTransformContext( context );
1051
1052 //fallback to QGIS default measurement unit
1053 bool ok = false;
1054 const Qgis::DistanceUnit distanceUnit = QgsUnitTypes::decodeDistanceUnit( mSettings.value( QStringLiteral( "/qgis/measure/displayunits" ) ).toString(), &ok );
1055 setDistanceUnits( ok ? distanceUnit : Qgis::DistanceUnit::Meters );
1056 ok = false;
1057 const Qgis::AreaUnit areaUnits = QgsUnitTypes::decodeAreaUnit( mSettings.value( QStringLiteral( "/qgis/measure/areaunits" ) ).toString(), &ok );
1058 setAreaUnits( ok ? areaUnits : Qgis::AreaUnit::SquareMeters );
1059
1060 mEmbeddedLayers.clear();
1061 mRelationManager->clear();
1062 mAnnotationManager->clear();
1063 mLayoutManager->clear();
1064 m3DViewsManager->clear();
1065 mBookmarkManager->clear();
1066 mSensorManager->clear();
1067 mViewSettings->reset();
1068 mTimeSettings->reset();
1069 mElevationProperties->reset();
1070 mDisplaySettings->reset();
1071 mGpsSettings->reset();
1072 mSnappingConfig.reset();
1073 mAvoidIntersectionsMode = Qgis::AvoidIntersectionsMode::AllowIntersections;
1076
1077 mMapThemeCollection.reset( new QgsMapThemeCollection( this ) );
1079
1080 mLabelingEngineSettings->clear();
1081
1082 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage() );
1083 mArchive.reset( new QgsArchive() );
1084
1085 // must happen after archive reset!
1086 mStyleSettings->reset();
1087
1089
1090 if ( !mIsBeingDeleted )
1091 {
1092 // possibly other signals should also not be thrown on destruction -- e.g. labelEngineSettingsChanged, etc.
1093 emit projectColorsChanged();
1094 }
1095
1096 // reset some default project properties
1097 // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
1098 writeEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/Automatic" ), true );
1099 writeEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DecimalPlaces" ), 2 );
1100
1101 const bool defaultRelativePaths = mSettings.value( QStringLiteral( "/qgis/defaultProjectPathsRelative" ), true ).toBool();
1103
1104 int red = mSettings.value( QStringLiteral( "qgis/default_canvas_color_red" ), 255 ).toInt();
1105 int green = mSettings.value( QStringLiteral( "qgis/default_canvas_color_green" ), 255 ).toInt();
1106 int blue = mSettings.value( QStringLiteral( "qgis/default_canvas_color_blue" ), 255 ).toInt();
1107 setBackgroundColor( QColor( red, green, blue ) );
1108
1109 red = mSettings.value( QStringLiteral( "qgis/default_selection_color_red" ), 255 ).toInt();
1110 green = mSettings.value( QStringLiteral( "qgis/default_selection_color_green" ), 255 ).toInt();
1111 blue = mSettings.value( QStringLiteral( "qgis/default_selection_color_blue" ), 0 ).toInt();
1112 const int alpha = mSettings.value( QStringLiteral( "qgis/default_selection_color_alpha" ), 255 ).toInt();
1113 setSelectionColor( QColor( red, green, blue, alpha ) );
1114
1115 mSnappingConfig.clearIndividualLayerSettings();
1116
1118 mRootGroup->clear();
1119 if ( mMainAnnotationLayer )
1120 mMainAnnotationLayer->reset();
1121
1122 snapSingleBlocker.release();
1123
1124 if ( !mBlockSnappingUpdates )
1125 emit snappingConfigChanged( mSnappingConfig );
1126
1127 setDirty( false );
1128 emit homePathChanged();
1129 emit cleared();
1130}
1131
1132// basically a debugging tool to dump property list values
1133void dump_( const QgsProjectPropertyKey &topQgsPropertyKey )
1134{
1135 QgsDebugMsgLevel( QStringLiteral( "current properties:" ), 3 );
1136 topQgsPropertyKey.dump();
1137}
1138
1167void _getProperties( const QDomDocument &doc, QgsProjectPropertyKey &project_properties )
1168{
1169 const QDomElement propertiesElem = doc.documentElement().firstChildElement( QStringLiteral( "properties" ) );
1170
1171 if ( propertiesElem.isNull() ) // no properties found, so we're done
1172 {
1173 return;
1174 }
1175
1176 const QDomNodeList scopes = propertiesElem.childNodes();
1177
1178 if ( propertiesElem.firstChild().isNull() )
1179 {
1180 QgsDebugError( QStringLiteral( "empty ``properties'' XML tag ... bailing" ) );
1181 return;
1182 }
1183
1184 if ( ! project_properties.readXml( propertiesElem ) )
1185 {
1186 QgsDebugError( QStringLiteral( "Project_properties.readXml() failed" ) );
1187 }
1188}
1189
1196QgsPropertyCollection getDataDefinedServerProperties( const QDomDocument &doc, const QgsPropertiesDefinition &dataDefinedServerPropertyDefinitions )
1197{
1198 QgsPropertyCollection ddServerProperties;
1199 // Read data defined server properties
1200 const QDomElement ddElem = doc.documentElement().firstChildElement( QStringLiteral( "dataDefinedServerProperties" ) );
1201 if ( !ddElem.isNull() )
1202 {
1203 if ( !ddServerProperties.readXml( ddElem, dataDefinedServerPropertyDefinitions ) )
1204 {
1205 QgsDebugError( QStringLiteral( "dataDefinedServerProperties.readXml() failed" ) );
1206 }
1207 }
1208 return ddServerProperties;
1209}
1210
1215static void _getTitle( const QDomDocument &doc, QString &title )
1216{
1217 const QDomElement titleNode = doc.documentElement().firstChildElement( QStringLiteral( "title" ) );
1218
1219 title.clear(); // by default the title will be empty
1220
1221 if ( titleNode.isNull() )
1222 {
1223 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1224 return;
1225 }
1226
1227 if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text
1228 {
1229 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1230 return;
1231 }
1232
1233 const QDomNode titleTextNode = titleNode.firstChild(); // should only have one child
1234
1235 if ( !titleTextNode.isText() )
1236 {
1237 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1238 return;
1239 }
1240
1241 const QDomText titleText = titleTextNode.toText();
1242
1243 title = titleText.data();
1244
1245}
1246
1247static void readProjectFileMetadata( const QDomDocument &doc, QString &lastUser, QString &lastUserFull, QDateTime &lastSaveDateTime )
1248{
1249 const QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
1250
1251 if ( !nl.count() )
1252 {
1253 QgsDebugError( QStringLiteral( "unable to find qgis element" ) );
1254 return;
1255 }
1256
1257 const QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1258
1259 const QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1260 lastUser = qgisElement.attribute( QStringLiteral( "saveUser" ), QString() );
1261 lastUserFull = qgisElement.attribute( QStringLiteral( "saveUserFull" ), QString() );
1262 lastSaveDateTime = QDateTime::fromString( qgisElement.attribute( QStringLiteral( "saveDateTime" ), QString() ), Qt::ISODate );
1263}
1264
1265QgsProjectVersion getVersion( const QDomDocument &doc )
1266{
1267 const QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
1268
1269 if ( !nl.count() )
1270 {
1271 QgsDebugError( QStringLiteral( " unable to find qgis element in project file" ) );
1272 return QgsProjectVersion( 0, 0, 0, QString() );
1273 }
1274
1275 const QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1276
1277 const QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1278 QgsProjectVersion projectVersion( qgisElement.attribute( QStringLiteral( "version" ) ) );
1279 return projectVersion;
1280}
1281
1283{
1285
1286 return mSnappingConfig;
1287}
1288
1290{
1292
1293 if ( mSnappingConfig == snappingConfig )
1294 return;
1295
1296 mSnappingConfig = snappingConfig;
1297 setDirty( true );
1298 emit snappingConfigChanged( mSnappingConfig );
1299}
1300
1302{
1304
1305 if ( mAvoidIntersectionsMode == mode )
1306 return;
1307
1308 mAvoidIntersectionsMode = mode;
1310}
1311
1312static QgsMapLayer::ReadFlags projectFlagsToLayerReadFlags( Qgis::ProjectReadFlags projectReadFlags, Qgis::ProjectFlags projectFlags )
1313{
1314 QgsMapLayer::ReadFlags layerFlags = QgsMapLayer::ReadFlags();
1315 if ( projectReadFlags & Qgis::ProjectReadFlag::DontResolveLayers )
1317 // Propagate trust layer metadata flag
1318 if ( ( projectFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics ) || ( projectReadFlags & Qgis::ProjectReadFlag::TrustLayerMetadata ) )
1320 // Propagate open layers in read-only mode
1321 if ( ( projectReadFlags & Qgis::ProjectReadFlag::ForceReadOnlyLayers ) )
1322 layerFlags |= QgsMapLayer::FlagForceReadOnly;
1323
1324 return layerFlags;
1325}
1326
1327void QgsProject::preloadProviders( const QVector<QDomNode> &parallelLayerNodes,
1328 const QgsReadWriteContext &context,
1329 QMap<QString, QgsDataProvider *> &loadedProviders,
1330 QgsMapLayer::ReadFlags layerReadFlags,
1331 int totalProviderCount )
1332{
1333
1334 QVector<QgsRunnableProviderCreator *> runnables;
1335 QThreadPool threadPool;
1336
1337 threadPool.setMaxThreadCount( QgsSettingsRegistryCore::settingsLayerParallelLoadingMaxCount->value() );
1338
1339 int i = 0;
1340 int validLayerCount = 0;
1341 QEventLoop loop;
1342 for ( const QDomNode &node : parallelLayerNodes )
1343 {
1344 const QDomElement layerElement = node.toElement();
1345 QString layerId = layerElement.namedItem( QStringLiteral( "id" ) ).toElement().text();
1346 QString provider = layerElement.namedItem( QStringLiteral( "provider" ) ).toElement().text();
1347 QString dataSource = layerElement.namedItem( QStringLiteral( "datasource" ) ).toElement().text();
1348
1349 dataSource = QgsProviderRegistry::instance()->relativeToAbsoluteUri( provider, dataSource, context );
1350
1352 QgsDataProvider::ReadFlags flags = QgsMapLayer::providerReadFlags( node, layerReadFlags );
1353
1354 // Requesting credential from worker thread could lead to deadlocks because the main thread is waiting for worker thread to fininsh
1356
1357 QgsRunnableProviderCreator *run = new QgsRunnableProviderCreator( layerId, provider, dataSource, options, flags );
1358 runnables.append( run );
1359 QObject::connect( run, &QgsRunnableProviderCreator::providerCreated, this, [&]( bool isValid )
1360 {
1361 i++;
1362 if ( isValid )
1363 {
1364 validLayerCount++;
1365 emit layerLoaded( validLayerCount, totalProviderCount );
1366 }
1367 if ( i == parallelLayerNodes.count() )
1368 loop.quit();
1369 } );
1370 threadPool.start( run );
1371 }
1372 if ( !parallelLayerNodes.isEmpty() )
1373 loop.exec();
1374
1375 for ( QgsRunnableProviderCreator *run : std::as_const( runnables ) )
1376 {
1377 // let's include all layers if the provider is valid (if not, an attempt will be done in the main thread later)
1378 std::unique_ptr<QgsDataProvider> provider( run->dataProvider() );
1379 if ( provider->isValid() )
1380 loadedProviders.insert( run->layerId(), provider.release() );
1381 }
1382
1383 qDeleteAll( runnables );
1384}
1385
1386bool QgsProject::_getMapLayers( const QDomDocument &doc, QList<QDomNode> &brokenNodes, Qgis::ProjectReadFlags flags )
1387{
1389
1390 // Layer order is set by the restoring the legend settings from project file.
1391 // This is done on the 'readProject( ... )' signal
1392
1393 QDomElement layerElement = doc.documentElement().firstChildElement( QStringLiteral( "projectlayers" ) ).firstChildElement( QStringLiteral( "maplayer" ) );
1394
1395 // process the map layer nodes
1396
1397 if ( layerElement.isNull() ) // if we have no layers to process, bail
1398 {
1399 return true; // Decided to return "true" since it's
1400 // possible for there to be a project with no
1401 // layers; but also, more imporantly, this
1402 // would cause the tests/qgsproject to fail
1403 // since the test suite doesn't currently
1404 // support test layers
1405 }
1406
1407 bool returnStatus = true;
1408 int numLayers = 0;
1409
1410 while ( ! layerElement.isNull() )
1411 {
1412 numLayers++;
1413 layerElement = layerElement.nextSiblingElement( QStringLiteral( "maplayer" ) );
1414 }
1415
1416 // order layers based on their dependencies
1417 QgsScopedRuntimeProfile profile( tr( "Sorting layers" ), QStringLiteral( "projectload" ) );
1418 const QgsLayerDefinition::DependencySorter depSorter( doc );
1419 if ( depSorter.hasCycle() )
1420 return false;
1421
1422 // Missing a dependency? We still load all the layers, otherwise the project is completely broken!
1423 if ( depSorter.hasMissingDependency() )
1424 returnStatus = false;
1425
1426 emit layerLoaded( 0, numLayers );
1427
1428 const QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
1429 const int totalLayerCount = sortedLayerNodes.count();
1430
1431 QVector<QDomNode> parallelLoading;
1432 QMap<QString, QgsDataProvider *> loadedProviders;
1433
1435 {
1436 profile.switchTask( tr( "Load providers in parallel" ) );
1437 for ( const QDomNode &node : sortedLayerNodes )
1438 {
1439 const QDomElement element = node.toElement();
1440 if ( element.attribute( QStringLiteral( "embedded" ) ) != QLatin1String( "1" ) )
1441 {
1442 const QString layerId = node.namedItem( QStringLiteral( "id" ) ).toElement().text();
1443 if ( !depSorter.isLayerDependent( layerId ) )
1444 {
1445 const QDomNode mnl = element.namedItem( QStringLiteral( "provider" ) );
1446 const QDomElement mne = mnl.toElement();
1447 const QString provider = mne.text();
1449 if ( meta && meta->providerCapabilities().testFlag( QgsProviderMetadata::ParallelCreateProvider ) )
1450 {
1451 parallelLoading.append( node );
1452 continue;
1453 }
1454 }
1455 }
1456 }
1457
1458 QgsReadWriteContext context;
1459 context.setPathResolver( pathResolver() );
1460 if ( !parallelLoading.isEmpty() )
1461 preloadProviders( parallelLoading, context, loadedProviders, projectFlagsToLayerReadFlags( flags, mFlags ), sortedLayerNodes.count() );
1462 }
1463
1464 int i = loadedProviders.count();
1465 for ( const QDomNode &node : std::as_const( sortedLayerNodes ) )
1466 {
1467 const QDomElement element = node.toElement();
1468 const QString name = translate( QStringLiteral( "project:layers:%1" ).arg( node.namedItem( QStringLiteral( "id" ) ).toElement().text() ), node.namedItem( QStringLiteral( "layername" ) ).toElement().text() );
1469 if ( !name.isNull() )
1470 emit loadingLayer( tr( "Loading layer %1" ).arg( name ) );
1471
1472 profile.switchTask( name );
1473 if ( element.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
1474 {
1475 createEmbeddedLayer( element.attribute( QStringLiteral( "id" ) ), readPath( element.attribute( QStringLiteral( "project" ) ) ), brokenNodes, true, flags );
1476 }
1477 else
1478 {
1479 QgsReadWriteContext context;
1480 context.setPathResolver( pathResolver() );
1481 context.setProjectTranslator( this );
1483 QString layerId = element.namedItem( QStringLiteral( "id" ) ).toElement().text();
1484
1485 if ( !addLayer( element, brokenNodes, context, flags, loadedProviders.take( layerId ) ) )
1486 {
1487 returnStatus = false;
1488 }
1489 const auto messages = context.takeMessages();
1490 if ( !messages.isEmpty() )
1491 {
1492 emit loadingLayerMessageReceived( tr( "Loading layer %1" ).arg( name ), messages );
1493 }
1494 }
1495 emit layerLoaded( i + 1, totalLayerCount );
1496 i++;
1497 }
1498
1499 return returnStatus;
1500}
1501
1502bool QgsProject::addLayer( const QDomElement &layerElem,
1503 QList<QDomNode> &brokenNodes,
1504 QgsReadWriteContext &context,
1505 Qgis::ProjectReadFlags flags,
1506 QgsDataProvider *provider )
1507{
1509
1510 const QString type = layerElem.attribute( QStringLiteral( "type" ) );
1511 QgsDebugMsgLevel( "Layer type is " + type, 4 );
1512 std::unique_ptr<QgsMapLayer> mapLayer;
1513
1514 QgsScopedRuntimeProfile profile( tr( "Create layer" ), QStringLiteral( "projectload" ) );
1515
1516 bool ok = false;
1517 const Qgis::LayerType layerType( QgsMapLayerFactory::typeFromString( type, ok ) );
1518 if ( !ok )
1519 {
1520 QgsDebugError( QStringLiteral( "Unknown layer type \"%1\"" ).arg( type ) );
1521 return false;
1522 }
1523
1524 switch ( layerType )
1525 {
1526 case Qgis::LayerType::Vector:
1527 mapLayer = std::make_unique<QgsVectorLayer>();
1528 break;
1529
1530 case Qgis::LayerType::Raster:
1531 mapLayer = std::make_unique<QgsRasterLayer>();
1532 break;
1533
1534 case Qgis::LayerType::Mesh:
1535 mapLayer = std::make_unique<QgsMeshLayer>();
1536 break;
1537
1538 case Qgis::LayerType::VectorTile:
1539 mapLayer = std::make_unique<QgsVectorTileLayer>();
1540 break;
1541
1542 case Qgis::LayerType::PointCloud:
1543 mapLayer = std::make_unique<QgsPointCloudLayer>();
1544 break;
1545
1546 case Qgis::LayerType::Plugin:
1547 {
1548 const QString typeName = layerElem.attribute( QStringLiteral( "name" ) );
1549 mapLayer.reset( QgsApplication::pluginLayerRegistry()->createLayer( typeName ) );
1550 break;
1551 }
1552
1553 case Qgis::LayerType::Annotation:
1554 {
1555 const QgsAnnotationLayer::LayerOptions options( mTransformContext );
1556 mapLayer = std::make_unique<QgsAnnotationLayer>( QString(), options );
1557 break;
1558 }
1559
1560 case Qgis::LayerType::Group:
1561 {
1562 const QgsGroupLayer::LayerOptions options( mTransformContext );
1563 mapLayer = std::make_unique<QgsGroupLayer>( QString(), options );
1564 break;
1565 }
1566 }
1567
1568 if ( !mapLayer )
1569 {
1570 QgsDebugError( QStringLiteral( "Unable to create layer" ) );
1571 return false;
1572 }
1573
1574 Q_CHECK_PTR( mapLayer ); // NOLINT
1575
1576 // This is tricky: to avoid a leak we need to check if the layer was already in the store
1577 // because if it was, the newly created layer will not be added to the store and it would leak.
1578 const QString layerId { layerElem.namedItem( QStringLiteral( "id" ) ).toElement().text() };
1579 Q_ASSERT( ! layerId.isEmpty() );
1580 const bool layerWasStored { layerStore()->mapLayer( layerId ) != nullptr };
1581
1582 // have the layer restore state that is stored in Dom node
1583 QgsMapLayer::ReadFlags layerFlags = projectFlagsToLayerReadFlags( flags, mFlags );
1584
1585 profile.switchTask( tr( "Load layer source" ) );
1586 const bool layerIsValid = mapLayer->readLayerXml( layerElem, context, layerFlags, provider ) && mapLayer->isValid();
1587
1588 // apply specific settings to vector layer
1589 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1590 {
1591 vl->setReadExtentFromXml( ( mFlags & Qgis::ProjectFlag::TrustStoredLayerStatistics ) || ( flags & Qgis::ProjectReadFlag::TrustLayerMetadata ) );
1592 if ( vl->dataProvider() )
1593 {
1595 vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, evaluateDefaultValues );
1596 }
1597 }
1598
1599 profile.switchTask( tr( "Add layer to project" ) );
1600 QList<QgsMapLayer *> newLayers;
1601 newLayers << mapLayer.get();
1602 if ( layerIsValid || flags & Qgis::ProjectReadFlag::DontResolveLayers )
1603 {
1604 emit readMapLayer( mapLayer.get(), layerElem );
1605 addMapLayers( newLayers );
1606 // Try to resolve references here (this is necessary to set up joined fields that will be possibly used by
1607 // virtual layers that point to this layer's joined field in their query otherwise they won't be valid ),
1608 // a second attempt to resolve references will be done after all layers are loaded
1609 // see https://github.com/qgis/QGIS/issues/46834
1610 if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1611 {
1612 vLayer->joinBuffer()->resolveReferences( this );
1613 }
1614 }
1615 else
1616 {
1617 // It's a bad layer: do not add to legend (the user will decide if she wants to do so)
1618 addMapLayers( newLayers, false );
1619 newLayers.first();
1620 QgsDebugError( "Unable to load " + type + " layer" );
1621 brokenNodes.push_back( layerElem );
1622 }
1623
1624 const bool wasEditable = layerElem.attribute( QStringLiteral( "editable" ), QStringLiteral( "0" ) ).toInt();
1625 if ( wasEditable )
1626 {
1627 mapLayer->setCustomProperty( QStringLiteral( "_layer_was_editable" ), true );
1628 }
1629 else
1630 {
1631 mapLayer->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
1632 }
1633
1634 // It should be safe to delete the layer now if layer was stored, because all the store
1635 // had to to was to reset the data source in case the validity changed.
1636 if ( ! layerWasStored )
1637 {
1638 mapLayer.release();
1639 }
1640
1641 return layerIsValid;
1642}
1643
1644bool QgsProject::read( const QString &filename, Qgis::ProjectReadFlags flags )
1645{
1647
1648 mFile.setFileName( filename );
1649 mCachedHomePath.clear();
1650 mProjectScope.reset();
1651
1652 return read( flags );
1653}
1654
1655bool QgsProject::read( Qgis::ProjectReadFlags flags )
1656{
1658
1659 const QString filename = mFile.fileName();
1660 bool returnValue;
1661
1662 if ( QgsProjectStorage *storage = projectStorage() )
1663 {
1664 QTemporaryFile inDevice;
1665 if ( !inDevice.open() )
1666 {
1667 setError( tr( "Unable to open %1" ).arg( inDevice.fileName() ) );
1668 return false;
1669 }
1670
1671 QgsReadWriteContext context;
1672 context.setProjectTranslator( this );
1673 if ( !storage->readProject( filename, &inDevice, context ) )
1674 {
1675 QString err = tr( "Unable to open %1" ).arg( filename );
1676 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
1677 if ( !messages.isEmpty() )
1678 err += QStringLiteral( "\n\n" ) + messages.last().message();
1679 setError( err );
1680 return false;
1681 }
1682 returnValue = unzip( inDevice.fileName(), flags ); // calls setError() if returning false
1683 }
1684 else
1685 {
1686 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
1687 {
1688 returnValue = unzip( mFile.fileName(), flags );
1689 }
1690 else
1691 {
1692 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
1693 const QFileInfo finfo( mFile.fileName() );
1694 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
1695 if ( QFile( attachmentsZip ).exists() )
1696 {
1697 std::unique_ptr<QgsArchive> archive( new QgsArchive() );
1698 if ( archive->unzip( attachmentsZip ) )
1699 {
1700 mArchive = std::move( archive );
1701 }
1702 }
1703 returnValue = readProjectFile( mFile.fileName(), flags );
1704 }
1705
1706 //on translation we should not change the filename back
1707 if ( !mTranslator )
1708 {
1709 mFile.setFileName( filename );
1710 mCachedHomePath.clear();
1711 mProjectScope.reset();
1712 }
1713 else
1714 {
1715 //but delete the translator
1716 mTranslator.reset( nullptr );
1717 }
1718 }
1719 emit homePathChanged();
1720 return returnValue;
1721}
1722
1723bool QgsProject::readProjectFile( const QString &filename, Qgis::ProjectReadFlags flags )
1724{
1726
1727 // avoid multiple emission of snapping updated signals
1728 ScopedIntIncrementor snapSignalBlock( &mBlockSnappingUpdates );
1729
1730 QFile projectFile( filename );
1731 clearError();
1732
1733 QgsApplication::profiler()->clear( QStringLiteral( "projectload" ) );
1734 QgsScopedRuntimeProfile profile( tr( "Setting up translations" ), QStringLiteral( "projectload" ) );
1735
1736 const QString localeFileName = QStringLiteral( "%1_%2" ).arg( QFileInfo( projectFile.fileName() ).baseName(), QgsApplication::settingsLocaleUserLocale->value() );
1737
1738 if ( QFile( QStringLiteral( "%1/%2.qm" ).arg( QFileInfo( projectFile.fileName() ).absolutePath(), localeFileName ) ).exists() )
1739 {
1740 mTranslator.reset( new QTranslator() );
1741 ( void )mTranslator->load( localeFileName, QFileInfo( projectFile.fileName() ).absolutePath() );
1742 }
1743
1744 profile.switchTask( tr( "Reading project file" ) );
1745 std::unique_ptr<QDomDocument> doc( new QDomDocument( QStringLiteral( "qgis" ) ) );
1746
1747 if ( !projectFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
1748 {
1749 projectFile.close();
1750
1751 setError( tr( "Unable to open %1" ).arg( projectFile.fileName() ) );
1752
1753 return false;
1754 }
1755
1756 // location of problem associated with errorMsg
1757 int line, column;
1758 QString errorMsg;
1759
1760 if ( !doc->setContent( &projectFile, &errorMsg, &line, &column ) )
1761 {
1762 const QString errorString = tr( "Project file read error in file %1: %2 at line %3 column %4" )
1763 .arg( projectFile.fileName(), errorMsg ).arg( line ).arg( column );
1764
1765 QgsDebugError( errorString );
1766
1767 projectFile.close();
1768
1769 setError( errorString );
1770
1771 return false;
1772 }
1773
1774 projectFile.close();
1775
1776 QgsDebugMsgLevel( "Opened document " + projectFile.fileName(), 2 );
1777
1778 // get project version string, if any
1779 const QgsProjectVersion fileVersion = getVersion( *doc );
1780 const QgsProjectVersion thisVersion( Qgis::version() );
1781
1782 profile.switchTask( tr( "Updating project file" ) );
1783 if ( thisVersion > fileVersion )
1784 {
1785 const bool isOlderMajorVersion = fileVersion.majorVersion() < thisVersion.majorVersion();
1786
1787 if ( isOlderMajorVersion )
1788 {
1789 QgsLogger::warning( "Loading a file that was saved with an older "
1790 "version of qgis (saved in " + fileVersion.text() +
1791 ", loaded in " + Qgis::version() +
1792 "). Problems may occur." );
1793 }
1794
1795 QgsProjectFileTransform projectFile( *doc, fileVersion );
1796
1797 // Shows a warning when an old project file is read.
1799 emit oldProjectVersionWarning( fileVersion.text() );
1801 emit readVersionMismatchOccurred( fileVersion.text() );
1802
1803 projectFile.updateRevision( thisVersion );
1804 }
1805 else if ( fileVersion > thisVersion )
1806 {
1807 QgsLogger::warning( "Loading a file that was saved with a newer "
1808 "version of qgis (saved in " + fileVersion.text() +
1809 ", loaded in " + Qgis::version() +
1810 "). Problems may occur." );
1811
1812 emit readVersionMismatchOccurred( fileVersion.text() );
1813 }
1814
1815 // start new project, just keep the file name and auxiliary storage
1816 profile.switchTask( tr( "Creating auxiliary storage" ) );
1817 const QString fileName = mFile.fileName();
1818 std::unique_ptr<QgsAuxiliaryStorage> aStorage = std::move( mAuxiliaryStorage );
1819 std::unique_ptr<QgsArchive> archive = std::move( mArchive );
1820 clear();
1821 mAuxiliaryStorage = std::move( aStorage );
1822 mArchive = std::move( archive );
1823 mFile.setFileName( fileName );
1824 mCachedHomePath.clear();
1825 mProjectScope.reset();
1826 mSaveVersion = fileVersion;
1827
1828 // now get any properties
1829 profile.switchTask( tr( "Reading properties" ) );
1830 _getProperties( *doc, mProperties );
1831
1832 // now get the data defined server properties
1833 mDataDefinedServerProperties = getDataDefinedServerProperties( *doc, dataDefinedServerPropertyDefinitions() );
1834
1835 QgsDebugMsgLevel( QString::number( mProperties.count() ) + " properties read", 2 );
1836
1837#if 0
1838 dump_( mProperties );
1839#endif
1840
1841 // get older style project title
1842 QString oldTitle;
1843 _getTitle( *doc, oldTitle );
1844
1845 readProjectFileMetadata( *doc, mSaveUser, mSaveUserFull, mSaveDateTime );
1846
1847 const QDomNodeList homePathNl = doc->elementsByTagName( QStringLiteral( "homePath" ) );
1848 if ( homePathNl.count() > 0 )
1849 {
1850 const QDomElement homePathElement = homePathNl.at( 0 ).toElement();
1851 const QString homePath = homePathElement.attribute( QStringLiteral( "path" ) );
1852 if ( !homePath.isEmpty() )
1854 }
1855 else
1856 {
1857 emit homePathChanged();
1858 }
1859
1860 const QColor backgroundColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), 255 ),
1861 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), 255 ),
1862 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), 255 ) );
1864 const QColor selectionColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), 255 ),
1865 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), 255 ),
1866 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), 255 ),
1867 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), 255 ) );
1869
1870
1871 const QString distanceUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QString() );
1872 if ( !distanceUnitString.isEmpty() )
1873 setDistanceUnits( QgsUnitTypes::decodeDistanceUnit( distanceUnitString ) );
1874
1875 const QString areaUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QString() );
1876 if ( !areaUnitString.isEmpty() )
1877 setAreaUnits( QgsUnitTypes::decodeAreaUnit( areaUnitString ) );
1878
1879 QgsReadWriteContext context;
1880 context.setPathResolver( pathResolver() );
1881 context.setProjectTranslator( this );
1882
1883 //crs
1885 if ( readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), 0 ) )
1886 {
1887 // first preference - dedicated projectCrs node
1888 const QDomNode srsNode = doc->documentElement().namedItem( QStringLiteral( "projectCrs" ) );
1889 if ( !srsNode.isNull() )
1890 {
1891 projectCrs.readXml( srsNode );
1892 }
1893
1894 if ( !projectCrs.isValid() )
1895 {
1896 const QString projCrsString = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSProj4String" ) );
1897 const long currentCRS = readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSID" ), -1 );
1898 const QString authid = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCrs" ) );
1899
1900 // authid should be prioritized over all
1901 const bool isUserAuthId = authid.startsWith( QLatin1String( "USER:" ), Qt::CaseInsensitive );
1902 if ( !authid.isEmpty() && !isUserAuthId )
1903 projectCrs = QgsCoordinateReferenceSystem( authid );
1904
1905 // try the CRS
1906 if ( !projectCrs.isValid() && currentCRS >= 0 )
1907 {
1908 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
1909 }
1910
1911 // if that didn't produce a match, try the proj.4 string
1912 if ( !projCrsString.isEmpty() && ( authid.isEmpty() || isUserAuthId ) && ( !projectCrs.isValid() || projectCrs.toProj() != projCrsString ) )
1913 {
1914 projectCrs = QgsCoordinateReferenceSystem::fromProj( projCrsString );
1915 }
1916
1917 // last just take the given id
1918 if ( !projectCrs.isValid() )
1919 {
1920 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
1921 }
1922 }
1923 }
1924 mCrs = projectCrs;
1925
1926 QStringList datumErrors;
1927 if ( !mTransformContext.readXml( doc->documentElement(), context, datumErrors ) && !datumErrors.empty() )
1928 {
1929 emit missingDatumTransforms( datumErrors );
1930 }
1932
1933 // map shading
1934 const QDomNode elevationShadingNode = doc->documentElement().namedItem( QStringLiteral( "elevation-shading-renderer" ) );
1935 if ( !elevationShadingNode.isNull() )
1936 {
1937 mElevationShadingRenderer.readXml( elevationShadingNode.toElement(), context );
1938 }
1940
1941
1942 //add variables defined in project file - do this early in the reading cycle, as other components
1943 //(e.g. layouts) may depend on these variables
1944 const QStringList variableNames = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ) );
1945 const QStringList variableValues = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ) );
1946
1947 mCustomVariables.clear();
1948 if ( variableNames.length() == variableValues.length() )
1949 {
1950 for ( int i = 0; i < variableNames.length(); ++i )
1951 {
1952 mCustomVariables.insert( variableNames.at( i ), variableValues.at( i ) );
1953 }
1954 }
1955 else
1956 {
1957 QgsMessageLog::logMessage( tr( "Project Variables Invalid" ), tr( "The project contains invalid variable settings." ) );
1958 }
1959
1960 QDomElement element = doc->documentElement().firstChildElement( QStringLiteral( "projectMetadata" ) );
1961
1962 if ( !element.isNull() )
1963 {
1964 mMetadata.readMetadataXml( element );
1965 }
1966 else
1967 {
1968 // older project, no metadata => remove auto generated metadata which is populated on QgsProject::clear()
1969 mMetadata = QgsProjectMetadata();
1970 }
1971 if ( mMetadata.title().isEmpty() && !oldTitle.isEmpty() )
1972 {
1973 // upgrade older title storage to storing within project metadata.
1974 mMetadata.setTitle( oldTitle );
1975 }
1976 emit metadataChanged();
1977
1978 // Transaction mode
1979 element = doc->documentElement().firstChildElement( QStringLiteral( "transaction" ) );
1980 if ( !element.isNull() )
1981 {
1982 mTransactionMode = qgsEnumKeyToValue( element.attribute( QStringLiteral( "mode" ) ), Qgis::TransactionMode::Disabled );
1983 }
1984 else
1985 {
1986 // maybe older project => try read autotransaction
1987 element = doc->documentElement().firstChildElement( QStringLiteral( "autotransaction" ) );
1988 if ( ! element.isNull() )
1989 {
1990 mTransactionMode = static_cast<Qgis::TransactionMode>( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() );
1991 }
1992 }
1993
1994 // read the layer tree from project file
1995 profile.switchTask( tr( "Loading layer tree" ) );
1996 mRootGroup->setCustomProperty( QStringLiteral( "loading" ), 1 );
1997
1998 QDomElement layerTreeElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
1999 if ( !layerTreeElem.isNull() )
2000 {
2001 // Use a temporary tree to read the nodes to prevent signals being delivered to the models
2002 QgsLayerTree tempTree;
2003 tempTree.readChildrenFromXml( layerTreeElem, context );
2004 mRootGroup->insertChildNodes( -1, tempTree.abandonChildren() );
2005 }
2006 else
2007 {
2008 QgsLayerTreeUtils::readOldLegend( mRootGroup, doc->documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
2009 }
2010
2011 mLayerTreeRegistryBridge->setEnabled( false );
2012
2013 // get the map layers
2014 profile.switchTask( tr( "Reading map layers" ) );
2015
2016 loadProjectFlags( doc.get() );
2017
2018 QList<QDomNode> brokenNodes;
2019 const bool clean = _getMapLayers( *doc, brokenNodes, flags );
2020
2021 // review the integrity of the retrieved map layers
2022 if ( !clean && !( flags & Qgis::ProjectReadFlag::DontResolveLayers ) )
2023 {
2024 QgsDebugError( QStringLiteral( "Unable to get map layers from project file." ) );
2025
2026 if ( !brokenNodes.isEmpty() )
2027 {
2028 QgsDebugError( "there are " + QString::number( brokenNodes.size() ) + " broken layers" );
2029 }
2030
2031 // we let a custom handler decide what to do with missing layers
2032 // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
2033 mBadLayerHandler->handleBadLayers( brokenNodes );
2034 }
2035
2036 mMainAnnotationLayer->readLayerXml( doc->documentElement().firstChildElement( QStringLiteral( "main-annotation-layer" ) ), context );
2037 mMainAnnotationLayer->setTransformContext( mTransformContext );
2038
2039 // load embedded groups and layers
2040 profile.switchTask( tr( "Loading embedded layers" ) );
2041 loadEmbeddedNodes( mRootGroup, flags );
2042
2043 // Resolve references to other layers
2044 // Needs to be done here once all dependent layers are loaded
2045 profile.switchTask( tr( "Resolving layer references" ) );
2046 QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
2047 for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
2048 {
2049 it.value()->resolveReferences( this );
2050 }
2051
2052 mLayerTreeRegistryBridge->setEnabled( true );
2053
2054 // now that layers are loaded, we can resolve layer tree's references to the layers
2055 profile.switchTask( tr( "Resolving references" ) );
2056 mRootGroup->resolveReferences( this );
2057
2058 // we need to migrate old fashion designed QgsSymbolLayerReference to new ones
2059 if ( QgsProjectVersion( 3, 28, 0 ) > mSaveVersion )
2060 {
2064 }
2065
2066 if ( !layerTreeElem.isNull() )
2067 {
2068 mRootGroup->readLayerOrderFromXml( layerTreeElem );
2069 }
2070
2071 // Load pre 3.0 configuration
2072 const QDomElement layerTreeCanvasElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-canvas" ) );
2073 if ( !layerTreeCanvasElem.isNull( ) )
2074 {
2075 mRootGroup->readLayerOrderFromXml( layerTreeCanvasElem );
2076 }
2077
2078 // Convert pre 3.4 to create layers flags
2079 if ( QgsProjectVersion( 3, 4, 0 ) > mSaveVersion )
2080 {
2081 const QStringList requiredLayerIds = readListEntry( QStringLiteral( "RequiredLayers" ), QStringLiteral( "Layers" ) );
2082 for ( const QString &layerId : requiredLayerIds )
2083 {
2084 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2085 {
2086 layer->setFlags( layer->flags() & ~QgsMapLayer::Removable );
2087 }
2088 }
2089 const QStringList disabledLayerIds = readListEntry( QStringLiteral( "Identify" ), QStringLiteral( "/disabledLayers" ) );
2090 for ( const QString &layerId : disabledLayerIds )
2091 {
2092 if ( QgsMapLayer *layer = mapLayer( layerId ) )
2093 {
2094 layer->setFlags( layer->flags() & ~QgsMapLayer::Identifiable );
2095 }
2096 }
2097 }
2098
2099 // Convert pre 3.26 default styles
2100 if ( QgsProjectVersion( 3, 26, 0 ) > mSaveVersion )
2101 {
2102 // Convert default symbols
2103 QString styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Marker" ) );
2104 if ( !styleName.isEmpty() )
2105 {
2106 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2108 }
2109 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Line" ) );
2110 if ( !styleName.isEmpty() )
2111 {
2112 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2114 }
2115 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Fill" ) );
2116 if ( !styleName.isEmpty() )
2117 {
2118 std::unique_ptr<QgsSymbol> symbol( QgsStyle::defaultStyle()->symbol( styleName ) );
2120 }
2121 styleName = readEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ) );
2122 if ( !styleName.isEmpty() )
2123 {
2124 std::unique_ptr<QgsColorRamp> colorRamp( QgsStyle::defaultStyle()->colorRamp( styleName ) );
2125 styleSettings()->setDefaultColorRamp( colorRamp.get() );
2126 }
2127
2128 // Convert randomize default symbol fill color
2129 styleSettings()->setRandomizeDefaultSymbolColor( readBoolEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ), true ) );
2130
2131 // Convert default symbol opacity
2132 double opacity = 1.0;
2133 bool ok = false;
2134 // upgrade old setting
2135 double alpha = readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/AlphaInt" ), 255, &ok );
2136 if ( ok )
2137 opacity = alpha / 255.0;
2138 double newOpacity = readDoubleEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Opacity" ), 1.0, &ok );
2139 if ( ok )
2140 opacity = newOpacity;
2142
2143 // Cleanup
2144 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Marker" ) );
2145 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Line" ) );
2146 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Fill" ) );
2147 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/ColorRamp" ) );
2148 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ) );
2149 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/AlphaInt" ) );
2150 removeEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/Opacity" ) );
2151 }
2152
2153 // After bad layer handling we might still have invalid layers,
2154 // store them in case the user wanted to handle them later
2155 // or wanted to pass them through when saving
2156 if ( !( flags & Qgis::ProjectReadFlag::DontStoreOriginalStyles ) )
2157 {
2158 profile.switchTask( tr( "Storing original layer properties" ) );
2159 QgsLayerTreeUtils::storeOriginalLayersProperties( mRootGroup, doc.get() );
2160 }
2161
2162 mRootGroup->removeCustomProperty( QStringLiteral( "loading" ) );
2163
2164 profile.switchTask( tr( "Loading map themes" ) );
2165 mMapThemeCollection.reset( new QgsMapThemeCollection( this ) );
2167 mMapThemeCollection->readXml( *doc );
2168
2169 profile.switchTask( tr( "Loading label settings" ) );
2170 mLabelingEngineSettings->readSettingsFromProject( this );
2172
2173 profile.switchTask( tr( "Loading annotations" ) );
2174 mAnnotationManager->readXml( doc->documentElement(), context );
2175 if ( !( flags & Qgis::ProjectReadFlag::DontLoadLayouts ) )
2176 {
2177 profile.switchTask( tr( "Loading layouts" ) );
2178 mLayoutManager->readXml( doc->documentElement(), *doc );
2179 }
2180
2181 if ( !( flags & Qgis::ProjectReadFlag::DontLoad3DViews ) )
2182 {
2183 profile.switchTask( tr( "Loading 3D Views" ) );
2184 m3DViewsManager->readXml( doc->documentElement(), *doc );
2185 }
2186
2187 profile.switchTask( tr( "Loading bookmarks" ) );
2188 mBookmarkManager->readXml( doc->documentElement(), *doc );
2189
2190 profile.switchTask( tr( "Loading sensors" ) );
2191 mSensorManager->readXml( doc->documentElement(), *doc );
2192
2193 // reassign change dependencies now that all layers are loaded
2194 QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2195 for ( QMap<QString, QgsMapLayer *>::iterator it = existingMaps.begin(); it != existingMaps.end(); ++it )
2196 {
2197 it.value()->setDependencies( it.value()->dependencies() );
2198 }
2199
2200 profile.switchTask( tr( "Loading snapping settings" ) );
2201 mSnappingConfig.readProject( *doc );
2202 mAvoidIntersectionsMode = static_cast<Qgis::AvoidIntersectionsMode>( readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( Qgis::AvoidIntersectionsMode::AvoidIntersectionsLayers ) ) );
2203
2204 profile.switchTask( tr( "Loading view settings" ) );
2205 // restore older project scales settings
2206 mViewSettings->setUseProjectScales( readBoolEntry( QStringLiteral( "Scales" ), QStringLiteral( "/useProjectScales" ) ) );
2207 const QStringList scales = readListEntry( QStringLiteral( "Scales" ), QStringLiteral( "/ScalesList" ) );
2208 QVector<double> res;
2209 for ( const QString &scale : scales )
2210 {
2211 const QStringList parts = scale.split( ':' );
2212 if ( parts.size() != 2 )
2213 continue;
2214
2215 bool ok = false;
2216 const double denominator = QLocale().toDouble( parts[1], &ok );
2217 if ( ok )
2218 {
2219 res << denominator;
2220 }
2221 }
2222 mViewSettings->setMapScales( res );
2223 const QDomElement viewSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectViewSettings" ) );
2224 if ( !viewSettingsElement.isNull() )
2225 mViewSettings->readXml( viewSettingsElement, context );
2226
2227 // restore style settings
2228 profile.switchTask( tr( "Loading style properties" ) );
2229 const QDomElement styleSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectStyleSettings" ) );
2230 if ( !styleSettingsElement.isNull() )
2231 mStyleSettings->readXml( styleSettingsElement, context, flags );
2232
2233 // restore time settings
2234 profile.switchTask( tr( "Loading temporal settings" ) );
2235 const QDomElement timeSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectTimeSettings" ) );
2236 if ( !timeSettingsElement.isNull() )
2237 mTimeSettings->readXml( timeSettingsElement, context );
2238
2239
2240 profile.switchTask( tr( "Loading elevation properties" ) );
2241 const QDomElement elevationPropertiesElement = doc->documentElement().firstChildElement( QStringLiteral( "ElevationProperties" ) );
2242 if ( !elevationPropertiesElement.isNull() )
2243 mElevationProperties->readXml( elevationPropertiesElement, context );
2244 mElevationProperties->resolveReferences( this );
2245
2246 profile.switchTask( tr( "Loading display settings" ) );
2247 {
2248 const QDomElement displaySettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectDisplaySettings" ) );
2249 if ( !displaySettingsElement.isNull() )
2250 mDisplaySettings->readXml( displaySettingsElement, context );
2251 }
2252
2253 profile.switchTask( tr( "Loading GPS settings" ) );
2254 {
2255 const QDomElement gpsSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectGpsSettings" ) );
2256 if ( !gpsSettingsElement.isNull() )
2257 mGpsSettings->readXml( gpsSettingsElement, context );
2258 mGpsSettings->resolveReferences( this );
2259 }
2260
2261 profile.switchTask( tr( "Updating variables" ) );
2263 profile.switchTask( tr( "Updating CRS" ) );
2264 emit crsChanged();
2265 emit ellipsoidChanged( ellipsoid() );
2266
2267 // read the project: used by map canvas and legend
2268 profile.switchTask( tr( "Reading external settings" ) );
2269 emit readProject( *doc );
2270 emit readProjectWithContext( *doc, context );
2271
2272 profile.switchTask( tr( "Updating interface" ) );
2273
2274 snapSignalBlock.release();
2275 if ( !mBlockSnappingUpdates )
2276 emit snappingConfigChanged( mSnappingConfig );
2277
2280 emit projectColorsChanged();
2281
2282 // if all went well, we're allegedly in pristine state
2283 if ( clean )
2284 setDirty( false );
2285
2286 QgsDebugMsgLevel( QStringLiteral( "Project save user: %1" ).arg( mSaveUser ), 2 );
2287 QgsDebugMsgLevel( QStringLiteral( "Project save user: %1" ).arg( mSaveUserFull ), 2 );
2288
2292
2293 if ( mTranslator )
2294 {
2295 //project possibly translated -> rename it with locale postfix
2296 const QString newFileName( QStringLiteral( "%1/%2.qgs" ).arg( QFileInfo( projectFile.fileName() ).absolutePath(), localeFileName ) );
2297 setFileName( newFileName );
2298
2299 if ( write() )
2300 {
2301 setTitle( localeFileName );
2302 QgsMessageLog::logMessage( tr( "Translated project saved with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Success );
2303 }
2304 else
2305 {
2306 QgsMessageLog::logMessage( tr( "Error saving translated project with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::MessageLevel::Critical );
2307 }
2308 }
2309
2310 // lastly, make any previously editable layers editable
2311 const QMap<QString, QgsMapLayer *> loadedLayers = mapLayers();
2312 for ( auto it = loadedLayers.constBegin(); it != loadedLayers.constEnd(); ++it )
2313 {
2314 if ( it.value()->isValid() && it.value()->customProperty( QStringLiteral( "_layer_was_editable" ) ).toBool() )
2315 {
2316 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( it.value() ) )
2317 vl->startEditing();
2318 it.value()->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
2319 }
2320 }
2321
2322 return true;
2323}
2324
2325bool QgsProject::loadEmbeddedNodes( QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
2326{
2328
2329 bool valid = true;
2330 const auto constChildren = group->children();
2331 for ( QgsLayerTreeNode *child : constChildren )
2332 {
2333 if ( QgsLayerTree::isGroup( child ) )
2334 {
2335 QgsLayerTreeGroup *childGroup = QgsLayerTree::toGroup( child );
2336 if ( childGroup->customProperty( QStringLiteral( "embedded" ) ).toInt() )
2337 {
2338 // make sure to convert the path from relative to absolute
2339 const QString projectPath = readPath( childGroup->customProperty( QStringLiteral( "embedded_project" ) ).toString() );
2340 childGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectPath );
2341 QgsLayerTreeGroup *newGroup = createEmbeddedGroup( childGroup->name(), projectPath, childGroup->customProperty( QStringLiteral( "embedded-invisible-layers" ) ).toStringList(), flags );
2342 if ( newGroup )
2343 {
2344 QList<QgsLayerTreeNode *> clonedChildren;
2345 const QList<QgsLayerTreeNode *> constChildren = newGroup->children();
2346 clonedChildren.reserve( constChildren.size() );
2347 for ( QgsLayerTreeNode *newGroupChild : constChildren )
2348 clonedChildren << newGroupChild->clone();
2349 delete newGroup;
2350
2351 childGroup->insertChildNodes( 0, clonedChildren );
2352 }
2353 }
2354 else
2355 {
2356 loadEmbeddedNodes( childGroup, flags );
2357 }
2358 }
2359 else if ( QgsLayerTree::isLayer( child ) )
2360 {
2361 if ( child->customProperty( QStringLiteral( "embedded" ) ).toInt() )
2362 {
2363 QList<QDomNode> brokenNodes;
2364 if ( ! createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), readPath( child->customProperty( QStringLiteral( "embedded_project" ) ).toString() ), brokenNodes, true, flags ) )
2365 {
2366 valid = valid && false;
2367 }
2368 }
2369 }
2370
2371 }
2372
2373 return valid;
2374}
2375
2377{
2378 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
2380
2381 return mCustomVariables;
2382}
2383
2384void QgsProject::setCustomVariables( const QVariantMap &variables )
2385{
2387
2388 if ( variables == mCustomVariables )
2389 return;
2390
2391 //write variable to project
2392 QStringList variableNames;
2393 QStringList variableValues;
2394
2395 QVariantMap::const_iterator it = variables.constBegin();
2396 for ( ; it != variables.constEnd(); ++it )
2397 {
2398 variableNames << it.key();
2399 variableValues << it.value().toString();
2400 }
2401
2402 writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ), variableNames );
2403 writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ), variableValues );
2404
2405 mCustomVariables = variables;
2406 mProjectScope.reset();
2407
2409}
2410
2412{
2414
2415 *mLabelingEngineSettings = settings;
2417}
2418
2420{
2422
2423 return *mLabelingEngineSettings;
2424}
2425
2427{
2429
2430 mProjectScope.reset();
2431 return mLayerStore.get();
2432}
2433
2435{
2437
2438 return mLayerStore.get();
2439}
2440
2441QList<QgsVectorLayer *> QgsProject::avoidIntersectionsLayers() const
2442{
2444
2445 QList<QgsVectorLayer *> layers;
2446 const QStringList layerIds = readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), QStringList() );
2447 const auto constLayerIds = layerIds;
2448 for ( const QString &layerId : constLayerIds )
2449 {
2450 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer( layerId ) ) )
2451 layers << vlayer;
2452 }
2453 return layers;
2454}
2455
2456void QgsProject::setAvoidIntersectionsLayers( const QList<QgsVectorLayer *> &layers )
2457{
2459
2460 QStringList list;
2461 list.reserve( layers.size() );
2462 for ( QgsVectorLayer *layer : layers )
2463 list << layer->id();
2464 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), list );
2466}
2467
2469{
2471
2472 QgsExpressionContext context;
2473
2476
2477 return context;
2478}
2479
2481{
2482 // this method is called quite extensively using QgsProject::instance()
2484
2485 // MUCH cheaper to clone than build
2486 if ( mProjectScope )
2487 {
2488 std::unique_ptr< QgsExpressionContextScope > projectScope = std::make_unique< QgsExpressionContextScope >( *mProjectScope );
2489
2490 // we can't cache these variables
2491 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_distance_units" ), QgsUnitTypes::toString( distanceUnits() ), true, true ) );
2492 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_area_units" ), QgsUnitTypes::toString( areaUnits() ), true, true ) );
2493
2494 // neither this function
2495 projectScope->addFunction( QStringLiteral( "sensor_data" ), new GetSensorData( sensorManager()->sensorsData() ) );
2496
2497 return projectScope.release();
2498 }
2499
2500 mProjectScope = std::make_unique< QgsExpressionContextScope >( QObject::tr( "Project" ) );
2501
2502 const QVariantMap vars = customVariables();
2503
2504 QVariantMap::const_iterator it = vars.constBegin();
2505
2506 for ( ; it != vars.constEnd(); ++it )
2507 {
2508 mProjectScope->setVariable( it.key(), it.value(), true );
2509 }
2510
2511 QString projectPath = projectStorage() ? fileName() : absoluteFilePath();
2512 if ( projectPath.isEmpty() )
2513 projectPath = mOriginalPath;
2514 const QString projectFolder = QFileInfo( projectPath ).path();
2515 const QString projectFilename = QFileInfo( projectPath ).fileName();
2516 const QString projectBasename = baseName();
2517
2518 //add other known project variables
2519 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_title" ), title(), true, true ) );
2520 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_path" ), QDir::toNativeSeparators( projectPath ), true, true ) );
2521 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_folder" ), QDir::toNativeSeparators( projectFolder ), true, true ) );
2522 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_filename" ), projectFilename, true, true ) );
2523 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_basename" ), projectBasename, true, true ) );
2524 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_home" ), QDir::toNativeSeparators( homePath() ), true, true ) );
2525 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_last_saved" ), mSaveDateTime.isNull() ? QVariant() : QVariant( mSaveDateTime ), true, true ) );
2526 const QgsCoordinateReferenceSystem projectCrs = crs();
2527 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs" ), projectCrs.authid(), true, true ) );
2528 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_definition" ), projectCrs.toProj(), true, true ) );
2529 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_description" ), projectCrs.description(), true, true ) );
2530 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_ellipsoid" ), ellipsoid(), true, true ) );
2531 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "_project_transform_context" ), QVariant::fromValue<QgsCoordinateTransformContext>( transformContext() ), true, true ) );
2532 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_units" ), QgsUnitTypes::toString( projectCrs.mapUnits() ), true ) );
2533 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_acronym" ), projectCrs.projectionAcronym(), true ) );
2534 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_ellipsoid" ), projectCrs.ellipsoidAcronym(), true ) );
2535 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_proj4" ), projectCrs.toProj(), true ) );
2536 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_wkt" ), projectCrs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ), true ) );
2537
2538 // metadata
2539 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_author" ), metadata().author(), true, true ) );
2540 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_abstract" ), metadata().abstract(), true, true ) );
2541 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_creation_date" ), metadata().creationDateTime(), true, true ) );
2542 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_identifier" ), metadata().identifier(), true, true ) );
2543
2544 // keywords
2545 QVariantMap keywords;
2546 const QgsAbstractMetadataBase::KeywordMap metadataKeywords = metadata().keywords();
2547 for ( auto it = metadataKeywords.constBegin(); it != metadataKeywords.constEnd(); ++it )
2548 {
2549 keywords.insert( it.key(), it.value() );
2550 }
2551 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_keywords" ), keywords, true, true ) );
2552
2553 // layers
2554 QVariantList layersIds;
2555 QVariantList layers;
2556 const QMap<QString, QgsMapLayer *> layersInProject = mLayerStore->mapLayers();
2557 layersIds.reserve( layersInProject.count() );
2558 layers.reserve( layersInProject.count() );
2559 for ( auto it = layersInProject.constBegin(); it != layersInProject.constEnd(); ++it )
2560 {
2561 layersIds << it.value()->id();
2562 layers << QVariant::fromValue<QgsWeakMapLayerPointer>( QgsWeakMapLayerPointer( it.value() ) );
2563 }
2564 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer_ids" ), layersIds, true ) );
2565 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layers" ), layers, true ) );
2566
2567 mProjectScope->addFunction( QStringLiteral( "project_color" ), new GetNamedProjectColor( this ) );
2568
2570}
2571
2572void QgsProject::onMapLayersAdded( const QList<QgsMapLayer *> &layers )
2573{
2575
2576 const QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2577
2578 const auto constLayers = layers;
2579 for ( QgsMapLayer *layer : constLayers )
2580 {
2581 if ( ! layer->isValid() )
2582 return;
2583
2584 connect( layer, &QgsMapLayer::configChanged, this, [ = ] { setDirty(); } );
2585
2586 // check if we have to update connections for layers with dependencies
2587 for ( QMap<QString, QgsMapLayer *>::const_iterator it = existingMaps.cbegin(); it != existingMaps.cend(); ++it )
2588 {
2589 const QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
2590 if ( deps.contains( layer->id() ) )
2591 {
2592 // reconnect to change signals
2593 it.value()->setDependencies( deps );
2594 }
2595 }
2596 }
2597
2598 updateTransactionGroups();
2599
2600 if ( !mBlockSnappingUpdates && mSnappingConfig.addLayers( layers ) )
2601 emit snappingConfigChanged( mSnappingConfig );
2602}
2603
2604void QgsProject::onMapLayersRemoved( const QList<QgsMapLayer *> &layers )
2605{
2607
2608 if ( !mBlockSnappingUpdates && mSnappingConfig.removeLayers( layers ) )
2609 emit snappingConfigChanged( mSnappingConfig );
2610}
2611
2612void QgsProject::cleanTransactionGroups( bool force )
2613{
2615
2616 bool changed = false;
2617 for ( QMap< QPair< QString, QString>, QgsTransactionGroup *>::Iterator tg = mTransactionGroups.begin(); tg != mTransactionGroups.end(); )
2618 {
2619 if ( tg.value()->isEmpty() || force )
2620 {
2621 delete tg.value();
2622 tg = mTransactionGroups.erase( tg );
2623 changed = true;
2624 }
2625 else
2626 {
2627 ++tg;
2628 }
2629 }
2630 if ( changed )
2632}
2633
2634void QgsProject::updateTransactionGroups()
2635{
2637
2638 mEditBufferGroup.clear();
2639
2640 switch ( mTransactionMode )
2641 {
2643 {
2644 cleanTransactionGroups( true );
2645 return;
2646 }
2647 break;
2649 cleanTransactionGroups( true );
2650 break;
2652 cleanTransactionGroups( false );
2653 break;
2654 }
2655
2656 bool tgChanged = false;
2657 const auto constLayers = mapLayers().values();
2658 for ( QgsMapLayer *layer : constLayers )
2659 {
2660 if ( ! layer->isValid() )
2661 continue;
2662
2663 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
2664 if ( ! vlayer )
2665 continue;
2666
2667 switch ( mTransactionMode )
2668 {
2670 Q_ASSERT( false );
2671 break;
2673 {
2675 {
2676 const QString connString = QgsTransaction::connectionString( vlayer->source() );
2677 const QString key = vlayer->providerType();
2678
2679 QgsTransactionGroup *tg = mTransactionGroups.value( qMakePair( key, connString ) );
2680
2681 if ( !tg )
2682 {
2683 tg = new QgsTransactionGroup();
2684 mTransactionGroups.insert( qMakePair( key, connString ), tg );
2685 tgChanged = true;
2686 }
2687 tg->addLayer( vlayer );
2688 }
2689 }
2690 break;
2692 {
2693 if ( vlayer->supportsEditing() )
2694 mEditBufferGroup.addLayer( vlayer );
2695 }
2696 break;
2697 }
2698 }
2699
2700 if ( tgChanged )
2702}
2703
2704bool QgsProject::readLayer( const QDomNode &layerNode )
2705{
2707
2708 QgsReadWriteContext context;
2709 context.setPathResolver( pathResolver() );
2710 context.setProjectTranslator( this );
2711 context.setTransformContext( transformContext() );
2712 QList<QDomNode> brokenNodes;
2713 if ( addLayer( layerNode.toElement(), brokenNodes, context ) )
2714 {
2715 // have to try to update joins for all layers now - a previously added layer may be dependent on this newly
2716 // added layer for joins
2717 const QVector<QgsVectorLayer *> vectorLayers = layers<QgsVectorLayer *>();
2718 for ( QgsVectorLayer *layer : vectorLayers )
2719 {
2720 // TODO: should be only done later - and with all layers (other layers may have referenced this layer)
2721 layer->resolveReferences( this );
2722
2723 if ( layer->isValid() && layer->customProperty( QStringLiteral( "_layer_was_editable" ) ).toBool() )
2724 {
2725 layer->startEditing();
2726 layer->removeCustomProperty( QStringLiteral( "_layer_was_editable" ) );
2727 }
2728 }
2729 return true;
2730 }
2731 return false;
2732}
2733
2734bool QgsProject::write( const QString &filename )
2735{
2737
2738 mFile.setFileName( filename );
2739 mCachedHomePath.clear();
2740 return write();
2741}
2742
2744{
2746
2747 mProjectScope.reset();
2748 if ( QgsProjectStorage *storage = projectStorage() )
2749 {
2750 QgsReadWriteContext context;
2751 // for projects stored in a custom storage, we have to check for the support
2752 // of relative paths since the storage most likely will not be in a file system
2753 const QString storageFilePath { storage->filePath( mFile.fileName() ) };
2754 if ( storageFilePath.isEmpty() )
2755 {
2757 }
2758 context.setPathResolver( pathResolver() );
2759
2760 const QString tempPath = QStandardPaths::standardLocations( QStandardPaths::TempLocation ).at( 0 );
2761 const QString tmpZipFilename( tempPath + QDir::separator() + QUuid::createUuid().toString() );
2762
2763 if ( !zip( tmpZipFilename ) )
2764 return false; // zip() already calls setError() when returning false
2765
2766 QFile tmpZipFile( tmpZipFilename );
2767 if ( !tmpZipFile.open( QIODevice::ReadOnly ) )
2768 {
2769 setError( tr( "Unable to read file %1" ).arg( tmpZipFilename ) );
2770 return false;
2771 }
2772
2774 if ( !storage->writeProject( mFile.fileName(), &tmpZipFile, context ) )
2775 {
2776 QString err = tr( "Unable to save project to storage %1" ).arg( mFile.fileName() );
2777 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
2778 if ( !messages.isEmpty() )
2779 err += QStringLiteral( "\n\n" ) + messages.last().message();
2780 setError( err );
2781 return false;
2782 }
2783
2784 tmpZipFile.close();
2785 QFile::remove( tmpZipFilename );
2786
2787 return true;
2788 }
2789
2790 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
2791 {
2792 return zip( mFile.fileName() );
2793 }
2794 else
2795 {
2796 // write project file even if the auxiliary storage is not correctly
2797 // saved
2798 const bool asOk = saveAuxiliaryStorage();
2799 const bool writeOk = writeProjectFile( mFile.fileName() );
2800 bool attachmentsOk = true;
2801 if ( !mArchive->files().isEmpty() )
2802 {
2803 const QFileInfo finfo( mFile.fileName() );
2804 const QString attachmentsZip = finfo.absoluteDir().absoluteFilePath( QStringLiteral( "%1_attachments.zip" ).arg( finfo.completeBaseName() ) );
2805 attachmentsOk = mArchive->zip( attachmentsZip );
2806 }
2807
2808 // errors raised during writing project file are more important
2809 if ( ( !asOk || !attachmentsOk ) && writeOk )
2810 {
2811 QStringList errorMessage;
2812 if ( !asOk )
2813 {
2814 const QString err = mAuxiliaryStorage->errorString();
2815 errorMessage.append( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
2816 }
2817 if ( !attachmentsOk )
2818 {
2819 errorMessage.append( tr( "Unable to save attachments archive" ) );
2820 }
2821 setError( errorMessage.join( '\n' ) );
2822 }
2823
2824 return asOk && writeOk && attachmentsOk;
2825 }
2826}
2827
2828bool QgsProject::writeProjectFile( const QString &filename )
2829{
2831
2832 QFile projectFile( filename );
2833 clearError();
2834
2835 // if we have problems creating or otherwise writing to the project file,
2836 // let's find out up front before we go through all the hand-waving
2837 // necessary to create all the Dom objects
2838 const QFileInfo myFileInfo( projectFile );
2839 if ( myFileInfo.exists() && !myFileInfo.isWritable() )
2840 {
2841 setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." )
2842 .arg( projectFile.fileName() ) );
2843 return false;
2844 }
2845
2846 QgsReadWriteContext context;
2847 context.setPathResolver( pathResolver() );
2849
2850 QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
2851
2852 const QDomDocumentType documentType =
2853 QDomImplementation().createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ),
2854 QStringLiteral( "SYSTEM" ) );
2855 std::unique_ptr<QDomDocument> doc( new QDomDocument( documentType ) );
2856
2857 QDomElement qgisNode = doc->createElement( QStringLiteral( "qgis" ) );
2858 qgisNode.setAttribute( QStringLiteral( "projectname" ), title() );
2859 qgisNode.setAttribute( QStringLiteral( "version" ), Qgis::version() );
2860
2861 if ( !mSettings.value( QStringLiteral( "projects/anonymize_saved_projects" ), false, QgsSettings::Core ).toBool() )
2862 {
2863 const QString newSaveUser = QgsApplication::userLoginName();
2864 const QString newSaveUserFull = QgsApplication::userFullName();
2865 qgisNode.setAttribute( QStringLiteral( "saveUser" ), newSaveUser );
2866 qgisNode.setAttribute( QStringLiteral( "saveUserFull" ), newSaveUserFull );
2867 mSaveUser = newSaveUser;
2868 mSaveUserFull = newSaveUserFull;
2869 mSaveDateTime = QDateTime::currentDateTime();
2870 qgisNode.setAttribute( QStringLiteral( "saveDateTime" ), mSaveDateTime.toString( Qt::ISODate ) );
2871 }
2872 else
2873 {
2874 mSaveUser.clear();
2875 mSaveUserFull.clear();
2876 mSaveDateTime = QDateTime();
2877 }
2878 doc->appendChild( qgisNode );
2879 mSaveVersion = QgsProjectVersion( Qgis::version() );
2880
2881 QDomElement homePathNode = doc->createElement( QStringLiteral( "homePath" ) );
2882 homePathNode.setAttribute( QStringLiteral( "path" ), mHomePath );
2883 qgisNode.appendChild( homePathNode );
2884
2885 // title
2886 QDomElement titleNode = doc->createElement( QStringLiteral( "title" ) );
2887 qgisNode.appendChild( titleNode );
2888
2889 QDomElement transactionNode = doc->createElement( QStringLiteral( "transaction" ) );
2890 transactionNode.setAttribute( QStringLiteral( "mode" ), qgsEnumValueToKey( mTransactionMode ) );
2891 qgisNode.appendChild( transactionNode );
2892
2893 QDomElement flagsNode = doc->createElement( QStringLiteral( "projectFlags" ) );
2894 flagsNode.setAttribute( QStringLiteral( "set" ), qgsFlagValueToKeys( mFlags ) );
2895 qgisNode.appendChild( flagsNode );
2896
2897 const QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
2898 titleNode.appendChild( titleText );
2899
2900 // write project CRS
2901 QDomElement srsNode = doc->createElement( QStringLiteral( "projectCrs" ) );
2902 mCrs.writeXml( srsNode, *doc );
2903 qgisNode.appendChild( srsNode );
2904
2905 QDomElement elevationShadingNode = doc->createElement( QStringLiteral( "elevation-shading-renderer" ) );
2906 mElevationShadingRenderer.writeXml( elevationShadingNode, context );
2907 qgisNode.appendChild( elevationShadingNode );
2908
2909 // write layer tree - make sure it is without embedded subgroups
2910 QgsLayerTreeNode *clonedRoot = mRootGroup->clone();
2912 QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ), this ); // convert absolute paths to relative paths if required
2913
2914 clonedRoot->writeXml( qgisNode, context );
2915 delete clonedRoot;
2916
2917 mSnappingConfig.writeProject( *doc );
2918 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( mAvoidIntersectionsMode ) );
2919
2920 // let map canvas and legend write their information
2921 emit writeProject( *doc );
2922
2923 // within top level node save list of layers
2924 const QMap<QString, QgsMapLayer *> layers = mapLayers();
2925
2926 QDomElement annotationLayerNode = doc->createElement( QStringLiteral( "main-annotation-layer" ) );
2927 mMainAnnotationLayer->writeLayerXml( annotationLayerNode, *doc, context );
2928 qgisNode.appendChild( annotationLayerNode );
2929
2930 // Iterate over layers in zOrder
2931 // Call writeXml() on each
2932 QDomElement projectLayersNode = doc->createElement( QStringLiteral( "projectlayers" ) );
2933
2934 QMap<QString, QgsMapLayer *>::ConstIterator li = layers.constBegin();
2935 while ( li != layers.end() )
2936 {
2937 QgsMapLayer *ml = li.value();
2938
2939 if ( ml )
2940 {
2941 const QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.constFind( ml->id() );
2942 if ( emIt == mEmbeddedLayers.constEnd() )
2943 {
2944 QDomElement maplayerElem;
2945 // If layer is not valid, prefer to restore saved properties from invalidLayerProperties. But if that's
2946 // not available, just write what we DO have
2947 if ( ml->isValid() || ml->originalXmlProperties().isEmpty() )
2948 {
2949 // general layer metadata
2950 maplayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
2951 ml->writeLayerXml( maplayerElem, *doc, context );
2952
2954 maplayerElem.setAttribute( QStringLiteral( "editable" ), QStringLiteral( "1" ) );
2955 }
2956 else if ( ! ml->originalXmlProperties().isEmpty() )
2957 {
2958 QDomDocument document;
2959 if ( document.setContent( ml->originalXmlProperties() ) )
2960 {
2961 maplayerElem = document.firstChildElement();
2962 }
2963 else
2964 {
2965 QgsDebugError( QStringLiteral( "Could not restore layer properties for layer %1" ).arg( ml->id() ) );
2966 }
2967 }
2968
2969 emit writeMapLayer( ml, maplayerElem, *doc );
2970
2971 projectLayersNode.appendChild( maplayerElem );
2972 }
2973 else
2974 {
2975 // layer defined in an external project file
2976 // only save embedded layer if not managed by a legend group
2977 if ( emIt.value().second )
2978 {
2979 QDomElement mapLayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
2980 mapLayerElem.setAttribute( QStringLiteral( "embedded" ), 1 );
2981 mapLayerElem.setAttribute( QStringLiteral( "project" ), writePath( emIt.value().first ) );
2982 mapLayerElem.setAttribute( QStringLiteral( "id" ), ml->id() );
2983 projectLayersNode.appendChild( mapLayerElem );
2984 }
2985 }
2986 }
2987 li++;
2988 }
2989
2990 qgisNode.appendChild( projectLayersNode );
2991
2992 QDomElement layerOrderNode = doc->createElement( QStringLiteral( "layerorder" ) );
2993 const auto constCustomLayerOrder = mRootGroup->customLayerOrder();
2994 for ( QgsMapLayer *layer : constCustomLayerOrder )
2995 {
2996 QDomElement mapLayerElem = doc->createElement( QStringLiteral( "layer" ) );
2997 mapLayerElem.setAttribute( QStringLiteral( "id" ), layer->id() );
2998 layerOrderNode.appendChild( mapLayerElem );
2999 }
3000 qgisNode.appendChild( layerOrderNode );
3001
3002 mLabelingEngineSettings->writeSettingsToProject( this );
3003
3004 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), mBackgroundColor.red() );
3005 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), mBackgroundColor.green() );
3006 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), mBackgroundColor.blue() );
3007
3008 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), mSelectionColor.red() );
3009 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), mSelectionColor.green() );
3010 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), mSelectionColor.blue() );
3011 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), mSelectionColor.alpha() );
3012
3013 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QgsUnitTypes::encodeUnit( mDistanceUnits ) );
3014 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QgsUnitTypes::encodeUnit( mAreaUnits ) );
3015
3016 // now add the optional extra properties
3017#if 0
3018 dump_( mProperties );
3019#endif
3020
3021 QgsDebugMsgLevel( QStringLiteral( "there are %1 property scopes" ).arg( static_cast<int>( mProperties.count() ) ), 2 );
3022
3023 if ( !mProperties.isEmpty() ) // only worry about properties if we
3024 // actually have any properties
3025 {
3026 mProperties.writeXml( QStringLiteral( "properties" ), qgisNode, *doc );
3027 }
3028
3029 QDomElement ddElem = doc->createElement( QStringLiteral( "dataDefinedServerProperties" ) );
3030 mDataDefinedServerProperties.writeXml( ddElem, dataDefinedServerPropertyDefinitions() );
3031 qgisNode.appendChild( ddElem );
3032
3033 mMapThemeCollection->writeXml( *doc );
3034
3035 mTransformContext.writeXml( qgisNode, context );
3036
3037 QDomElement metadataElem = doc->createElement( QStringLiteral( "projectMetadata" ) );
3038 mMetadata.writeMetadataXml( metadataElem, *doc );
3039 qgisNode.appendChild( metadataElem );
3040
3041 {
3042 const QDomElement annotationsElem = mAnnotationManager->writeXml( *doc, context );
3043 qgisNode.appendChild( annotationsElem );
3044 }
3045
3046 {
3047 const QDomElement layoutElem = mLayoutManager->writeXml( *doc );
3048 qgisNode.appendChild( layoutElem );
3049 }
3050
3051 {
3052 const QDomElement views3DElem = m3DViewsManager->writeXml( *doc );
3053 qgisNode.appendChild( views3DElem );
3054 }
3055
3056 {
3057 const QDomElement bookmarkElem = mBookmarkManager->writeXml( *doc );
3058 qgisNode.appendChild( bookmarkElem );
3059 }
3060
3061 {
3062 const QDomElement sensorElem = mSensorManager->writeXml( *doc );
3063 qgisNode.appendChild( sensorElem );
3064 }
3065
3066 {
3067 const QDomElement viewSettingsElem = mViewSettings->writeXml( *doc, context );
3068 qgisNode.appendChild( viewSettingsElem );
3069 }
3070
3071 {
3072 const QDomElement styleSettingsElem = mStyleSettings->writeXml( *doc, context );
3073 qgisNode.appendChild( styleSettingsElem );
3074 }
3075
3076 {
3077 const QDomElement timeSettingsElement = mTimeSettings->writeXml( *doc, context );
3078 qgisNode.appendChild( timeSettingsElement );
3079 }
3080
3081 {
3082 const QDomElement elevationPropertiesElement = mElevationProperties->writeXml( *doc, context );
3083 qgisNode.appendChild( elevationPropertiesElement );
3084 }
3085
3086 {
3087 const QDomElement displaySettingsElem = mDisplaySettings->writeXml( *doc, context );
3088 qgisNode.appendChild( displaySettingsElem );
3089 }
3090
3091 {
3092 const QDomElement gpsSettingsElem = mGpsSettings->writeXml( *doc, context );
3093 qgisNode.appendChild( gpsSettingsElem );
3094 }
3095
3096 // now wrap it up and ship it to the project file
3097 doc->normalize(); // XXX I'm not entirely sure what this does
3098
3099 // Create backup file
3100 if ( QFile::exists( fileName() ) )
3101 {
3102 QFile backupFile( QStringLiteral( "%1~" ).arg( filename ) );
3103 bool ok = true;
3104 ok &= backupFile.open( QIODevice::WriteOnly | QIODevice::Truncate );
3105 ok &= projectFile.open( QIODevice::ReadOnly );
3106
3107 QByteArray ba;
3108 while ( ok && !projectFile.atEnd() )
3109 {
3110 ba = projectFile.read( 10240 );
3111 ok &= backupFile.write( ba ) == ba.size();
3112 }
3113
3114 projectFile.close();
3115 backupFile.close();
3116
3117 if ( !ok )
3118 {
3119 setError( tr( "Unable to create backup file %1" ).arg( backupFile.fileName() ) );
3120 return false;
3121 }
3122
3123 const QFileInfo fi( fileName() );
3124 struct utimbuf tb = { static_cast<time_t>( fi.lastRead().toSecsSinceEpoch() ), static_cast<time_t>( fi.lastModified().toSecsSinceEpoch() ) };
3125 utime( backupFile.fileName().toUtf8().constData(), &tb );
3126 }
3127
3128 if ( !projectFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
3129 {
3130 projectFile.close(); // even though we got an error, let's make
3131 // sure it's closed anyway
3132
3133 setError( tr( "Unable to save to file %1" ).arg( projectFile.fileName() ) );
3134 return false;
3135 }
3136
3137 QTemporaryFile tempFile;
3138 bool ok = tempFile.open();
3139 if ( ok )
3140 {
3141 QTextStream projectFileStream( &tempFile );
3142 doc->save( projectFileStream, 2 ); // save as utf-8
3143 ok &= projectFileStream.pos() > -1;
3144
3145 ok &= tempFile.seek( 0 );
3146
3147 QByteArray ba;
3148 while ( ok && !tempFile.atEnd() )
3149 {
3150 ba = tempFile.read( 10240 );
3151 ok &= projectFile.write( ba ) == ba.size();
3152 }
3153
3154 ok &= projectFile.error() == QFile::NoError;
3155
3156 projectFile.close();
3157 }
3158
3159 tempFile.close();
3160
3161 if ( !ok )
3162 {
3163 setError( tr( "Unable to save to file %1. Your project "
3164 "may be corrupted on disk. Try clearing some space on the volume and "
3165 "check file permissions before pressing save again." )
3166 .arg( projectFile.fileName() ) );
3167 return false;
3168 }
3169
3170 setDirty( false ); // reset to pristine state
3171
3172 emit projectSaved();
3173 return true;
3174}
3175
3176bool QgsProject::writeEntry( const QString &scope, QString const &key, bool value )
3177{
3179
3180 bool propertiesModified;
3181 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3182
3183 if ( propertiesModified )
3184 setDirty( true );
3185
3186 return success;
3187}
3188
3189bool QgsProject::writeEntry( const QString &scope, const QString &key, double value )
3190{
3192
3193 bool propertiesModified;
3194 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3195
3196 if ( propertiesModified )
3197 setDirty( true );
3198
3199 return success;
3200}
3201
3202bool QgsProject::writeEntry( const QString &scope, QString const &key, int value )
3203{
3205
3206 bool propertiesModified;
3207 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3208
3209 if ( propertiesModified )
3210 setDirty( true );
3211
3212 return success;
3213}
3214
3215bool QgsProject::writeEntry( const QString &scope, const QString &key, const QString &value )
3216{
3218
3219 bool propertiesModified;
3220 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3221
3222 if ( propertiesModified )
3223 setDirty( true );
3224
3225 return success;
3226}
3227
3228bool QgsProject::writeEntry( const QString &scope, const QString &key, const QStringList &value )
3229{
3231
3232 bool propertiesModified;
3233 const bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
3234
3235 if ( propertiesModified )
3236 setDirty( true );
3237
3238 return success;
3239}
3240
3241QStringList QgsProject::readListEntry( const QString &scope,
3242 const QString &key,
3243 const QStringList &def,
3244 bool *ok ) const
3245{
3246 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
3248
3249 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3250
3251 QVariant value;
3252
3253 if ( property )
3254 {
3255 value = property->value();
3256
3257 const bool valid = QVariant::StringList == value.type();
3258 if ( ok )
3259 *ok = valid;
3260
3261 if ( valid )
3262 {
3263 return value.toStringList();
3264 }
3265 }
3266 else if ( ok )
3267 *ok = false;
3268
3269
3270 return def;
3271}
3272
3273QString QgsProject::readEntry( const QString &scope,
3274 const QString &key,
3275 const QString &def,
3276 bool *ok ) const
3277{
3279
3280 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3281
3282 QVariant value;
3283
3284 if ( property )
3285 {
3286 value = property->value();
3287
3288 const bool valid = value.canConvert( QVariant::String );
3289 if ( ok )
3290 *ok = valid;
3291
3292 if ( valid )
3293 return value.toString();
3294 }
3295 else if ( ok )
3296 *ok = false;
3297
3298 return def;
3299}
3300
3301int QgsProject::readNumEntry( const QString &scope, const QString &key, int def,
3302 bool *ok ) const
3303{
3305
3306 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3307
3308 QVariant value;
3309
3310 if ( property )
3311 {
3312 value = property->value();
3313 }
3314
3315 const bool valid = value.canConvert( QVariant::Int );
3316
3317 if ( ok )
3318 {
3319 *ok = valid;
3320 }
3321
3322 if ( valid )
3323 {
3324 return value.toInt();
3325 }
3326
3327 return def;
3328}
3329
3330double QgsProject::readDoubleEntry( const QString &scope, const QString &key,
3331 double def,
3332 bool *ok ) const
3333{
3335
3336 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3337 if ( property )
3338 {
3339 const QVariant value = property->value();
3340
3341 const bool valid = value.canConvert( QVariant::Double );
3342 if ( ok )
3343 *ok = valid;
3344
3345 if ( valid )
3346 return value.toDouble();
3347 }
3348 else if ( ok )
3349 *ok = false;
3350
3351 return def;
3352}
3353
3354bool QgsProject::readBoolEntry( const QString &scope, const QString &key, bool def,
3355 bool *ok ) const
3356{
3358
3359 QgsProjectProperty *property = findKey_( scope, key, mProperties );
3360
3361 if ( property )
3362 {
3363 const QVariant value = property->value();
3364
3365 const bool valid = value.canConvert( QVariant::Bool );
3366 if ( ok )
3367 *ok = valid;
3368
3369 if ( valid )
3370 return value.toBool();
3371 }
3372 else if ( ok )
3373 *ok = false;
3374
3375 return def;
3376}
3377
3378bool QgsProject::removeEntry( const QString &scope, const QString &key )
3379{
3381
3382 if ( findKey_( scope, key, mProperties ) )
3383 {
3384 removeKey_( scope, key, mProperties );
3385 setDirty( true );
3386 }
3387
3388 return !findKey_( scope, key, mProperties );
3389}
3390
3391QStringList QgsProject::entryList( const QString &scope, const QString &key ) const
3392{
3394
3395 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3396
3397 QStringList entries;
3398
3399 if ( foundProperty )
3400 {
3401 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3402
3403 if ( propertyKey )
3404 { propertyKey->entryList( entries ); }
3405 }
3406
3407 return entries;
3408}
3409
3410QStringList QgsProject::subkeyList( const QString &scope, const QString &key ) const
3411{
3413
3414 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
3415
3416 QStringList entries;
3417
3418 if ( foundProperty )
3419 {
3420 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
3421
3422 if ( propertyKey )
3423 { propertyKey->subkeyList( entries ); }
3424 }
3425
3426 return entries;
3427}
3428
3430{
3432
3433 dump_( mProperties );
3434}
3435
3437{
3439
3440 QString filePath;
3441 switch ( filePathStorage() )
3442 {
3444 break;
3445
3447 {
3448 // for projects stored in a custom storage, we need to ask to the
3449 // storage for the path, if the storage returns an empty path
3450 // relative paths are not supported
3451 if ( QgsProjectStorage *storage = projectStorage() )
3452 {
3453 filePath = storage->filePath( mFile.fileName() );
3454 }
3455 else
3456 {
3457 filePath = fileName();
3458 }
3459 break;
3460 }
3461 }
3462
3463 return QgsPathResolver( filePath, mArchive->dir() );
3464}
3465
3466QString QgsProject::readPath( const QString &src ) const
3467{
3469
3470 return pathResolver().readPath( src );
3471}
3472
3473QString QgsProject::writePath( const QString &src ) const
3474{
3476
3477 return pathResolver().writePath( src );
3478}
3479
3480void QgsProject::setError( const QString &errorMessage )
3481{
3483
3484 mErrorMessage = errorMessage;
3485}
3486
3487QString QgsProject::error() const
3488{
3490
3491 return mErrorMessage;
3492}
3493
3494void QgsProject::clearError()
3495{
3497
3498 setError( QString() );
3499}
3500
3502{
3504
3505 delete mBadLayerHandler;
3506 mBadLayerHandler = handler;
3507}
3508
3509QString QgsProject::layerIsEmbedded( const QString &id ) const
3510{
3512
3513 const QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
3514 if ( it == mEmbeddedLayers.constEnd() )
3515 {
3516 return QString();
3517 }
3518 return it.value().first;
3519}
3520
3521bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &projectFilePath, QList<QDomNode> &brokenNodes,
3522 bool saveFlag, Qgis::ProjectReadFlags flags )
3523{
3525
3527
3528 static QString sPrevProjectFilePath;
3529 static QDateTime sPrevProjectFileTimestamp;
3530 static QDomDocument sProjectDocument;
3531
3532 QString qgsProjectFile = projectFilePath;
3533 QgsProjectArchive archive;
3534 if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
3535 {
3536 archive.unzip( projectFilePath );
3537 qgsProjectFile = archive.projectFile();
3538 }
3539
3540 const QDateTime projectFileTimestamp = QFileInfo( projectFilePath ).lastModified();
3541
3542 if ( projectFilePath != sPrevProjectFilePath || projectFileTimestamp != sPrevProjectFileTimestamp )
3543 {
3544 sPrevProjectFilePath.clear();
3545
3546 QFile projectFile( qgsProjectFile );
3547 if ( !projectFile.open( QIODevice::ReadOnly ) )
3548 {
3549 return false;
3550 }
3551
3552 if ( !sProjectDocument.setContent( &projectFile ) )
3553 {
3554 return false;
3555 }
3556
3557 sPrevProjectFilePath = projectFilePath;
3558 sPrevProjectFileTimestamp = projectFileTimestamp;
3559 }
3560
3561 // does project store paths absolute or relative?
3562 bool useAbsolutePaths = true;
3563
3564 const QDomElement propertiesElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "properties" ) );
3565 if ( !propertiesElem.isNull() )
3566 {
3567 const QDomElement absElem = propertiesElem.firstChildElement( QStringLiteral( "Paths" ) ).firstChildElement( QStringLiteral( "Absolute" ) );
3568 if ( !absElem.isNull() )
3569 {
3570 useAbsolutePaths = absElem.text().compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
3571 }
3572 }
3573
3574 QgsReadWriteContext embeddedContext;
3575 if ( !useAbsolutePaths )
3576 embeddedContext.setPathResolver( QgsPathResolver( projectFilePath ) );
3577 embeddedContext.setProjectTranslator( this );
3578 embeddedContext.setTransformContext( transformContext() );
3579
3580 const QDomElement projectLayersElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "projectlayers" ) );
3581 if ( projectLayersElem.isNull() )
3582 {
3583 return false;
3584 }
3585
3586 QDomElement mapLayerElem = projectLayersElem.firstChildElement( QStringLiteral( "maplayer" ) );
3587 while ( ! mapLayerElem.isNull() )
3588 {
3589 // get layer id
3590 const QString id = mapLayerElem.firstChildElement( QStringLiteral( "id" ) ).text();
3591 if ( id == layerId )
3592 {
3593 // layer can be embedded only once
3594 if ( mapLayerElem.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
3595 {
3596 return false;
3597 }
3598
3599 mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
3600
3601 if ( addLayer( mapLayerElem, brokenNodes, embeddedContext, flags ) )
3602 {
3603 return true;
3604 }
3605 else
3606 {
3607 mEmbeddedLayers.remove( layerId );
3608 return false;
3609 }
3610 }
3611 mapLayerElem = mapLayerElem.nextSiblingElement( QStringLiteral( "maplayer" ) );
3612 }
3613
3614 return false;
3615}
3616
3617QgsLayerTreeGroup *QgsProject::createEmbeddedGroup( const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers, Qgis::ProjectReadFlags flags )
3618{
3620
3621 QString qgsProjectFile = projectFilePath;
3622 QgsProjectArchive archive;
3623 if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
3624 {
3625 archive.unzip( projectFilePath );
3626 qgsProjectFile = archive.projectFile();
3627 }
3628
3629 // open project file, get layer ids in group, add the layers
3630 QFile projectFile( qgsProjectFile );
3631 if ( !projectFile.open( QIODevice::ReadOnly ) )
3632 {
3633 return nullptr;
3634 }
3635
3636 QDomDocument projectDocument;
3637 if ( !projectDocument.setContent( &projectFile ) )
3638 {
3639 return nullptr;
3640 }
3641
3642 QgsReadWriteContext context;
3643 context.setPathResolver( pathResolver() );
3644 context.setProjectTranslator( this );
3646
3648
3649 QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
3650 if ( !layerTreeElem.isNull() )
3651 {
3652 root->readChildrenFromXml( layerTreeElem, context );
3653 }
3654 else
3655 {
3656 QgsLayerTreeUtils::readOldLegend( root, projectDocument.documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
3657 }
3658
3659 QgsLayerTreeGroup *group = root->findGroup( groupName );
3660 if ( !group || group->customProperty( QStringLiteral( "embedded" ) ).toBool() )
3661 {
3662 // embedded groups cannot be embedded again
3663 delete root;
3664 return nullptr;
3665 }
3666
3667 // clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
3668 QgsLayerTreeGroup *newGroup = QgsLayerTree::toGroup( group->clone() );
3669 delete root;
3670 root = nullptr;
3671
3672 newGroup->setCustomProperty( QStringLiteral( "embedded" ), 1 );
3673 newGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectFilePath );
3674
3675 // set "embedded" to all children + load embedded layers
3676 mLayerTreeRegistryBridge->setEnabled( false );
3677 initializeEmbeddedSubtree( projectFilePath, newGroup, flags );
3678 mLayerTreeRegistryBridge->setEnabled( true );
3679
3680 // consider the layers might be identify disabled in its project
3681 const auto constFindLayerIds = newGroup->findLayerIds();
3682 for ( const QString &layerId : constFindLayerIds )
3683 {
3684 QgsLayerTreeLayer *layer = newGroup->findLayer( layerId );
3685 if ( layer )
3686 {
3687 layer->resolveReferences( this );
3688 layer->setItemVisibilityChecked( !invisibleLayers.contains( layerId ) );
3689 }
3690 }
3691
3692 return newGroup;
3693}
3694
3695void QgsProject::initializeEmbeddedSubtree( const QString &projectFilePath, QgsLayerTreeGroup *group, Qgis::ProjectReadFlags flags )
3696{
3698
3699 const auto constChildren = group->children();
3700 for ( QgsLayerTreeNode *child : constChildren )
3701 {
3702 // all nodes in the subtree will have "embedded" custom property set
3703 child->setCustomProperty( QStringLiteral( "embedded" ), 1 );
3704
3705 if ( QgsLayerTree::isGroup( child ) )
3706 {
3707 initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ), flags );
3708 }
3709 else if ( QgsLayerTree::isLayer( child ) )
3710 {
3711 // load the layer into our project
3712 QList<QDomNode> brokenNodes;
3713 createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, false, flags );
3714 }
3715 }
3716}
3717
3719{
3721
3723}
3724
3725void QgsProject::setEvaluateDefaultValues( bool evaluateDefaultValues )
3726{
3728
3730}
3731
3733{
3735
3736 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), ( enabled ? 1 : 0 ) );
3738}
3739
3741{
3743
3744 return readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), 0 );
3745}
3746
3748{
3750
3751 if ( mDistanceUnits == unit )
3752 return;
3753
3754 mDistanceUnits = unit;
3755
3756 emit distanceUnitsChanged();
3757}
3758
3760{
3762
3763 if ( mAreaUnits == unit )
3764 return;
3765
3766 mAreaUnits = unit;
3767
3768 emit areaUnitsChanged();
3769}
3770
3772{
3773 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
3775
3776 if ( !mCachedHomePath.isEmpty() )
3777 return mCachedHomePath;
3778
3779 const QFileInfo pfi( fileName() );
3780
3781 if ( !mHomePath.isEmpty() )
3782 {
3783 const QFileInfo homeInfo( mHomePath );
3784 if ( !homeInfo.isRelative() )
3785 {
3786 mCachedHomePath = mHomePath;
3787 return mHomePath;
3788 }
3789 }
3790 else if ( !fileName().isEmpty() )
3791 {
3792 mCachedHomePath = pfi.path();
3793
3794 return mCachedHomePath;
3795 }
3796
3797 if ( !pfi.exists() )
3798 {
3799 mCachedHomePath = mHomePath;
3800 return mHomePath;
3801 }
3802
3803 if ( !mHomePath.isEmpty() )
3804 {
3805 // path is relative to project file
3806 mCachedHomePath = QDir::cleanPath( pfi.path() + '/' + mHomePath );
3807 }
3808 else
3809 {
3810 mCachedHomePath = pfi.canonicalPath();
3811 }
3812 return mCachedHomePath;
3813}
3814
3816{
3818
3819 return mHomePath;
3820}
3821
3823{
3824 // because relation aggregate functions are not thread safe
3826
3827 return mRelationManager;
3828}
3829
3831{
3833
3834 return mLayoutManager.get();
3835}
3836
3838{
3840
3841 return mLayoutManager.get();
3842}
3843
3845{
3847
3848 return m3DViewsManager.get();
3849}
3850
3852{
3854
3855 return m3DViewsManager.get();
3856}
3857
3859{
3861
3862 return mBookmarkManager;
3863}
3864
3866{
3868
3869 return mBookmarkManager;
3870}
3871
3873{
3875
3876 return mSensorManager;
3877}
3878
3880{
3882
3883 return mSensorManager;
3884}
3885
3887{
3889
3890 return mViewSettings;
3891}
3892
3894{
3896
3897 return mViewSettings;
3898}
3899
3901{
3903
3904 return mStyleSettings;
3905}
3906
3908{
3909 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
3911
3912 return mStyleSettings;
3913}
3914
3916{
3918
3919 return mTimeSettings;
3920}
3921
3923{
3925
3926 return mTimeSettings;
3927}
3928
3930{
3932
3933 return mElevationProperties;
3934}
3935
3937{
3939
3940 return mElevationProperties;
3941}
3942
3944{
3946
3947 return mDisplaySettings;
3948}
3949
3951{
3953
3954 return mDisplaySettings;
3955}
3956
3958{
3960
3961 return mGpsSettings;
3962}
3963
3965{
3967
3968 return mGpsSettings;
3969}
3970
3972{
3974
3975 return mRootGroup;
3976}
3977
3979{
3981
3982 return mMapThemeCollection.get();
3983}
3984
3986{
3988
3989 return mAnnotationManager.get();
3990}
3991
3993{
3995
3996 return mAnnotationManager.get();
3997}
3998
3999void QgsProject::setNonIdentifiableLayers( const QList<QgsMapLayer *> &layers )
4000{
4002
4003 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
4004 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
4005 {
4006 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4007 continue;
4008
4009 if ( layers.contains( it.value() ) )
4010 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Identifiable );
4011 else
4012 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Identifiable );
4013 }
4014
4018}
4019
4020void QgsProject::setNonIdentifiableLayers( const QStringList &layerIds )
4021{
4023
4024 QList<QgsMapLayer *> nonIdentifiableLayers;
4025 nonIdentifiableLayers.reserve( layerIds.count() );
4026 for ( const QString &layerId : layerIds )
4027 {
4028 QgsMapLayer *layer = mapLayer( layerId );
4029 if ( layer )
4030 nonIdentifiableLayers << layer;
4031 }
4035}
4036
4038{
4040
4041 QStringList nonIdentifiableLayers;
4042
4043 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
4044 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
4045 {
4046 if ( !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
4047 {
4048 nonIdentifiableLayers.append( it.value()->id() );
4049 }
4050 }
4051 return nonIdentifiableLayers;
4052}
4053
4055{
4057
4058 return mTransactionMode == Qgis::TransactionMode::AutomaticGroups;
4059}
4060
4061void QgsProject::setAutoTransaction( bool autoTransaction )
4062{
4064
4065 if ( autoTransaction
4066 && mTransactionMode == Qgis::TransactionMode::AutomaticGroups )
4067 return;
4068
4069 if ( ! autoTransaction
4070 && mTransactionMode == Qgis::TransactionMode::Disabled )
4071 return;
4072
4073 if ( autoTransaction )
4074 mTransactionMode = Qgis::TransactionMode::AutomaticGroups;
4075 else
4076 mTransactionMode = Qgis::TransactionMode::Disabled;
4077
4078 updateTransactionGroups();
4079}
4080
4082{
4084
4085 return mTransactionMode;
4086}
4087
4089{
4091
4092 if ( transactionMode == mTransactionMode )
4093 return true;
4094
4095 // Check that all layer are not in edit mode
4096 const auto constLayers = mapLayers().values();
4097 for ( QgsMapLayer *layer : constLayers )
4098 {
4099 if ( layer->isEditable() )
4100 {
4101 QgsLogger::warning( tr( "Transaction mode can be changed only if all layers are not editable." ) );
4102 return false;
4103 }
4104 }
4105
4106 mTransactionMode = transactionMode;
4107 updateTransactionGroups();
4108 return true;
4109}
4110
4111QMap<QPair<QString, QString>, QgsTransactionGroup *> QgsProject::transactionGroups()
4112{
4114
4115 return mTransactionGroups;
4116}
4117
4118
4119//
4120// QgsMapLayerStore methods
4121//
4122
4123
4125{
4127
4128 return mLayerStore->count();
4129}
4130
4132{
4134
4135 return mLayerStore->validCount();
4136}
4137
4138QgsMapLayer *QgsProject::mapLayer( const QString &layerId ) const
4139{
4140 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
4142
4143 return mLayerStore->mapLayer( layerId );
4144}
4145
4146QList<QgsMapLayer *> QgsProject::mapLayersByName( const QString &layerName ) const
4147{
4149
4150 return mLayerStore->mapLayersByName( layerName );
4151}
4152
4153QList<QgsMapLayer *> QgsProject::mapLayersByShortName( const QString &shortName ) const
4154{
4156
4157 QList<QgsMapLayer *> layers;
4158 const auto constMapLayers { mLayerStore->mapLayers() };
4159 for ( const auto &l : constMapLayers )
4160 {
4161 if ( ! l->shortName().isEmpty() )
4162 {
4163 if ( l->shortName() == shortName )
4164 layers << l;
4165 }
4166 else if ( l->name() == shortName )
4167 {
4168 layers << l;
4169 }
4170 }
4171 return layers;
4172}
4173
4174bool QgsProject::unzip( const QString &filename, Qgis::ProjectReadFlags flags )
4175{
4177
4178 clearError();
4179 std::unique_ptr<QgsProjectArchive> archive( new QgsProjectArchive() );
4180
4181 // unzip the archive
4182 if ( !archive->unzip( filename ) )
4183 {
4184 setError( tr( "Unable to unzip file '%1'" ).arg( filename ) );
4185 return false;
4186 }
4187
4188 // test if zip provides a .qgs file
4189 if ( archive->projectFile().isEmpty() )
4190 {
4191 setError( tr( "Zip archive does not provide a project file" ) );
4192 return false;
4193 }
4194
4195 // Keep the archive
4196 mArchive = std::move( archive );
4197
4198 // load auxiliary storage
4199 if ( !static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile().isEmpty() )
4200 {
4201 // database file is already a copy as it's been unzipped. So we don't open
4202 // auxiliary storage in copy mode in this case
4203 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile(), false ) );
4204 }
4205 else
4206 {
4207 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
4208 }
4209
4210 // read the project file
4211 if ( ! readProjectFile( static_cast<QgsProjectArchive *>( mArchive.get() )->projectFile(), flags ) )
4212 {
4213 setError( tr( "Cannot read unzipped qgs project file" ) + QStringLiteral( ": " ) + error() );
4214 return false;
4215 }
4216
4217 // Remove the temporary .qgs file
4218 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4219
4220 return true;
4221}
4222
4223bool QgsProject::zip( const QString &filename )
4224{
4226
4227 clearError();
4228
4229 // save the current project in a temporary .qgs file
4230 std::unique_ptr<QgsProjectArchive> archive( new QgsProjectArchive() );
4231 const QString baseName = QFileInfo( filename ).baseName();
4232 const QString qgsFileName = QStringLiteral( "%1.qgs" ).arg( baseName );
4233 QFile qgsFile( QDir( archive->dir() ).filePath( qgsFileName ) );
4234
4235 bool writeOk = false;
4236 if ( qgsFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
4237 {
4238 writeOk = writeProjectFile( qgsFile.fileName() );
4239 qgsFile.close();
4240 }
4241
4242 // stop here with an error message
4243 if ( ! writeOk )
4244 {
4245 setError( tr( "Unable to write temporary qgs file" ) );
4246 return false;
4247 }
4248
4249 // save auxiliary storage
4250 const QFileInfo info( qgsFile );
4251 const QString asExt = QStringLiteral( ".%1" ).arg( QgsAuxiliaryStorage::extension() );
4252 const QString asFileName = info.path() + QDir::separator() + info.completeBaseName() + asExt;
4253
4254 bool auxiliaryStorageSavedOk = true;
4255 if ( ! saveAuxiliaryStorage( asFileName ) )
4256 {
4257 const QString err = mAuxiliaryStorage->errorString();
4258 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 ) );
4259 auxiliaryStorageSavedOk = false;
4260
4261 // fixes the current archive and keep the previous version of qgd
4262 if ( !mArchive->exists() )
4263 {
4264 mArchive.reset( new QgsProjectArchive() );
4265 mArchive->unzip( mFile.fileName() );
4266 static_cast<QgsProjectArchive *>( mArchive.get() )->clearProjectFile();
4267
4268 const QString auxiliaryStorageFile = static_cast<QgsProjectArchive *>( mArchive.get() )->auxiliaryStorageFile();
4269 if ( ! auxiliaryStorageFile.isEmpty() )
4270 {
4271 archive->addFile( auxiliaryStorageFile );
4272 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( auxiliaryStorageFile, false ) );
4273 }
4274 }
4275 }
4276 else
4277 {
4278 // in this case, an empty filename means that the auxiliary database is
4279 // empty, so we don't want to save it
4280 if ( QFile::exists( asFileName ) )
4281 {
4282 archive->addFile( asFileName );
4283 }
4284 }
4285
4286 // create the archive
4287 archive->addFile( qgsFile.fileName() );
4288
4289 // Add all other files
4290 const QStringList &files = mArchive->files();
4291 for ( const QString &file : files )
4292 {
4293 if ( !file.endsWith( QLatin1String( ".qgs" ), Qt::CaseInsensitive ) && !file.endsWith( asExt, Qt::CaseInsensitive ) )
4294 {
4295 archive->addFile( file );
4296 }
4297 }
4298
4299 // zip
4300 bool zipOk = true;
4301 if ( !archive->zip( filename ) )
4302 {
4303 setError( tr( "Unable to perform zip" ) );
4304 zipOk = false;
4305 }
4306
4307 return auxiliaryStorageSavedOk && zipOk;
4308}
4309
4311{
4313
4314 return QgsZipUtils::isZipFile( mFile.fileName() );
4315}
4316
4317QList<QgsMapLayer *> QgsProject::addMapLayers(
4318 const QList<QgsMapLayer *> &layers,
4319 bool addToLegend,
4320 bool takeOwnership )
4321{
4323
4324 const QList<QgsMapLayer *> myResultList { mLayerStore->addMapLayers( layers, takeOwnership ) };
4325 if ( !myResultList.isEmpty() )
4326 {
4327 // Update transform context
4328 for ( auto &l : myResultList )
4329 {
4330 l->setTransformContext( transformContext() );
4331 }
4332 if ( addToLegend )
4333 {
4334 emit legendLayersAdded( myResultList );
4335 }
4336 }
4337
4338 if ( mAuxiliaryStorage )
4339 {
4340 for ( QgsMapLayer *mlayer : myResultList )
4341 {
4342 if ( mlayer->type() != Qgis::LayerType::Vector )
4343 continue;
4344
4345 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mlayer );
4346 if ( vl )
4347 {
4348 vl->loadAuxiliaryLayer( *mAuxiliaryStorage );
4349 }
4350 }
4351 }
4352
4353 mProjectScope.reset();
4354
4355 return myResultList;
4356}
4357
4360 bool addToLegend,
4361 bool takeOwnership )
4362{
4364
4365 QList<QgsMapLayer *> addedLayers;
4366 addedLayers = addMapLayers( QList<QgsMapLayer *>() << layer, addToLegend, takeOwnership );
4367 return addedLayers.isEmpty() ? nullptr : addedLayers[0];
4368}
4369
4370void QgsProject::removeAuxiliaryLayer( const QgsMapLayer *ml )
4371{
4373
4374 if ( ! ml || ml->type() != Qgis::LayerType::Vector )
4375 return;
4376
4377 const QgsVectorLayer *vl = qobject_cast<const QgsVectorLayer *>( ml );
4378 if ( vl && vl->auxiliaryLayer() )
4379 {
4380 const QgsDataSourceUri uri( vl->auxiliaryLayer()->source() );
4382 }
4383}
4384
4385void QgsProject::removeMapLayers( const QStringList &layerIds )
4386{
4388
4389 for ( const auto &layerId : layerIds )
4390 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4391
4392 mProjectScope.reset();
4393 mLayerStore->removeMapLayers( layerIds );
4394}
4395
4396void QgsProject::removeMapLayers( const QList<QgsMapLayer *> &layers )
4397{
4399
4400 for ( const auto &layer : layers )
4401 removeAuxiliaryLayer( layer );
4402
4403 mProjectScope.reset();
4404 mLayerStore->removeMapLayers( layers );
4405}
4406
4407void QgsProject::removeMapLayer( const QString &layerId )
4408{
4410
4411 removeAuxiliaryLayer( mLayerStore->mapLayer( layerId ) );
4412 mProjectScope.reset();
4413 mLayerStore->removeMapLayer( layerId );
4414}
4415
4417{
4419
4420 removeAuxiliaryLayer( layer );
4421 mProjectScope.reset();
4422 mLayerStore->removeMapLayer( layer );
4423}
4424
4426{
4428
4429 mProjectScope.reset();
4430 return mLayerStore->takeMapLayer( layer );
4431}
4432
4434{
4436
4437 return mMainAnnotationLayer;
4438}
4439
4441{
4443
4444 if ( mLayerStore->count() == 0 )
4445 return;
4446
4447 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
4448 mProjectScope.reset();
4449 mLayerStore->removeAllMapLayers();
4450
4451 snapSingleBlocker.release();
4452 mSnappingConfig.clearIndividualLayerSettings();
4453 if ( !mBlockSnappingUpdates )
4454 emit snappingConfigChanged( mSnappingConfig );
4455}
4456
4458{
4460
4461 const QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
4462 QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin();
4463 for ( ; it != layers.constEnd(); ++it )
4464 {
4465 it.value()->reload();
4466 }
4467}
4468
4469QMap<QString, QgsMapLayer *> QgsProject::mapLayers( const bool validOnly ) const
4470{
4471 // because QgsVirtualLayerProvider is not anywhere NEAR thread safe:
4473
4474 return validOnly ? mLayerStore->validMapLayers() : mLayerStore->mapLayers();
4475}
4476
4477QgsTransactionGroup *QgsProject::transactionGroup( const QString &providerKey, const QString &connString )
4478{
4480
4481 return mTransactionGroups.value( qMakePair( providerKey, connString ) );
4482}
4483
4485{
4487
4488 return &mEditBufferGroup;
4489}
4490
4492{
4494
4496
4497 // TODO QGIS 4.0 -- remove this method, and place it somewhere in app (where it belongs)
4498 // 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)
4499 if ( mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), QStringLiteral( "NoAction" ), QgsSettings::App ).toString() == QStringLiteral( "UseProjectCrs" )
4500 || mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), 0, QgsSettings::App ).toString() == QLatin1String( "2" ) )
4501 {
4502 // for new layers if the new layer crs method is set to either prompt or use project, then we use the project crs
4503 defaultCrs = crs();
4504 }
4505 else
4506 {
4507 // global crs
4508 const QString layerDefaultCrs = mSettings.value( QStringLiteral( "/Projections/layerDefaultCrs" ), geoEpsgCrsAuthId() ).toString();
4509 defaultCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( layerDefaultCrs );
4510 }
4511
4512 return defaultCrs;
4513}
4514
4516{
4518
4520}
4521
4523{
4525
4527}
4528
4529bool QgsProject::saveAuxiliaryStorage( const QString &filename )
4530{
4532
4533 const QMap<QString, QgsMapLayer *> layers = mapLayers();
4534 bool empty = true;
4535 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
4536 {
4537 if ( it.value()->type() != Qgis::LayerType::Vector )
4538 continue;
4539
4540 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
4541 if ( vl && vl->auxiliaryLayer() )
4542 {
4543 vl->auxiliaryLayer()->save();
4544 empty &= vl->auxiliaryLayer()->auxiliaryFields().isEmpty();
4545 }
4546 }
4547
4548 if ( !mAuxiliaryStorage->exists( *this ) && empty )
4549 {
4550 return true; // it's not an error
4551 }
4552 else if ( !filename.isEmpty() )
4553 {
4554 return mAuxiliaryStorage->saveAs( filename );
4555 }
4556 else
4557 {
4558 return mAuxiliaryStorage->saveAs( *this );
4559 }
4560}
4561
4562QgsPropertiesDefinition &QgsProject::dataDefinedServerPropertyDefinitions()
4563{
4564 static QgsPropertiesDefinition sPropertyDefinitions
4565 {
4566 {
4567 QgsProject::DataDefinedServerProperty::WMSOnlineResource,
4568 QgsPropertyDefinition( "WMSOnlineResource", QObject::tr( "WMS Online Resource" ), QgsPropertyDefinition::String )
4569 },
4570 };
4571 return sPropertyDefinitions;
4572}
4573
4575{
4576 mElevationShadingRenderer = elevationShadingRenderer;
4578}
4579
4581{
4583
4584 return mAuxiliaryStorage.get();
4585}
4586
4588{
4590
4591 return mAuxiliaryStorage.get();
4592}
4593
4594QString QgsProject::createAttachedFile( const QString &nameTemplate )
4595{
4597
4598 const QDir archiveDir( mArchive->dir() );
4599 QTemporaryFile tmpFile( archiveDir.filePath( "XXXXXX_" + nameTemplate ), this );
4600 tmpFile.setAutoRemove( false );
4601 tmpFile.open();
4602 mArchive->addFile( tmpFile.fileName() );
4603 return tmpFile.fileName();
4604}
4605
4606QStringList QgsProject::attachedFiles() const
4607{
4609
4610 QStringList attachments;
4611 const QString baseName = QFileInfo( fileName() ).baseName();
4612 const QStringList files = mArchive->files();
4613 attachments.reserve( files.size() );
4614 for ( const QString &file : files )
4615 {
4616 if ( QFileInfo( file ).baseName() != baseName )
4617 {
4618 attachments.append( file );
4619 }
4620 }
4621 return attachments;
4622}
4623
4624bool QgsProject::removeAttachedFile( const QString &path )
4625{
4627
4628 return mArchive->removeFile( path );
4629}
4630
4631QString QgsProject::attachmentIdentifier( const QString &attachedFile ) const
4632{
4634
4635 return QStringLiteral( "attachment:///%1" ).arg( QFileInfo( attachedFile ).fileName() );
4636}
4637
4638QString QgsProject::resolveAttachmentIdentifier( const QString &identifier ) const
4639{
4641
4642 if ( identifier.startsWith( QLatin1String( "attachment:///" ) ) )
4643 {
4644 return QDir( mArchive->dir() ).absoluteFilePath( identifier.mid( 14 ) );
4645 }
4646 return QString();
4647}
4648
4650{
4651 // this method is called quite extensively from other threads via QgsProject::createExpressionContextScope()
4653
4654 return mMetadata;
4655}
4656
4658{
4660
4661 if ( metadata == mMetadata )
4662 return;
4663
4664 mMetadata = metadata;
4665 mProjectScope.reset();
4666
4667 emit metadataChanged();
4668
4669 setDirty( true );
4670}
4671
4672QSet<QgsMapLayer *> QgsProject::requiredLayers() const
4673{
4675
4676 QSet<QgsMapLayer *> requiredLayers;
4677
4678 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
4679 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
4680 {
4681 if ( !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
4682 {
4683 requiredLayers.insert( it.value() );
4684 }
4685 }
4686 return requiredLayers;
4687}
4688
4689void QgsProject::setRequiredLayers( const QSet<QgsMapLayer *> &layers )
4690{
4692
4693 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
4694 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
4695 {
4696 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
4697 continue;
4698
4699 if ( layers.contains( it.value() ) )
4700 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Removable );
4701 else
4702 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Removable );
4703 }
4704}
4705
4707{
4709
4710 // save colors to project
4711 QStringList customColors;
4712 QStringList customColorLabels;
4713
4714 QgsNamedColorList::const_iterator colorIt = colors.constBegin();
4715 for ( ; colorIt != colors.constEnd(); ++colorIt )
4716 {
4717 const QString color = QgsSymbolLayerUtils::encodeColor( ( *colorIt ).first );
4718 const QString label = ( *colorIt ).second;
4719 customColors.append( color );
4720 customColorLabels.append( label );
4721 }
4722 writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ), customColors );
4723 writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ), customColorLabels );
4724 mProjectScope.reset();
4725 emit projectColorsChanged();
4726}
4727
4728void QgsProject::setBackgroundColor( const QColor &color )
4729{
4731
4732 if ( mBackgroundColor == color )
4733 return;
4734
4735 mBackgroundColor = color;
4737}
4738
4740{
4742
4743 return mBackgroundColor;
4744}
4745
4746void QgsProject::setSelectionColor( const QColor &color )
4747{
4749
4750 if ( mSelectionColor == color )
4751 return;
4752
4753 mSelectionColor = color;
4754 emit selectionColorChanged();
4755}
4756
4758{
4760
4761 return mSelectionColor;
4762}
4763
4764void QgsProject::setMapScales( const QVector<double> &scales )
4765{
4767
4768 mViewSettings->setMapScales( scales );
4769}
4770
4771QVector<double> QgsProject::mapScales() const
4772{
4774
4775 return mViewSettings->mapScales();
4776}
4777
4779{
4781
4782 mViewSettings->setUseProjectScales( enabled );
4783}
4784
4786{
4788
4789 return mViewSettings->useProjectScales();
4790}
4791
4792void QgsProject::generateTsFile( const QString &locale )
4793{
4795
4796 QgsTranslationContext translationContext;
4797 translationContext.setProject( this );
4798 translationContext.setFileName( QStringLiteral( "%1/%2.ts" ).arg( absolutePath(), baseName() ) );
4799
4800 QgsApplication::instance()->collectTranslatableObjects( &translationContext );
4801
4802 translationContext.writeTsFile( locale );
4803}
4804
4805QString QgsProject::translate( const QString &context, const QString &sourceText, const char *disambiguation, int n ) const
4806{
4808
4809 if ( !mTranslator )
4810 {
4811 return sourceText;
4812 }
4813
4814 QString result = mTranslator->translate( context.toUtf8(), sourceText.toUtf8(), disambiguation, n );
4815
4816 if ( result.isEmpty() )
4817 {
4818 return sourceText;
4819 }
4820 return result;
4821}
4822
4824{
4826
4827 const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
4828 if ( !layers.empty() )
4829 {
4830 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
4831 {
4832 // NOTE: if visitEnter returns false it means "don't visit this layer", not "abort all further visitations"
4833 if ( visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
4834 {
4835 if ( !( ( *it )->accept( visitor ) ) )
4836 return false;
4837
4838 if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
4839 return false;
4840 }
4841 }
4842 }
4843
4844 if ( !mLayoutManager->accept( visitor ) )
4845 return false;
4846
4847 if ( !mAnnotationManager->accept( visitor ) )
4848 return false;
4849
4850 return true;
4851}
4852
4854{
4855 return mElevationShadingRenderer;
4856}
4857
4858void QgsProject::loadProjectFlags( const QDomDocument *doc )
4859{
4861
4862 QDomElement element = doc->documentElement().firstChildElement( QStringLiteral( "projectFlags" ) );
4863 Qgis::ProjectFlags flags;
4864 if ( !element.isNull() )
4865 {
4866 flags = qgsFlagKeysToValue( element.attribute( QStringLiteral( "set" ) ), Qgis::ProjectFlags() );
4867 }
4868 else
4869 {
4870 // older project compatibility
4871 element = doc->documentElement().firstChildElement( QStringLiteral( "evaluateDefaultValues" ) );
4872 if ( !element.isNull() )
4873 {
4874 if ( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
4876 }
4877
4878 // Read trust layer metadata config in the project
4879 element = doc->documentElement().firstChildElement( QStringLiteral( "trust" ) );
4880 if ( !element.isNull() )
4881 {
4882 if ( element.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
4884 }
4885 }
4886
4887 setFlags( flags );
4888}
4889
4891GetNamedProjectColor::GetNamedProjectColor( const QgsProject *project )
4892 : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
4893{
4894 if ( !project )
4895 return;
4896
4897 //build up color list from project. Do this in advance for speed
4898 QStringList colorStrings = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ) );
4899 const QStringList colorLabels = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ) );
4900
4901 //generate list from custom colors
4902 int colorIndex = 0;
4903 for ( QStringList::iterator it = colorStrings.begin();
4904 it != colorStrings.end(); ++it )
4905 {
4906 const QColor color = QgsSymbolLayerUtils::decodeColor( *it );
4907 QString label;
4908 if ( colorLabels.length() > colorIndex )
4909 {
4910 label = colorLabels.at( colorIndex );
4911 }
4912
4913 mColors.insert( label.toLower(), color );
4914 colorIndex++;
4915 }
4916}
4917
4918GetNamedProjectColor::GetNamedProjectColor( const QHash<QString, QColor> &colors )
4919 : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
4920 , mColors( colors )
4921{
4922}
4923
4924QVariant GetNamedProjectColor::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
4925{
4926 const QString colorName = values.at( 0 ).toString().toLower();
4927 if ( mColors.contains( colorName ) )
4928 {
4929 return QStringLiteral( "%1,%2,%3" ).arg( mColors.value( colorName ).red() ).arg( mColors.value( colorName ).green() ).arg( mColors.value( colorName ).blue() );
4930 }
4931 else
4932 return QVariant();
4933}
4934
4935QgsScopedExpressionFunction *GetNamedProjectColor::clone() const
4936{
4937 return new GetNamedProjectColor( mColors );
4938}
4939
4940// ----------------
4941
4942GetSensorData::GetSensorData( const QMap<QString, QgsAbstractSensor::SensorData> &sensorData )
4943 : QgsScopedExpressionFunction( QStringLiteral( "sensor_data" ),
4944 QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "name" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "expiration" ), true, 0 ),
4945 QStringLiteral( "Sensors" ) )
4946 , mSensorData( sensorData )
4947{
4948}
4949
4950QVariant GetSensorData::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
4951{
4952 const QString sensorName = values.at( 0 ).toString();
4953 const int expiration = values.at( 1 ).toInt();
4954 const qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
4955 if ( mSensorData.contains( sensorName ) )
4956 {
4957 if ( expiration <= 0 || ( timestamp - mSensorData[sensorName].lastTimestamp.toMSecsSinceEpoch() ) < expiration )
4958 {
4959 return mSensorData[sensorName].lastValue;
4960 }
4961 }
4962
4963 return QVariant();
4964}
4965
4966QgsScopedExpressionFunction *GetSensorData::clone() const
4967{
4968 return new GetSensorData( mSensorData );
4969}
@ ForceReadOnlyLayers
Open layers in a read-only mode. (since QGIS 3.28)
static QString version()
Version string.
Definition: qgis.cpp:277
DistanceUnit
Units of distance.
Definition: qgis.h:3225
FilePathType
File path types.
Definition: qgis.h:1085
@ Relative
Relative path.
@ Absolute
Absolute path.
TransactionMode
Transaction mode.
Definition: qgis.h:2431
@ AutomaticGroups
Automatic transactional editing means that on supported datasources (postgres and geopackage database...
@ BufferedGroups
Buffered transactional editing means that all editable layers in the buffered transaction group are t...
@ Disabled
Edits are buffered locally and sent to the provider when toggling layer editing mode.
AreaUnit
Units of area.
Definition: qgis.h:3263
AvoidIntersectionsMode
Flags which control how intersections of pre-existing feature are handled when digitizing new feature...
Definition: qgis.h:2646
@ AvoidIntersectionsLayers
Overlap with features from a specified list of layers when digitizing new features not allowed.
@ AllowIntersections
Overlap with any feature allowed when digitizing new features.
ProjectFlag
Flags which control the behavior of QgsProjects.
Definition: qgis.h:2513
@ RememberLayerEditStatusBetweenSessions
If set, then any layers set to be editable will be stored in the project and immediately made editabl...
@ EvaluateDefaultValuesOnProviderSide
If set, default values for fields will be evaluated on the provider side when features from the proje...
@ TrustStoredLayerStatistics
If set, then layer statistics (such as the layer extent) will be read from values stored in the proje...
LayerType
Types of layers that can be added to a map.
Definition: qgis.h:115
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
QMap< QString, QStringList > KeywordMap
Map of vocabulary string to keyword list.
void setTitle(const QString &title)
Sets the human readable title (name) of the resource, typically displayed in search results.
QString title() const
Returns the human readable name of the resource, typically displayed in search results.
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.
virtual bool writeXml(QDomElement &collectionElem, const QgsPropertiesDefinition &definitions) const
Writes the current state of the property collection into an XML element.
Represents a map layer containing a set of georeferenced annotations, e.g.
void setTransformContext(const QgsCoordinateTransformContext &context) override
Sets the coordinate transform context to transformContext.
void reset()
Resets the annotation layer to a default state, and clears all items from it.
bool isEmpty() const
Returns true if the annotation layer is empty and contains no annotations.
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.
Class allowing to manage the zip/unzip actions.
Definition: qgsarchive.h:36
void addFile(const QString &filename)
Add a new file to this archive.
Definition: qgsarchive.cpp:113
This is a container for attribute editors, used to group them visually in the attribute form if it is...
QList< QgsAttributeEditorElement * > children() const
Gets a list of the children elements of this container.
This is 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.
Class 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.
bool readXml(const QDomElement &element, const QDomDocument &doc)
Reads the manager's state from a DOM element, restoring all bookmarks present in the XML document.
void clear()
Removes and deletes all bookmarks from the manager.
QDomElement writeXml(QDomDocument &doc) const
Returns a DOM element representing the state of the manager.
This class 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.
Q_GADGET Qgis::DistanceUnit mapUnits
QString toProj() const
Returns a Proj string representation of this CRS.
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
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.
@ WKT_PREFERRED
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
static QgsCoordinateReferenceSystem fromSrsId(long srsId)
Creates a CRS from a specified QGIS SRS ID.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
Contains information about the context in which a coordinate transform is executed.
void readSettings()
Reads the context's state from application settings.
void writeXml(QDomElement &element, const QgsReadWriteContext &context) const
Writes the context's state to a DOM element.
bool readXml(const QDomElement &element, const QgsReadWriteContext &context, QStringList &missingTransforms)
Reads the context's state from a DOM element.
Abstract base class for spatial data provider implementations.
@ EvaluateDefaultValues
Evaluate default values on provider side when calling QgsVectorDataProvider::defaultValue( int index ...
Class for storing the component parts of a RDBMS data source URI (e.g.
QgsAttributeEditorContainer * invisibleRootContainer()
Gets the invisible root container for the drag and drop designer form (EditorLayout::TabLayout).
QVariantMap config() const
This class can render elevation shading on an image with different methods (eye dome lighting,...
void writeXml(QDomElement &elem, const QgsReadWriteContext &context) const
Writes configuration on a DOM element.
void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads configuration from a DOM element.
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...
A abstract base class for defining QgsExpression functions.
An expression node for expression functions.
Class for parsing and evaluation of expressions (formerly called "search strings").
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:53
QString name
Definition: qgsfield.h:62
QString alias
Definition: qgsfield.h:63
QgsEditorWidgetSetup editorWidgetSetup() const
Gets the editor widget setup for the field.
Definition: qgsfield.cpp:714
Container of fields for a vector layer.
Definition: qgsfields.h:45
bool isEmpty() const
Checks whether the container is empty.
Definition: qgsfields.cpp:128
Stores global configuration for labeling engine.
Class used to work with layer dependencies stored in a XML project or layer definition file.
Layer tree group node serves as a container for layers and further groups.
void resolveReferences(const QgsProject *project, bool looseMatching=false) override
Calls resolveReferences() on child tree nodes.
QgsLayerTreeGroup * findGroup(const QString &name)
Find group node with specified name.
QList< QgsLayerTreeGroup * > findGroups(bool recursive=false) const
Find group layer nodes.
QString name() const override
Returns the group's name.
QStringList findLayerIds() const
Find layer IDs used in all layer nodes.
void insertChildNodes(int index, const QList< QgsLayerTreeNode * > &nodes)
Insert existing nodes at specified position.
void readChildrenFromXml(QDomElement &element, const QgsReadWriteContext &context)
Read children from XML and append them to the group.
QgsLayerTreeGroup * clone() const override
Returns a clone of the group.
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
QgsLayerTreeLayer * findLayer(QgsMapLayer *layer) const
Find layer node representing the map layer.
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)
This class is a base class for nodes in a layer tree.
QList< QgsLayerTreeNode * > abandonChildren()
Removes the childrens, disconnect all the forwarded and external signals and sets their parent to nul...
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.
virtual void writeXml(QDomElement &parentElement, const QgsReadWriteContext &context)=0
Write layer tree to XML.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
void removeCustomProperty(const QString &key)
Remove a custom property from layer. Properties are stored in a map and saved in project file.
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)
Listens to the updates in map layer registry and does changes in layer tree.
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.
Definition: qgslayertree.h:33
void readLayerOrderFromXml(const QDomElement &doc)
Load the layer order from an XML element.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:75
void clear()
Clear any information from this layer tree.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:53
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
Definition: qgslayertree.h:43
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
Definition: qgslayertree.h:64
QList< QgsMapLayer * > customLayerOrder() const
The order in which layers will be rendered on the canvas.
QgsLayerTree * clone() const override
Create a copy of the node. Returns new instance.
Manages storage of a set of layouts.
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:131
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:73
QString source() const
Returns the source for the layer.
QString providerType() const
Returns the provider type (provider key) for this layer.
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
void configChanged()
Emitted whenever the configuration is changed.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
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:80
Q_INVOKABLE void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
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.
bool isValid
Definition: qgsmaplayer.h:81
@ Identifiable
If the layer is identifiable using the identify map tool and as a WMS layer.
Definition: qgsmaplayer.h:147
@ Removable
If the layer can be removed from the project. The layer will not be removable from the legend menu en...
Definition: qgsmaplayer.h:148
@ FlagTrustLayerMetadata
Trust layer metadata. Improves layer load time by skipping expensive checks like primary key unicity,...
Definition: qgsmaplayer.h:635
@ FlagForceReadOnly
Force open as read only.
Definition: qgsmaplayer.h:637
@ FlagDontResolveLayers
Don't resolve layer paths or create data providers for layers.
Definition: qgsmaplayer.h:634
bool readLayerXml(const QDomElement &layerElement, QgsReadWriteContext &context, QgsMapLayer::ReadFlags flags=QgsMapLayer::ReadFlags(), QgsDataProvider *preloadedProvider=nullptr)
Sets state from DOM document.
void setCrs(const QgsCoordinateReferenceSystem &srs, bool emitSignal=true)
Sets layer's spatial reference system.
static QgsDataProvider::ReadFlags providerReadFlags(const QDomNode &layerNode, QgsMapLayer::ReadFlags layerReadFlags)
Returns provider read flag deduced from layer read flags layerReadFlags and a dom node layerNode that...
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)
Adds a message to the log instance (and creates it if necessary).
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.
Class allowing to manage the zip/unzip actions on project file.
Definition: qgsarchive.h:122
QString projectFile() const
Returns the current .qgs project file or an empty string if there's none.
Definition: qgsarchive.cpp:140
QString auxiliaryStorageFile() const
Returns the current .qgd auxiliary storage file or an empty string if there's none.
Definition: qgsarchive.cpp:166
bool unzip(const QString &zipFilename) override
Clear the current content of this archive and unzip.
Definition: qgsarchive.cpp:153
Interface for classes that handle missing layer files when reading project file.
virtual void handleBadLayers(const QList< QDomNode > &layers)
This method will be called whenever the project tries to load layers which cannot be accessed.
Contains settings and properties relating to how a QgsProject should display values such as map coord...
void reset()
Resets the settings to a default state.
bool readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads the settings's state from a DOM element.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Returns a DOM element representing the settings.
Contains elevation properties for a QgsProject.
bool readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads the property state from a DOM element.
void reset()
Resets the properties to a default state.
QDomElement writeXml(QDomDocument &document, const QgsReadWriteContext &context) const
Returns a DOM element representing the properties.
void resolveReferences(const QgsProject *project)
Resolves reference to layers from stored layer ID.
Class to convert from older project file versions to newer.
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.
bool readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads the settings's state from a DOM element.
void reset()
Resets the settings to a default state.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Returns a DOM element representing the settings.
void resolveReferences(const QgsProject *project)
Resolves reference to layers from stored layer ID (if it has not been resolved already)
A structured metadata store for a map layer.
bool writeMetadataXml(QDomElement &metadataElement, QDomDocument &document) const override
Stores state in a DOM node.
void setCreationDateTime(const QDateTime &creationDateTime)
Sets the project's creation date/timestamp.
bool readMetadataXml(const QDomElement &metadataElement) override
Sets state from DOM document.
void setAuthor(const QString &author)
Sets the project author string.
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.
bool isEmpty() const
Returns true if this property contains no sub-keys.
virtual void clearKeys()
Deletes any sub-nodes from the property.
bool writeXml(const QString &nodeName, QDomElement &element, QDomDocument &document) override
Writes the property hierarchy to a specified DOM element.
void subkeyList(QStringList &entries) const
Returns any sub-keys contained by this property which themselves contain other keys.
void setName(const QString &name)
The name of the property is used as identifier.
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.
int count() const
Returns the number of sub-keys contained by this property.
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.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Returns a DOM element representing the settings.
void setDefaultSymbol(Qgis::SymbolType symbolType, QgsSymbol *symbol)
Sets the project default symbol for a given type.
void reset()
Resets the settings to a default state.
void setRandomizeDefaultSymbolColor(bool randomized)
Sets whether the default symbol fill color is randomized.
void setDefaultColorRamp(QgsColorRamp *colorRamp)
Sets the project default color ramp.
bool readXml(const QDomElement &element, const QgsReadWriteContext &context, Qgis::ProjectReadFlags flags=Qgis::ProjectReadFlags())
Reads the settings's state from a DOM element.
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...
bool readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads the settings's state from a DOM element.
void reset()
Resets the settings to a default state.
QDomElement writeXml(QDomDocument &document, const QgsReadWriteContext &context) const
Returns a DOM element representing the settings.
A class to describe 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,...
bool useProjectScales() const
Returns true if project mapScales() are enabled.
void reset()
Resets the settings to a default state.
bool readXml(const QDomElement &element, const QgsReadWriteContext &context)
Reads the settings's state from a DOM element.
void setMapScales(const QVector< double > &scales)
Sets the list of custom project map scales.
void setUseProjectScales(bool enabled)
Sets whether project mapScales() are enabled.
QVector< double > mapScales() const
Returns the list of custom project map scales.
void mapScalesChanged()
Emitted when the list of custom project map scales changes.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Returns a DOM element representing the settings.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:107
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:117
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.
Definition: qgsproject.cpp:372
void removeMapLayer(const QString &layerId)
Remove a layer from the registry by layer ID.
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:124
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
Definition: qgsproject.cpp:462
QString error() const
Returns error message from previous read/write.
Q_DECL_DEPRECATED void setUseProjectScales(bool enabled)
Sets whether project mapScales() are enabled.
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:205
Q_DECL_DEPRECATED QFileInfo fileInfo() const
Returns QFileInfo object for the project's associated file.
Definition: qgsproject.cpp:841
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.
Definition: qgsproject.cpp:947
QColor selectionColor
Definition: qgsproject.h:122
QString title() const
Returns the project's title.
Definition: qgsproject.cpp:508
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...
Definition: qgsproject.cpp:745
void mapThemeCollectionChanged()
Emitted when the map theme collection changes.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:483
Qgis::FilePathType filePathStorage() const
Returns the type of paths used when storing file paths in a QGS/QGZ project file.
Definition: qgsproject.cpp:916
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:114
void fileNameChanged()
Emitted when the file name of the project changes.
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
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.
Definition: qgsproject.cpp:717
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...
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.
Definition: qgsproject.cpp:771
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:125
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 the project with QTranslator and qm file.
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:116
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.
Definition: qgsproject.cpp:797
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.
Definition: qgsproject.cpp:710
void registerTranslatableObjects(QgsTranslationContext *translationContext)
Registers the objects that require translation into the translationContext.
Definition: qgsproject.cpp:655
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.
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.
Definition: qgsproject.cpp:724
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...
Q_DECL_DEPRECATED void oldProjectVersionWarning(const QString &)
Emitted when an old project file is read.
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 ...
Definition: qgsproject.cpp:872
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:119
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...
Definition: qgsproject.cpp:899
void ellipsoidChanged(const QString &ellipsoid)
Emitted when the project ellipsoid is changed.
QgsMapThemeCollection * mapThemeCollection
Definition: qgsproject.h:115
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 the specified scope and key.
QgsAnnotationManager * annotationManager()
Returns pointer to the project's annotation manager.
QgsProjectDisplaySettings * displaySettings
Definition: qgsproject.h:126
Qgis::TransactionMode transactionMode() const
Returns the transaction mode.
QgsProjectMetadata metadata
Definition: qgsproject.h:120
void projectColorsChanged()
Emitted whenever the project's color scheme has been changed.
QString saveUser() const
Returns the user name that did the last save.
Definition: qgsproject.cpp:567
QVector< T > layers() const
Returns a list of registered map layers with a specified layer type.
Definition: qgsproject.h:1211
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:113
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.
void readProject(const QDomDocument &)
Emitted when a project is being read.
QString originalPath() const
Returns the original path associated with the project.
Definition: qgsproject.cpp:834
void setOriginalPath(const QString &path)
Sets the original path associated with the project.
Definition: qgsproject.cpp:827
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.
Definition: qgsproject.cpp:477
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.
QList< QgsMapLayer * > mapLayersByName(const QString &layerName) const
Retrieve a list of matching registered layers by layer name.
QString fileName
Definition: qgsproject.h:110
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:112
void readProjectWithContext(const QDomDocument &, QgsReadWriteContext &context)
Emitted when a project is being read.
QgsMapLayer * addMapLayer(QgsMapLayer *mapLayer, bool addToLegend=true, bool takeOwnership=true)
Add a layer to the map of loaded layers.
QStringList nonIdentifiableLayers
Definition: qgsproject.h:109
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.
Definition: qgsproject.cpp:494
QMap< QPair< QString, QString >, QgsTransactionGroup * > transactionGroups()
Map of transaction groups.
void writeProject(QDomDocument &)
Emitted when the project is being written.
void setFlag(Qgis::ProjectFlag flag, bool enabled=true)
Sets whether a project flag is enabled.
Definition: qgsproject.cpp:555
QDateTime lastModified() const
Returns last modified time of the project file as returned by the file system (or other project stora...
Definition: qgsproject.cpp:856
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...
Definition: qgsproject.cpp:885
QDateTime lastSaveDateTime() const
Returns the date and time when the project was last saved.
Definition: qgsproject.cpp:581
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".
Definition: qgsproject.cpp:981
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the project's coordinate transform context, which stores various information regarding which dat...
QColor backgroundColor
Definition: qgsproject.h:121
void layerLoaded(int