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