1 /***************************************************************************
2 qgsproject.cpp - description
3 -------------------
4 begin : July 23, 2004
5 copyright : (C) 2004 by Mark Coletti
6 email : mcoletti at gmail.com
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "qgsproject.h"
19
20 #include "qgsdatasourceuri.h"
21 #include "qgslabelingenginesettings.h"
22 #include "qgslayertree.h"
23 #include "qgslayertreeutils.h"
24 #include "qgslayertreeregistrybridge.h"
25 #include "qgslogger.h"
26 #include "qgsmessagelog.h"
27 #include "qgspluginlayer.h"
28 #include "qgspluginlayerregistry.h"
29 #include "qgsprojectfiletransform.h"
30 #include "qgssnappingconfig.h"
31 #include "qgspathresolver.h"
32 #include "qgsprojectstorage.h"
33 #include "qgsprojectstorageregistry.h"
34 #include "qgsprojectversion.h"
35 #include "qgsrasterlayer.h"
36 #include "qgsreadwritecontext.h"
37 #include "qgsrectangle.h"
38 #include "qgsrelationmanager.h"
39 #include "qgsannotationmanager.h"
40 #include "qgsvectorlayerjoininfo.h"
41 #include "qgsmapthemecollection.h"
42 #include "qgslayerdefinition.h"
43 #include "qgsunittypes.h"
44 #include "qgstransaction.h"
45 #include "qgstransactiongroup.h"
46 #include "qgsvectordataprovider.h"
47 #include "qgsprojectbadlayerhandler.h"
48 #include "qgssettings.h"
49 #include "qgsmaplayerlistutils.h"
50 #include "qgsmeshlayer.h"
51 #include "qgslayoutmanager.h"
52 #include "qgsbookmarkmanager.h"
53 #include "qgsmaplayerstore.h"
54 #include "qgsziputils.h"
55 #include "qgsauxiliarystorage.h"
56 #include "qgssymbollayerutils.h"
57 #include "qgsapplication.h"
58 #include "qgsexpressioncontextutils.h"
59 #include "qgsstyleentityvisitor.h"
60 #include "qgsprojectviewsettings.h"
61 #include "qgsprojectdisplaysettings.h"
62 #include "qgsprojecttimesettings.h"
63 #include "qgsvectortilelayer.h"
64 #include "qgsruntimeprofiler.h"
65 #include "qgsannotationlayer.h"
66
67 #include <algorithm>
68 #include <QApplication>
69 #include <QFileInfo>
70 #include <QDomNode>
71 #include <QObject>
72 #include <QTextStream>
73 #include <QTemporaryFile>
74 #include <QDir>
75 #include <QUrl>
76
77
78 #ifdef _MSC_VER
79 #include <sys/utime.h>
80 #else
81 #include <utime.h>
82 #endif
83
84 // canonical project instance
85 QgsProject *QgsProject::sProject = nullptr;
86
87 ///@cond PRIVATE
88 class ScopedIntIncrementor
89 {
90 public:
91
ScopedIntIncrementor(int * variable)92 ScopedIntIncrementor( int *variable )
93 : mVariable( variable )
94 {
95 ( *mVariable )++;
96 }
97
98 ScopedIntIncrementor( const ScopedIntIncrementor &other ) = delete;
99 ScopedIntIncrementor &operator=( const ScopedIntIncrementor &other ) = delete;
100
release()101 void release()
102 {
103 if ( mVariable )
104 ( *mVariable )--;
105
106 mVariable = nullptr;
107 }
108
~ScopedIntIncrementor()109 ~ScopedIntIncrementor()
110 {
111 release();
112 }
113
114 private:
115 int *mVariable = nullptr;
116 };
117 ///@endcond
118
119
120 /**
121 Take the given scope and key and convert them to a string list of key
122 tokens that will be used to navigate through a Property hierarchy
123
124 E.g., scope "someplugin" and key "/foo/bar/baz" will become a string list
125 of { "properties", "someplugin", "foo", "bar", "baz" }. "properties" is
126 always first because that's the permanent ``root'' Property node.
127 */
makeKeyTokens_(const QString & scope,const QString & key)128 QStringList makeKeyTokens_( const QString &scope, const QString &key )
129 {
130 QStringList keyTokens = QStringList( scope );
131 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
132 keyTokens += key.split( '/', QString::SkipEmptyParts );
133 #else
134 keyTokens += key.split( '/', Qt::SkipEmptyParts );
135 #endif
136
137 // be sure to include the canonical root node
138 keyTokens.push_front( QStringLiteral( "properties" ) );
139
140 //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.
141 for ( int i = 0; i < keyTokens.size(); ++i )
142 {
143 QString keyToken = keyTokens.at( i );
144
145 //invalid chars in XML are found at http://www.w3.org/TR/REC-xml/#NT-NameChar
146 //note : it seems \x10000-\xEFFFF is valid, but it when added to the regexp, a lot of unwanted characters remain
147 QString nameCharRegexp = QStringLiteral( "[^:A-Z_a-z\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\x2FF\\x370-\\x37D\\x37F-\\x1FFF\\x200C-\\x200D\\x2070-\\x218F\\x2C00-\\x2FEF\\x3001-\\xD7FF\\xF900-\\xFDCF\\xFDF0-\\xFFFD\\-\\.0-9\\xB7\\x0300-\\x036F\\x203F-\\x2040]" );
148 QString nameStartCharRegexp = QStringLiteral( "^[^:A-Z_a-z\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\x2FF\\x370-\\x37D\\x37F-\\x1FFF\\x200C-\\x200D\\x2070-\\x218F\\x2C00-\\x2FEF\\x3001-\\xD7FF\\xF900-\\xFDCF\\xFDF0-\\xFFFD]" );
149
150 if ( keyToken.contains( QRegExp( nameCharRegexp ) ) || keyToken.contains( QRegExp( nameStartCharRegexp ) ) )
151 {
152
153 QString errorString = QObject::tr( "Entry token invalid : '%1'. The token will not be saved to file." ).arg( keyToken );
154 QgsMessageLog::logMessage( errorString, QString(), Qgis::Critical );
155
156 }
157
158 }
159
160 return keyTokens;
161 }
162
163
164
165 /**
166 return the property that matches the given key sequence, if any
167
168 \param scope scope of key
169 \param key keyname
170 \param rootProperty is likely to be the top level QgsProjectPropertyKey in QgsProject:e:Imp.
171
172 \return null if not found, otherwise located Property
173 */
findKey_(const QString & scope,const QString & key,QgsProjectPropertyKey & rootProperty)174 QgsProjectProperty *findKey_( const QString &scope,
175 const QString &key,
176 QgsProjectPropertyKey &rootProperty )
177 {
178 QgsProjectPropertyKey *currentProperty = &rootProperty;
179 QgsProjectProperty *nextProperty; // link to next property down hierarchy
180
181 QStringList keySequence = makeKeyTokens_( scope, key );
182
183 while ( !keySequence.isEmpty() )
184 {
185 // if the current head of the sequence list matches the property name,
186 // then traverse down the property hierarchy
187 if ( keySequence.first() == currentProperty->name() )
188 {
189 // remove front key since we're traversing down a level
190 keySequence.pop_front();
191
192 if ( 1 == keySequence.count() )
193 {
194 // if we have only one key name left, then return the key found
195 return currentProperty->find( keySequence.front() );
196 }
197 else if ( keySequence.isEmpty() )
198 {
199 // if we're out of keys then the current property is the one we
200 // want; i.e., we're in the rate case of being at the top-most
201 // property node
202 return currentProperty;
203 }
204 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
205 {
206 if ( nextProperty->isKey() )
207 {
208 currentProperty = static_cast<QgsProjectPropertyKey *>( nextProperty );
209 }
210 else if ( nextProperty->isValue() && 1 == keySequence.count() )
211 {
212 // it may be that this may be one of several property value
213 // nodes keyed by QDict string; if this is the last remaining
214 // key token and the next property is a value node, then
215 // that's the situation, so return the currentProperty
216 return currentProperty;
217 }
218 else
219 {
220 // QgsProjectPropertyValue not Key, so return null
221 return nullptr;
222 }
223 }
224 else
225 {
226 // if the next key down isn't found
227 // then the overall key sequence doesn't exist
228 return nullptr;
229 }
230 }
231 else
232 {
233 return nullptr;
234 }
235 }
236
237 return nullptr;
238 }
239
240
241
242 /**
243 * Add the given key and value
244
245 \param scope scope of key
246 \param key key name
247 \param rootProperty is the property from which to start adding
248 \param value the value associated with the key
249 \param propertiesModified the parameter will be set to true if the written entry modifies pre-existing properties
250 */
addKey_(const QString & scope,const QString & key,QgsProjectPropertyKey * rootProperty,const QVariant & value,bool & propertiesModified)251 QgsProjectProperty *addKey_( const QString &scope,
252 const QString &key,
253 QgsProjectPropertyKey *rootProperty,
254 const QVariant &value,
255 bool &propertiesModified )
256 {
257 QStringList keySequence = makeKeyTokens_( scope, key );
258
259 // cursor through property key/value hierarchy
260 QgsProjectPropertyKey *currentProperty = rootProperty;
261 QgsProjectProperty *nextProperty; // link to next property down hierarchy
262 QgsProjectPropertyKey *newPropertyKey = nullptr;
263
264 propertiesModified = false;
265 while ( ! keySequence.isEmpty() )
266 {
267 // if the current head of the sequence list matches the property name,
268 // then traverse down the property hierarchy
269 if ( keySequence.first() == currentProperty->name() )
270 {
271 // remove front key since we're traversing down a level
272 keySequence.pop_front();
273
274 // if key sequence has one last element, then we use that as the
275 // name to store the value
276 if ( 1 == keySequence.count() )
277 {
278 QgsProjectProperty *property = currentProperty->find( keySequence.front() );
279 if ( !property || property->value() != value )
280 {
281 currentProperty->setValue( keySequence.front(), value );
282 propertiesModified = true;
283 }
284
285 return currentProperty;
286 }
287 // we're at the top element if popping the keySequence element
288 // will leave it empty; in that case, just add the key
289 else if ( keySequence.isEmpty() )
290 {
291 if ( currentProperty->value() != value )
292 {
293 currentProperty->setValue( value );
294 propertiesModified = true;
295 }
296
297 return currentProperty;
298 }
299 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
300 {
301 currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
302
303 if ( currentProperty )
304 {
305 continue;
306 }
307 else // QgsProjectPropertyValue not Key, so return null
308 {
309 return nullptr;
310 }
311 }
312 else // the next subkey doesn't exist, so add it
313 {
314 if ( ( newPropertyKey = currentProperty->addKey( keySequence.first() ) ) )
315 {
316 currentProperty = newPropertyKey;
317 }
318 continue;
319 }
320 }
321 else
322 {
323 return nullptr;
324 }
325 }
326
327 return nullptr;
328 }
329
330 /**
331 * Remove a given key
332
333 \param scope scope of key
334 \param key key name
335 \param rootProperty is the property from which to start adding
336 */
337
removeKey_(const QString & scope,const QString & key,QgsProjectPropertyKey & rootProperty)338 void removeKey_( const QString &scope,
339 const QString &key,
340 QgsProjectPropertyKey &rootProperty )
341 {
342 QgsProjectPropertyKey *currentProperty = &rootProperty;
343
344 QgsProjectProperty *nextProperty = nullptr; // link to next property down hierarchy
345 QgsProjectPropertyKey *previousQgsPropertyKey = nullptr; // link to previous property up hierarchy
346
347 QStringList keySequence = makeKeyTokens_( scope, key );
348
349 while ( ! keySequence.isEmpty() )
350 {
351 // if the current head of the sequence list matches the property name,
352 // then traverse down the property hierarchy
353 if ( keySequence.first() == currentProperty->name() )
354 {
355 // remove front key since we're traversing down a level
356 keySequence.pop_front();
357
358 // if we have only one key name left, then try to remove the key
359 // with that name
360 if ( 1 == keySequence.count() )
361 {
362 currentProperty->removeKey( keySequence.front() );
363 }
364 // if we're out of keys then the current property is the one we
365 // want to remove, but we can't delete it directly; we need to
366 // delete it from the parent property key container
367 else if ( keySequence.isEmpty() )
368 {
369 previousQgsPropertyKey->removeKey( currentProperty->name() );
370 }
371 else if ( ( nextProperty = currentProperty->find( keySequence.first() ) ) )
372 {
373 previousQgsPropertyKey = currentProperty;
374 currentProperty = dynamic_cast<QgsProjectPropertyKey *>( nextProperty );
375
376 if ( currentProperty )
377 {
378 continue;
379 }
380 else // QgsProjectPropertyValue not Key, so return null
381 {
382 return;
383 }
384 }
385 else // if the next key down isn't found
386 {
387 // then the overall key sequence doesn't exist
388 return;
389 }
390 }
391 else
392 {
393 return;
394 }
395 }
396 }
397
QgsProject(QObject * parent)398 QgsProject::QgsProject( QObject *parent )
399 : QObject( parent )
400 , mLayerStore( new QgsMapLayerStore( this ) )
401 , mBadLayerHandler( new QgsProjectBadLayerHandler() )
402 , mSnappingConfig( this )
403 , mRelationManager( new QgsRelationManager( this ) )
404 , mAnnotationManager( new QgsAnnotationManager( this ) )
405 , mLayoutManager( new QgsLayoutManager( this ) )
406 , mBookmarkManager( QgsBookmarkManager::createProjectBasedManager( this ) )
407 , mViewSettings( new QgsProjectViewSettings( this ) )
408 , mTimeSettings( new QgsProjectTimeSettings( this ) )
409 , mDisplaySettings( new QgsProjectDisplaySettings( this ) )
410 , mRootGroup( new QgsLayerTree )
411 , mLabelingEngineSettings( new QgsLabelingEngineSettings )
412 , mArchive( new QgsProjectArchive() )
413 , mAuxiliaryStorage( new QgsAuxiliaryStorage() )
414 {
415 mProperties.setName( QStringLiteral( "properties" ) );
416
417 mMainAnnotationLayer = new QgsAnnotationLayer( QObject::tr( "Annotations" ), QgsAnnotationLayer::LayerOptions( mTransformContext ) );
418 mMainAnnotationLayer->setParent( this );
419
420 clear();
421
422 // bind the layer tree to the map layer registry.
423 // whenever layers are added to or removed from the registry,
424 // layer tree will be updated
425 mLayerTreeRegistryBridge = new QgsLayerTreeRegistryBridge( mRootGroup, this, this );
426 connect( this, &QgsProject::layersAdded, this, &QgsProject::onMapLayersAdded );
427 connect( this, &QgsProject::layersRemoved, this, [ = ] { cleanTransactionGroups(); } );
428 connect( this, qgis::overload< const QList<QgsMapLayer *> & >::of( &QgsProject::layersWillBeRemoved ), this, &QgsProject::onMapLayersRemoved );
429
430 // proxy map layer store signals to this
431 connect( mLayerStore.get(), qgis::overload<const QStringList &>::of( &QgsMapLayerStore::layersWillBeRemoved ),
432 this, [ = ]( const QStringList & layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
433 connect( mLayerStore.get(), qgis::overload< const QList<QgsMapLayer *> & >::of( &QgsMapLayerStore::layersWillBeRemoved ),
434 this, [ = ]( const QList<QgsMapLayer *> &layers ) { mProjectScope.reset(); emit layersWillBeRemoved( layers ); } );
435 connect( mLayerStore.get(), qgis::overload< const QString & >::of( &QgsMapLayerStore::layerWillBeRemoved ),
436 this, [ = ]( const QString & layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
437 connect( mLayerStore.get(), qgis::overload< QgsMapLayer * >::of( &QgsMapLayerStore::layerWillBeRemoved ),
438 this, [ = ]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWillBeRemoved( layer ); } );
439 connect( mLayerStore.get(), qgis::overload<const QStringList & >::of( &QgsMapLayerStore::layersRemoved ), this,
440 [ = ]( const QStringList & layers ) { mProjectScope.reset(); emit layersRemoved( layers ); } );
441 connect( mLayerStore.get(), &QgsMapLayerStore::layerRemoved, this,
442 [ = ]( const QString & layer ) { mProjectScope.reset(); emit layerRemoved( layer ); } );
443 connect( mLayerStore.get(), &QgsMapLayerStore::allLayersRemoved, this,
444 [ = ]() { mProjectScope.reset(); emit removeAll(); } );
445 connect( mLayerStore.get(), &QgsMapLayerStore::layersAdded, this,
446 [ = ]( const QList< QgsMapLayer * > &layers ) { mProjectScope.reset(); emit layersAdded( layers ); } );
447 connect( mLayerStore.get(), &QgsMapLayerStore::layerWasAdded, this,
448 [ = ]( QgsMapLayer * layer ) { mProjectScope.reset(); emit layerWasAdded( layer ); } );
449
450 if ( QgsApplication::instance() )
451 {
452 connect( QgsApplication::instance(), &QgsApplication::requestForTranslatableObjects, this, &QgsProject::registerTranslatableObjects );
453 }
454
455 connect( mLayerStore.get(), qgis::overload< const QList<QgsMapLayer *> & >::of( &QgsMapLayerStore::layersWillBeRemoved ), this,
456 [ = ]( const QList<QgsMapLayer *> &layers )
457 {
458 for ( const auto &layer : layers )
459 {
460 disconnect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager, &QgsRelationManager::updateRelationsStatus );
461 }
462 }
463 );
464 connect( mLayerStore.get(), qgis::overload< const QList<QgsMapLayer *> & >::of( &QgsMapLayerStore::layersAdded ), this,
465 [ = ]( const QList<QgsMapLayer *> &layers )
466 {
467 for ( const auto &layer : layers )
468 {
469 connect( layer, &QgsMapLayer::dataSourceChanged, mRelationManager, &QgsRelationManager::updateRelationsStatus );
470 }
471 }
472 );
473
474 Q_NOWARN_DEPRECATED_PUSH
475 connect( mViewSettings, &QgsProjectViewSettings::mapScalesChanged, this, &QgsProject::mapScalesChanged );
476 Q_NOWARN_DEPRECATED_POP
477 }
478
479
~QgsProject()480 QgsProject::~QgsProject()
481 {
482 mIsBeingDeleted = true;
483
484 clear();
485 delete mBadLayerHandler;
486 delete mRelationManager;
487 delete mLayerTreeRegistryBridge;
488 delete mRootGroup;
489 if ( this == sProject )
490 {
491 sProject = nullptr;
492 }
493 }
494
setInstance(QgsProject * project)495 void QgsProject::setInstance( QgsProject *project )
496 {
497 sProject = project;
498 }
499
500
instance()501 QgsProject *QgsProject::instance()
502 {
503 if ( !sProject )
504 {
505 sProject = new QgsProject;
506 }
507 return sProject;
508 }
509
setTitle(const QString & title)510 void QgsProject::setTitle( const QString &title )
511 {
512 if ( title == mMetadata.title() )
513 return;
514
515 mMetadata.setTitle( title );
516 mProjectScope.reset();
517 emit metadataChanged();
518
519 setDirty( true );
520 }
521
title() const522 QString QgsProject::title() const
523 {
524 return mMetadata.title();
525 }
526
saveUser() const527 QString QgsProject::saveUser() const
528 {
529 return mSaveUser;
530 }
531
saveUserFullName() const532 QString QgsProject::saveUserFullName() const
533 {
534 return mSaveUserFull;
535 }
536
lastSaveDateTime() const537 QDateTime QgsProject::lastSaveDateTime() const
538 {
539 return mSaveDateTime;
540 }
541
lastSaveVersion() const542 QgsProjectVersion QgsProject::lastSaveVersion() const
543 {
544 return mSaveVersion;
545 }
546
isDirty() const547 bool QgsProject::isDirty() const
548 {
549 return mDirty;
550 }
551
setDirty(const bool dirty)552 void QgsProject::setDirty( const bool dirty )
553 {
554 if ( dirty && mDirtyBlockCount > 0 )
555 return;
556
557 if ( mDirty == dirty )
558 return;
559
560 mDirty = dirty;
561 emit isDirtyChanged( mDirty );
562 }
563
setPresetHomePath(const QString & path)564 void QgsProject::setPresetHomePath( const QString &path )
565 {
566 if ( path == mHomePath )
567 return;
568
569 mHomePath = path;
570 mCachedHomePath.clear();
571 mProjectScope.reset();
572
573 emit homePathChanged();
574
575 setDirty( true );
576 }
577
registerTranslatableContainers(QgsTranslationContext * translationContext,QgsAttributeEditorContainer * parent,const QString & layerId)578 void QgsProject::registerTranslatableContainers( QgsTranslationContext *translationContext, QgsAttributeEditorContainer *parent, const QString &layerId )
579 {
580 const QList<QgsAttributeEditorElement *> elements = parent->children();
581
582 for ( QgsAttributeEditorElement *element : elements )
583 {
584 if ( element->type() == QgsAttributeEditorElement::AeTypeContainer )
585 {
586 QgsAttributeEditorContainer *container = dynamic_cast<QgsAttributeEditorContainer *>( element );
587
588 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:formcontainers" ).arg( layerId ), container->name() );
589
590 if ( !container->children().empty() )
591 registerTranslatableContainers( translationContext, container, layerId );
592 }
593 }
594 }
595
registerTranslatableObjects(QgsTranslationContext * translationContext)596 void QgsProject::registerTranslatableObjects( QgsTranslationContext *translationContext )
597 {
598 //register layers
599 const QList<QgsLayerTreeLayer *> layers = mRootGroup->findLayers();
600
601 for ( const QgsLayerTreeLayer *layer : layers )
602 {
603 translationContext->registerTranslation( QStringLiteral( "project:layers:%1" ).arg( layer->layerId() ), layer->name() );
604
605 QgsMapLayer *mapLayer = layer->layer();
606 if ( mapLayer && mapLayer->type() == QgsMapLayerType::VectorLayer )
607 {
608 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer );
609
610 //register aliases and fields
611 const QgsFields fields = vlayer->fields();
612 for ( const QgsField &field : fields )
613 {
614 QString fieldName;
615 if ( field.alias().isEmpty() )
616 fieldName = field.name();
617 else
618 fieldName = field.alias();
619
620 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:fieldaliases" ).arg( vlayer->id() ), fieldName );
621
622 if ( field.editorWidgetSetup().type() == QLatin1String( "ValueRelation" ) )
623 {
624 translationContext->registerTranslation( QStringLiteral( "project:layers:%1:fields:%2:valuerelationvalue" ).arg( vlayer->id(), field.name() ), field.editorWidgetSetup().config().value( QStringLiteral( "Value" ) ).toString() );
625 }
626 }
627
628 //register formcontainers
629 registerTranslatableContainers( translationContext, vlayer->editFormConfig().invisibleRootContainer(), vlayer->id() );
630
631 }
632 }
633
634 //register layergroups
635 const QList<QgsLayerTreeGroup *> groupLayers = mRootGroup->findGroups();
636 for ( const QgsLayerTreeGroup *groupLayer : groupLayers )
637 {
638 translationContext->registerTranslation( QStringLiteral( "project:layergroups" ), groupLayer->name() );
639 }
640
641 //register relations
642 const QList<QgsRelation> &relations = mRelationManager->relations().values();
643 for ( const QgsRelation &relation : relations )
644 {
645 translationContext->registerTranslation( QStringLiteral( "project:relations" ), relation.name() );
646 }
647 }
648
setDataDefinedServerProperties(const QgsPropertyCollection & properties)649 void QgsProject::setDataDefinedServerProperties( const QgsPropertyCollection &properties )
650 {
651 mDataDefinedServerProperties = properties;
652 }
653
dataDefinedServerProperties() const654 QgsPropertyCollection QgsProject::dataDefinedServerProperties() const
655 {
656 return mDataDefinedServerProperties;
657 }
658
setFileName(const QString & name)659 void QgsProject::setFileName( const QString &name )
660 {
661 if ( name == mFile.fileName() )
662 return;
663
664 QString oldHomePath = homePath();
665
666 mFile.setFileName( name );
667 mCachedHomePath.clear();
668 mProjectScope.reset();
669
670 emit fileNameChanged();
671
672 QString newHomePath = homePath();
673 if ( newHomePath != oldHomePath )
674 emit homePathChanged();
675
676 setDirty( true );
677 }
678
fileName() const679 QString QgsProject::fileName() const
680 {
681 return mFile.fileName();
682 }
683
setOriginalPath(const QString & path)684 void QgsProject::setOriginalPath( const QString &path )
685 {
686 mOriginalPath = path;
687 }
688
originalPath() const689 QString QgsProject::originalPath() const
690 {
691 return mOriginalPath;
692 }
693
fileInfo() const694 QFileInfo QgsProject::fileInfo() const
695 {
696 return QFileInfo( mFile );
697 }
698
projectStorage() const699 QgsProjectStorage *QgsProject::projectStorage() const
700 {
701 return QgsApplication::projectStorageRegistry()->projectStorageFromUri( mFile.fileName() );
702 }
703
lastModified() const704 QDateTime QgsProject::lastModified() const
705 {
706 if ( QgsProjectStorage *storage = projectStorage() )
707 {
708 QgsProjectStorage::Metadata metadata;
709 storage->readProjectStorageMetadata( mFile.fileName(), metadata );
710 return metadata.lastModified;
711 }
712 else
713 {
714 return QFileInfo( mFile.fileName() ).lastModified();
715 }
716 }
717
absolutePath() const718 QString QgsProject::absolutePath() const
719 {
720 if ( projectStorage() )
721 return QString();
722
723 if ( mFile.fileName().isEmpty() )
724 return QString(); // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
725
726 return QFileInfo( mFile.fileName() ).absolutePath();
727 }
728
absoluteFilePath() const729 QString QgsProject::absoluteFilePath() const
730 {
731 if ( projectStorage() )
732 return QString();
733
734 if ( mFile.fileName().isEmpty() )
735 return QString(); // this is to protect ourselves from getting current directory from QFileInfo::absoluteFilePath()
736
737 return QFileInfo( mFile.fileName() ).absoluteFilePath();
738 }
739
baseName() const740 QString QgsProject::baseName() const
741 {
742 if ( QgsProjectStorage *storage = projectStorage() )
743 {
744 QgsProjectStorage::Metadata metadata;
745 storage->readProjectStorageMetadata( mFile.fileName(), metadata );
746 return metadata.name;
747 }
748 else
749 {
750 return QFileInfo( mFile.fileName() ).completeBaseName();
751 }
752 }
753
crs() const754 QgsCoordinateReferenceSystem QgsProject::crs() const
755 {
756 return mCrs;
757 }
758
setCrs(const QgsCoordinateReferenceSystem & crs,bool adjustEllipsoid)759 void QgsProject::setCrs( const QgsCoordinateReferenceSystem &crs, bool adjustEllipsoid )
760 {
761 if ( crs != mCrs )
762 {
763 mCrs = crs;
764 writeEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), crs.isValid() ? 1 : 0 );
765 mProjectScope.reset();
766 setDirty( true );
767 emit crsChanged();
768 }
769
770 if ( adjustEllipsoid )
771 setEllipsoid( crs.ellipsoidAcronym() );
772 }
773
ellipsoid() const774 QString QgsProject::ellipsoid() const
775 {
776 if ( !crs().isValid() )
777 return geoNone();
778
779 return readEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ), geoNone() );
780 }
781
setEllipsoid(const QString & ellipsoid)782 void QgsProject::setEllipsoid( const QString &ellipsoid )
783 {
784 if ( ellipsoid == readEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ) ) )
785 return;
786
787 mProjectScope.reset();
788 writeEntry( QStringLiteral( "Measure" ), QStringLiteral( "/Ellipsoid" ), ellipsoid );
789 emit ellipsoidChanged( ellipsoid );
790 }
791
transformContext() const792 QgsCoordinateTransformContext QgsProject::transformContext() const
793 {
794 return mTransformContext;
795 }
796
setTransformContext(const QgsCoordinateTransformContext & context)797 void QgsProject::setTransformContext( const QgsCoordinateTransformContext &context )
798 {
799 if ( context == mTransformContext )
800 return;
801
802 mTransformContext = context;
803 mProjectScope.reset();
804
805 mMainAnnotationLayer->setTransformContext( context );
806 for ( auto &layer : mLayerStore.get()->mapLayers() )
807 {
808 layer->setTransformContext( context );
809 }
810 emit transformContextChanged();
811 }
812
clear()813 void QgsProject::clear()
814 {
815 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
816
817 QgsSettings settings;
818
819 mProjectScope.reset();
820 mFile.setFileName( QString() );
821 mProperties.clearKeys();
822 mSaveUser.clear();
823 mSaveUserFull.clear();
824 mSaveDateTime = QDateTime();
825 mSaveVersion = QgsProjectVersion();
826 mHomePath.clear();
827 mCachedHomePath.clear();
828 mAutoTransaction = false;
829 mEvaluateDefaultValues = false;
830 mDirty = false;
831 mTrustLayerMetadata = false;
832 mCustomVariables.clear();
833 mCrs = QgsCoordinateReferenceSystem();
834 mMetadata = QgsProjectMetadata();
835 if ( !mSettings.value( QStringLiteral( "projects/anonymize_new_projects" ), false, QgsSettings::Core ).toBool() )
836 {
837 mMetadata.setCreationDateTime( QDateTime::currentDateTime() );
838 mMetadata.setAuthor( QgsApplication::userFullName() );
839 }
840 emit metadataChanged();
841
842 QgsCoordinateTransformContext context;
843 context.readSettings();
844 setTransformContext( context );
845
846 mEmbeddedLayers.clear();
847 mRelationManager->clear();
848 mAnnotationManager->clear();
849 mLayoutManager->clear();
850 mBookmarkManager->clear();
851 mViewSettings->reset();
852 mTimeSettings->reset();
853 mDisplaySettings->reset();
854 mSnappingConfig.reset();
855 emit avoidIntersectionsModeChanged();
856 emit topologicalEditingChanged();
857
858 mMapThemeCollection.reset( new QgsMapThemeCollection( this ) );
859 emit mapThemeCollectionChanged();
860
861 mLabelingEngineSettings->clear();
862
863 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage() );
864 mArchive->clear();
865
866 emit labelingEngineSettingsChanged();
867
868 if ( !mIsBeingDeleted )
869 {
870 // possibly other signals should also not be thrown on destruction -- e.g. labelEngineSettingsChanged, etc.
871 emit projectColorsChanged();
872 }
873
874 // reset some default project properties
875 // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
876 writeEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/Automatic" ), true );
877 writeEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DecimalPlaces" ), 2 );
878 writeEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), false );
879
880 //copy default units to project
881 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), mSettings.value( QStringLiteral( "/qgis/measure/displayunits" ) ).toString() );
882 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), mSettings.value( QStringLiteral( "/qgis/measure/areaunits" ) ).toString() );
883
884 int red = mSettings.value( QStringLiteral( "qgis/default_canvas_color_red" ), 255 ).toInt();
885 int green = mSettings.value( QStringLiteral( "qgis/default_canvas_color_green" ), 255 ).toInt();
886 int blue = mSettings.value( QStringLiteral( "qgis/default_canvas_color_blue" ), 255 ).toInt();
887 setBackgroundColor( QColor( red, green, blue ) );
888
889 red = mSettings.value( QStringLiteral( "qgis/default_selection_color_red" ), 255 ).toInt();
890 green = mSettings.value( QStringLiteral( "qgis/default_selection_color_green" ), 255 ).toInt();
891 blue = mSettings.value( QStringLiteral( "qgis/default_selection_color_blue" ), 0 ).toInt();
892 int alpha = mSettings.value( QStringLiteral( "qgis/default_selection_color_alpha" ), 255 ).toInt();
893 setSelectionColor( QColor( red, green, blue, alpha ) );
894
895 mSnappingConfig.clearIndividualLayerSettings();
896
897 removeAllMapLayers();
898 mRootGroup->clear();
899 if ( mMainAnnotationLayer )
900 mMainAnnotationLayer->reset();
901
902 snapSingleBlocker.release();
903
904 if ( !mBlockSnappingUpdates )
905 emit snappingConfigChanged( mSnappingConfig );
906
907 setDirty( false );
908 emit homePathChanged();
909 emit cleared();
910 }
911
912 // basically a debugging tool to dump property list values
dump_(const QgsProjectPropertyKey & topQgsPropertyKey)913 void dump_( const QgsProjectPropertyKey &topQgsPropertyKey )
914 {
915 QgsDebugMsgLevel( QStringLiteral( "current properties:" ), 3 );
916 topQgsPropertyKey.dump();
917 }
918
919
920 /**
921
922 Restore any optional properties found in "doc" to "properties".
923
924 properties tags for all optional properties. Within that there will be scope
925 tags. In the following example there exist one property in the "fsplugin"
926 scope. "layers" is a list containing three string values.
927
928 \code{.xml}
929 <properties>
930 <fsplugin>
931 <foo type="int" >42</foo>
932 <baz type="int" >1</baz>
933 <layers type="QStringList" >
934 <value>railroad</value>
935 <value>airport</value>
936 </layers>
937 <xyqzzy type="int" >1</xyqzzy>
938 <bar type="double" >123.456</bar>
939 <feature_types type="QStringList" >
940 <value>type</value>
941 </feature_types>
942 </fsplugin>
943 </properties>
944 \endcode
945
946 \param doc xml document
947 \param project_properties should be the top QgsProjectPropertyKey node.
948
949 */
_getProperties(const QDomDocument & doc,QgsProjectPropertyKey & project_properties)950 void _getProperties( const QDomDocument &doc, QgsProjectPropertyKey &project_properties )
951 {
952 QDomElement propertiesElem = doc.documentElement().firstChildElement( QStringLiteral( "properties" ) );
953
954 if ( propertiesElem.isNull() ) // no properties found, so we're done
955 {
956 return;
957 }
958
959 QDomNodeList scopes = propertiesElem.childNodes();
960
961 if ( scopes.count() < 1 )
962 {
963 QgsDebugMsg( QStringLiteral( "empty ``properties'' XML tag ... bailing" ) );
964 return;
965 }
966
967 if ( ! project_properties.readXml( propertiesElem ) )
968 {
969 QgsDebugMsg( QStringLiteral( "Project_properties.readXml() failed" ) );
970 }
971 }
972
973 /**
974 * Returns the data defined server properties collection found in "doc" to "dataDefinedServerProperties".
975 * \param doc xml document
976 * \param dataDefinedServerPropertyDefinitions property collection of the server overrides
977 * \since QGIS 3.14
978 **/
getDataDefinedServerProperties(const QDomDocument & doc,const QgsPropertiesDefinition & dataDefinedServerPropertyDefinitions)979 QgsPropertyCollection getDataDefinedServerProperties( const QDomDocument &doc, const QgsPropertiesDefinition &dataDefinedServerPropertyDefinitions )
980 {
981 QgsPropertyCollection ddServerProperties;
982 // Read data defined server properties
983 QDomElement ddElem = doc.documentElement().firstChildElement( QStringLiteral( "dataDefinedServerProperties" ) );
984 if ( !ddElem.isNull() )
985 {
986 if ( !ddServerProperties.readXml( ddElem, dataDefinedServerPropertyDefinitions ) )
987 {
988 QgsDebugMsg( QStringLiteral( "dataDefinedServerProperties.readXml() failed" ) );
989 }
990 }
991 return ddServerProperties;
992 }
993
994 /**
995 Get the project title
996 \todo XXX we should go with the attribute xor title, not both.
997 */
_getTitle(const QDomDocument & doc,QString & title)998 static void _getTitle( const QDomDocument &doc, QString &title )
999 {
1000 QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "title" ) );
1001
1002 title.clear(); // by default the title will be empty
1003
1004 if ( !nl.count() )
1005 {
1006 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1007 return;
1008 }
1009
1010 QDomNode titleNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1011
1012 if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text
1013 {
1014 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1015 return;
1016 }
1017
1018 QDomNode titleTextNode = titleNode.firstChild(); // should only have one child
1019
1020 if ( !titleTextNode.isText() )
1021 {
1022 QgsDebugMsgLevel( QStringLiteral( "unable to find title element" ), 2 );
1023 return;
1024 }
1025
1026 QDomText titleText = titleTextNode.toText();
1027
1028 title = titleText.data();
1029
1030 }
1031
readProjectFileMetadata(const QDomDocument & doc,QString & lastUser,QString & lastUserFull,QDateTime & lastSaveDateTime)1032 static void readProjectFileMetadata( const QDomDocument &doc, QString &lastUser, QString &lastUserFull, QDateTime &lastSaveDateTime )
1033 {
1034 QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
1035
1036 if ( !nl.count() )
1037 {
1038 QgsDebugMsg( "unable to find qgis element" );
1039 return;
1040 }
1041
1042 QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1043
1044 QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1045 lastUser = qgisElement.attribute( QStringLiteral( "saveUser" ), QString() );
1046 lastUserFull = qgisElement.attribute( QStringLiteral( "saveUserFull" ), QString() );
1047 lastSaveDateTime = QDateTime::fromString( qgisElement.attribute( QStringLiteral( "saveDateTime" ), QString() ), Qt::ISODate );
1048 }
1049
1050
getVersion(const QDomDocument & doc)1051 QgsProjectVersion getVersion( const QDomDocument &doc )
1052 {
1053 QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "qgis" ) );
1054
1055 if ( !nl.count() )
1056 {
1057 QgsDebugMsg( QStringLiteral( " unable to find qgis element in project file" ) );
1058 return QgsProjectVersion( 0, 0, 0, QString() );
1059 }
1060
1061 QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element OK
1062
1063 QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
1064 QgsProjectVersion projectVersion( qgisElement.attribute( QStringLiteral( "version" ) ) );
1065 return projectVersion;
1066 }
1067
1068
snappingConfig() const1069 QgsSnappingConfig QgsProject::snappingConfig() const
1070 {
1071 return mSnappingConfig;
1072 }
1073
setSnappingConfig(const QgsSnappingConfig & snappingConfig)1074 void QgsProject::setSnappingConfig( const QgsSnappingConfig &snappingConfig )
1075 {
1076 if ( mSnappingConfig == snappingConfig )
1077 return;
1078
1079 mSnappingConfig = snappingConfig;
1080 setDirty( true );
1081 emit snappingConfigChanged( mSnappingConfig );
1082 }
1083
setAvoidIntersectionsMode(const AvoidIntersectionsMode mode)1084 void QgsProject::setAvoidIntersectionsMode( const AvoidIntersectionsMode mode )
1085 {
1086 if ( mAvoidIntersectionsMode == mode )
1087 return;
1088
1089 mAvoidIntersectionsMode = mode;
1090 emit avoidIntersectionsModeChanged();
1091 }
1092
_getMapLayers(const QDomDocument & doc,QList<QDomNode> & brokenNodes,QgsProject::ReadFlags flags)1093 bool QgsProject::_getMapLayers( const QDomDocument &doc, QList<QDomNode> &brokenNodes, QgsProject::ReadFlags flags )
1094 {
1095 // Layer order is set by the restoring the legend settings from project file.
1096 // This is done on the 'readProject( ... )' signal
1097
1098 QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "maplayer" ) );
1099
1100 // process the map layer nodes
1101
1102 if ( 0 == nl.count() ) // if we have no layers to process, bail
1103 {
1104 return true; // Decided to return "true" since it's
1105 // possible for there to be a project with no
1106 // layers; but also, more imporantly, this
1107 // would cause the tests/qgsproject to fail
1108 // since the test suite doesn't currently
1109 // support test layers
1110 }
1111
1112 bool returnStatus = true;
1113
1114 // order layers based on their dependencies
1115 QgsScopedRuntimeProfile profile( tr( "Sorting layers" ), QStringLiteral( "projectload" ) );
1116 QgsLayerDefinition::DependencySorter depSorter( doc );
1117 if ( depSorter.hasCycle() )
1118 return false;
1119
1120 // Missing a dependency? We still load all the layers, otherwise the project is completely broken!
1121 if ( depSorter.hasMissingDependency() )
1122 returnStatus = false;
1123
1124 emit layerLoaded( 0, nl.count() );
1125
1126 const QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
1127 const int totalLayerCount = sortedLayerNodes.count();
1128
1129 int i = 0;
1130 for ( const QDomNode &node : sortedLayerNodes )
1131 {
1132 const QDomElement element = node.toElement();
1133
1134 const QString name = translate( QStringLiteral( "project:layers:%1" ).arg( node.namedItem( QStringLiteral( "id" ) ).toElement().text() ), node.namedItem( QStringLiteral( "layername" ) ).toElement().text() );
1135 if ( !name.isNull() )
1136 emit loadingLayer( tr( "Loading layer %1" ).arg( name ) );
1137
1138 profile.switchTask( name );
1139
1140 if ( element.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
1141 {
1142 createEmbeddedLayer( element.attribute( QStringLiteral( "id" ) ), readPath( element.attribute( QStringLiteral( "project" ) ) ), brokenNodes, true, flags );
1143 }
1144 else
1145 {
1146 QgsReadWriteContext context;
1147 context.setPathResolver( pathResolver() );
1148 context.setProjectTranslator( this );
1149 context.setTransformContext( transformContext() );
1150
1151 if ( !addLayer( element, brokenNodes, context, flags ) )
1152 {
1153 returnStatus = false;
1154 }
1155 const auto messages = context.takeMessages();
1156 if ( !messages.isEmpty() )
1157 {
1158 emit loadingLayerMessageReceived( tr( "Loading layer %1" ).arg( name ), messages );
1159 }
1160 }
1161 emit layerLoaded( i + 1, totalLayerCount );
1162 i++;
1163 }
1164
1165 return returnStatus;
1166 }
1167
addLayer(const QDomElement & layerElem,QList<QDomNode> & brokenNodes,QgsReadWriteContext & context,QgsProject::ReadFlags flags)1168 bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &brokenNodes, QgsReadWriteContext &context, QgsProject::ReadFlags flags )
1169 {
1170 QString type = layerElem.attribute( QStringLiteral( "type" ) );
1171 QgsDebugMsgLevel( "Layer type is " + type, 4 );
1172 std::unique_ptr<QgsMapLayer> mapLayer;
1173
1174 QgsScopedRuntimeProfile profile( tr( "Create layer" ), QStringLiteral( "projectload" ) );
1175 if ( type == QLatin1String( "vector" ) )
1176 {
1177 mapLayer = qgis::make_unique<QgsVectorLayer>();
1178 // apply specific settings to vector layer
1179 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
1180 {
1181 vl->setReadExtentFromXml( mTrustLayerMetadata || ( flags & QgsProject::ReadFlag::FlagTrustLayerMetadata ) );
1182 }
1183 }
1184 else if ( type == QLatin1String( "raster" ) )
1185 {
1186 mapLayer = qgis::make_unique<QgsRasterLayer>();
1187 }
1188 else if ( type == QLatin1String( "mesh" ) )
1189 {
1190 mapLayer = qgis::make_unique<QgsMeshLayer>();
1191 }
1192 else if ( type == QLatin1String( "vector-tile" ) )
1193 {
1194 mapLayer = qgis::make_unique<QgsVectorTileLayer>();
1195 }
1196 else if ( type == QLatin1String( "plugin" ) )
1197 {
1198 QString typeName = layerElem.attribute( QStringLiteral( "name" ) );
1199 mapLayer.reset( QgsApplication::pluginLayerRegistry()->createLayer( typeName ) );
1200 }
1201 else if ( type == QLatin1String( "annotation" ) )
1202 {
1203 QgsAnnotationLayer::LayerOptions options( mTransformContext );
1204 mapLayer = qgis::make_unique<QgsAnnotationLayer>( QString(), options );
1205 }
1206 if ( !mapLayer )
1207 {
1208 QgsDebugMsg( QStringLiteral( "Unable to create layer" ) );
1209 return false;
1210 }
1211
1212 Q_CHECK_PTR( mapLayer ); // NOLINT
1213
1214 // This is tricky: to avoid a leak we need to check if the layer was already in the store
1215 // because if it was, the newly created layer will not be added to the store and it would leak.
1216 const QString layerId { layerElem.namedItem( QStringLiteral( "id" ) ).toElement().text() };
1217 Q_ASSERT( ! layerId.isEmpty() );
1218 const bool layerWasStored { layerStore()->mapLayer( layerId ) != nullptr };
1219
1220 // have the layer restore state that is stored in Dom node
1221 QgsMapLayer::ReadFlags layerFlags = QgsMapLayer::ReadFlags();
1222 if ( flags & QgsProject::ReadFlag::FlagDontResolveLayers )
1223 layerFlags |= QgsMapLayer::FlagDontResolveLayers;
1224 // Propagate trust layer metadata flag
1225 if ( mTrustLayerMetadata || ( flags & QgsProject::ReadFlag::FlagTrustLayerMetadata ) )
1226 layerFlags |= QgsMapLayer::FlagTrustLayerMetadata;
1227
1228 profile.switchTask( tr( "Load layer source" ) );
1229 bool layerIsValid = mapLayer->readLayerXml( layerElem, context, layerFlags ) && mapLayer->isValid();
1230
1231 profile.switchTask( tr( "Add layer to project" ) );
1232 QList<QgsMapLayer *> newLayers;
1233 newLayers << mapLayer.get();
1234 if ( layerIsValid || flags & QgsProject::ReadFlag::FlagDontResolveLayers )
1235 {
1236 emit readMapLayer( mapLayer.get(), layerElem );
1237 addMapLayers( newLayers );
1238 }
1239 else
1240 {
1241 // It's a bad layer: do not add to legend (the user will decide if she wants to do so)
1242 addMapLayers( newLayers, false );
1243 newLayers.first();
1244 QgsDebugMsg( "Unable to load " + type + " layer" );
1245 brokenNodes.push_back( layerElem );
1246 }
1247
1248 // It should be safe to delete the layer now if layer was stored, because all the store
1249 // had to to was to reset the data source in case the validity changed.
1250 if ( ! layerWasStored )
1251 {
1252 mapLayer.release();
1253 }
1254
1255 return layerIsValid;
1256 }
1257
read(const QString & filename,QgsProject::ReadFlags flags)1258 bool QgsProject::read( const QString &filename, QgsProject::ReadFlags flags )
1259 {
1260 mFile.setFileName( filename );
1261 mCachedHomePath.clear();
1262 mProjectScope.reset();
1263
1264 return read( flags );
1265 }
1266
read(QgsProject::ReadFlags flags)1267 bool QgsProject::read( QgsProject::ReadFlags flags )
1268 {
1269 QString filename = mFile.fileName();
1270 bool returnValue;
1271
1272 if ( QgsProjectStorage *storage = projectStorage() )
1273 {
1274 QTemporaryFile inDevice;
1275 if ( !inDevice.open() )
1276 {
1277 setError( tr( "Unable to open %1" ).arg( inDevice.fileName() ) );
1278 return false;
1279 }
1280
1281 QgsReadWriteContext context;
1282 context.setProjectTranslator( this );
1283 if ( !storage->readProject( filename, &inDevice, context ) )
1284 {
1285 QString err = tr( "Unable to open %1" ).arg( filename );
1286 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
1287 if ( !messages.isEmpty() )
1288 err += QStringLiteral( "\n\n" ) + messages.last().message();
1289 setError( err );
1290 return false;
1291 }
1292 returnValue = unzip( inDevice.fileName(), flags ); // calls setError() if returning false
1293 }
1294 else
1295 {
1296 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
1297 {
1298 returnValue = unzip( mFile.fileName(), flags );
1299 }
1300 else
1301 {
1302 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
1303 returnValue = readProjectFile( mFile.fileName(), flags );
1304 }
1305
1306 //on translation we should not change the filename back
1307 if ( !mTranslator )
1308 {
1309 mFile.setFileName( filename );
1310 mCachedHomePath.clear();
1311 mProjectScope.reset();
1312 }
1313 else
1314 {
1315 //but delete the translator
1316 mTranslator.reset( nullptr );
1317 }
1318 }
1319 emit homePathChanged();
1320 return returnValue;
1321 }
1322
readProjectFile(const QString & filename,QgsProject::ReadFlags flags)1323 bool QgsProject::readProjectFile( const QString &filename, QgsProject::ReadFlags flags )
1324 {
1325 // avoid multiple emission of snapping updated signals
1326 ScopedIntIncrementor snapSignalBlock( &mBlockSnappingUpdates );
1327
1328 QFile projectFile( filename );
1329 clearError();
1330
1331 QgsApplication::profiler()->clear( QStringLiteral( "projectload" ) );
1332 QgsScopedRuntimeProfile profile( tr( "Setting up translations" ), QStringLiteral( "projectload" ) );
1333
1334 QString localeFileName = QStringLiteral( "%1_%2" ).arg( QFileInfo( projectFile.fileName() ).baseName(), mSettings.value( QStringLiteral( "locale/userLocale" ), QString() ).toString() );
1335
1336 if ( QFile( QStringLiteral( "%1/%2.qm" ).arg( QFileInfo( projectFile.fileName() ).absolutePath(), localeFileName ) ).exists() )
1337 {
1338 mTranslator.reset( new QTranslator() );
1339 mTranslator->load( localeFileName, QFileInfo( projectFile.fileName() ).absolutePath() );
1340 }
1341
1342 profile.switchTask( tr( "Reading project file" ) );
1343 std::unique_ptr<QDomDocument> doc( new QDomDocument( QStringLiteral( "qgis" ) ) );
1344
1345 if ( !projectFile.open( QIODevice::ReadOnly | QIODevice::Text ) )
1346 {
1347 projectFile.close();
1348
1349 setError( tr( "Unable to open %1" ).arg( projectFile.fileName() ) );
1350
1351 return false;
1352 }
1353
1354 // location of problem associated with errorMsg
1355 int line, column;
1356 QString errorMsg;
1357
1358 if ( !doc->setContent( &projectFile, &errorMsg, &line, &column ) )
1359 {
1360 // want to make this class as GUI independent as possible; so commented out
1361 #if 0
1362 QMessageBox::critical( 0, tr( "Read Project File" ),
1363 tr( "%1 at line %2 column %3" ).arg( errorMsg ).arg( line ).arg( column ) );
1364 #endif
1365
1366 QString errorString = tr( "Project file read error in file %1: %2 at line %3 column %4" )
1367 .arg( projectFile.fileName(), errorMsg ).arg( line ).arg( column );
1368
1369 QgsDebugMsg( errorString );
1370
1371 projectFile.close();
1372
1373 setError( tr( "%1 for file %2" ).arg( errorString, projectFile.fileName() ) );
1374
1375 return false;
1376 }
1377
1378 projectFile.close();
1379
1380 QgsDebugMsgLevel( "Opened document " + projectFile.fileName(), 2 );
1381
1382 // get project version string, if any
1383 QgsProjectVersion fileVersion = getVersion( *doc );
1384 const QgsProjectVersion thisVersion( Qgis::version() );
1385
1386 profile.switchTask( tr( "Updating project file" ) );
1387 if ( thisVersion > fileVersion )
1388 {
1389 QgsLogger::warning( "Loading a file that was saved with an older "
1390 "version of qgis (saved in " + fileVersion.text() +
1391 ", loaded in " + Qgis::version() +
1392 "). Problems may occur." );
1393
1394 QgsProjectFileTransform projectFile( *doc, fileVersion );
1395
1396 // Shows a warning when an old project file is read.
1397 emit oldProjectVersionWarning( fileVersion.text() );
1398
1399 projectFile.updateRevision( thisVersion );
1400 }
1401
1402 // start new project, just keep the file name and auxiliary storage
1403 profile.switchTask( tr( "Creating auxiliary storage" ) );
1404 QString fileName = mFile.fileName();
1405 std::unique_ptr<QgsAuxiliaryStorage> aStorage = std::move( mAuxiliaryStorage );
1406 clear();
1407 mAuxiliaryStorage = std::move( aStorage );
1408 mFile.setFileName( fileName );
1409 mCachedHomePath.clear();
1410 mProjectScope.reset();
1411 mSaveVersion = fileVersion;
1412
1413 // now get any properties
1414 profile.switchTask( tr( "Reading properties" ) );
1415 _getProperties( *doc, mProperties );
1416
1417 // now get the data defined server properties
1418 mDataDefinedServerProperties = getDataDefinedServerProperties( *doc, dataDefinedServerPropertyDefinitions() );
1419
1420 QgsDebugMsgLevel( QString::number( mProperties.count() ) + " properties read", 2 );
1421
1422 #if 0
1423 dump_( mProperties );
1424 #endif
1425
1426 // get older style project title
1427 QString oldTitle;
1428 _getTitle( *doc, oldTitle );
1429
1430 readProjectFileMetadata( *doc, mSaveUser, mSaveUserFull, mSaveDateTime );
1431
1432 QDomNodeList homePathNl = doc->elementsByTagName( QStringLiteral( "homePath" ) );
1433 if ( homePathNl.count() > 0 )
1434 {
1435 QDomElement homePathElement = homePathNl.at( 0 ).toElement();
1436 QString homePath = homePathElement.attribute( QStringLiteral( "path" ) );
1437 if ( !homePath.isEmpty() )
1438 setPresetHomePath( homePath );
1439 }
1440 else
1441 {
1442 emit homePathChanged();
1443 }
1444
1445 const QColor backgroundColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), 255 ),
1446 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), 255 ),
1447 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), 255 ) );
1448 setBackgroundColor( backgroundColor );
1449 const QColor selectionColor( readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), 255 ),
1450 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), 255 ),
1451 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), 255 ),
1452 readNumEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), 255 ) );
1453 setSelectionColor( selectionColor );
1454
1455 QgsReadWriteContext context;
1456 context.setPathResolver( pathResolver() );
1457 context.setProjectTranslator( this );
1458
1459 //crs
1460 QgsCoordinateReferenceSystem projectCrs;
1461 if ( readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectionsEnabled" ), 0 ) )
1462 {
1463 // first preference - dedicated projectCrs node
1464 QDomNode srsNode = doc->documentElement().namedItem( QStringLiteral( "projectCrs" ) );
1465 if ( !srsNode.isNull() )
1466 {
1467 projectCrs.readXml( srsNode );
1468 }
1469
1470 if ( !projectCrs.isValid() )
1471 {
1472 QString projCrsString = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSProj4String" ) );
1473 long currentCRS = readNumEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCRSID" ), -1 );
1474 const QString authid = readEntry( QStringLiteral( "SpatialRefSys" ), QStringLiteral( "/ProjectCrs" ) );
1475
1476 // authid should be prioritized over all
1477 bool isUserAuthId = authid.startsWith( QLatin1String( "USER:" ), Qt::CaseInsensitive );
1478 if ( !authid.isEmpty() && !isUserAuthId )
1479 projectCrs = QgsCoordinateReferenceSystem( authid );
1480
1481 // try the CRS
1482 if ( !projectCrs.isValid() && currentCRS >= 0 )
1483 {
1484 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
1485 }
1486
1487 // if that didn't produce a match, try the proj.4 string
1488 if ( !projCrsString.isEmpty() && ( authid.isEmpty() || isUserAuthId ) && ( !projectCrs.isValid() || projectCrs.toProj() != projCrsString ) )
1489 {
1490 projectCrs = QgsCoordinateReferenceSystem::fromProj( projCrsString );
1491 }
1492
1493 // last just take the given id
1494 if ( !projectCrs.isValid() )
1495 {
1496 projectCrs = QgsCoordinateReferenceSystem::fromSrsId( currentCRS );
1497 }
1498 }
1499 }
1500 mCrs = projectCrs;
1501
1502 QStringList datumErrors;
1503 if ( !mTransformContext.readXml( doc->documentElement(), context, datumErrors ) && !datumErrors.empty() )
1504 {
1505 emit missingDatumTransforms( datumErrors );
1506 }
1507 emit transformContextChanged();
1508
1509 //add variables defined in project file - do this early in the reading cycle, as other components
1510 //(e.g. layouts) may depend on these variables
1511 QStringList variableNames = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ) );
1512 QStringList variableValues = readListEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ) );
1513
1514 mCustomVariables.clear();
1515 if ( variableNames.length() == variableValues.length() )
1516 {
1517 for ( int i = 0; i < variableNames.length(); ++i )
1518 {
1519 mCustomVariables.insert( variableNames.at( i ), variableValues.at( i ) );
1520 }
1521 }
1522 else
1523 {
1524 QgsMessageLog::logMessage( tr( "Project Variables Invalid" ), tr( "The project contains invalid variable settings." ) );
1525 }
1526
1527 QDomNodeList nl = doc->elementsByTagName( QStringLiteral( "projectMetadata" ) );
1528 if ( !nl.isEmpty() )
1529 {
1530 QDomElement metadataElement = nl.at( 0 ).toElement();
1531 mMetadata.readMetadataXml( metadataElement );
1532 }
1533 else
1534 {
1535 // older project, no metadata => remove auto generated metadata which is populated on QgsProject::clear()
1536 mMetadata = QgsProjectMetadata();
1537 }
1538 if ( mMetadata.title().isEmpty() && !oldTitle.isEmpty() )
1539 {
1540 // upgrade older title storage to storing within project metadata.
1541 mMetadata.setTitle( oldTitle );
1542 }
1543 emit metadataChanged();
1544
1545 nl = doc->elementsByTagName( QStringLiteral( "autotransaction" ) );
1546 if ( nl.count() )
1547 {
1548 QDomElement transactionElement = nl.at( 0 ).toElement();
1549 if ( transactionElement.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
1550 mAutoTransaction = true;
1551 }
1552
1553 nl = doc->elementsByTagName( QStringLiteral( "evaluateDefaultValues" ) );
1554 if ( nl.count() )
1555 {
1556 QDomElement evaluateDefaultValuesElement = nl.at( 0 ).toElement();
1557 if ( evaluateDefaultValuesElement.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
1558 mEvaluateDefaultValues = true;
1559 }
1560
1561 // Read trust layer metadata config in the project
1562 nl = doc->elementsByTagName( QStringLiteral( "trust" ) );
1563 if ( nl.count() )
1564 {
1565 QDomElement trustElement = nl.at( 0 ).toElement();
1566 if ( trustElement.attribute( QStringLiteral( "active" ), QStringLiteral( "0" ) ).toInt() == 1 )
1567 mTrustLayerMetadata = true;
1568 }
1569
1570 // read the layer tree from project file
1571 profile.switchTask( tr( "Loading layer tree" ) );
1572 mRootGroup->setCustomProperty( QStringLiteral( "loading" ), 1 );
1573
1574 QDomElement layerTreeElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
1575 if ( !layerTreeElem.isNull() )
1576 {
1577 mRootGroup->readChildrenFromXml( layerTreeElem, context );
1578 }
1579 else
1580 {
1581 QgsLayerTreeUtils::readOldLegend( mRootGroup, doc->documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
1582 }
1583
1584 mLayerTreeRegistryBridge->setEnabled( false );
1585
1586 // get the map layers
1587 profile.switchTask( tr( "Reading map layers" ) );
1588
1589 QList<QDomNode> brokenNodes;
1590 bool clean = _getMapLayers( *doc, brokenNodes, flags );
1591
1592 // review the integrity of the retrieved map layers
1593 if ( !clean )
1594 {
1595 QgsDebugMsg( QStringLiteral( "Unable to get map layers from project file." ) );
1596
1597 if ( !brokenNodes.isEmpty() )
1598 {
1599 QgsDebugMsg( "there are " + QString::number( brokenNodes.size() ) + " broken layers" );
1600 }
1601
1602 // we let a custom handler decide what to do with missing layers
1603 // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
1604 mBadLayerHandler->handleBadLayers( brokenNodes );
1605 }
1606
1607 mMainAnnotationLayer->readLayerXml( doc->documentElement().firstChildElement( QStringLiteral( "main-annotation-layer" ) ), context );
1608 mMainAnnotationLayer->setTransformContext( mTransformContext );
1609
1610 // Resolve references to other layers
1611 // Needs to be done here once all dependent layers are loaded
1612 profile.switchTask( tr( "Resolving layer references" ) );
1613 QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
1614 for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); ++it )
1615 {
1616 it.value()->resolveReferences( this );
1617 }
1618
1619 mLayerTreeRegistryBridge->setEnabled( true );
1620
1621 // load embedded groups and layers
1622 profile.switchTask( tr( "Loading embedded layers" ) );
1623 loadEmbeddedNodes( mRootGroup, flags );
1624
1625 // now that layers are loaded, we can resolve layer tree's references to the layers
1626 profile.switchTask( tr( "Resolving references" ) );
1627 mRootGroup->resolveReferences( this );
1628
1629 if ( !layerTreeElem.isNull() )
1630 {
1631 mRootGroup->readLayerOrderFromXml( layerTreeElem );
1632 }
1633
1634 // Load pre 3.0 configuration
1635 QDomElement layerTreeCanvasElem = doc->documentElement().firstChildElement( QStringLiteral( "layer-tree-canvas" ) );
1636 if ( !layerTreeCanvasElem.isNull( ) )
1637 {
1638 mRootGroup->readLayerOrderFromXml( layerTreeCanvasElem );
1639 }
1640
1641 // Convert pre 3.4 to create layers flags
1642 if ( QgsProjectVersion( 3, 4, 0 ) > mSaveVersion )
1643 {
1644 const QStringList requiredLayerIds = readListEntry( QStringLiteral( "RequiredLayers" ), QStringLiteral( "Layers" ) );
1645 for ( const QString &layerId : requiredLayerIds )
1646 {
1647 if ( QgsMapLayer *layer = mapLayer( layerId ) )
1648 {
1649 layer->setFlags( layer->flags() & ~QgsMapLayer::Removable );
1650 }
1651 }
1652 const QStringList disabledLayerIds = readListEntry( QStringLiteral( "Identify" ), QStringLiteral( "/disabledLayers" ) );
1653 for ( const QString &layerId : disabledLayerIds )
1654 {
1655 if ( QgsMapLayer *layer = mapLayer( layerId ) )
1656 {
1657 layer->setFlags( layer->flags() & ~QgsMapLayer::Identifiable );
1658 }
1659 }
1660 }
1661
1662 // After bad layer handling we might still have invalid layers,
1663 // store them in case the user wanted to handle them later
1664 // or wanted to pass them through when saving
1665 if ( !( flags & QgsProject::ReadFlag::FlagDontStoreOriginalStyles ) )
1666 {
1667 profile.switchTask( tr( "Storing original layer properties" ) );
1668 QgsLayerTreeUtils::storeOriginalLayersProperties( mRootGroup, doc.get() );
1669 }
1670
1671 mRootGroup->removeCustomProperty( QStringLiteral( "loading" ) );
1672
1673 profile.switchTask( tr( "Loading map themes" ) );
1674 mMapThemeCollection.reset( new QgsMapThemeCollection( this ) );
1675 emit mapThemeCollectionChanged();
1676 mMapThemeCollection->readXml( *doc );
1677
1678 profile.switchTask( tr( "Loading label settings" ) );
1679 mLabelingEngineSettings->readSettingsFromProject( this );
1680 emit labelingEngineSettingsChanged();
1681
1682 profile.switchTask( tr( "Loading annotations" ) );
1683 mAnnotationManager->readXml( doc->documentElement(), context );
1684 if ( !( flags & QgsProject::ReadFlag::FlagDontLoadLayouts ) )
1685 {
1686 profile.switchTask( tr( "Loading layouts" ) );
1687 mLayoutManager->readXml( doc->documentElement(), *doc );
1688 }
1689 profile.switchTask( tr( "Loading bookmarks" ) );
1690 mBookmarkManager->readXml( doc->documentElement(), *doc );
1691
1692 // reassign change dependencies now that all layers are loaded
1693 QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
1694 for ( QMap<QString, QgsMapLayer *>::iterator it = existingMaps.begin(); it != existingMaps.end(); ++it )
1695 {
1696 it.value()->setDependencies( it.value()->dependencies() );
1697 }
1698
1699 profile.switchTask( tr( "Loading snapping settings" ) );
1700 mSnappingConfig.readProject( *doc );
1701 mAvoidIntersectionsMode = static_cast<AvoidIntersectionsMode>( readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( AvoidIntersectionsMode::AvoidIntersectionsLayers ) ) );
1702
1703 profile.switchTask( tr( "Loading view settings" ) );
1704 // restore older project scales settings
1705 mViewSettings->setUseProjectScales( readBoolEntry( QStringLiteral( "Scales" ), QStringLiteral( "/useProjectScales" ) ) );
1706 const QStringList scales = readListEntry( QStringLiteral( "Scales" ), QStringLiteral( "/ScalesList" ) );
1707 QVector<double> res;
1708 for ( const QString &scale : scales )
1709 {
1710 const QStringList parts = scale.split( ':' );
1711 if ( parts.size() != 2 )
1712 continue;
1713
1714 bool ok = false;
1715 const double denominator = QLocale().toDouble( parts[1], &ok );
1716 if ( ok )
1717 {
1718 res << denominator;
1719 }
1720 }
1721 mViewSettings->setMapScales( res );
1722 QDomElement viewSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectViewSettings" ) );
1723 if ( !viewSettingsElement.isNull() )
1724 mViewSettings->readXml( viewSettingsElement, context );
1725
1726 // restore time settings
1727 profile.switchTask( tr( "Loading temporal settings" ) );
1728 QDomElement timeSettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectTimeSettings" ) );
1729 if ( !timeSettingsElement.isNull() )
1730 mTimeSettings->readXml( timeSettingsElement, context );
1731
1732 profile.switchTask( tr( "Loading display settings" ) );
1733 QDomElement displaySettingsElement = doc->documentElement().firstChildElement( QStringLiteral( "ProjectDisplaySettings" ) );
1734 if ( !displaySettingsElement.isNull() )
1735 mDisplaySettings->readXml( displaySettingsElement, context );
1736
1737 profile.switchTask( tr( "Updating variables" ) );
1738 emit customVariablesChanged();
1739 profile.switchTask( tr( "Updating CRS" ) );
1740 emit crsChanged();
1741 emit ellipsoidChanged( ellipsoid() );
1742
1743 // read the project: used by map canvas and legend
1744 profile.switchTask( tr( "Reading external settings" ) );
1745 emit readProject( *doc );
1746 emit readProjectWithContext( *doc, context );
1747
1748 profile.switchTask( tr( "Updating interface" ) );
1749
1750 snapSignalBlock.release();
1751 if ( !mBlockSnappingUpdates )
1752 emit snappingConfigChanged( mSnappingConfig );
1753
1754 emit avoidIntersectionsModeChanged();
1755 emit topologicalEditingChanged();
1756 emit projectColorsChanged();
1757
1758 // if all went well, we're allegedly in pristine state
1759 if ( clean )
1760 setDirty( false );
1761
1762 QgsDebugMsgLevel( QString( "Project save user: %1" ).arg( mSaveUser ), 2 );
1763 QgsDebugMsgLevel( QString( "Project save user: %1" ).arg( mSaveUserFull ), 2 );
1764
1765 Q_NOWARN_DEPRECATED_PUSH
1766 emit nonIdentifiableLayersChanged( nonIdentifiableLayers() );
1767 Q_NOWARN_DEPRECATED_POP
1768
1769 if ( mTranslator )
1770 {
1771 //project possibly translated -> rename it with locale postfix
1772 QString newFileName( QStringLiteral( "%1/%2.qgs" ).arg( QFileInfo( projectFile.fileName() ).absolutePath(), localeFileName ) );
1773 setFileName( newFileName );
1774
1775 if ( write() )
1776 {
1777 setTitle( localeFileName );
1778 QgsMessageLog::logMessage( tr( "Translated project saved with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::Success );
1779 }
1780 else
1781 {
1782 QgsMessageLog::logMessage( tr( "Error saving translated project with locale prefix %1" ).arg( newFileName ), QObject::tr( "Project translation" ), Qgis::Critical );
1783 }
1784 }
1785 return true;
1786 }
1787
1788
loadEmbeddedNodes(QgsLayerTreeGroup * group,QgsProject::ReadFlags flags)1789 bool QgsProject::loadEmbeddedNodes( QgsLayerTreeGroup *group, QgsProject::ReadFlags flags )
1790 {
1791 bool valid = true;
1792 const auto constChildren = group->children();
1793 for ( QgsLayerTreeNode *child : constChildren )
1794 {
1795 if ( QgsLayerTree::isGroup( child ) )
1796 {
1797 QgsLayerTreeGroup *childGroup = QgsLayerTree::toGroup( child );
1798 if ( childGroup->customProperty( QStringLiteral( "embedded" ) ).toInt() )
1799 {
1800 // make sure to convert the path from relative to absolute
1801 QString projectPath = readPath( childGroup->customProperty( QStringLiteral( "embedded_project" ) ).toString() );
1802 childGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectPath );
1803 QgsLayerTreeGroup *newGroup = createEmbeddedGroup( childGroup->name(), projectPath, childGroup->customProperty( QStringLiteral( "embedded-invisible-layers" ) ).toStringList(), flags );
1804 if ( newGroup )
1805 {
1806 QList<QgsLayerTreeNode *> clonedChildren;
1807 const auto constChildren = newGroup->children();
1808 for ( QgsLayerTreeNode *newGroupChild : constChildren )
1809 clonedChildren << newGroupChild->clone();
1810 delete newGroup;
1811
1812 childGroup->insertChildNodes( 0, clonedChildren );
1813 }
1814 }
1815 else
1816 {
1817 loadEmbeddedNodes( childGroup, flags );
1818 }
1819 }
1820 else if ( QgsLayerTree::isLayer( child ) )
1821 {
1822 if ( child->customProperty( QStringLiteral( "embedded" ) ).toInt() )
1823 {
1824 QList<QDomNode> brokenNodes;
1825 if ( ! createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), readPath( child->customProperty( QStringLiteral( "embedded_project" ) ).toString() ), brokenNodes, true, flags ) )
1826 {
1827 valid = valid && false;
1828 }
1829 }
1830 }
1831
1832 }
1833
1834 return valid;
1835 }
1836
customVariables() const1837 QVariantMap QgsProject::customVariables() const
1838 {
1839 return mCustomVariables;
1840 }
1841
setCustomVariables(const QVariantMap & variables)1842 void QgsProject::setCustomVariables( const QVariantMap &variables )
1843 {
1844 if ( variables == mCustomVariables )
1845 return;
1846
1847 //write variable to project
1848 QStringList variableNames;
1849 QStringList variableValues;
1850
1851 QVariantMap::const_iterator it = variables.constBegin();
1852 for ( ; it != variables.constEnd(); ++it )
1853 {
1854 variableNames << it.key();
1855 variableValues << it.value().toString();
1856 }
1857
1858 writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableNames" ), variableNames );
1859 writeEntry( QStringLiteral( "Variables" ), QStringLiteral( "/variableValues" ), variableValues );
1860
1861 mCustomVariables = variables;
1862 mProjectScope.reset();
1863
1864 emit customVariablesChanged();
1865 }
1866
setLabelingEngineSettings(const QgsLabelingEngineSettings & settings)1867 void QgsProject::setLabelingEngineSettings( const QgsLabelingEngineSettings &settings )
1868 {
1869 *mLabelingEngineSettings = settings;
1870 emit labelingEngineSettingsChanged();
1871 }
1872
labelingEngineSettings() const1873 const QgsLabelingEngineSettings &QgsProject::labelingEngineSettings() const
1874 {
1875 return *mLabelingEngineSettings;
1876 }
1877
layerStore()1878 QgsMapLayerStore *QgsProject::layerStore()
1879 {
1880 mProjectScope.reset();
1881 return mLayerStore.get();
1882 }
1883
layerStore() const1884 const QgsMapLayerStore *QgsProject::layerStore() const
1885 {
1886 return mLayerStore.get();
1887 }
1888
avoidIntersectionsLayers() const1889 QList<QgsVectorLayer *> QgsProject::avoidIntersectionsLayers() const
1890 {
1891 QList<QgsVectorLayer *> layers;
1892 QStringList layerIds = readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), QStringList() );
1893 const auto constLayerIds = layerIds;
1894 for ( const QString &layerId : constLayerIds )
1895 {
1896 if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mapLayer( layerId ) ) )
1897 layers << vlayer;
1898 }
1899 return layers;
1900 }
1901
setAvoidIntersectionsLayers(const QList<QgsVectorLayer * > & layers)1902 void QgsProject::setAvoidIntersectionsLayers( const QList<QgsVectorLayer *> &layers )
1903 {
1904 QStringList list;
1905 const auto constLayers = layers;
1906 for ( QgsVectorLayer *layer : constLayers )
1907 list << layer->id();
1908 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsList" ), list );
1909 emit avoidIntersectionsLayersChanged();
1910 }
1911
createExpressionContext() const1912 QgsExpressionContext QgsProject::createExpressionContext() const
1913 {
1914 QgsExpressionContext context;
1915
1916 context << QgsExpressionContextUtils::globalScope()
1917 << QgsExpressionContextUtils::projectScope( this );
1918
1919 return context;
1920 }
1921
createExpressionContextScope() const1922 QgsExpressionContextScope *QgsProject::createExpressionContextScope() const
1923 {
1924 // MUCH cheaper to clone than build
1925 if ( mProjectScope )
1926 {
1927 std::unique_ptr< QgsExpressionContextScope > projectScope = qgis::make_unique< QgsExpressionContextScope >( *mProjectScope );
1928 // we can't cache these
1929 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_distance_units" ), QgsUnitTypes::toString( distanceUnits() ), true, true ) );
1930 projectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_area_units" ), QgsUnitTypes::toString( areaUnits() ), true, true ) );
1931 return projectScope.release();
1932 }
1933
1934 mProjectScope = qgis::make_unique< QgsExpressionContextScope >( QObject::tr( "Project" ) );
1935
1936 const QVariantMap vars = customVariables();
1937
1938 QVariantMap::const_iterator it = vars.constBegin();
1939
1940 for ( ; it != vars.constEnd(); ++it )
1941 {
1942 mProjectScope->setVariable( it.key(), it.value(), true );
1943 }
1944
1945 QString projectPath = projectStorage() ? fileName() : absoluteFilePath();
1946 if ( projectPath.isEmpty() )
1947 projectPath = mOriginalPath;
1948 QString projectFolder = QFileInfo( projectPath ).path();
1949 QString projectFilename = QFileInfo( projectPath ).fileName();
1950 QString projectBasename = baseName();
1951
1952 //add other known project variables
1953 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_title" ), title(), true, true ) );
1954 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_path" ), QDir::toNativeSeparators( projectPath ), true, true ) );
1955 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_folder" ), QDir::toNativeSeparators( projectFolder ), true, true ) );
1956 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_filename" ), projectFilename, true, true ) );
1957 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_basename" ), projectBasename, true, true ) );
1958 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_home" ), QDir::toNativeSeparators( homePath() ), true, true ) );
1959 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_last_saved" ), mSaveDateTime.isNull() ? QVariant() : QVariant( mSaveDateTime ), true, true ) );
1960 QgsCoordinateReferenceSystem projectCrs = crs();
1961 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs" ), projectCrs.authid(), true, true ) );
1962 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_definition" ), projectCrs.toProj(), true, true ) );
1963 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_description" ), projectCrs.description(), true, true ) );
1964 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_ellipsoid" ), ellipsoid(), true, true ) );
1965 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "_project_transform_context" ), QVariant::fromValue<QgsCoordinateTransformContext>( transformContext() ), true, true ) );
1966 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_units" ), QgsUnitTypes::toString( projectCrs.mapUnits() ), true ) );
1967 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_acronym" ), projectCrs.projectionAcronym(), true ) );
1968 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_ellipsoid" ), projectCrs.ellipsoidAcronym(), true ) );
1969 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_proj4" ), projectCrs.toProj(), true ) );
1970 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_crs_wkt" ), projectCrs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ), true ) );
1971
1972 // metadata
1973 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_author" ), metadata().author(), true, true ) );
1974 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_abstract" ), metadata().abstract(), true, true ) );
1975 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_creation_date" ), metadata().creationDateTime(), true, true ) );
1976 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_identifier" ), metadata().identifier(), true, true ) );
1977
1978 // keywords
1979 QVariantMap keywords;
1980 QgsAbstractMetadataBase::KeywordMap metadataKeywords = metadata().keywords();
1981 for ( auto it = metadataKeywords.constBegin(); it != metadataKeywords.constEnd(); ++it )
1982 {
1983 keywords.insert( it.key(), it.value() );
1984 }
1985 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "project_keywords" ), keywords, true, true ) );
1986
1987 // layers
1988 QVariantList layersIds;
1989 QVariantList layers;
1990 const QMap<QString, QgsMapLayer *> layersInProject = mLayerStore->mapLayers();
1991 layersIds.reserve( layersInProject.count() );
1992 layers.reserve( layersInProject.count() );
1993 for ( auto it = layersInProject.constBegin(); it != layersInProject.constEnd(); ++it )
1994 {
1995 layersIds << it.value()->id();
1996 layers << QVariant::fromValue<QgsWeakMapLayerPointer>( QgsWeakMapLayerPointer( it.value() ) );
1997 }
1998 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layer_ids" ), layersIds, true ) );
1999 mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layers" ), layers, true ) );
2000
2001 mProjectScope->addFunction( QStringLiteral( "project_color" ), new GetNamedProjectColor( this ) );
2002
2003 return createExpressionContextScope();
2004 }
2005
onMapLayersAdded(const QList<QgsMapLayer * > & layers)2006 void QgsProject::onMapLayersAdded( const QList<QgsMapLayer *> &layers )
2007 {
2008 QMap<QString, QgsMapLayer *> existingMaps = mapLayers();
2009
2010 bool tgChanged = false;
2011
2012 const auto constLayers = layers;
2013 for ( QgsMapLayer *layer : constLayers )
2014 {
2015 if ( layer->isValid() )
2016 {
2017 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
2018 if ( vlayer )
2019 {
2020 if ( autoTransaction() )
2021 {
2022 if ( QgsTransaction::supportsTransaction( vlayer ) )
2023 {
2024 const QString connString = QgsTransaction::connectionString( vlayer->source() );
2025 const QString key = vlayer->providerType();
2026
2027 QgsTransactionGroup *tg = mTransactionGroups.value( qMakePair( key, connString ) );
2028
2029 if ( !tg )
2030 {
2031 tg = new QgsTransactionGroup();
2032 mTransactionGroups.insert( qMakePair( key, connString ), tg );
2033 tgChanged = true;
2034 }
2035 tg->addLayer( vlayer );
2036 }
2037 }
2038 vlayer->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, evaluateDefaultValues() );
2039 }
2040
2041 if ( tgChanged )
2042 emit transactionGroupsChanged();
2043
2044 connect( layer, &QgsMapLayer::configChanged, this, [ = ] { setDirty(); } );
2045
2046 // check if we have to update connections for layers with dependencies
2047 for ( QMap<QString, QgsMapLayer *>::const_iterator it = existingMaps.cbegin(); it != existingMaps.cend(); ++it )
2048 {
2049 QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
2050 if ( deps.contains( layer->id() ) )
2051 {
2052 // reconnect to change signals
2053 it.value()->setDependencies( deps );
2054 }
2055 }
2056 }
2057 }
2058
2059 if ( !mBlockSnappingUpdates && mSnappingConfig.addLayers( layers ) )
2060 emit snappingConfigChanged( mSnappingConfig );
2061 }
2062
onMapLayersRemoved(const QList<QgsMapLayer * > & layers)2063 void QgsProject::onMapLayersRemoved( const QList<QgsMapLayer *> &layers )
2064 {
2065 if ( !mBlockSnappingUpdates && mSnappingConfig.removeLayers( layers ) )
2066 emit snappingConfigChanged( mSnappingConfig );
2067 }
2068
cleanTransactionGroups(bool force)2069 void QgsProject::cleanTransactionGroups( bool force )
2070 {
2071 bool changed = false;
2072 for ( QMap< QPair< QString, QString>, QgsTransactionGroup *>::Iterator tg = mTransactionGroups.begin(); tg != mTransactionGroups.end(); )
2073 {
2074 if ( tg.value()->isEmpty() || force )
2075 {
2076 delete tg.value();
2077 tg = mTransactionGroups.erase( tg );
2078 changed = true;
2079 }
2080 else
2081 {
2082 ++tg;
2083 }
2084 }
2085 if ( changed )
2086 emit transactionGroupsChanged();
2087 }
2088
readLayer(const QDomNode & layerNode)2089 bool QgsProject::readLayer( const QDomNode &layerNode )
2090 {
2091 QgsReadWriteContext context;
2092 context.setPathResolver( pathResolver() );
2093 context.setProjectTranslator( this );
2094 context.setTransformContext( transformContext() );
2095 QList<QDomNode> brokenNodes;
2096 if ( addLayer( layerNode.toElement(), brokenNodes, context ) )
2097 {
2098 // have to try to update joins for all layers now - a previously added layer may be dependent on this newly
2099 // added layer for joins
2100 QVector<QgsVectorLayer *> vectorLayers = layers<QgsVectorLayer *>();
2101 const auto constVectorLayers = vectorLayers;
2102 for ( QgsVectorLayer *layer : constVectorLayers )
2103 {
2104 // TODO: should be only done later - and with all layers (other layers may have referenced this layer)
2105 layer->resolveReferences( this );
2106 }
2107
2108 return true;
2109 }
2110 return false;
2111 }
2112
write(const QString & filename)2113 bool QgsProject::write( const QString &filename )
2114 {
2115 mFile.setFileName( filename );
2116 mCachedHomePath.clear();
2117 return write();
2118 }
2119
write()2120 bool QgsProject::write()
2121 {
2122 mProjectScope.reset();
2123 if ( QgsProjectStorage *storage = projectStorage() )
2124 {
2125 QgsReadWriteContext context;
2126 // for projects stored in a custom storage, we have to check for the support
2127 // of relative paths since the storage most likely will not be in a file system
2128 QString storageFilePath { storage->filePath( mFile.fileName() ) };
2129 if ( storageFilePath.isEmpty() )
2130 {
2131 writeEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), true );
2132 }
2133 context.setPathResolver( pathResolver() );
2134
2135 QString tempPath = QStandardPaths::standardLocations( QStandardPaths::TempLocation ).at( 0 );
2136 QString tmpZipFilename( tempPath + QDir::separator() + QUuid::createUuid().toString() );
2137
2138 if ( !zip( tmpZipFilename ) )
2139 return false; // zip() already calls setError() when returning false
2140
2141 QFile tmpZipFile( tmpZipFilename );
2142 if ( !tmpZipFile.open( QIODevice::ReadOnly ) )
2143 {
2144 setError( tr( "Unable to read file %1" ).arg( tmpZipFilename ) );
2145 return false;
2146 }
2147
2148 context.setTransformContext( transformContext() );
2149 if ( !storage->writeProject( mFile.fileName(), &tmpZipFile, context ) )
2150 {
2151 QString err = tr( "Unable to save project to storage %1" ).arg( mFile.fileName() );
2152 QList<QgsReadWriteContext::ReadWriteMessage> messages = context.takeMessages();
2153 if ( !messages.isEmpty() )
2154 err += QStringLiteral( "\n\n" ) + messages.last().message();
2155 setError( err );
2156 return false;
2157 }
2158
2159 tmpZipFile.close();
2160 QFile::remove( tmpZipFilename );
2161
2162 return true;
2163 }
2164
2165 if ( QgsZipUtils::isZipFile( mFile.fileName() ) )
2166 {
2167 return zip( mFile.fileName() );
2168 }
2169 else
2170 {
2171 // write project file even if the auxiliary storage is not correctly
2172 // saved
2173 const bool asOk = saveAuxiliaryStorage();
2174 const bool writeOk = writeProjectFile( mFile.fileName() );
2175
2176 // errors raised during writing project file are more important
2177 if ( !asOk && writeOk )
2178 {
2179 const QString err = mAuxiliaryStorage->errorString();
2180 setError( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
2181 }
2182
2183 return asOk && writeOk;
2184 }
2185 }
2186
writeProjectFile(const QString & filename)2187 bool QgsProject::writeProjectFile( const QString &filename )
2188 {
2189 QFile projectFile( filename );
2190 clearError();
2191
2192 // if we have problems creating or otherwise writing to the project file,
2193 // let's find out up front before we go through all the hand-waving
2194 // necessary to create all the Dom objects
2195 QFileInfo myFileInfo( projectFile );
2196 if ( myFileInfo.exists() && !myFileInfo.isWritable() )
2197 {
2198 setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." )
2199 .arg( projectFile.fileName() ) );
2200 return false;
2201 }
2202
2203 QgsReadWriteContext context;
2204 context.setPathResolver( pathResolver() );
2205 context.setTransformContext( transformContext() );
2206
2207 QDomImplementation DomImplementation;
2208 DomImplementation.setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
2209
2210 QDomDocumentType documentType =
2211 DomImplementation.createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ),
2212 QStringLiteral( "SYSTEM" ) );
2213 std::unique_ptr<QDomDocument> doc( new QDomDocument( documentType ) );
2214
2215 QDomElement qgisNode = doc->createElement( QStringLiteral( "qgis" ) );
2216 qgisNode.setAttribute( QStringLiteral( "projectname" ), title() );
2217 qgisNode.setAttribute( QStringLiteral( "version" ), Qgis::version() );
2218
2219 if ( !mSettings.value( QStringLiteral( "projects/anonymize_saved_projects" ), false, QgsSettings::Core ).toBool() )
2220 {
2221 QString newSaveUser = QgsApplication::userLoginName();
2222 QString newSaveUserFull = QgsApplication::userFullName();
2223 qgisNode.setAttribute( QStringLiteral( "saveUser" ), newSaveUser );
2224 qgisNode.setAttribute( QStringLiteral( "saveUserFull" ), newSaveUserFull );
2225 mSaveUser = newSaveUser;
2226 mSaveUserFull = newSaveUserFull;
2227 mSaveDateTime = QDateTime::currentDateTime();
2228 qgisNode.setAttribute( QStringLiteral( "saveDateTime" ), mSaveDateTime.toString( Qt::ISODate ) );
2229 }
2230 else
2231 {
2232 mSaveUser.clear();
2233 mSaveUserFull.clear();
2234 mSaveDateTime = QDateTime();
2235 }
2236 doc->appendChild( qgisNode );
2237 mSaveVersion = QgsProjectVersion( Qgis::version() );
2238
2239 QDomElement homePathNode = doc->createElement( QStringLiteral( "homePath" ) );
2240 homePathNode.setAttribute( QStringLiteral( "path" ), mHomePath );
2241 qgisNode.appendChild( homePathNode );
2242
2243 // title
2244 QDomElement titleNode = doc->createElement( QStringLiteral( "title" ) );
2245 qgisNode.appendChild( titleNode );
2246
2247 QDomElement transactionNode = doc->createElement( QStringLiteral( "autotransaction" ) );
2248 transactionNode.setAttribute( QStringLiteral( "active" ), mAutoTransaction ? 1 : 0 );
2249 qgisNode.appendChild( transactionNode );
2250
2251 QDomElement evaluateDefaultValuesNode = doc->createElement( QStringLiteral( "evaluateDefaultValues" ) );
2252 evaluateDefaultValuesNode.setAttribute( QStringLiteral( "active" ), mEvaluateDefaultValues ? 1 : 0 );
2253 qgisNode.appendChild( evaluateDefaultValuesNode );
2254
2255 QDomElement trustNode = doc->createElement( QStringLiteral( "trust" ) );
2256 trustNode.setAttribute( QStringLiteral( "active" ), mTrustLayerMetadata ? 1 : 0 );
2257 qgisNode.appendChild( trustNode );
2258
2259 QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
2260 titleNode.appendChild( titleText );
2261
2262 // write project CRS
2263 QDomElement srsNode = doc->createElement( QStringLiteral( "projectCrs" ) );
2264 mCrs.writeXml( srsNode, *doc );
2265 qgisNode.appendChild( srsNode );
2266
2267 // write layer tree - make sure it is without embedded subgroups
2268 QgsLayerTreeNode *clonedRoot = mRootGroup->clone();
2269 QgsLayerTreeUtils::replaceChildrenOfEmbeddedGroups( QgsLayerTree::toGroup( clonedRoot ) );
2270 QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ), this ); // convert absolute paths to relative paths if required
2271
2272 clonedRoot->writeXml( qgisNode, context );
2273 delete clonedRoot;
2274
2275 mSnappingConfig.writeProject( *doc );
2276 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/AvoidIntersectionsMode" ), static_cast<int>( mAvoidIntersectionsMode ) );
2277
2278 // let map canvas and legend write their information
2279 emit writeProject( *doc );
2280
2281 // within top level node save list of layers
2282 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
2283
2284 QDomElement annotationLayerNode = doc->createElement( QStringLiteral( "main-annotation-layer" ) );
2285 mMainAnnotationLayer->writeLayerXml( annotationLayerNode, *doc, context );
2286 qgisNode.appendChild( annotationLayerNode );
2287
2288 // Iterate over layers in zOrder
2289 // Call writeXml() on each
2290 QDomElement projectLayersNode = doc->createElement( QStringLiteral( "projectlayers" ) );
2291
2292 QMap<QString, QgsMapLayer *>::ConstIterator li = layers.constBegin();
2293 while ( li != layers.end() )
2294 {
2295 QgsMapLayer *ml = li.value();
2296
2297 if ( ml )
2298 {
2299 QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.constFind( ml->id() );
2300 if ( emIt == mEmbeddedLayers.constEnd() )
2301 {
2302 QDomElement maplayerElem;
2303 // If layer is not valid, prefer to restore saved properties from invalidLayerProperties. But if that's
2304 // not available, just write what we DO have
2305 if ( ml->isValid() || ml->originalXmlProperties().isEmpty() )
2306 {
2307 // general layer metadata
2308 maplayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
2309 ml->writeLayerXml( maplayerElem, *doc, context );
2310 }
2311 else if ( ! ml->originalXmlProperties().isEmpty() )
2312 {
2313 QDomDocument document;
2314 if ( document.setContent( ml->originalXmlProperties() ) )
2315 {
2316 maplayerElem = document.firstChildElement();
2317 }
2318 else
2319 {
2320 QgsDebugMsg( QStringLiteral( "Could not restore layer properties for layer %1" ).arg( ml->id() ) );
2321 }
2322 }
2323
2324 emit writeMapLayer( ml, maplayerElem, *doc );
2325
2326 projectLayersNode.appendChild( maplayerElem );
2327 }
2328 else
2329 {
2330 // layer defined in an external project file
2331 // only save embedded layer if not managed by a legend group
2332 if ( emIt.value().second )
2333 {
2334 QDomElement mapLayerElem = doc->createElement( QStringLiteral( "maplayer" ) );
2335 mapLayerElem.setAttribute( QStringLiteral( "embedded" ), 1 );
2336 mapLayerElem.setAttribute( QStringLiteral( "project" ), writePath( emIt.value().first ) );
2337 mapLayerElem.setAttribute( QStringLiteral( "id" ), ml->id() );
2338 projectLayersNode.appendChild( mapLayerElem );
2339 }
2340 }
2341 }
2342 li++;
2343 }
2344
2345 qgisNode.appendChild( projectLayersNode );
2346
2347 QDomElement layerOrderNode = doc->createElement( QStringLiteral( "layerorder" ) );
2348 const auto constCustomLayerOrder = mRootGroup->customLayerOrder();
2349 for ( QgsMapLayer *layer : constCustomLayerOrder )
2350 {
2351 QDomElement mapLayerElem = doc->createElement( QStringLiteral( "layer" ) );
2352 mapLayerElem.setAttribute( QStringLiteral( "id" ), layer->id() );
2353 layerOrderNode.appendChild( mapLayerElem );
2354 }
2355 qgisNode.appendChild( layerOrderNode );
2356
2357 mLabelingEngineSettings->writeSettingsToProject( this );
2358
2359 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorRedPart" ), mBackgroundColor.red() );
2360 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorGreenPart" ), mBackgroundColor.green() );
2361 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/CanvasColorBluePart" ), mBackgroundColor.blue() );
2362
2363 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorRedPart" ), mSelectionColor.red() );
2364 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorGreenPart" ), mSelectionColor.green() );
2365 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorBluePart" ), mSelectionColor.blue() );
2366 writeEntry( QStringLiteral( "Gui" ), QStringLiteral( "/SelectionColorAlphaPart" ), mSelectionColor.alpha() );
2367
2368 // now add the optional extra properties
2369 #if 0
2370 dump_( mProperties );
2371 #endif
2372
2373 QgsDebugMsgLevel( QStringLiteral( "there are %1 property scopes" ).arg( static_cast<int>( mProperties.count() ) ), 2 );
2374
2375 if ( !mProperties.isEmpty() ) // only worry about properties if we
2376 // actually have any properties
2377 {
2378 mProperties.writeXml( QStringLiteral( "properties" ), qgisNode, *doc );
2379 }
2380
2381 QDomElement ddElem = doc->createElement( QStringLiteral( "dataDefinedServerProperties" ) );
2382 mDataDefinedServerProperties.writeXml( ddElem, dataDefinedServerPropertyDefinitions() );
2383 qgisNode.appendChild( ddElem );
2384
2385 mMapThemeCollection->writeXml( *doc );
2386
2387 mTransformContext.writeXml( qgisNode, context );
2388
2389 QDomElement metadataElem = doc->createElement( QStringLiteral( "projectMetadata" ) );
2390 mMetadata.writeMetadataXml( metadataElem, *doc );
2391 qgisNode.appendChild( metadataElem );
2392
2393 QDomElement annotationsElem = mAnnotationManager->writeXml( *doc, context );
2394 qgisNode.appendChild( annotationsElem );
2395
2396 QDomElement layoutElem = mLayoutManager->writeXml( *doc );
2397 qgisNode.appendChild( layoutElem );
2398
2399 QDomElement bookmarkElem = mBookmarkManager->writeXml( *doc );
2400 qgisNode.appendChild( bookmarkElem );
2401
2402 QDomElement viewSettingsElem = mViewSettings->writeXml( *doc, context );
2403 qgisNode.appendChild( viewSettingsElem );
2404
2405 QDomElement timeSettingsElement = mTimeSettings->writeXml( *doc, context );
2406 qgisNode.appendChild( timeSettingsElement );
2407
2408 QDomElement displaySettingsElem = mDisplaySettings->writeXml( *doc, context );
2409 qgisNode.appendChild( displaySettingsElem );
2410
2411 // now wrap it up and ship it to the project file
2412 doc->normalize(); // XXX I'm not entirely sure what this does
2413
2414 // Create backup file
2415 if ( QFile::exists( fileName() ) )
2416 {
2417 QFile backupFile( QStringLiteral( "%1~" ).arg( filename ) );
2418 bool ok = true;
2419 ok &= backupFile.open( QIODevice::WriteOnly | QIODevice::Truncate );
2420 ok &= projectFile.open( QIODevice::ReadOnly );
2421
2422 QByteArray ba;
2423 while ( ok && !projectFile.atEnd() )
2424 {
2425 ba = projectFile.read( 10240 );
2426 ok &= backupFile.write( ba ) == ba.size();
2427 }
2428
2429 projectFile.close();
2430 backupFile.close();
2431
2432 if ( !ok )
2433 {
2434 setError( tr( "Unable to create backup file %1" ).arg( backupFile.fileName() ) );
2435 return false;
2436 }
2437
2438 QFileInfo fi( fileName() );
2439 struct utimbuf tb = { static_cast<time_t>( fi.lastRead().toSecsSinceEpoch() ), static_cast<time_t>( fi.lastModified().toSecsSinceEpoch() ) };
2440 utime( backupFile.fileName().toUtf8().constData(), &tb );
2441 }
2442
2443 if ( !projectFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
2444 {
2445 projectFile.close(); // even though we got an error, let's make
2446 // sure it's closed anyway
2447
2448 setError( tr( "Unable to save to file %1" ).arg( projectFile.fileName() ) );
2449 return false;
2450 }
2451
2452 QTemporaryFile tempFile;
2453 bool ok = tempFile.open();
2454 if ( ok )
2455 {
2456 QTextStream projectFileStream( &tempFile );
2457 doc->save( projectFileStream, 2 ); // save as utf-8
2458 ok &= projectFileStream.pos() > -1;
2459
2460 ok &= tempFile.seek( 0 );
2461
2462 QByteArray ba;
2463 while ( ok && !tempFile.atEnd() )
2464 {
2465 ba = tempFile.read( 10240 );
2466 ok &= projectFile.write( ba ) == ba.size();
2467 }
2468
2469 ok &= projectFile.error() == QFile::NoError;
2470
2471 projectFile.close();
2472 }
2473
2474 tempFile.close();
2475
2476 if ( !ok )
2477 {
2478 setError( tr( "Unable to save to file %1. Your project "
2479 "may be corrupted on disk. Try clearing some space on the volume and "
2480 "check file permissions before pressing save again." )
2481 .arg( projectFile.fileName() ) );
2482 return false;
2483 }
2484
2485 setDirty( false ); // reset to pristine state
2486
2487 emit projectSaved();
2488 return true;
2489 }
2490
writeEntry(const QString & scope,QString const & key,bool value)2491 bool QgsProject::writeEntry( const QString &scope, QString const &key, bool value )
2492 {
2493 bool propertiesModified;
2494 bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
2495
2496 if ( propertiesModified )
2497 setDirty( true );
2498
2499 return success;
2500 }
2501
writeEntry(const QString & scope,const QString & key,double value)2502 bool QgsProject::writeEntry( const QString &scope, const QString &key, double value )
2503 {
2504 bool propertiesModified;
2505 bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
2506
2507 if ( propertiesModified )
2508 setDirty( true );
2509
2510 return success;
2511 }
2512
writeEntry(const QString & scope,QString const & key,int value)2513 bool QgsProject::writeEntry( const QString &scope, QString const &key, int value )
2514 {
2515 bool propertiesModified;
2516 bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
2517
2518 if ( propertiesModified )
2519 setDirty( true );
2520
2521 return success;
2522 }
2523
writeEntry(const QString & scope,const QString & key,const QString & value)2524 bool QgsProject::writeEntry( const QString &scope, const QString &key, const QString &value )
2525 {
2526 bool propertiesModified;
2527 bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
2528
2529 if ( propertiesModified )
2530 setDirty( true );
2531
2532 return success;
2533 }
2534
writeEntry(const QString & scope,const QString & key,const QStringList & value)2535 bool QgsProject::writeEntry( const QString &scope, const QString &key, const QStringList &value )
2536 {
2537 bool propertiesModified;
2538 bool success = addKey_( scope, key, &mProperties, value, propertiesModified );
2539
2540 if ( propertiesModified )
2541 setDirty( true );
2542
2543 return success;
2544 }
2545
readListEntry(const QString & scope,const QString & key,const QStringList & def,bool * ok) const2546 QStringList QgsProject::readListEntry( const QString &scope,
2547 const QString &key,
2548 const QStringList &def,
2549 bool *ok ) const
2550 {
2551 QgsProjectProperty *property = findKey_( scope, key, mProperties );
2552
2553 QVariant value;
2554
2555 if ( property )
2556 {
2557 value = property->value();
2558
2559 bool valid = QVariant::StringList == value.type();
2560 if ( ok )
2561 *ok = valid;
2562
2563 if ( valid )
2564 {
2565 return value.toStringList();
2566 }
2567 }
2568 else if ( ok )
2569 *ok = false;
2570
2571
2572 return def;
2573 }
2574
2575
readEntry(const QString & scope,const QString & key,const QString & def,bool * ok) const2576 QString QgsProject::readEntry( const QString &scope,
2577 const QString &key,
2578 const QString &def,
2579 bool *ok ) const
2580 {
2581 QgsProjectProperty *property = findKey_( scope, key, mProperties );
2582
2583 QVariant value;
2584
2585 if ( property )
2586 {
2587 value = property->value();
2588
2589 bool valid = value.canConvert( QVariant::String );
2590 if ( ok )
2591 *ok = valid;
2592
2593 if ( valid )
2594 return value.toString();
2595 }
2596 else if ( ok )
2597 *ok = false;
2598
2599 return def;
2600 }
2601
readNumEntry(const QString & scope,const QString & key,int def,bool * ok) const2602 int QgsProject::readNumEntry( const QString &scope, const QString &key, int def,
2603 bool *ok ) const
2604 {
2605 QgsProjectProperty *property = findKey_( scope, key, mProperties );
2606
2607 QVariant value;
2608
2609 if ( property )
2610 {
2611 value = property->value();
2612 }
2613
2614 bool valid = value.canConvert( QVariant::Int );
2615
2616 if ( ok )
2617 {
2618 *ok = valid;
2619 }
2620
2621 if ( valid )
2622 {
2623 return value.toInt();
2624 }
2625
2626 return def;
2627 }
2628
readDoubleEntry(const QString & scope,const QString & key,double def,bool * ok) const2629 double QgsProject::readDoubleEntry( const QString &scope, const QString &key,
2630 double def,
2631 bool *ok ) const
2632 {
2633 QgsProjectProperty *property = findKey_( scope, key, mProperties );
2634 if ( property )
2635 {
2636 QVariant value = property->value();
2637
2638 bool valid = value.canConvert( QVariant::Double );
2639 if ( ok )
2640 *ok = valid;
2641
2642 if ( valid )
2643 return value.toDouble();
2644 }
2645 else if ( ok )
2646 *ok = false;
2647
2648 return def;
2649 }
2650
readBoolEntry(const QString & scope,const QString & key,bool def,bool * ok) const2651 bool QgsProject::readBoolEntry( const QString &scope, const QString &key, bool def,
2652 bool *ok ) const
2653 {
2654 QgsProjectProperty *property = findKey_( scope, key, mProperties );
2655
2656 if ( property )
2657 {
2658 QVariant value = property->value();
2659
2660 bool valid = value.canConvert( QVariant::Bool );
2661 if ( ok )
2662 *ok = valid;
2663
2664 if ( valid )
2665 return value.toBool();
2666 }
2667 else if ( ok )
2668 *ok = false;
2669
2670 return def;
2671 }
2672
removeEntry(const QString & scope,const QString & key)2673 bool QgsProject::removeEntry( const QString &scope, const QString &key )
2674 {
2675 if ( findKey_( scope, key, mProperties ) )
2676 {
2677 removeKey_( scope, key, mProperties );
2678 setDirty( true );
2679 }
2680
2681 return !findKey_( scope, key, mProperties );
2682 }
2683
2684
entryList(const QString & scope,const QString & key) const2685 QStringList QgsProject::entryList( const QString &scope, const QString &key ) const
2686 {
2687 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
2688
2689 QStringList entries;
2690
2691 if ( foundProperty )
2692 {
2693 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
2694
2695 if ( propertyKey )
2696 { propertyKey->entryList( entries ); }
2697 }
2698
2699 return entries;
2700 }
2701
subkeyList(const QString & scope,const QString & key) const2702 QStringList QgsProject::subkeyList( const QString &scope, const QString &key ) const
2703 {
2704 QgsProjectProperty *foundProperty = findKey_( scope, key, mProperties );
2705
2706 QStringList entries;
2707
2708 if ( foundProperty )
2709 {
2710 QgsProjectPropertyKey *propertyKey = dynamic_cast<QgsProjectPropertyKey *>( foundProperty );
2711
2712 if ( propertyKey )
2713 { propertyKey->subkeyList( entries ); }
2714 }
2715
2716 return entries;
2717 }
2718
dumpProperties() const2719 void QgsProject::dumpProperties() const
2720 {
2721 dump_( mProperties );
2722 }
2723
pathResolver() const2724 QgsPathResolver QgsProject::pathResolver() const
2725 {
2726 bool absolutePaths = readBoolEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), false );
2727 QString filePath;
2728 if ( ! absolutePaths )
2729 {
2730 // for projects stored in a custom storage, we need to ask to the
2731 // storage for the path, if the storage returns an empty path
2732 // relative paths are not supported
2733 if ( QgsProjectStorage *storage = projectStorage() )
2734 {
2735 filePath = storage->filePath( mFile.fileName() );
2736 }
2737 else
2738 {
2739 filePath = fileName();
2740 }
2741 }
2742 return QgsPathResolver( filePath );
2743 }
2744
readPath(const QString & src) const2745 QString QgsProject::readPath( const QString &src ) const
2746 {
2747 return pathResolver().readPath( src );
2748 }
2749
writePath(const QString & src) const2750 QString QgsProject::writePath( const QString &src ) const
2751 {
2752 return pathResolver().writePath( src );
2753 }
2754
setError(const QString & errorMessage)2755 void QgsProject::setError( const QString &errorMessage )
2756 {
2757 mErrorMessage = errorMessage;
2758 }
2759
error() const2760 QString QgsProject::error() const
2761 {
2762 return mErrorMessage;
2763 }
2764
clearError()2765 void QgsProject::clearError()
2766 {
2767 setError( QString() );
2768 }
2769
setBadLayerHandler(QgsProjectBadLayerHandler * handler)2770 void QgsProject::setBadLayerHandler( QgsProjectBadLayerHandler *handler )
2771 {
2772 delete mBadLayerHandler;
2773 mBadLayerHandler = handler;
2774 }
2775
layerIsEmbedded(const QString & id) const2776 QString QgsProject::layerIsEmbedded( const QString &id ) const
2777 {
2778 QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
2779 if ( it == mEmbeddedLayers.constEnd() )
2780 {
2781 return QString();
2782 }
2783 return it.value().first;
2784 }
2785
createEmbeddedLayer(const QString & layerId,const QString & projectFilePath,QList<QDomNode> & brokenNodes,bool saveFlag,QgsProject::ReadFlags flags)2786 bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &projectFilePath, QList<QDomNode> &brokenNodes,
2787 bool saveFlag, QgsProject::ReadFlags flags )
2788 {
2789 QgsDebugCall;
2790
2791 static QString sPrevProjectFilePath;
2792 static QDateTime sPrevProjectFileTimestamp;
2793 static QDomDocument sProjectDocument;
2794
2795 QString qgsProjectFile = projectFilePath;
2796 QgsProjectArchive archive;
2797 if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
2798 {
2799 archive.unzip( projectFilePath );
2800 qgsProjectFile = archive.projectFile();
2801 }
2802
2803 QDateTime projectFileTimestamp = QFileInfo( projectFilePath ).lastModified();
2804
2805 if ( projectFilePath != sPrevProjectFilePath || projectFileTimestamp != sPrevProjectFileTimestamp )
2806 {
2807 sPrevProjectFilePath.clear();
2808
2809 QFile projectFile( qgsProjectFile );
2810 if ( !projectFile.open( QIODevice::ReadOnly ) )
2811 {
2812 return false;
2813 }
2814
2815 if ( !sProjectDocument.setContent( &projectFile ) )
2816 {
2817 return false;
2818 }
2819
2820 sPrevProjectFilePath = projectFilePath;
2821 sPrevProjectFileTimestamp = projectFileTimestamp;
2822 }
2823
2824 // does project store paths absolute or relative?
2825 bool useAbsolutePaths = true;
2826
2827 QDomElement propertiesElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "properties" ) );
2828 if ( !propertiesElem.isNull() )
2829 {
2830 QDomElement absElem = propertiesElem.firstChildElement( QStringLiteral( "Paths" ) ).firstChildElement( QStringLiteral( "Absolute" ) );
2831 if ( !absElem.isNull() )
2832 {
2833 useAbsolutePaths = absElem.text().compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0;
2834 }
2835 }
2836
2837 QgsReadWriteContext embeddedContext;
2838 if ( !useAbsolutePaths )
2839 embeddedContext.setPathResolver( QgsPathResolver( projectFilePath ) );
2840 embeddedContext.setProjectTranslator( this );
2841 embeddedContext.setTransformContext( transformContext() );
2842
2843 QDomElement projectLayersElem = sProjectDocument.documentElement().firstChildElement( QStringLiteral( "projectlayers" ) );
2844 if ( projectLayersElem.isNull() )
2845 {
2846 return false;
2847 }
2848
2849 QDomNodeList mapLayerNodes = projectLayersElem.elementsByTagName( QStringLiteral( "maplayer" ) );
2850 for ( int i = 0; i < mapLayerNodes.size(); ++i )
2851 {
2852 // get layer id
2853 QDomElement mapLayerElem = mapLayerNodes.at( i ).toElement();
2854 QString id = mapLayerElem.firstChildElement( QStringLiteral( "id" ) ).text();
2855 if ( id == layerId )
2856 {
2857 // layer can be embedded only once
2858 if ( mapLayerElem.attribute( QStringLiteral( "embedded" ) ) == QLatin1String( "1" ) )
2859 {
2860 return false;
2861 }
2862
2863 mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
2864
2865 if ( addLayer( mapLayerElem, brokenNodes, embeddedContext, flags ) )
2866 {
2867 return true;
2868 }
2869 else
2870 {
2871 mEmbeddedLayers.remove( layerId );
2872 return false;
2873 }
2874 }
2875 }
2876
2877 return false;
2878 }
2879
2880
createEmbeddedGroup(const QString & groupName,const QString & projectFilePath,const QStringList & invisibleLayers,QgsProject::ReadFlags flags)2881 QgsLayerTreeGroup *QgsProject::createEmbeddedGroup( const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers, QgsProject::ReadFlags flags )
2882 {
2883 QString qgsProjectFile = projectFilePath;
2884 QgsProjectArchive archive;
2885 if ( projectFilePath.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
2886 {
2887 archive.unzip( projectFilePath );
2888 qgsProjectFile = archive.projectFile();
2889 }
2890
2891 // open project file, get layer ids in group, add the layers
2892 QFile projectFile( qgsProjectFile );
2893 if ( !projectFile.open( QIODevice::ReadOnly ) )
2894 {
2895 return nullptr;
2896 }
2897
2898 QDomDocument projectDocument;
2899 if ( !projectDocument.setContent( &projectFile ) )
2900 {
2901 return nullptr;
2902 }
2903
2904 QgsReadWriteContext context;
2905 context.setPathResolver( pathResolver() );
2906 context.setProjectTranslator( this );
2907 context.setTransformContext( transformContext() );
2908
2909 QgsLayerTreeGroup *root = new QgsLayerTreeGroup;
2910
2911 QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
2912 if ( !layerTreeElem.isNull() )
2913 {
2914 root->readChildrenFromXml( layerTreeElem, context );
2915 }
2916 else
2917 {
2918 QgsLayerTreeUtils::readOldLegend( root, projectDocument.documentElement().firstChildElement( QStringLiteral( "legend" ) ) );
2919 }
2920
2921 QgsLayerTreeGroup *group = root->findGroup( groupName );
2922 if ( !group || group->customProperty( QStringLiteral( "embedded" ) ).toBool() )
2923 {
2924 // embedded groups cannot be embedded again
2925 delete root;
2926 return nullptr;
2927 }
2928
2929 // clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
2930 QgsLayerTreeGroup *newGroup = QgsLayerTree::toGroup( group->clone() );
2931 delete root;
2932 root = nullptr;
2933
2934 newGroup->setCustomProperty( QStringLiteral( "embedded" ), 1 );
2935 newGroup->setCustomProperty( QStringLiteral( "embedded_project" ), projectFilePath );
2936
2937 // set "embedded" to all children + load embedded layers
2938 mLayerTreeRegistryBridge->setEnabled( false );
2939 initializeEmbeddedSubtree( projectFilePath, newGroup, flags );
2940 mLayerTreeRegistryBridge->setEnabled( true );
2941
2942 // consider the layers might be identify disabled in its project
2943 const auto constFindLayerIds = newGroup->findLayerIds();
2944 for ( const QString &layerId : constFindLayerIds )
2945 {
2946 QgsLayerTreeLayer *layer = newGroup->findLayer( layerId );
2947 if ( layer )
2948 {
2949 layer->resolveReferences( this );
2950 layer->setItemVisibilityChecked( !invisibleLayers.contains( layerId ) );
2951 }
2952 }
2953
2954 return newGroup;
2955 }
2956
initializeEmbeddedSubtree(const QString & projectFilePath,QgsLayerTreeGroup * group,QgsProject::ReadFlags flags)2957 void QgsProject::initializeEmbeddedSubtree( const QString &projectFilePath, QgsLayerTreeGroup *group, QgsProject::ReadFlags flags )
2958 {
2959 const auto constChildren = group->children();
2960 for ( QgsLayerTreeNode *child : constChildren )
2961 {
2962 // all nodes in the subtree will have "embedded" custom property set
2963 child->setCustomProperty( QStringLiteral( "embedded" ), 1 );
2964
2965 if ( QgsLayerTree::isGroup( child ) )
2966 {
2967 initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ), flags );
2968 }
2969 else if ( QgsLayerTree::isLayer( child ) )
2970 {
2971 // load the layer into our project
2972 QList<QDomNode> brokenNodes;
2973 createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, false, flags );
2974 }
2975 }
2976 }
2977
evaluateDefaultValues() const2978 bool QgsProject::evaluateDefaultValues() const
2979 {
2980 return mEvaluateDefaultValues;
2981 }
2982
setEvaluateDefaultValues(bool evaluateDefaultValues)2983 void QgsProject::setEvaluateDefaultValues( bool evaluateDefaultValues )
2984 {
2985 if ( evaluateDefaultValues == mEvaluateDefaultValues )
2986 return;
2987
2988 const QMap<QString, QgsMapLayer *> layers = mapLayers();
2989 QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
2990 for ( ; layerIt != layers.constEnd(); ++layerIt )
2991 {
2992 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layerIt.value() );
2993 if ( vl )
2994 {
2995 vl->dataProvider()->setProviderProperty( QgsVectorDataProvider::EvaluateDefaultValues, evaluateDefaultValues );
2996 }
2997 }
2998
2999 mEvaluateDefaultValues = evaluateDefaultValues;
3000 }
3001
setTopologicalEditing(bool enabled)3002 void QgsProject::setTopologicalEditing( bool enabled )
3003 {
3004 writeEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), ( enabled ? 1 : 0 ) );
3005 emit topologicalEditingChanged();
3006 }
3007
topologicalEditing() const3008 bool QgsProject::topologicalEditing() const
3009 {
3010 return readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/TopologicalEditing" ), 0 );
3011 }
3012
distanceUnits() const3013 QgsUnitTypes::DistanceUnit QgsProject::distanceUnits() const
3014 {
3015 QString distanceUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QString() );
3016 if ( !distanceUnitString.isEmpty() )
3017 return QgsUnitTypes::decodeDistanceUnit( distanceUnitString );
3018
3019 //fallback to QGIS default measurement unit
3020 bool ok = false;
3021 QgsUnitTypes::DistanceUnit type = QgsUnitTypes::decodeDistanceUnit( mSettings.value( QStringLiteral( "/qgis/measure/displayunits" ) ).toString(), &ok );
3022 return ok ? type : QgsUnitTypes::DistanceMeters;
3023 }
3024
setDistanceUnits(QgsUnitTypes::DistanceUnit unit)3025 void QgsProject::setDistanceUnits( QgsUnitTypes::DistanceUnit unit )
3026 {
3027 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/DistanceUnits" ), QgsUnitTypes::encodeUnit( unit ) );
3028 }
3029
areaUnits() const3030 QgsUnitTypes::AreaUnit QgsProject::areaUnits() const
3031 {
3032 QString areaUnitString = readEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QString() );
3033 if ( !areaUnitString.isEmpty() )
3034 return QgsUnitTypes::decodeAreaUnit( areaUnitString );
3035
3036 //fallback to QGIS default area unit
3037 bool ok = false;
3038 QgsUnitTypes::AreaUnit type = QgsUnitTypes::decodeAreaUnit( mSettings.value( QStringLiteral( "/qgis/measure/areaunits" ) ).toString(), &ok );
3039 return ok ? type : QgsUnitTypes::AreaSquareMeters;
3040 }
3041
setAreaUnits(QgsUnitTypes::AreaUnit unit)3042 void QgsProject::setAreaUnits( QgsUnitTypes::AreaUnit unit )
3043 {
3044 writeEntry( QStringLiteral( "Measurement" ), QStringLiteral( "/AreaUnits" ), QgsUnitTypes::encodeUnit( unit ) );
3045 }
3046
homePath() const3047 QString QgsProject::homePath() const
3048 {
3049 if ( !mCachedHomePath.isEmpty() )
3050 return mCachedHomePath;
3051
3052 QFileInfo pfi( fileName() );
3053
3054 if ( !mHomePath.isEmpty() )
3055 {
3056 QFileInfo homeInfo( mHomePath );
3057 if ( !homeInfo.isRelative() )
3058 {
3059 mCachedHomePath = mHomePath;
3060 return mHomePath;
3061 }
3062 }
3063 else if ( !fileName().isEmpty() )
3064 {
3065 mCachedHomePath = pfi.path();
3066
3067 return mCachedHomePath;
3068 }
3069
3070 if ( !pfi.exists() )
3071 {
3072 mCachedHomePath = mHomePath;
3073 return mHomePath;
3074 }
3075
3076 if ( !mHomePath.isEmpty() )
3077 {
3078 // path is relative to project file
3079 mCachedHomePath = QDir::cleanPath( pfi.path() + '/' + mHomePath );
3080 }
3081 else
3082 {
3083 mCachedHomePath = pfi.canonicalPath();
3084 }
3085 return mCachedHomePath;
3086 }
3087
presetHomePath() const3088 QString QgsProject::presetHomePath() const
3089 {
3090 return mHomePath;
3091 }
3092
relationManager() const3093 QgsRelationManager *QgsProject::relationManager() const
3094 {
3095 return mRelationManager;
3096 }
3097
layoutManager() const3098 const QgsLayoutManager *QgsProject::layoutManager() const
3099 {
3100 return mLayoutManager.get();
3101 }
3102
layoutManager()3103 QgsLayoutManager *QgsProject::layoutManager()
3104 {
3105 return mLayoutManager.get();
3106 }
3107
bookmarkManager() const3108 const QgsBookmarkManager *QgsProject::bookmarkManager() const
3109 {
3110 return mBookmarkManager;
3111 }
3112
bookmarkManager()3113 QgsBookmarkManager *QgsProject::bookmarkManager()
3114 {
3115 return mBookmarkManager;
3116 }
3117
viewSettings() const3118 const QgsProjectViewSettings *QgsProject::viewSettings() const
3119 {
3120 return mViewSettings;
3121 }
3122
viewSettings()3123 QgsProjectViewSettings *QgsProject::viewSettings()
3124 {
3125 return mViewSettings;
3126 }
3127
timeSettings() const3128 const QgsProjectTimeSettings *QgsProject::timeSettings() const
3129 {
3130 return mTimeSettings;
3131 }
3132
timeSettings()3133 QgsProjectTimeSettings *QgsProject::timeSettings()
3134 {
3135 return mTimeSettings;
3136 }
3137
displaySettings() const3138 const QgsProjectDisplaySettings *QgsProject::displaySettings() const
3139 {
3140 return mDisplaySettings;
3141 }
3142
displaySettings()3143 QgsProjectDisplaySettings *QgsProject::displaySettings()
3144 {
3145 return mDisplaySettings;
3146 }
3147
layerTreeRoot() const3148 QgsLayerTree *QgsProject::layerTreeRoot() const
3149 {
3150 return mRootGroup;
3151 }
3152
mapThemeCollection()3153 QgsMapThemeCollection *QgsProject::mapThemeCollection()
3154 {
3155 return mMapThemeCollection.get();
3156 }
3157
annotationManager()3158 QgsAnnotationManager *QgsProject::annotationManager()
3159 {
3160 return mAnnotationManager.get();
3161 }
3162
annotationManager() const3163 const QgsAnnotationManager *QgsProject::annotationManager() const
3164 {
3165 return mAnnotationManager.get();
3166 }
3167
setNonIdentifiableLayers(const QList<QgsMapLayer * > & layers)3168 void QgsProject::setNonIdentifiableLayers( const QList<QgsMapLayer *> &layers )
3169 {
3170 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
3171 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
3172 {
3173 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
3174 continue;
3175
3176 if ( layers.contains( it.value() ) )
3177 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Identifiable );
3178 else
3179 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Identifiable );
3180 }
3181
3182 Q_NOWARN_DEPRECATED_PUSH
3183 emit nonIdentifiableLayersChanged( nonIdentifiableLayers() );
3184 Q_NOWARN_DEPRECATED_POP
3185 }
3186
setNonIdentifiableLayers(const QStringList & layerIds)3187 void QgsProject::setNonIdentifiableLayers( const QStringList &layerIds )
3188 {
3189 QList<QgsMapLayer *> nonIdentifiableLayers;
3190 nonIdentifiableLayers.reserve( layerIds.count() );
3191 for ( const QString &layerId : layerIds )
3192 {
3193 QgsMapLayer *layer = mapLayer( layerId );
3194 if ( layer )
3195 nonIdentifiableLayers << layer;
3196 }
3197 Q_NOWARN_DEPRECATED_PUSH
3198 setNonIdentifiableLayers( nonIdentifiableLayers );
3199 Q_NOWARN_DEPRECATED_POP
3200 }
3201
nonIdentifiableLayers() const3202 QStringList QgsProject::nonIdentifiableLayers() const
3203 {
3204 QStringList nonIdentifiableLayers;
3205
3206 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
3207 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
3208 {
3209 if ( !it.value()->flags().testFlag( QgsMapLayer::Identifiable ) )
3210 {
3211 nonIdentifiableLayers.append( it.value()->id() );
3212 }
3213 }
3214 return nonIdentifiableLayers;
3215 }
3216
autoTransaction() const3217 bool QgsProject::autoTransaction() const
3218 {
3219 return mAutoTransaction;
3220 }
3221
setAutoTransaction(bool autoTransaction)3222 void QgsProject::setAutoTransaction( bool autoTransaction )
3223 {
3224 if ( autoTransaction != mAutoTransaction )
3225 {
3226 mAutoTransaction = autoTransaction;
3227
3228 if ( autoTransaction )
3229 onMapLayersAdded( mapLayers().values() );
3230 else
3231 cleanTransactionGroups( true );
3232 }
3233 }
3234
transactionGroups()3235 QMap<QPair<QString, QString>, QgsTransactionGroup *> QgsProject::transactionGroups()
3236 {
3237 return mTransactionGroups;
3238 }
3239
3240
3241 //
3242 // QgsMapLayerStore methods
3243 //
3244
3245
count() const3246 int QgsProject::count() const
3247 {
3248 return mLayerStore->count();
3249 }
3250
validCount() const3251 int QgsProject::validCount() const
3252 {
3253 return mLayerStore->validCount();
3254 }
3255
mapLayer(const QString & layerId) const3256 QgsMapLayer *QgsProject::mapLayer( const QString &layerId ) const
3257 {
3258 return mLayerStore->mapLayer( layerId );
3259 }
3260
mapLayersByName(const QString & layerName) const3261 QList<QgsMapLayer *> QgsProject::mapLayersByName( const QString &layerName ) const
3262 {
3263 return mLayerStore->mapLayersByName( layerName );
3264 }
3265
mapLayersByShortName(const QString & shortName) const3266 QList<QgsMapLayer *> QgsProject::mapLayersByShortName( const QString &shortName ) const
3267 {
3268 QList<QgsMapLayer *> layers;
3269 const auto constMapLayers { mLayerStore->mapLayers() };
3270 for ( const auto &l : constMapLayers )
3271 {
3272 if ( ! l->shortName().isEmpty() )
3273 {
3274 if ( l->shortName() == shortName )
3275 layers << l;
3276 }
3277 else if ( l->name() == shortName )
3278 {
3279 layers << l;
3280 }
3281 }
3282 return layers;
3283 }
3284
unzip(const QString & filename,QgsProject::ReadFlags flags)3285 bool QgsProject::unzip( const QString &filename, QgsProject::ReadFlags flags )
3286 {
3287 clearError();
3288 std::unique_ptr<QgsProjectArchive> archive( new QgsProjectArchive() );
3289
3290 // unzip the archive
3291 if ( !archive->unzip( filename ) )
3292 {
3293 setError( tr( "Unable to unzip file '%1'" ).arg( filename ) );
3294 return false;
3295 }
3296
3297 // test if zip provides a .qgs file
3298 if ( archive->projectFile().isEmpty() )
3299 {
3300 setError( tr( "Zip archive does not provide a project file" ) );
3301 return false;
3302 }
3303
3304 // load auxiliary storage
3305 if ( !archive->auxiliaryStorageFile().isEmpty() )
3306 {
3307 // database file is already a copy as it's been unzipped. So we don't open
3308 // auxiliary storage in copy mode in this case
3309 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( archive->auxiliaryStorageFile(), false ) );
3310 }
3311 else
3312 {
3313 mAuxiliaryStorage.reset( new QgsAuxiliaryStorage( *this ) );
3314 }
3315
3316 // read the project file
3317 if ( ! readProjectFile( archive->projectFile(), flags ) )
3318 {
3319 setError( tr( "Cannot read unzipped qgs project file" ) );
3320 return false;
3321 }
3322
3323 // keep the archive and remove the temporary .qgs file
3324 mArchive = std::move( archive );
3325 mArchive->clearProjectFile();
3326
3327 return true;
3328 }
3329
zip(const QString & filename)3330 bool QgsProject::zip( const QString &filename )
3331 {
3332 clearError();
3333
3334 // save the current project in a temporary .qgs file
3335 std::unique_ptr<QgsProjectArchive> archive( new QgsProjectArchive() );
3336 const QString baseName = QFileInfo( filename ).baseName();
3337 const QString qgsFileName = QStringLiteral( "%1.qgs" ).arg( baseName );
3338 QFile qgsFile( QDir( archive->dir() ).filePath( qgsFileName ) );
3339
3340 bool writeOk = false;
3341 if ( qgsFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
3342 {
3343 writeOk = writeProjectFile( qgsFile.fileName() );
3344 qgsFile.close();
3345 }
3346
3347 // stop here with an error message
3348 if ( ! writeOk )
3349 {
3350 setError( tr( "Unable to write temporary qgs file" ) );
3351 return false;
3352 }
3353
3354 // save auxiliary storage
3355 const QFileInfo info( qgsFile );
3356 const QString asFileName = info.path() + QDir::separator() + info.completeBaseName() + "." + QgsAuxiliaryStorage::extension();
3357
3358 if ( ! saveAuxiliaryStorage( asFileName ) )
3359 {
3360 const QString err = mAuxiliaryStorage->errorString();
3361 setError( tr( "Unable to save auxiliary storage ('%1')" ).arg( err ) );
3362 return false;
3363 }
3364
3365 // create the archive
3366 archive->addFile( qgsFile.fileName() );
3367 archive->addFile( asFileName );
3368
3369 // zip
3370 if ( !archive->zip( filename ) )
3371 {
3372 setError( tr( "Unable to perform zip" ) );
3373 return false;
3374 }
3375
3376 return true;
3377 }
3378
isZipped() const3379 bool QgsProject::isZipped() const
3380 {
3381 return QgsZipUtils::isZipFile( mFile.fileName() );
3382 }
3383
addMapLayers(const QList<QgsMapLayer * > & layers,bool addToLegend,bool takeOwnership)3384 QList<QgsMapLayer *> QgsProject::addMapLayers(
3385 const QList<QgsMapLayer *> &layers,
3386 bool addToLegend,
3387 bool takeOwnership )
3388 {
3389 const QList<QgsMapLayer *> myResultList { mLayerStore->addMapLayers( layers, takeOwnership ) };
3390 if ( !myResultList.isEmpty() )
3391 {
3392 // Update transform context
3393 for ( auto &l : myResultList )
3394 {
3395 l->setTransformContext( transformContext() );
3396 }
3397 if ( addToLegend )
3398 {
3399 emit legendLayersAdded( myResultList );
3400 }
3401 }
3402
3403 if ( mAuxiliaryStorage )
3404 {
3405 for ( QgsMapLayer *mlayer : myResultList )
3406 {
3407 if ( mlayer->type() != QgsMapLayerType::VectorLayer )
3408 continue;
3409
3410 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mlayer );
3411 if ( vl )
3412 {
3413 vl->loadAuxiliaryLayer( *mAuxiliaryStorage );
3414 }
3415 }
3416 }
3417
3418 mProjectScope.reset();
3419
3420 return myResultList;
3421 }
3422
3423 QgsMapLayer *
addMapLayer(QgsMapLayer * layer,bool addToLegend,bool takeOwnership)3424 QgsProject::addMapLayer( QgsMapLayer *layer,
3425 bool addToLegend,
3426 bool takeOwnership )
3427 {
3428 QList<QgsMapLayer *> addedLayers;
3429 addedLayers = addMapLayers( QList<QgsMapLayer *>() << layer, addToLegend, takeOwnership );
3430 return addedLayers.isEmpty() ? nullptr : addedLayers[0];
3431 }
3432
removeMapLayers(const QStringList & layerIds)3433 void QgsProject::removeMapLayers( const QStringList &layerIds )
3434 {
3435 mProjectScope.reset();
3436 mLayerStore->removeMapLayers( layerIds );
3437 }
3438
removeMapLayers(const QList<QgsMapLayer * > & layers)3439 void QgsProject::removeMapLayers( const QList<QgsMapLayer *> &layers )
3440 {
3441 mProjectScope.reset();
3442 mLayerStore->removeMapLayers( layers );
3443 }
3444
removeMapLayer(const QString & layerId)3445 void QgsProject::removeMapLayer( const QString &layerId )
3446 {
3447 mProjectScope.reset();
3448 mLayerStore->removeMapLayer( layerId );
3449 }
3450
removeMapLayer(QgsMapLayer * layer)3451 void QgsProject::removeMapLayer( QgsMapLayer *layer )
3452 {
3453 mProjectScope.reset();
3454 mLayerStore->removeMapLayer( layer );
3455 }
3456
takeMapLayer(QgsMapLayer * layer)3457 QgsMapLayer *QgsProject::takeMapLayer( QgsMapLayer *layer )
3458 {
3459 mProjectScope.reset();
3460 return mLayerStore->takeMapLayer( layer );
3461 }
3462
mainAnnotationLayer()3463 QgsAnnotationLayer *QgsProject::mainAnnotationLayer()
3464 {
3465 return mMainAnnotationLayer;
3466 }
3467
removeAllMapLayers()3468 void QgsProject::removeAllMapLayers()
3469 {
3470 if ( mLayerStore->count() == 0 )
3471 return;
3472
3473 ScopedIntIncrementor snapSingleBlocker( &mBlockSnappingUpdates );
3474 mProjectScope.reset();
3475 mLayerStore->removeAllMapLayers();
3476
3477 snapSingleBlocker.release();
3478 mSnappingConfig.clearIndividualLayerSettings();
3479 if ( !mBlockSnappingUpdates )
3480 emit snappingConfigChanged( mSnappingConfig );
3481 }
3482
reloadAllLayers()3483 void QgsProject::reloadAllLayers()
3484 {
3485 QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
3486 QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin();
3487 for ( ; it != layers.constEnd(); ++it )
3488 {
3489 it.value()->reload();
3490 }
3491 }
3492
mapLayers(const bool validOnly) const3493 QMap<QString, QgsMapLayer *> QgsProject::mapLayers( const bool validOnly ) const
3494 {
3495 return validOnly ? mLayerStore->validMapLayers() : mLayerStore->mapLayers();
3496 }
3497
transactionGroup(const QString & providerKey,const QString & connString)3498 QgsTransactionGroup *QgsProject::transactionGroup( const QString &providerKey, const QString &connString )
3499 {
3500 return mTransactionGroups.value( qMakePair( providerKey, connString ) );
3501 }
3502
defaultCrsForNewLayers() const3503 QgsCoordinateReferenceSystem QgsProject::defaultCrsForNewLayers() const
3504 {
3505 QgsCoordinateReferenceSystem defaultCrs;
3506
3507 // TODO QGIS 4.0 -- remove this method, and place it somewhere in app (where it belongs)
3508 // 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)
3509 if ( mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), QStringLiteral( "NoAction" ), QgsSettings::App ).toString() == QStringLiteral( "UseProjectCrs" )
3510 || mSettings.value( QStringLiteral( "/projections/unknownCrsBehavior" ), 0, QgsSettings::App ).toString() == 2 )
3511 {
3512 // for new layers if the new layer crs method is set to either prompt or use project, then we use the project crs
3513 defaultCrs = crs();
3514 }
3515 else
3516 {
3517 // global crs
3518 QString layerDefaultCrs = mSettings.value( QStringLiteral( "/Projections/layerDefaultCrs" ), geoEpsgCrsAuthId() ).toString();
3519 defaultCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( layerDefaultCrs );
3520 }
3521
3522 return defaultCrs;
3523 }
3524
setTrustLayerMetadata(bool trust)3525 void QgsProject::setTrustLayerMetadata( bool trust )
3526 {
3527 mTrustLayerMetadata = trust;
3528
3529 auto layers = mapLayers();
3530 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
3531 {
3532 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
3533 if ( vl )
3534 {
3535 vl->setReadExtentFromXml( trust );
3536 }
3537 }
3538 }
3539
saveAuxiliaryStorage(const QString & filename)3540 bool QgsProject::saveAuxiliaryStorage( const QString &filename )
3541 {
3542 const QMap<QString, QgsMapLayer *> layers = mapLayers();
3543 bool empty = true;
3544 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
3545 {
3546 if ( it.value()->type() != QgsMapLayerType::VectorLayer )
3547 continue;
3548
3549 QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() );
3550 if ( vl && vl->auxiliaryLayer() )
3551 {
3552 vl->auxiliaryLayer()->save();
3553 empty &= vl->auxiliaryLayer()->auxiliaryFields().isEmpty();
3554 }
3555 }
3556
3557 if ( !mAuxiliaryStorage->exists( *this ) && filename.isEmpty() && empty )
3558 {
3559 return true; // it's not an error
3560 }
3561 else if ( !filename.isEmpty() )
3562 {
3563 return mAuxiliaryStorage->saveAs( filename );
3564 }
3565 else
3566 {
3567 return mAuxiliaryStorage->saveAs( *this );
3568 }
3569 }
3570
dataDefinedServerPropertyDefinitions()3571 QgsPropertiesDefinition &QgsProject::dataDefinedServerPropertyDefinitions()
3572 {
3573 static QgsPropertiesDefinition sPropertyDefinitions
3574 {
3575 {
3576 QgsProject::DataDefinedServerProperty::WMSOnlineResource,
3577 QgsPropertyDefinition( "WMSOnlineResource", QObject::tr( "WMS Online Resource" ), QgsPropertyDefinition::String )
3578 },
3579 };
3580 return sPropertyDefinitions;
3581 }
3582
auxiliaryStorage() const3583 const QgsAuxiliaryStorage *QgsProject::auxiliaryStorage() const
3584 {
3585 return mAuxiliaryStorage.get();
3586 }
3587
auxiliaryStorage()3588 QgsAuxiliaryStorage *QgsProject::auxiliaryStorage()
3589 {
3590 return mAuxiliaryStorage.get();
3591 }
3592
metadata() const3593 const QgsProjectMetadata &QgsProject::metadata() const
3594 {
3595 return mMetadata;
3596 }
3597
setMetadata(const QgsProjectMetadata & metadata)3598 void QgsProject::setMetadata( const QgsProjectMetadata &metadata )
3599 {
3600 if ( metadata == mMetadata )
3601 return;
3602
3603 mMetadata = metadata;
3604 mProjectScope.reset();
3605
3606 emit metadataChanged();
3607
3608 setDirty( true );
3609 }
3610
requiredLayers() const3611 QSet<QgsMapLayer *> QgsProject::requiredLayers() const
3612 {
3613 QSet<QgsMapLayer *> requiredLayers;
3614
3615 const QMap<QString, QgsMapLayer *> &layers = mapLayers();
3616 for ( QMap<QString, QgsMapLayer *>::const_iterator it = layers.constBegin(); it != layers.constEnd(); ++it )
3617 {
3618 if ( !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
3619 {
3620 requiredLayers.insert( it.value() );
3621 }
3622 }
3623 return requiredLayers;
3624 }
3625
setRequiredLayers(const QSet<QgsMapLayer * > & layers)3626 void QgsProject::setRequiredLayers( const QSet<QgsMapLayer *> &layers )
3627 {
3628 const QMap<QString, QgsMapLayer *> &projectLayers = mapLayers();
3629 for ( QMap<QString, QgsMapLayer *>::const_iterator it = projectLayers.constBegin(); it != projectLayers.constEnd(); ++it )
3630 {
3631 if ( layers.contains( it.value() ) == !it.value()->flags().testFlag( QgsMapLayer::Removable ) )
3632 continue;
3633
3634 if ( layers.contains( it.value() ) )
3635 it.value()->setFlags( it.value()->flags() & ~QgsMapLayer::Removable );
3636 else
3637 it.value()->setFlags( it.value()->flags() | QgsMapLayer::Removable );
3638 }
3639 }
3640
setProjectColors(const QgsNamedColorList & colors)3641 void QgsProject::setProjectColors( const QgsNamedColorList &colors )
3642 {
3643 // save colors to project
3644 QStringList customColors;
3645 QStringList customColorLabels;
3646
3647 QgsNamedColorList::const_iterator colorIt = colors.constBegin();
3648 for ( ; colorIt != colors.constEnd(); ++colorIt )
3649 {
3650 QString color = QgsSymbolLayerUtils::encodeColor( ( *colorIt ).first );
3651 QString label = ( *colorIt ).second;
3652 customColors.append( color );
3653 customColorLabels.append( label );
3654 }
3655 writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ), customColors );
3656 writeEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ), customColorLabels );
3657 mProjectScope.reset();
3658 emit projectColorsChanged();
3659 }
3660
setBackgroundColor(const QColor & color)3661 void QgsProject::setBackgroundColor( const QColor &color )
3662 {
3663 if ( mBackgroundColor == color )
3664 return;
3665
3666 mBackgroundColor = color;
3667 emit backgroundColorChanged();
3668 }
3669
backgroundColor() const3670 QColor QgsProject::backgroundColor() const
3671 {
3672 return mBackgroundColor;
3673 }
3674
setSelectionColor(const QColor & color)3675 void QgsProject::setSelectionColor( const QColor &color )
3676 {
3677 if ( mSelectionColor == color )
3678 return;
3679
3680 mSelectionColor = color;
3681 emit selectionColorChanged();
3682 }
3683
selectionColor() const3684 QColor QgsProject::selectionColor() const
3685 {
3686 return mSelectionColor;
3687 }
3688
setMapScales(const QVector<double> & scales)3689 void QgsProject::setMapScales( const QVector<double> &scales )
3690 {
3691 mViewSettings->setMapScales( scales );
3692 }
3693
mapScales() const3694 QVector<double> QgsProject::mapScales() const
3695 {
3696 return mViewSettings->mapScales();
3697 }
3698
setUseProjectScales(bool enabled)3699 void QgsProject::setUseProjectScales( bool enabled )
3700 {
3701 mViewSettings->setUseProjectScales( enabled );
3702 }
3703
useProjectScales() const3704 bool QgsProject::useProjectScales() const
3705 {
3706 return mViewSettings->useProjectScales();
3707 }
3708
generateTsFile(const QString & locale)3709 void QgsProject::generateTsFile( const QString &locale )
3710 {
3711 QgsTranslationContext translationContext;
3712 translationContext.setProject( this );
3713 translationContext.setFileName( QStringLiteral( "%1/%2.ts" ).arg( absolutePath(), baseName() ) );
3714
3715 QgsApplication::instance()->collectTranslatableObjects( &translationContext );
3716
3717 translationContext.writeTsFile( locale );
3718 }
3719
translate(const QString & context,const QString & sourceText,const char * disambiguation,int n) const3720 QString QgsProject::translate( const QString &context, const QString &sourceText, const char *disambiguation, int n ) const
3721 {
3722 if ( !mTranslator )
3723 {
3724 return sourceText;
3725 }
3726
3727 QString result = mTranslator->translate( context.toUtf8(), sourceText.toUtf8(), disambiguation, n );
3728
3729 if ( result.isEmpty() )
3730 {
3731 return sourceText;
3732 }
3733 return result;
3734 }
3735
accept(QgsStyleEntityVisitorInterface * visitor) const3736 bool QgsProject::accept( QgsStyleEntityVisitorInterface *visitor ) const
3737 {
3738 const QMap<QString, QgsMapLayer *> layers = mapLayers( false );
3739 if ( !layers.empty() )
3740 {
3741 for ( auto it = layers.constBegin(); it != layers.constEnd(); ++it )
3742 {
3743 // NOTE: if visitEnter returns false it means "don't visit this layer", not "abort all further visitations"
3744 if ( visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
3745 {
3746 if ( !( ( *it )->accept( visitor ) ) )
3747 return false;
3748
3749 if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layer, ( *it )->id(), ( *it )->name() ) ) )
3750 return false;
3751 }
3752 }
3753 }
3754
3755 if ( !mLayoutManager->accept( visitor ) )
3756 return false;
3757
3758 if ( !mAnnotationManager->accept( visitor ) )
3759 return false;
3760
3761 return true;
3762 }
3763
3764 /// @cond PRIVATE
GetNamedProjectColor(const QgsProject * project)3765 GetNamedProjectColor::GetNamedProjectColor( const QgsProject *project )
3766 : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
3767 {
3768 if ( !project )
3769 return;
3770
3771 //build up color list from project. Do this in advance for speed
3772 QStringList colorStrings = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ) );
3773 QStringList colorLabels = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Labels" ) );
3774
3775 //generate list from custom colors
3776 int colorIndex = 0;
3777 for ( QStringList::iterator it = colorStrings.begin();
3778 it != colorStrings.end(); ++it )
3779 {
3780 QColor color = QgsSymbolLayerUtils::decodeColor( *it );
3781 QString label;
3782 if ( colorLabels.length() > colorIndex )
3783 {
3784 label = colorLabels.at( colorIndex );
3785 }
3786
3787 mColors.insert( label.toLower(), color );
3788 colorIndex++;
3789 }
3790 }
3791
GetNamedProjectColor(const QHash<QString,QColor> & colors)3792 GetNamedProjectColor::GetNamedProjectColor( const QHash<QString, QColor> &colors )
3793 : QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
3794 , mColors( colors )
3795 {
3796 }
3797
func(const QVariantList & values,const QgsExpressionContext *,QgsExpression *,const QgsExpressionNodeFunction *)3798 QVariant GetNamedProjectColor::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
3799 {
3800 QString colorName = values.at( 0 ).toString().toLower();
3801 if ( mColors.contains( colorName ) )
3802 {
3803 return QStringLiteral( "%1,%2,%3" ).arg( mColors.value( colorName ).red() ).arg( mColors.value( colorName ).green() ).arg( mColors.value( colorName ).blue() );
3804 }
3805 else
3806 return QVariant();
3807 }
3808
clone() const3809 QgsScopedExpressionFunction *GetNamedProjectColor::clone() const
3810 {
3811 return new GetNamedProjectColor( mColors );
3812 }
3813 ///@endcond
3814