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