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