QGIS API Documentation  2.11.0-Master
qgsproject.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsproject.cpp - description
3  -------------------
4  begin : July 23, 2004
5  copyright : (C) 2004 by Mark Coletti
6  email : mcoletti at gmail.com
7 ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgsproject.h"
19 
20 #include <deque>
21 #include <memory>
22 
23 #include "qgsdatasourceuri.h"
24 #include "qgsexception.h"
25 #include "qgslayertree.h"
26 #include "qgslayertreeutils.h"
28 #include "qgslogger.h"
29 #include "qgsmaplayerregistry.h"
30 #include "qgspluginlayer.h"
31 #include "qgspluginlayerregistry.h"
33 #include "qgsprojectproperty.h"
34 #include "qgsprojectversion.h"
35 #include "qgsrasterlayer.h"
36 #include "qgsrectangle.h"
37 #include "qgsrelationmanager.h"
38 #include "qgsvectorlayer.h"
40 
41 #include <QApplication>
42 #include <QFileInfo>
43 #include <QDomNode>
44 #include <QObject>
45 #include <QTextStream>
46 #include <QDir>
47 
48 // canonical project instance
49 QgsProject *QgsProject::theProject_ = 0;
50 
59 static
60 QStringList makeKeyTokens_( QString const &scope, QString const &key )
61 {
62  QStringList keyTokens = QStringList( scope );
63  keyTokens += key.split( '/', QString::SkipEmptyParts );
64 
65  // be sure to include the canonical root node
66  keyTokens.push_front( "properties" );
67 
68  return keyTokens;
69 } // makeKeyTokens_
70 
71 
72 
73 
83 static
84 QgsProperty *findKey_( QString const &scope,
85  QString const &key,
86  QgsPropertyKey &rootProperty )
87 {
88  QgsPropertyKey *currentProperty = &rootProperty;
89  QgsProperty *nextProperty; // link to next property down hiearchy
90 
91  QStringList keySequence = makeKeyTokens_( scope, key );
92 
93  while ( !keySequence.isEmpty() )
94  {
95  // if the current head of the sequence list matches the property name,
96  // then traverse down the property hierarchy
97  if ( keySequence.first() == currentProperty->name() )
98  {
99  // remove front key since we're traversing down a level
100  keySequence.pop_front();
101 
102  if ( 1 == keySequence.count() )
103  {
104  // if we have only one key name left, then return the key found
105  return currentProperty->find( keySequence.front() );
106  }
107  else if ( keySequence.isEmpty() )
108  {
109  // if we're out of keys then the current property is the one we
110  // want; i.e., we're in the rate case of being at the top-most
111  // property node
112  return currentProperty;
113  }
114  else if (( nextProperty = currentProperty->find( keySequence.first() ) ) )
115  {
116  if ( nextProperty->isKey() )
117  {
118  currentProperty = static_cast<QgsPropertyKey*>( nextProperty );
119  }
120  else if ( nextProperty->isValue() && 1 == keySequence.count() )
121  {
122  // it may be that this may be one of several property value
123  // nodes keyed by QDict string; if this is the last remaining
124  // key token and the next property is a value node, then
125  // that's the situation, so return the currentProperty
126  return currentProperty;
127  }
128  else
129  {
130  // QgsPropertyValue not Key, so return null
131  return 0;
132  }
133  }
134  else
135  {
136  // if the next key down isn't found
137  // then the overall key sequence doesn't exist
138  return 0;
139  }
140  }
141  else
142  {
143  return 0;
144  }
145  }
146 
147  return 0;
148 } // findKey_
149 
150 
151 
159 static
160 QgsProperty *addKey_( QString const &scope,
161  QString const &key,
162  QgsPropertyKey *rootProperty,
163  QVariant value )
164 {
165  QStringList keySequence = makeKeyTokens_( scope, key );
166 
167  // cursor through property key/value hierarchy
168  QgsPropertyKey *currentProperty = rootProperty;
169  QgsProperty *nextProperty; // link to next property down hiearchy
170  QgsPropertyKey* newPropertyKey;
171 
172  while ( ! keySequence.isEmpty() )
173  {
174  // if the current head of the sequence list matches the property name,
175  // then traverse down the property hierarchy
176  if ( keySequence.first() == currentProperty->name() )
177  {
178  // remove front key since we're traversing down a level
179  keySequence.pop_front();
180 
181  // if key sequence has one last element, then we use that as the
182  // name to store the value
183  if ( 1 == keySequence.count() )
184  {
185  currentProperty->setValue( keySequence.front(), value );
186  return currentProperty;
187  }
188  // we're at the top element if popping the keySequence element
189  // will leave it empty; in that case, just add the key
190  else if ( keySequence.isEmpty() )
191  {
192  currentProperty->setValue( value );
193 
194  return currentProperty;
195  }
196  else if (( nextProperty = currentProperty->find( keySequence.first() ) ) )
197  {
198  currentProperty = dynamic_cast<QgsPropertyKey*>( nextProperty );
199 
200  if ( currentProperty )
201  {
202  continue;
203  }
204  else // QgsPropertyValue not Key, so return null
205  {
206  return 0;
207  }
208  }
209  else // the next subkey doesn't exist, so add it
210  {
211  if (( newPropertyKey = currentProperty->addKey( keySequence.first() ) ) )
212  {
213  currentProperty = newPropertyKey;
214  }
215  continue;
216  }
217  }
218  else
219  {
220  return 0;
221  }
222  }
223 
224  return 0;
225 
226 } // addKey_
227 
228 
229 
230 static
231 void removeKey_( QString const &scope,
232  QString const &key,
233  QgsPropertyKey &rootProperty )
234 {
235  QgsPropertyKey *currentProperty = &rootProperty;
236 
237  QgsProperty *nextProperty = 0; // link to next property down hiearchy
238  QgsPropertyKey *previousQgsPropertyKey = 0; // link to previous property up hiearchy
239 
240  QStringList keySequence = makeKeyTokens_( scope, key );
241 
242  while ( ! keySequence.isEmpty() )
243  {
244  // if the current head of the sequence list matches the property name,
245  // then traverse down the property hierarchy
246  if ( keySequence.first() == currentProperty->name() )
247  {
248  // remove front key since we're traversing down a level
249  keySequence.pop_front();
250 
251  // if we have only one key name left, then try to remove the key
252  // with that name
253  if ( 1 == keySequence.count() )
254  {
255  currentProperty->removeKey( keySequence.front() );
256  }
257  // if we're out of keys then the current property is the one we
258  // want to remove, but we can't delete it directly; we need to
259  // delete it from the parent property key container
260  else if ( keySequence.isEmpty() )
261  {
262  previousQgsPropertyKey->removeKey( currentProperty->name() );
263  }
264  else if (( nextProperty = currentProperty->find( keySequence.first() ) ) )
265  {
266  previousQgsPropertyKey = currentProperty;
267  currentProperty = dynamic_cast<QgsPropertyKey*>( nextProperty );
268 
269  if ( currentProperty )
270  {
271  continue;
272  }
273  else // QgsPropertyValue not Key, so return null
274  {
275  return;
276  }
277  }
278  else // if the next key down isn't found
279  { // then the overall key sequence doesn't exist
280  return;
281  }
282  }
283  else
284  {
285  return;
286  }
287  }
288 
289 } // void removeKey_
290 
291 
292 
294 {
295  QFile file; // current physical project file
296  QgsPropertyKey properties_; // property hierarchy
297  QString title; // project title
298  bool dirty; // project has been modified since it has been read or saved
299 
300  Imp()
301  : title()
302  , dirty( false )
303  { // top property node is the root
304  // "properties" that contains all plug-in
305  // and extra property keys and values
306  properties_.name() = "properties"; // root property node always this value
307  }
308 
311  void clear()
312  {
313  // QgsDebugMsg( "Clearing project properties Impl->clear();" );
314 
315  file.setFileName( QString() );
316  properties_.clearKeys();
317  title.clear();
318  dirty = false;
319  }
320 
321 }; // struct QgsProject::Imp
322 
323 
324 
325 QgsProject::QgsProject()
326  : imp_( new QgsProject::Imp )
327  , mBadLayerHandler( new QgsProjectBadLayerDefaultHandler() )
328  , mRelationManager( new QgsRelationManager( this ) )
329  , mRootGroup( new QgsLayerTreeGroup )
330 {
331  clear();
332 
333  // bind the layer tree to the map layer registry.
334  // whenever layers are added to or removed from the registry,
335  // layer tree will be updated
336  mLayerTreeRegistryBridge = new QgsLayerTreeRegistryBridge( mRootGroup, this );
337 } // QgsProject ctor
338 
339 
340 
342 {
343  delete mBadLayerHandler;
344  delete mRelationManager;
345  delete mRootGroup;
346 
347  // note that QScopedPointer automatically deletes imp_ when it's destroyed
348 } // QgsProject dtor
349 
350 
351 
353 {
354  if ( !theProject_ )
355  {
356  theProject_ = new QgsProject;
357  }
358  return theProject_;
359 } // QgsProject *instance()
360 
362 {
363  imp_->title = title;
364 
365  dirty( true );
366 }
367 
368 
370 {
371  return imp_->title;
372 } // QgsProject::title() const
373 
374 
376 {
377  return imp_->dirty;
378 } // bool QgsProject::isDirty()
379 
380 
381 void QgsProject::dirty( bool b )
382 {
383  imp_->dirty = b;
384 } // bool QgsProject::isDirty()
385 
386 void QgsProject::setDirty( bool b )
387 {
388  dirty( b );
389 }
390 
391 
392 
394 {
395  imp_->file.setFileName( name );
396 
397  dirty( true );
398 } // void QgsProject::setFileName( QString const &name )
399 
400 
401 
403 {
404  return imp_->file.fileName();
405 }
406 
408 {
409  return QFileInfo( imp_->file );
410 }
411 
413 {
414  imp_->clear();
415  mEmbeddedLayers.clear();
416  mRelationManager->clear();
417 
418  mVisibilityPresetCollection.reset( new QgsVisibilityPresetCollection() );
419 
420  mRootGroup->removeAllChildren();
421 
422  // reset some default project properties
423  // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
424  writeEntry( "PositionPrecision", "/Automatic", true );
425  writeEntry( "PositionPrecision", "/DecimalPlaces", 2 );
426  writeEntry( "Paths", "/Absolute", false );
427 
428  setDirty( false );
429 }
430 
431 // basically a debugging tool to dump property list values
432 static void dump_( QgsPropertyKey const &topQgsPropertyKey )
433 {
434  QgsDebugMsg( "current properties:" );
435  topQgsPropertyKey.dump();
436 } // dump_
437 
438 
469 static
470 void
471 _getProperties( QDomDocument const &doc, QgsPropertyKey &project_properties )
472 {
473  QDomNodeList properties = doc.elementsByTagName( "properties" );
474 
475  if ( properties.count() > 1 )
476  {
477  QgsDebugMsg( "there appears to be more than one ``properties'' XML tag ... bailing" );
478  return;
479  }
480  else if ( properties.count() < 1 ) // no properties found, so we're done
481  {
482  return;
483  }
484 
485  // item(0) because there should only be ONE "properties" node
486  QDomNodeList scopes = properties.item( 0 ).childNodes();
487 
488  if ( scopes.count() < 1 )
489  {
490  QgsDebugMsg( "empty ``properties'' XML tag ... bailing" );
491  return;
492  }
493 
494  QDomNode propertyNode = properties.item( 0 );
495 
496  if ( ! project_properties.readXML( propertyNode ) )
497  {
498  QgsDebugMsg( "Project_properties.readXML() failed" );
499  }
500 } // _getProperties
501 
502 
503 
504 
516 static void _getTitle( QDomDocument const &doc, QString &title )
517 {
518  QDomNodeList nl = doc.elementsByTagName( "title" );
519 
520  title = ""; // by default the title will be empty
521 
522  if ( !nl.count() )
523  {
524  QgsDebugMsg( "unable to find title element" );
525  return;
526  }
527 
528  QDomNode titleNode = nl.item( 0 ); // there should only be one, so zeroth element ok
529 
530  if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text
531  {
532  QgsDebugMsg( "unable to find title element" );
533  return;
534  }
535 
536  QDomNode titleTextNode = titleNode.firstChild(); // should only have one child
537 
538  if ( !titleTextNode.isText() )
539  {
540  QgsDebugMsg( "unable to find title element" );
541  return;
542  }
543 
544  QDomText titleText = titleTextNode.toText();
545 
546  title = titleText.data();
547 
548 } // _getTitle
549 
550 
556 {
557  QDomNodeList nl = doc.elementsByTagName( "qgis" );
558 
559  if ( !nl.count() )
560  {
561  QgsDebugMsg( " unable to find qgis element in project file" );
562  return QgsProjectVersion( 0, 0, 0, QString( "" ) );
563  }
564 
565  QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element ok
566 
567  QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
568  QgsProjectVersion projectVersion( qgisElement.attribute( "version" ) );
569  return projectVersion;
570 } // _getVersion
571 
572 
573 
621 QPair< bool, QList<QDomNode> > QgsProject::_getMapLayers( QDomDocument const &doc )
622 {
623  // Layer order is set by the restoring the legend settings from project file.
624  // This is done on the 'readProject( ... )' signal
625 
626  QDomNodeList nl = doc.elementsByTagName( "maplayer" );
627 
628  QList<QDomNode> brokenNodes; // a list of Dom nodes corresponding to layers
629  // that we were unable to load; this could be
630  // because the layers were removed or
631  // re-located after the project was last saved
632 
633  // process the map layer nodes
634 
635  if ( 0 == nl.count() ) // if we have no layers to process, bail
636  {
637  return qMakePair( true, brokenNodes ); // Decided to return "true" since it's
638  // possible for there to be a project with no
639  // layers; but also, more imporantly, this
640  // would cause the tests/qgsproject to fail
641  // since the test suite doesn't currently
642  // support test layers
643  }
644 
645  bool returnStatus = true;
646 
647  emit layerLoaded( 0, nl.count() );
648 
649  // Collect vector layers with joins.
650  // They need to refresh join caches and symbology infos after all layers are loaded
652 
653  for ( int i = 0; i < nl.count(); i++ )
654  {
655  QDomNode node = nl.item( i );
656  QDomElement element = node.toElement();
657 
658  QString name = node.namedItem( "layername" ).toElement().text();
659  if ( !name.isNull() )
660  emit loadingLayer( tr( "Loading layer %1" ).arg( name ) );
661 
662  if ( element.attribute( "embedded" ) == "1" )
663  {
664  createEmbeddedLayer( element.attribute( "id" ), readPath( element.attribute( "project" ) ), brokenNodes, vLayerList );
665  continue;
666  }
667  else
668  {
669  if ( !addLayer( element, brokenNodes, vLayerList ) )
670  {
671  returnStatus = false;
672  }
673  }
674  emit layerLoaded( i + 1, nl.count() );
675  }
676 
677  // Update field map of layers with joins and create join caches if necessary
678  // Needs to be done here once all dependent layers are loaded
679  QList< QPair< QgsVectorLayer*, QDomElement > >::iterator vIt = vLayerList.begin();
680  for ( ; vIt != vLayerList.end(); ++vIt )
681  {
682  vIt->first->createJoinCaches();
683  vIt->first->updateFields();
684  }
685 
686  QSet<QgsVectorLayer *> notified;
687  for ( vIt = vLayerList.begin(); vIt != vLayerList.end(); ++vIt )
688  {
689  if ( notified.contains( vIt->first ) )
690  continue;
691 
692  notified << vIt->first;
693  emit readMapLayer( vIt->first, vIt->second );
694  }
695 
696 
697 
698  return qMakePair( returnStatus, brokenNodes );
699 } // _getMapLayers
700 
701 bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &brokenNodes, QList< QPair< QgsVectorLayer*, QDomElement > > &vectorLayerList )
702 {
703  QString type = layerElem.attribute( "type" );
704  QgsDebugMsg( "Layer type is " + type );
705  QgsMapLayer *mapLayer = 0;
706 
707  if ( type == "vector" )
708  {
709  mapLayer = new QgsVectorLayer;
710  }
711  else if ( type == "raster" )
712  {
713  mapLayer = new QgsRasterLayer;
714  }
715  else if ( type == "plugin" )
716  {
717  QString typeName = layerElem.attribute( "name" );
718  mapLayer = QgsPluginLayerRegistry::instance()->createLayer( typeName );
719  }
720 
721  if ( !mapLayer )
722  {
723  QgsDebugMsg( "Unable to create layer" );
724 
725  return false;
726  }
727 
728  Q_CHECK_PTR( mapLayer );
729 
730  // have the layer restore state that is stored in Dom node
731  if ( mapLayer->readLayerXML( layerElem ) && mapLayer->isValid() )
732  {
733  // postpone readMapLayer signal for vector layers with joins
734  QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer*>( mapLayer );
735  if ( !vLayer || vLayer->vectorJoins().size() == 0 )
736  emit readMapLayer( mapLayer, layerElem );
737  else
738  vectorLayerList.push_back( qMakePair( vLayer, layerElem ) );
739 
740  QList<QgsMapLayer *> myLayers;
741  myLayers << mapLayer;
743 
744  return true;
745  }
746  else
747  {
748  delete mapLayer;
749 
750  QgsDebugMsg( "Unable to load " + type + " layer" );
751  brokenNodes.push_back( layerElem );
752  return false;
753  }
754 }
755 
756 
760 bool QgsProject::read( QFileInfo const &file )
761 {
762  imp_->file.setFileName( file.filePath() );
763 
764  return read();
765 } // QgsProject::read
766 
767 
768 
773 {
774  clearError();
775 
776  QScopedPointer<QDomDocument> doc( new QDomDocument( "qgis" ) );
777 
778  if ( !imp_->file.open( QIODevice::ReadOnly | QIODevice::Text ) )
779  {
780  imp_->file.close();
781 
782  setError( tr( "Unable to open %1" ).arg( imp_->file.fileName() ) );
783 
784  return false;
785  }
786 
787  // location of problem associated with errorMsg
788  int line, column;
789  QString errorMsg;
790 
791  if ( !doc->setContent( &imp_->file, &errorMsg, &line, &column ) )
792  {
793  // want to make this class as GUI independent as possible; so commented out
794 #if 0
795  QMessageBox::critical( 0, tr( "Project File Read Error" ),
796  tr( "%1 at line %2 column %3" ).arg( errorMsg ).arg( line ).arg( column ) );
797 #endif
798 
799  QString errorString = tr( "Project file read error: %1 at line %2 column %3" )
800  .arg( errorMsg ).arg( line ).arg( column );
801 
802  QgsDebugMsg( errorString );
803 
804  imp_->file.close();
805 
806  setError( tr( "%1 for file %2" ).arg( errorString ).arg( imp_->file.fileName() ) );
807 
808  return false;
809  }
810 
811  imp_->file.close();
812 
813 
814  QgsDebugMsg( "Opened document " + imp_->file.fileName() );
815  QgsDebugMsg( "Project title: " + imp_->title );
816 
817  // get project version string, if any
818  QgsProjectVersion fileVersion = _getVersion( *doc );
819  QgsProjectVersion thisVersion( QGis::QGIS_VERSION );
820 
821  if ( thisVersion > fileVersion )
822  {
823  QgsLogger::warning( "Loading a file that was saved with an older "
824  "version of qgis (saved in " + fileVersion.text() +
825  ", loaded in " + QGis::QGIS_VERSION +
826  "). Problems may occur." );
827 
828  QgsProjectFileTransform projectFile( *doc, fileVersion );
829 
831  emit oldProjectVersionWarning( fileVersion.text() );
832  QgsDebugMsg( "Emitting oldProjectVersionWarning(oldVersion)." );
833 
834  projectFile.updateRevision( thisVersion );
835  }
836 
837  // start new project, just keep the file name
838  QString fileName = imp_->file.fileName();
839  clear();
840  imp_->file.setFileName( fileName );
841 
842  // now get any properties
843  _getProperties( *doc, imp_->properties_ );
844 
845  QgsDebugMsg( QString::number( imp_->properties_.count() ) + " properties read" );
846 
847  dump_( imp_->properties_ );
848 
849  // now get project title
850  _getTitle( *doc, imp_->title );
851 
852  // read the layer tree from project file
853 
854  mRootGroup->setCustomProperty( "loading", 1 );
855 
856  QDomElement layerTreeElem = doc->documentElement().firstChildElement( "layer-tree-group" );
857  if ( !layerTreeElem.isNull() )
858  {
859  mRootGroup->readChildrenFromXML( layerTreeElem );
860  }
861  else
862  {
863  QgsLayerTreeUtils::readOldLegend( mRootGroup, doc->documentElement().firstChildElement( "legend" ) );
864  }
865 
866  QgsDebugMsg( "Loaded layer tree:\n " + mRootGroup->dump() );
867 
868  mLayerTreeRegistryBridge->setEnabled( false );
869 
870  // get the map layers
871  QPair< bool, QList<QDomNode> > getMapLayersResults = _getMapLayers( *doc );
872 
873  // review the integrity of the retrieved map layers
874  bool clean = getMapLayersResults.first;
875 
876  if ( !clean )
877  {
878  QgsDebugMsg( "Unable to get map layers from project file." );
879 
880  if ( ! getMapLayersResults.second.isEmpty() )
881  {
882  QgsDebugMsg( "there are " + QString::number( getMapLayersResults.second.size() ) + " broken layers" );
883  }
884 
885  // we let a custom handler to decide what to do with missing layers
886  // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
887  mBadLayerHandler->handleBadLayers( getMapLayersResults.second, *doc );
888  }
889 
890  mLayerTreeRegistryBridge->setEnabled( true );
891 
892  // load embedded groups and layers
893  loadEmbeddedNodes( mRootGroup );
894 
895  // make sure the are just valid layers
897 
898  mRootGroup->removeCustomProperty( "loading" );
899 
900  mVisibilityPresetCollection.reset( new QgsVisibilityPresetCollection() );
901  mVisibilityPresetCollection->readXML( *doc );
902 
903  // read the project: used by map canvas and legend
904  emit readProject( *doc );
905 
906  // if all went well, we're allegedly in pristine state
907  if ( clean )
908  dirty( false );
909 
910  return true;
911 
912 } // QgsProject::read
913 
914 
916 {
917  foreach ( QgsLayerTreeNode *child, group->children() )
918  {
919  if ( QgsLayerTree::isGroup( child ) )
920  {
921  QgsLayerTreeGroup *childGroup = QgsLayerTree::toGroup( child );
922  if ( childGroup->customProperty( "embedded" ).toInt() )
923  {
924  // make sure to convert the path from relative to absolute
925  QString projectPath = readPath( childGroup->customProperty( "embedded_project" ).toString() );
926  childGroup->setCustomProperty( "embedded_project", projectPath );
927 
928  QgsLayerTreeGroup *newGroup = createEmbeddedGroup( childGroup->name(), projectPath, childGroup->customProperty( "embedded-invisible-layers" ).toStringList() );
929  if ( newGroup )
930  {
931  QList<QgsLayerTreeNode*> clonedChildren;
932  foreach ( QgsLayerTreeNode *newGroupChild, newGroup->children() )
933  clonedChildren << newGroupChild->clone();
934  delete newGroup;
935 
936  childGroup->insertChildNodes( 0, clonedChildren );
937  }
938  }
939  else
940  {
941  loadEmbeddedNodes( childGroup );
942  }
943  }
944  else if ( QgsLayerTree::isLayer( child ) )
945  {
946  if ( child->customProperty( "embedded" ).toInt() )
947  {
948  QList<QDomNode> brokenNodes;
950  createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), child->customProperty( "embedded_project" ).toString(), brokenNodes, vectorLayerList );
951  }
952  }
953 
954  }
955 }
956 
957 
958 bool QgsProject::read( QDomNode &layerNode )
959 {
960  QList<QDomNode> brokenNodes;
962  return addLayer( layerNode.toElement(), brokenNodes, vectorLayerList );
963 } // QgsProject::read( QDomNode &layerNode )
964 
965 
966 
967 bool QgsProject::write( QFileInfo const &file )
968 {
969  imp_->file.setFileName( file.filePath() );
970 
971  return write();
972 } // QgsProject::write( QFileInfo const &file )
973 
974 
976 {
977  clearError();
978 
979  // Create backup file
980  if ( QFile::exists( fileName() ) )
981  {
982  QString backup = fileName() + "~";
983  if ( QFile::exists( backup ) )
984  QFile::remove( backup );
985  QFile::rename( fileName(), backup );
986  }
987 
988  // if we have problems creating or otherwise writing to the project file,
989  // let's find out up front before we go through all the hand-waving
990  // necessary to create all the Dom objects
991  if ( !imp_->file.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
992  {
993  imp_->file.close(); // even though we got an error, let's make
994  // sure it's closed anyway
995 
996  setError( tr( "Unable to save to file %1" ).arg( imp_->file.fileName() ) );
997  return false;
998  }
999  QFileInfo myFileInfo( imp_->file );
1000  if ( !myFileInfo.isWritable() )
1001  {
1002  // even though we got an error, let's make
1003  // sure it's closed anyway
1004  imp_->file.close();
1005  setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." )
1006  .arg( imp_->file.fileName() ) );
1007  return false;
1008  }
1009 
1010 
1011 
1012  QDomImplementation DomImplementation;
1013  DomImplementation.setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
1014 
1015  QDomDocumentType documentType =
1016  DomImplementation.createDocumentType( "qgis", "http://mrcc.com/qgis.dtd",
1017  "SYSTEM" );
1018  QScopedPointer<QDomDocument> doc( new QDomDocument( documentType ) );
1019 
1020  QDomElement qgisNode = doc->createElement( "qgis" );
1021  qgisNode.setAttribute( "projectname", title() );
1022  qgisNode.setAttribute( "version", QString( "%1" ).arg( QGis::QGIS_VERSION ) );
1023 
1024  doc->appendChild( qgisNode );
1025 
1026  // title
1027  QDomElement titleNode = doc->createElement( "title" );
1028  qgisNode.appendChild( titleNode );
1029 
1030  QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
1031  titleNode.appendChild( titleText );
1032 
1033  // write layer tree - make sure it is without embedded subgroups
1034  QgsLayerTreeNode *clonedRoot = mRootGroup->clone();
1036  QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ) ); // convert absolute paths to relative paths if required
1037  clonedRoot->writeXML( qgisNode );
1038  delete clonedRoot;
1039 
1040  // let map canvas and legend write their information
1041  emit writeProject( *doc );
1042 
1043  // within top level node save list of layers
1045 
1046  // Iterate over layers in zOrder
1047  // Call writeXML() on each
1048  QDomElement projectLayersNode = doc->createElement( "projectlayers" );
1049  projectLayersNode.setAttribute( "layercount", qulonglong( layers.size() ) );
1050 
1052  while ( li != layers.end() )
1053  {
1054  QgsMapLayer *ml = li.value();
1055 
1056  if ( ml )
1057  {
1058  QString externalProjectFile = layerIsEmbedded( ml->id() );
1059  QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.find( ml->id() );
1060  if ( emIt == mEmbeddedLayers.constEnd() )
1061  {
1062  // general layer metadata
1063  QDomElement maplayerElem = doc->createElement( "maplayer" );
1064 
1065  ml->writeLayerXML( maplayerElem, *doc );
1066 
1067  emit writeMapLayer( ml, maplayerElem, *doc );
1068 
1069  projectLayersNode.appendChild( maplayerElem );
1070  }
1071  else
1072  {
1073  // layer defined in an external project file
1074  // only save embedded layer if not managed by a legend group
1075  if ( emIt.value().second )
1076  {
1077  QDomElement mapLayerElem = doc->createElement( "maplayer" );
1078  mapLayerElem.setAttribute( "embedded", 1 );
1079  mapLayerElem.setAttribute( "project", writePath( emIt.value().first ) );
1080  mapLayerElem.setAttribute( "id", ml->id() );
1081  projectLayersNode.appendChild( mapLayerElem );
1082  }
1083  }
1084  }
1085  li++;
1086  }
1087 
1088  qgisNode.appendChild( projectLayersNode );
1089 
1090  // now add the optional extra properties
1091 
1092  dump_( imp_->properties_ );
1093 
1094  QgsDebugMsg( QString( "there are %1 property scopes" ).arg( static_cast<int>( imp_->properties_.count() ) ) );
1095 
1096  if ( !imp_->properties_.isEmpty() ) // only worry about properties if we
1097  // actually have any properties
1098  {
1099  imp_->properties_.writeXML( "properties", qgisNode, *doc );
1100  }
1101 
1102  mVisibilityPresetCollection->writeXML( *doc );
1103 
1104  // now wrap it up and ship it to the project file
1105  doc->normalize(); // XXX I'm not entirely sure what this does
1106 
1107  QTextStream projectFileStream( &imp_->file );
1108 
1109  doc->save( projectFileStream, 2 ); // save as utf-8
1110  imp_->file.close();
1111 
1112  // check if the text stream had no error - if it does
1113  // the user will get a message so they can try to resolve the
1114  // situation e.g. by saving project to a volume with more space
1115  if ( projectFileStream.pos() == -1 || imp_->file.error() != QFile::NoError )
1116  {
1117  setError( tr( "Unable to save to file %1. Your project "
1118  "may be corrupted on disk. Try clearing some space on the volume and "
1119  "check file permissions before pressing save again." )
1120  .arg( imp_->file.fileName() ) );
1121  return false;
1122  }
1123 
1124  dirty( false ); // reset to pristine state
1125 
1126  emit projectSaved();
1127 
1128  return true;
1129 } // QgsProject::write
1130 
1131 
1132 
1134 {
1135  clear();
1136 
1137  dirty( true );
1138 } // QgsProject::clearProperties()
1139 
1140 
1141 
1142 bool
1143 QgsProject::writeEntry( QString const &scope, const QString &key, bool value )
1144 {
1145  dirty( true );
1146 
1147  return addKey_( scope, key, &imp_->properties_, value );
1148 } // QgsProject::writeEntry ( ..., bool value )
1149 
1150 
1151 bool
1152 QgsProject::writeEntry( QString const &scope, const QString &key,
1153  double value )
1154 {
1155  dirty( true );
1156 
1157  return addKey_( scope, key, &imp_->properties_, value );
1158 } // QgsProject::writeEntry ( ..., double value )
1159 
1160 
1161 bool
1162 QgsProject::writeEntry( QString const &scope, const QString &key, int value )
1163 {
1164  dirty( true );
1165 
1166  return addKey_( scope, key, &imp_->properties_, value );
1167 } // QgsProject::writeEntry ( ..., int value )
1168 
1169 
1170 bool
1171 QgsProject::writeEntry( QString const &scope, const QString &key,
1172  const QString &value )
1173 {
1174  dirty( true );
1175 
1176  return addKey_( scope, key, &imp_->properties_, value );
1177 } // QgsProject::writeEntry ( ..., const QString &value )
1178 
1179 
1180 bool
1181 QgsProject::writeEntry( QString const &scope, const QString &key,
1182  const QStringList &value )
1183 {
1184  dirty( true );
1185 
1186  return addKey_( scope, key, &imp_->properties_, value );
1187 } // QgsProject::writeEntry ( ..., const QStringList &value )
1188 
1191  const QString &key,
1192  QStringList def,
1193  bool *ok ) const
1194 {
1195  QgsProperty *property = findKey_( scope, key, imp_->properties_ );
1196 
1197  QVariant value;
1198 
1199  if ( property )
1200  {
1201  value = property->value();
1202 
1203  bool valid = QVariant::StringList == value.type();
1204  if ( ok )
1205  *ok = valid;
1206 
1207  if ( valid )
1208  {
1209  return value.toStringList();
1210  }
1211  }
1212 
1213  return def;
1214 } // QgsProject::readListEntry
1215 
1216 
1217 QString
1219  const QString &key,
1220  const QString &def,
1221  bool *ok ) const
1222 {
1223  QgsProperty *property = findKey_( scope, key, imp_->properties_ );
1224 
1225  QVariant value;
1226 
1227  if ( property )
1228  {
1229  value = property->value();
1230 
1231  bool valid = value.canConvert( QVariant::String );
1232  if ( ok )
1233  *ok = valid;
1234 
1235  if ( valid )
1236  return value.toString();
1237  }
1238 
1239  return def;
1240 } // QgsProject::readEntry
1241 
1242 
1243 int
1244 QgsProject::readNumEntry( QString const &scope, const QString &key, int def,
1245  bool *ok ) const
1246 {
1247  QgsProperty *property = findKey_( scope, key, imp_->properties_ );
1248 
1249  QVariant value;
1250 
1251  if ( property )
1252  {
1253  value = property->value();
1254  }
1255 
1256  bool valid = value.canConvert( QVariant::String );
1257 
1258  if ( ok )
1259  {
1260  *ok = valid;
1261  }
1262 
1263  if ( valid )
1264  {
1265  return value.toInt();
1266  }
1267 
1268  return def;
1269 } // QgsProject::readNumEntry
1270 
1271 
1272 double
1273 QgsProject::readDoubleEntry( QString const &scope, const QString &key,
1274  double def,
1275  bool *ok ) const
1276 {
1277  QgsProperty *property = findKey_( scope, key, imp_->properties_ );
1278  if ( property )
1279  {
1280  QVariant value = property->value();
1281 
1282  bool valid = value.canConvert( QVariant::Double );
1283  if ( ok )
1284  *ok = valid;
1285 
1286  if ( valid )
1287  return value.toDouble();
1288  }
1289 
1290  return def;
1291 } // QgsProject::readDoubleEntry
1292 
1293 
1294 bool
1295 QgsProject::readBoolEntry( QString const &scope, const QString &key, bool def,
1296  bool *ok ) const
1297 {
1298  QgsProperty *property = findKey_( scope, key, imp_->properties_ );
1299 
1300  if ( property )
1301  {
1302  QVariant value = property->value();
1303 
1304  bool valid = value.canConvert( QVariant::Bool );
1305  if ( ok )
1306  *ok = valid;
1307 
1308  if ( valid )
1309  return value.toBool();
1310  }
1311 
1312  return def;
1313 } // QgsProject::readBoolEntry
1314 
1315 
1316 bool QgsProject::removeEntry( QString const &scope, const QString &key )
1317 {
1318  removeKey_( scope, key, imp_->properties_ );
1319 
1320  dirty( true );
1321 
1322  return !findKey_( scope, key, imp_->properties_ );
1323 } // QgsProject::removeEntry
1324 
1325 
1326 
1327 QStringList QgsProject::entryList( QString const &scope, QString const &key ) const
1328 {
1329  QgsProperty *foundProperty = findKey_( scope, key, imp_->properties_ );
1330 
1331  QStringList entries;
1332 
1333  if ( foundProperty )
1334  {
1335  QgsPropertyKey *propertyKey = dynamic_cast<QgsPropertyKey*>( foundProperty );
1336 
1337  if ( propertyKey )
1338  { propertyKey->entryList( entries ); }
1339  }
1340 
1341  return entries;
1342 } // QgsProject::entryList
1343 
1344 
1345 QStringList QgsProject::subkeyList( QString const &scope, QString const &key ) const
1346 {
1347  QgsProperty *foundProperty = findKey_( scope, key, imp_->properties_ );
1348 
1349  QStringList entries;
1350 
1351  if ( foundProperty )
1352  {
1353  QgsPropertyKey *propertyKey = dynamic_cast<QgsPropertyKey*>( foundProperty );
1354 
1355  if ( propertyKey )
1356  { propertyKey->subkeyList( entries ); }
1357  }
1358 
1359  return entries;
1360 
1361 } // QgsProject::subkeyList
1362 
1363 
1364 
1366 {
1367  dump_( imp_->properties_ );
1368 } // QgsProject::dumpProperties
1369 
1370 
1371 // return the absolute path from a filename read from project file
1373 {
1374  if ( readBoolEntry( "Paths", "/Absolute", false ) )
1375  {
1376  return src;
1377  }
1378 
1379  // if this is a VSIFILE, remove the VSI prefix and append to final result
1380  QString vsiPrefix = qgsVsiPrefix( src );
1381  if ( ! vsiPrefix.isEmpty() )
1382  {
1383  // unfortunately qgsVsiPrefix returns prefix also for files like "/x/y/z.gz"
1384  // so we need to check if we really have the prefix
1385  if ( src.startsWith( "/vsi", Qt::CaseInsensitive ) )
1386  src.remove( 0, vsiPrefix.size() );
1387  else
1388  vsiPrefix.clear();
1389  }
1390 
1391  // relative path should always start with ./ or ../
1392  if ( !src.startsWith( "./" ) && !src.startsWith( "../" ) )
1393  {
1394 #if defined(Q_OS_WIN)
1395  if ( src.startsWith( "\\\\" ) ||
1396  src.startsWith( "//" ) ||
1397  ( src[0].isLetter() && src[1] == ':' ) )
1398  {
1399  // UNC or absolute path
1400  return vsiPrefix + src;
1401  }
1402 #else
1403  if ( src[0] == '/' )
1404  {
1405  // absolute path
1406  return vsiPrefix + src;
1407  }
1408 #endif
1409 
1410  // so this one isn't absolute, but also doesn't start // with ./ or ../.
1411  // That means that it was saved with an earlier version of "relative path support",
1412  // where the source file had to exist and only the project directory was stripped
1413  // from the filename.
1414  QString home = homePath();
1415  if ( home.isNull() )
1416  return vsiPrefix + src;
1417 
1418  QFileInfo fi( home + "/" + src );
1419 
1420  if ( !fi.exists() )
1421  {
1422  return vsiPrefix + src;
1423  }
1424  else
1425  {
1426  return vsiPrefix + fi.canonicalFilePath();
1427  }
1428  }
1429 
1430  QString srcPath = src;
1431  QString projPath = fileName();
1432 
1433  if ( projPath.isEmpty() )
1434  {
1435  return vsiPrefix + src;
1436  }
1437 
1438 #if defined(Q_OS_WIN)
1439  srcPath.replace( "\\", "/" );
1440  projPath.replace( "\\", "/" );
1441 
1442  bool uncPath = projPath.startsWith( "//" );
1443 #endif
1444 
1445  QStringList srcElems = srcPath.split( "/", QString::SkipEmptyParts );
1446  QStringList projElems = projPath.split( "/", QString::SkipEmptyParts );
1447 
1448 #if defined(Q_OS_WIN)
1449  if ( uncPath )
1450  {
1451  projElems.insert( 0, "" );
1452  projElems.insert( 0, "" );
1453  }
1454 #endif
1455 
1456  // remove project file element
1457  projElems.removeLast();
1458 
1459  // append source path elements
1460  projElems << srcElems;
1461  projElems.removeAll( "." );
1462 
1463  // resolve ..
1464  int pos;
1465  while (( pos = projElems.indexOf( ".." ) ) > 0 )
1466  {
1467  // remove preceding element and ..
1468  projElems.removeAt( pos - 1 );
1469  projElems.removeAt( pos - 1 );
1470  }
1471 
1472 #if !defined(Q_OS_WIN)
1473  // make path absolute
1474  projElems.prepend( "" );
1475 #endif
1476 
1477  return vsiPrefix + projElems.join( "/" );
1478 }
1479 
1480 // return the absolute or relative path to write it to the project file
1481 QString QgsProject::writePath( QString src, QString relativeBasePath ) const
1482 {
1483  if ( readBoolEntry( "Paths", "/Absolute", false ) || src.isEmpty() )
1484  {
1485  return src;
1486  }
1487 
1488  QFileInfo srcFileInfo( src );
1489  QFileInfo projFileInfo( fileName() );
1490  QString srcPath = srcFileInfo.exists() ? srcFileInfo.canonicalFilePath() : src;
1491  QString projPath = projFileInfo.canonicalFilePath();
1492 
1493  if ( !relativeBasePath.isNull() )
1494  {
1495  projPath = relativeBasePath;
1496  }
1497 
1498  if ( projPath.isEmpty() )
1499  {
1500  return src;
1501  }
1502 
1503  // if this is a VSIFILE, remove the VSI prefix and append to final result
1504  QString vsiPrefix = qgsVsiPrefix( src );
1505  if ( ! vsiPrefix.isEmpty() )
1506  {
1507  srcPath.remove( 0, vsiPrefix.size() );
1508  }
1509 
1510 #if defined( Q_OS_WIN )
1511  const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
1512 
1513  srcPath.replace( "\\", "/" );
1514 
1515  if ( srcPath.startsWith( "//" ) )
1516  {
1517  // keep UNC prefix
1518  srcPath = "\\\\" + srcPath.mid( 2 );
1519  }
1520 
1521  projPath.replace( "\\", "/" );
1522  if ( projPath.startsWith( "//" ) )
1523  {
1524  // keep UNC prefix
1525  projPath = "\\\\" + projPath.mid( 2 );
1526  }
1527 #else
1528  const Qt::CaseSensitivity cs = Qt::CaseSensitive;
1529 #endif
1530 
1531  QStringList projElems = projPath.split( "/", QString::SkipEmptyParts );
1532  QStringList srcElems = srcPath.split( "/", QString::SkipEmptyParts );
1533 
1534  // remove project file element
1535  projElems.removeLast();
1536 
1537  projElems.removeAll( "." );
1538  srcElems.removeAll( "." );
1539 
1540  // remove common part
1541  int n = 0;
1542  while ( srcElems.size() > 0 &&
1543  projElems.size() > 0 &&
1544  srcElems[0].compare( projElems[0], cs ) == 0 )
1545  {
1546  srcElems.removeFirst();
1547  projElems.removeFirst();
1548  n++;
1549  }
1550 
1551  if ( n == 0 )
1552  {
1553  // no common parts; might not even by a file
1554  return src;
1555  }
1556 
1557  if ( projElems.size() > 0 )
1558  {
1559  // go up to the common directory
1560  for ( int i = 0; i < projElems.size(); i++ )
1561  {
1562  srcElems.insert( 0, ".." );
1563  }
1564  }
1565  else
1566  {
1567  // let it start with . nevertheless,
1568  // so relative path always start with either ./ or ../
1569  srcElems.insert( 0, "." );
1570  }
1571 
1572  return vsiPrefix + srcElems.join( "/" );
1573 }
1574 
1575 void QgsProject::setError( QString errorMessage )
1576 {
1577  mErrorMessage = errorMessage;
1578 }
1579 
1581 {
1582  return mErrorMessage;
1583 }
1584 
1586 {
1587  setError( QString() );
1588 }
1589 
1591 {
1592  delete mBadLayerHandler;
1593  mBadLayerHandler = handler;
1594 }
1595 
1597 {
1598  QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
1599  if ( it == mEmbeddedLayers.constEnd() )
1600  {
1601  return QString();
1602  }
1603  return it.value().first;
1604 }
1605 
1606 bool QgsProject::createEmbeddedLayer( const QString &layerId, const QString &projectFilePath, QList<QDomNode> &brokenNodes,
1607  QList< QPair< QgsVectorLayer*, QDomElement > > &vectorLayerList, bool saveFlag )
1608 {
1609  QgsDebugCall;
1610 
1611  static QString prevProjectFilePath;
1612  static QDomDocument projectDocument;
1613 
1614  if ( projectFilePath != prevProjectFilePath )
1615  {
1616  prevProjectFilePath.clear();
1617 
1618  QFile projectFile( projectFilePath );
1619  if ( !projectFile.open( QIODevice::ReadOnly ) )
1620  {
1621  return false;
1622  }
1623 
1624  if ( !projectDocument.setContent( &projectFile ) )
1625  {
1626  return false;
1627  }
1628 
1629  prevProjectFilePath = projectFilePath;
1630  }
1631 
1632  // does project store pathes absolute or relative?
1633  bool useAbsolutePathes = true;
1634 
1635  QDomElement propertiesElem = projectDocument.documentElement().firstChildElement( "properties" );
1636  if ( !propertiesElem.isNull() )
1637  {
1638  QDomElement absElem = propertiesElem.firstChildElement( "Paths" ).firstChildElement( "Absolute" );
1639  if ( !absElem.isNull() )
1640  {
1641  useAbsolutePathes = absElem.text().compare( "true", Qt::CaseInsensitive ) == 0;
1642  }
1643  }
1644 
1645  QDomElement projectLayersElem = projectDocument.documentElement().firstChildElement( "projectlayers" );
1646  if ( projectLayersElem.isNull() )
1647  {
1648  return false;
1649  }
1650 
1651  QDomNodeList mapLayerNodes = projectLayersElem.elementsByTagName( "maplayer" );
1652  for ( int i = 0; i < mapLayerNodes.size(); ++i )
1653  {
1654  // get layer id
1655  QDomElement mapLayerElem = mapLayerNodes.at( i ).toElement();
1656  QString id = mapLayerElem.firstChildElement( "id" ).text();
1657  if ( id == layerId )
1658  {
1659  // layer can be embedded only once
1660  if ( mapLayerElem.attribute( "embedded" ) == "1" )
1661  {
1662  return false;
1663  }
1664 
1665  mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
1666 
1667  // change datasource path from relative to absolute if necessary
1668  if ( !useAbsolutePathes )
1669  {
1670  QDomElement provider = mapLayerElem.firstChildElement( "provider" );
1671  if ( provider.text() == "spatialite" )
1672  {
1673  QDomElement dsElem = mapLayerElem.firstChildElement( "datasource" );
1674 
1675  QgsDataSourceURI uri( dsElem.text() );
1676 
1677  QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + "/" + uri.database() );
1678  if ( absoluteDs.exists() )
1679  {
1680  uri.setDatabase( absoluteDs.absoluteFilePath() );
1681  dsElem.removeChild( dsElem.childNodes().at( 0 ) );
1682  dsElem.appendChild( projectDocument.createTextNode( uri.uri() ) );
1683  }
1684  }
1685  else
1686  {
1687  QDomElement dsElem = mapLayerElem.firstChildElement( "datasource" );
1688  QString debug( QFileInfo( projectFilePath ).absolutePath() + "/" + dsElem.text() );
1689  QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + "/" + dsElem.text() );
1690  if ( absoluteDs.exists() )
1691  {
1692  dsElem.removeChild( dsElem.childNodes().at( 0 ) );
1693  dsElem.appendChild( projectDocument.createTextNode( absoluteDs.absoluteFilePath() ) );
1694  }
1695  }
1696  }
1697 
1698  if ( addLayer( mapLayerElem, brokenNodes, vectorLayerList ) )
1699  {
1700  return true;
1701  }
1702  else
1703  {
1704  mEmbeddedLayers.remove( layerId );
1705  return false;
1706  }
1707  }
1708  }
1709 
1710  return false;
1711 }
1712 
1713 
1714 QgsLayerTreeGroup *QgsProject::createEmbeddedGroup( const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers )
1715 {
1716  // open project file, get layer ids in group, add the layers
1717  QFile projectFile( projectFilePath );
1718  if ( !projectFile.open( QIODevice::ReadOnly ) )
1719  {
1720  return 0;
1721  }
1722 
1723  QDomDocument projectDocument;
1724  if ( !projectDocument.setContent( &projectFile ) )
1725  {
1726  return 0;
1727  }
1728 
1729  // store identify disabled layers of the embedded project
1730  QSet<QString> embeddedIdentifyDisabledLayers;
1731  QDomElement disabledLayersElem = projectDocument.documentElement().firstChildElement( "properties" ).firstChildElement( "Identify" ).firstChildElement( "disabledLayers" );
1732  if ( !disabledLayersElem.isNull() )
1733  {
1734  QDomNodeList valueList = disabledLayersElem.elementsByTagName( "value" );
1735  for ( int i = 0; i < valueList.size(); ++i )
1736  {
1737  embeddedIdentifyDisabledLayers.insert( valueList.at( i ).toElement().text() );
1738  }
1739  }
1740 
1742 
1743  QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( "layer-tree-group" );
1744  if ( !layerTreeElem.isNull() )
1745  {
1746  root->readChildrenFromXML( layerTreeElem );
1747  }
1748  else
1749  {
1750  QgsLayerTreeUtils::readOldLegend( root, projectDocument.documentElement().firstChildElement( "legend" ) );
1751  }
1752 
1753  QgsLayerTreeGroup *group = root->findGroup( groupName );
1754  if ( !group || group->customProperty( "embedded" ).toBool() )
1755  {
1756  // embedded groups cannot be embedded again
1757  delete root;
1758  return 0;
1759  }
1760 
1761  // clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
1762  QgsLayerTreeGroup *newGroup = QgsLayerTree::toGroup( group->clone() );
1763  delete root;
1764  root = 0;
1765 
1766  newGroup->setCustomProperty( "embedded", 1 );
1767  newGroup->setCustomProperty( "embedded_project", projectFilePath );
1768 
1769  // set "embedded" to all children + load embedded layers
1770  mLayerTreeRegistryBridge->setEnabled( false );
1771  initializeEmbeddedSubtree( projectFilePath, newGroup );
1772  mLayerTreeRegistryBridge->setEnabled( true );
1773 
1774  // consider the layers might be identify disabled in its project
1775  foreach ( QString layerId, newGroup->findLayerIds() )
1776  {
1777  if ( embeddedIdentifyDisabledLayers.contains( layerId ) )
1778  {
1779  QStringList thisProjectIdentifyDisabledLayers = QgsProject::instance()->readListEntry( "Identify", "/disabledLayers" );
1780  thisProjectIdentifyDisabledLayers.append( layerId );
1781  QgsProject::instance()->writeEntry( "Identify", "/disabledLayers", thisProjectIdentifyDisabledLayers );
1782  }
1783 
1784  QgsLayerTreeLayer *layer = newGroup->findLayer( layerId );
1785  if ( layer )
1786  {
1787  layer->setVisible( invisibleLayers.contains( layerId ) ? Qt::Unchecked : Qt::Checked );
1788  }
1789  }
1790 
1791  return newGroup;
1792 }
1793 
1795 {
1796  foreach ( QgsLayerTreeNode *child, group->children() )
1797  {
1798  // all nodes in the subtree will have "embedded" custom property set
1799  child->setCustomProperty( "embedded", 1 );
1800 
1801  if ( QgsLayerTree::isGroup( child ) )
1802  {
1803  initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ) );
1804  }
1805  else if ( QgsLayerTree::isLayer( child ) )
1806  {
1807  // load the layer into our project
1808  QList<QDomNode> brokenNodes;
1810  createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, vectorLayerList, false );
1811  }
1812  }
1813 }
1814 
1815 void QgsProject::setSnapSettingsForLayer( const QString &layerId, bool enabled, QgsSnapper::SnappingType type, QgsTolerance::UnitType unit, double tolerance, bool avoidIntersection )
1816 {
1817  QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList;
1818  snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList );
1819  int idx = layerIdList.indexOf( layerId );
1820  if ( idx != -1 )
1821  {
1822  layerIdList.removeAt( idx );
1823  enabledList.removeAt( idx );
1824  snapTypeList.removeAt( idx );
1825  toleranceUnitList.removeAt( idx );
1826  toleranceList.removeAt( idx );
1827  avoidIntersectionList.removeOne( layerId );
1828  }
1829 
1830  layerIdList.append( layerId );
1831 
1832  // enabled
1833  enabledList.append( enabled ? "enabled" : "disabled" );
1834 
1835  // snap type
1836  QString typeString;
1837  if ( type == QgsSnapper::SnapToSegment )
1838  {
1839  typeString = "to_segment";
1840  }
1841  else if ( type == QgsSnapper::SnapToVertexAndSegment )
1842  {
1843  typeString = "to_vertex_and_segment";
1844  }
1845  else
1846  {
1847  typeString = "to_vertex";
1848  }
1849  snapTypeList.append( typeString );
1850 
1851  // units
1852  toleranceUnitList.append( QString::number( unit ) );
1853 
1854  // tolerance
1855  toleranceList.append( QString::number( tolerance ) );
1856 
1857  // avoid intersection
1858  if ( avoidIntersection )
1859  {
1860  avoidIntersectionList.append( layerId );
1861  }
1862 
1863  writeEntry( "Digitizing", "/LayerSnappingList", layerIdList );
1864  writeEntry( "Digitizing", "/LayerSnappingEnabledList", enabledList );
1865  writeEntry( "Digitizing", "/LayerSnappingToleranceList", toleranceList );
1866  writeEntry( "Digitizing", "/LayerSnappingToleranceUnitList", toleranceUnitList );
1867  writeEntry( "Digitizing", "/LayerSnapToList", snapTypeList );
1868  writeEntry( "Digitizing", "/AvoidIntersectionsList", avoidIntersectionList );
1869  emit snapSettingsChanged();
1870 }
1871 
1872 bool QgsProject::snapSettingsForLayer( const QString &layerId, bool &enabled, QgsSnapper::SnappingType &type, QgsTolerance::UnitType &units, double &tolerance,
1873  bool &avoidIntersection ) const
1874 {
1875  QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList;
1876  snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList );
1877  int idx = layerIdList.indexOf( layerId );
1878  if ( idx == -1 )
1879  {
1880  return false;
1881  }
1882 
1883  // make sure all lists are long enough
1884  int minListEntries = idx + 1;
1885  if ( layerIdList.size() < minListEntries || enabledList.size() < minListEntries || snapTypeList.size() < minListEntries ||
1886  toleranceUnitList.size() < minListEntries || toleranceList.size() < minListEntries )
1887  {
1888  return false;
1889  }
1890 
1891  // enabled
1892  enabled = enabledList.at( idx ) == "enabled";
1893 
1894  // snap type
1895  QString snapType = snapTypeList.at( idx );
1896  if ( snapType == "to_segment" )
1897  {
1899  }
1900  else if ( snapType == "to_vertex_and_segment" )
1901  {
1903  }
1904  else // to vertex
1905  {
1906  type = QgsSnapper::SnapToVertex;
1907  }
1908 
1909  // units
1910  if ( toleranceUnitList.at( idx ) == "1" )
1911  {
1912  units = QgsTolerance::Pixels;
1913  }
1914  else if ( toleranceUnitList.at( idx ) == "2" )
1915  {
1917  }
1918  else
1919  {
1920  units = QgsTolerance::LayerUnits;
1921  }
1922 
1923  // tolerance
1924  tolerance = toleranceList.at( idx ).toDouble();
1925 
1926  // avoid intersection
1927  avoidIntersection = ( avoidIntersectionList.indexOf( layerId ) != -1 );
1928 
1929  return true;
1930 }
1931 
1932 void QgsProject::snapSettings( QStringList &layerIdList, QStringList &enabledList, QStringList &snapTypeList, QStringList &toleranceUnitList, QStringList &toleranceList,
1933  QStringList &avoidIntersectionList ) const
1934 {
1935  layerIdList = readListEntry( "Digitizing", "/LayerSnappingList" );
1936  enabledList = readListEntry( "Digitizing", "/LayerSnappingEnabledList" );
1937  toleranceList = readListEntry( "Digitizing", "/LayerSnappingToleranceList" );
1938  toleranceUnitList = readListEntry( "Digitizing", "/LayerSnappingToleranceUnitList" );
1939  snapTypeList = readListEntry( "Digitizing", "/LayerSnapToList" );
1940  avoidIntersectionList = readListEntry( "Digitizing", "/AvoidIntersectionsList" );
1941 }
1942 
1944 {
1945  QgsProject::instance()->writeEntry( "Digitizing", "/TopologicalEditing", ( enabled ? 1 : 0 ) );
1946  emit snapSettingsChanged();
1947 }
1948 
1950 {
1951  return ( QgsProject::instance()->readNumEntry( "Digitizing", "/TopologicalEditing", 0 ) > 0 );
1952 }
1953 
1955 {
1956  // just ignore any bad layers
1957 }
1958 
1960 {
1961  QFileInfo pfi( fileName() );
1962  if ( !pfi.exists() )
1963  return QString::null;
1964 
1965  return pfi.canonicalPath();
1966 }
1967 
1969 {
1970  return mRelationManager;
1971 }
1972 
1974 {
1975  return mRootGroup;
1976 }
1977 
1979 {
1980  return mVisibilityPresetCollection.data();
1981 }
virtual void handleBadLayers(QList< QDomNode > layers, QDomDocument projectDom) override
QObject * child(const char *objName, const char *inheritsClass, bool recursiveSearch) const
static const char * QGIS_VERSION
Definition: qgis.h:40
bool canConvert(Type t) const
Layer tree group node serves as a container for layers and further groups.
bool topologicalEditing() const
Convenience function to query topological editing status.
static QgsProperty * findKey_(QString const &scope, QString const &key, QgsPropertyKey &rootProperty)
return the property that matches the given key sequence, if any
Definition: qgsproject.cpp:84
QDomNodeList elementsByTagName(const QString &tagname) const
Base class for all map layer types.
Definition: qgsmaplayer.h:49
static void removeInvalidLayers(QgsLayerTreeGroup *group)
Remove layer nodes that refer to invalid layers.
const QList< QgsVectorJoinInfo > vectorJoins() const
iterator insert(const Key &key, const T &value)
QDomNode item(int index) const
QString writePath(QString filename, QString relativeBasePath=QString::null) const
Prepare a filename to save it to the project file.
void readChildrenFromXML(QDomElement &element)
Read children from XML and append them to the group.
QgsPropertyKey properties_
Definition: qgsproject.cpp:296
QgsPropertyKey * addKey(const QString &keyName)
add the given property key
void setTopologicalEditing(bool enabled)
Convenience function to set topological editing.
qint64 pos() const
QDomNode appendChild(const QDomNode &newChild)
bool writeLayerXML(QDomElement &layerElement, QDomDocument &document, QString relativeBasePath=QString::null)
Stores state in Dom node.
bool snapSettingsForLayer(const QString &layerId, bool &enabled, QgsSnapper::SnappingType &type, QgsTolerance::UnitType &units, double &tolerance, bool &avoidIntersection) const
Convenience function to query snap settings of a layer.
void push_back(const T &value)
void entryList(QStringList &entries) const
return keys that do not contain other keys
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
QString attribute(const QString &name, const QString &defValue) const
static void _getProperties(QDomDocument const &doc, QgsPropertyKey &project_properties)
Restore any optional properties found in "doc" to "properties".
Definition: qgsproject.cpp:471
QgsPropertyValue * setValue(const QString &name, const QVariant &value)
Set the value associated with this key.
bool remove()
QString data() const
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
void removeAllChildren()
Remove all child nodes. The nodes will be deleted.
Pixels unit of tolerance.
Definition: qgstolerance.h:40
virtual QgsLayerTreeNode * clone() const =0
Create a copy of the node. Returns new instance.
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
static QgsProperty * addKey_(QString const &scope, QString const &key, QgsPropertyKey *rootProperty, QVariant value)
Add the given key and value.
Definition: qgsproject.cpp:160
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:124
void oldProjectVersionWarning(QString)
emitted when an old project file is read.
void removeFirst()
const_iterator constBegin() const
const T & at(int i) const
bool rename(const QString &newName)
int size() const
void removeAt(int i)
static bool readOldLegend(QgsLayerTreeGroup *root, const QDomElement &legendElem)
Try to load layer tree from.
bool contains(const QString &str, Qt::CaseSensitivity cs) const
void setFileName(const QString &name)
Every project has an associated file that contains its XML.
Definition: qgsproject.cpp:393
void setFileName(const QString &name)
QString qgsVsiPrefix(QString path)
Definition: qgis.cpp:269
void push_front(const T &value)
T value() const
Map (project) units.
Definition: qgstolerance.h:42
QString layerIsEmbedded(const QString &id) const
Returns project file path if layer is embedded from other project file.
void setSnapSettingsForLayer(const QString &layerId, bool enabled, QgsSnapper::SnappingType type, QgsTolerance::UnitType unit, double tolerance, bool avoidIntersection)
Convenience function to set snap settings per layer.
QDomElement documentElement() const
QString join(const QString &separator) const
QString homePath() const
Return project's home path.
void clear()
Clear project properties when a new project is started.
Definition: qgsproject.cpp:311
bool exists() const
const_iterator insert(const T &value)
QString & remove(int position, int n)
static void _getTitle(QDomDocument const &doc, QString &title)
Get the project title.
Definition: qgsproject.cpp:516
bool readBoolEntry(const QString &scope, const QString &key, bool def=false, bool *ok=0) const
void insertChildNodes(int index, QList< QgsLayerTreeNode * > nodes)
Insert existing nodes at specified position. The nodes must not have a parent yet. The nodes will be owned by this group.
void setDirty(bool b)
Set project as dirty (modified).
Definition: qgsproject.cpp:386
void projectSaved()
emitted when the project file has been written and closed
QDomNodeList childNodes() const
void loadEmbeddedNodes(QgsLayerTreeGroup *group)
Definition: qgsproject.cpp:915
QString tr(const char *sourceText, const char *disambiguation, int n)
QString readPath(QString filename) const
Turn filename read from the project file to an absolute path.
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=0) const
QgsPluginLayer * createLayer(QString typeName, const QString &uri=QString())
Return new layer if corresponding plugin has been found, else return NULL.
virtual void writeXML(QDomElement &parentElement)=0
Write layer tree to XML.
void clear()
Remove any relation managed by this class.
QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group. No type checking is done - use isGroup() to find out whether this operation is ...
Definition: qgslayertree.h:46
int size() const
bool isNull() const
void reset(T *other)
void clear()
QString filePath() const
QDomElement toElement() const
SnappingType
Snap to vertex, to segment or both.
Definition: qgssnapper.h:66
const char * name() const
bool writeEntry(const QString &scope, const QString &key, bool value)
QString canonicalFilePath() const
QStringList readListEntry(const QString &scope, const QString &key, QStringList def=QStringList(), bool *ok=0) const
Key value accessors.
bool isText() const
int count() const
QString number(int n, int base)
int count(const T &value) const
bool createEmbeddedLayer(const QString &layerId, const QString &projectFilePath, QList< QDomNode > &brokenNodes, QList< QPair< QgsVectorLayer *, QDomElement > > &vectorLayerList, bool saveFlag=true)
Creates a maplayer instance defined in an arbitrary project file.
bool read()
presuming that the caller has already reset the map canvas, map registry, and legend ...
Definition: qgsproject.cpp:772
static QgsProjectVersion _getVersion(QDomDocument const &doc)
Return the version string found in the given Dom document.
Definition: qgsproject.cpp:555
void append(const T &value)
void removeKey(const QString &keyName)
remove the given key
QStringList subkeyList(const QString &scope, const QString &key) const
Return keys with keys – do not return keys that contain only values.
void initializeEmbeddedSubtree(const QString &projectFilePath, QgsLayerTreeGroup *group)
QVariant property(const char *name) const
const_iterator constEnd() const
QString canonicalPath() const
void pop_front()
QString text() const
int toInt(bool *ok) const
QList< QgsMapLayer * > addMapLayers(QList< QgsMapLayer * > theMapLayers, bool addToLegend=true, bool takeOwnership=true)
Add a list of layers to the map of loaded layers.
virtual void clearKeys()
delete any sub-nodes
void setAttribute(const QString &name, const QString &value)
A class to describe the version of a project.
bool isEmpty() const
QDomNodeList elementsByTagName(const QString &tagname) const
void setBadLayerHandler(QgsProjectBadLayerHandler *handler)
Change handler for missing layers.
bool isEmpty() const
int removeAll(const T &value)
void readProject(const QDomDocument &)
emitted when project is being read
Listens to the updates in map layer registry and does changes in layer tree.
QgsPropertyKey node.
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
static void replaceChildrenOfEmbeddedGroups(QgsLayerTreeGroup *group)
Remove subtree of embedded groups and replaces it with a custom property embedded-visible-layers.
void layerLoaded(int i, int n)
emitted when a layer from a projects was read
QString fileName() const
Returns file name.
Definition: qgsproject.cpp:402
This class is a base class for nodes in a layer tree.
QString id() const
Get this layer's unique ID, this ID is used to access this layer from map layer registry.
Definition: qgsmaplayer.cpp:99
bool removeEntry(const QString &scope, const QString &key)
Remove the given key.
static void updateEmbeddedGroupsProjectPath(QgsLayerTreeGroup *group)
Reads and writes project states.
Definition: qgsproject.h:69
void setVisible(Qt::CheckState visible)
virtual QgsLayerTreeNode * clone() const override
Return a clone of the group. The children are cloned too.
T & front()
T & first()
void writeMapLayer(QgsMapLayer *mapLayer, QDomElement &layerElem, QDomDocument &doc)
Emitted, when a layer is being saved.
QString error() const
Return error message from previous read/write.
QString name() const
Get group's name.
iterator end()
int remove(const Key &key)
QList< QgsLayerTreeNode * > children()
Get list of children of the node. Children are owned by the parent.
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
bool isLayer(QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:40
bool isValid()
Return the status of the layer.
An Abstract Base Class for QGIS project property hierarchies.
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
bool hasChildNodes() const
double readDoubleEntry(const QString &scope, const QString &key, double def=0, bool *ok=0) const
QgsProperty * find(QString &propertyName)
bool write()
Definition: qgsproject.cpp:975
QDomText createTextNode(const QString &value)
#define QgsDebugCall
Definition: qgslogger.h:32
T * data() const
void clear()
iterator end()
const T value(const Key &key) const
bool exists() const
iterator find(const Key &key)
Class for storing the component parts of a PostgreSQL/RDBMS datasource URI.
void subkeyList(QStringList &entries) const
return keys that contain other keys
QDomNode removeChild(const QDomNode &oldChild)
QDomNode namedItem(const QString &name) const
QgsLayerTreeGroup * createEmbeddedGroup(const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers)
Create layer group instance defined in an arbitrary project file.
void clearError()
Clear error message.
void setError(QString errorMessage)
Set error message from read/write operation.
bool isDirty() const
the dirty flag is true if the project has been modified since the last write()
Definition: qgsproject.cpp:375
bool contains(const T &value) const
bool isNull() const
QString & replace(int position, int n, QChar after)
bool addLayer(const QDomElement &layerElem, QList< QDomNode > &brokenNodes, QList< QPair< QgsVectorLayer *, QDomElement > > &vectorLayerList)
Definition: qgsproject.cpp:701
void setInvalidDataPolicy(InvalidDataPolicy policy)
void writeProject(QDomDocument &)
emitted when project is being written
virtual QString dump() const override
Return text representation of the tree. For debugging purposes only.
QDomNode firstChild() const
QString readEntry(const QString &scope, const QString &key, const QString &def=QString::null, bool *ok=0) const
static QgsMapLayerRegistry * instance()
Returns the instance pointer, creating the object on the first call.
QString mid(int position, int n) const
QStringList toStringList() const
Layer unit value.
Definition: qgstolerance.h:38
QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer. No type checking is done - use isLayer() to find out whether this operation is ...
Definition: qgslayertree.h:52
void insert(int i, const T &value)
This class manages a set of relations between layers.
QStringList findLayerIds() const
Find layer IDs used in all layer nodes. Searches recursively the whole sub-tree.
QgsLayerTreeGroup * findGroup(const QString &name)
Find group node with specified name. Searches recursively the whole sub-tree.
static QgsProject * instance()
access to canonical QgsProject instance
Definition: qgsproject.cpp:352
QDomElement firstChildElement(const QString &tagName) const
static void removeKey_(QString const &scope, QString const &key, QgsPropertyKey &rootProperty)
Definition: qgsproject.cpp:231
void setTitle(const QString &title)
Set project title.
Definition: qgsproject.cpp:361
StandardButton critical(QWidget *parent, const QString &title, const QString &text, QFlags< QMessageBox::StandardButton > buttons, StandardButton defaultButton)
void loadingLayer(QString)
void removeLast()
const QMap< QString, QgsMapLayer * > & mapLayers()
Retrieve the mapLayers collection (mainly intended for use by projection)
bool isWritable() const
bool toBool() const
bool readXML(QDomNode &keyNode) override
restores property hierarchy to given Dom node
QStringList entryList(const QString &scope, const QString &key) const
Return keys with values – do not return keys that contain other keys.
QgsLayerTreeLayer * findLayer(const QString &layerId) const
Find layer node representing the map layer specified by its ID. Searches recursively the whole sub-tr...
bool readLayerXML(const QDomElement &layerElement)
Sets state from Dom document.
Container class that allows storage of visibility presets consisting of visible map layers and layer ...
int indexOf(const QRegExp &rx, int from) const
void prepend(const T &value)
double toDouble(bool *ok) const
const QString & title() const
Returns title.
Definition: qgsproject.cpp:369
void dumpProperties() const
Dump out current project properties to stderr.
void clearProperties()
removes all project properties
static QgsPluginLayerRegistry * instance()
Means of accessing canonical single instance.
static QStringList makeKeyTokens_(QString const &scope, QString const &key)
Take the given scope and key and convert them to a string list of key tokens that will be used to nav...
Definition: qgsproject.cpp:60
int size() const
const QString & name() const
every key has a name
void readMapLayer(QgsMapLayer *mapLayer, const QDomElement &layerNode)
Emitted, after the basic initialisation of a layer from the project file is done. ...
UnitType
Type of unit of tolerance value from settings.
Definition: qgstolerance.h:33
QFileInfo fileInfo() const
Returns QFileInfo object for the project's associated file.
Definition: qgsproject.cpp:407
QgsLayerTreeGroup * layerTreeRoot() const
Return pointer to the root (invisible) node of the project's layer tree.
QDomText toText() const
Type type() const
Default bad layer handler which ignores any missing layers.
Definition: qgsproject.h:424
virtual bool isKey() const =0
Returns true if is a QgsPropertyKey.
QDomDocumentType createDocumentType(const QString &qName, const QString &publicId, const QString &systemId)
QgsVisibilityPresetCollection * visibilityPresetCollection()
Returns pointer to the project's visibility preset collection.
Represents a vector layer which manages a vector based data sets.
int compare(const QString &other) const
QgsRelationManager * relationManager() const
bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
Definition: qgslayertree.h:34
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() const
bool removeOne(const T &value)
virtual bool isValue() const =0
Returns true if is a QgsPropertyValue.
static void dump_(QgsPropertyKey const &topQgsPropertyKey)
Definition: qgsproject.cpp:432
iterator begin()
int size() const
void clear()
Clear the project.
Definition: qgsproject.cpp:412
Interface for classes that handle missing layer files when reading project file.
Definition: qgsproject.h:415
Layer tree node points to a map layer.
void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for the node.
QDomNode at(int index) const
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
void dump(int tabs=0) const override
Dumps out the keys and values.
const T value(const Key &key) const
void snapSettingsChanged()
virtual void handleBadLayers(QList< QDomNode > layers, QDomDocument projectDom)=0
void dirty(bool b)
Definition: qgsproject.cpp:381