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