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