QGIS API Documentation  2.7.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules 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  // 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() );
325  properties_.clearKeys();
326  title.clear();
327  dirty = false;
328  }
329 
330 }; // struct QgsProject::Imp
331 
332 
333 
334 QgsProject::QgsProject()
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
345  mLayerTreeRegistryBridge = new QgsLayerTreeRegistryBridge( mRootGroup, this );
346 
347 } // QgsProject ctor
348 
349 
350 
352 {
353  delete mBadLayerHandler;
354  delete mRelationManager;
355  delete mRootGroup;
356 
357  // note that QScopedPointer 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();
426  mRelationManager->clear();
427 
428  mRootGroup->removeAllChildren();
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  QScopedPointer<QDomDocument> doc( new QDomDocument( "qgis" ) );
822 
823  if ( !imp_->file.open( QIODevice::ReadOnly | QIODevice::Text ) )
824  {
825  imp_->file.close();
826 
827  setError( tr( "Unable to open %1" ).arg( imp_->file.fileName() ) );
828 
829  return false;
830  }
831 
832  // location of problem associated with errorMsg
833  int line, column;
834  QString errorMsg;
835 
836  if ( !doc->setContent( &imp_->file, &errorMsg, &line, &column ) )
837  {
838  // want to make this class as GUI independent as possible; so commented out
839 #if 0
840  QMessageBox::critical( 0, tr( "Project File Read Error" ),
841  tr( "%1 at line %2 column %3" ).arg( errorMsg ).arg( line ).arg( column ) );
842 #endif
843 
844  QString errorString = tr( "Project file read error: %1 at line %2 column %3" )
845  .arg( errorMsg ).arg( line ).arg( column );
846 
847  QgsDebugMsg( errorString );
848 
849  imp_->file.close();
850 
851  setError( tr( "%1 for file %2" ).arg( errorString ).arg( imp_->file.fileName() ) );
852 
853  return false;
854  }
855 
856  imp_->file.close();
857 
858 
859  QgsDebugMsg( "Opened document " + imp_->file.fileName() );
860  QgsDebugMsg( "Project title: " + imp_->title );
861 
862  // get project version string, if any
863  QgsProjectVersion fileVersion = _getVersion( *doc );
864  QgsProjectVersion thisVersion( QGis::QGIS_VERSION );
865 
866  if ( thisVersion > fileVersion )
867  {
868  QgsLogger::warning( "Loading a file that was saved with an older "
869  "version of qgis (saved in " + fileVersion.text() +
870  ", loaded in " + QGis::QGIS_VERSION +
871  "). Problems may occur." );
872 
873  QgsProjectFileTransform projectFile( *doc, fileVersion );
874 
876  emit oldProjectVersionWarning( fileVersion.text() );
877  QgsDebugMsg( "Emitting oldProjectVersionWarning(oldVersion)." );
878 
879  // Commented out by Tim - overly verbose on console
880  // projectFile.dump();
881 
882  projectFile.updateRevision( thisVersion );
883 
884  // Commented out by Tim - overly verbose on console
885  // projectFile.dump();
886 
887  }
888 
889  // start new project, just keep the file name
890 
891  QString fileName = imp_->file.fileName();
892  clear();
893  imp_->file.setFileName( fileName );
894 
895  // now get any properties
896  _getProperties( *doc, imp_->properties_ );
897 
898  QgsDebugMsg( QString::number( imp_->properties_.count() ) + " properties read" );
899 
900  dump_( imp_->properties_ );
901 
902  // now get project title
903  _getTitle( *doc, imp_->title );
904 
905  // read the layer tree from project file
906 
907  mRootGroup->setCustomProperty( "loading", 1 );
908 
909  QDomElement layerTreeElem = doc->documentElement().firstChildElement( "layer-tree-group" );
910  if ( !layerTreeElem.isNull() )
911  {
912  mRootGroup->readChildrenFromXML( layerTreeElem );
913  }
914  else
915  {
916  QgsLayerTreeUtils::readOldLegend( mRootGroup, doc->documentElement().firstChildElement( "legend" ) );
917  }
918 
919  QgsDebugMsg( "Loaded layer tree:\n " + mRootGroup->dump() );
920 
921  mLayerTreeRegistryBridge->setEnabled( false );
922 
923  // get the map layers
924  QPair< bool, QList<QDomNode> > getMapLayersResults = _getMapLayers( *doc );
925 
926  // review the integrity of the retrieved map layers
927  bool clean = getMapLayersResults.first;
928 
929  if ( !clean )
930  {
931  QgsDebugMsg( "Unable to get map layers from project file." );
932 
933  if ( ! getMapLayersResults.second.isEmpty() )
934  {
935  QgsDebugMsg( "there are " + QString::number( getMapLayersResults.second.size() ) + " broken layers" );
936  }
937 
938  // we let a custom handler to decide what to do with missing layers
939  // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
940  mBadLayerHandler->handleBadLayers( getMapLayersResults.second, *doc );
941  }
942 
943  mLayerTreeRegistryBridge->setEnabled( true );
944 
945  // load embedded groups and layers
946  loadEmbeddedNodes( mRootGroup );
947 
948  // make sure the are just valid layers
950 
951  mRootGroup->removeCustomProperty( "loading" );
952 
953  // read the project: used by map canvas and legend
954  emit readProject( *doc );
955 
956  // if all went well, we're allegedly in pristine state
957  if ( clean )
958  dirty( false );
959 
960  return true;
961 
962 } // QgsProject::read
963 
964 
966 {
967  foreach ( QgsLayerTreeNode* child, group->children() )
968  {
969  if ( QgsLayerTree::isGroup( child ) )
970  {
971  QgsLayerTreeGroup* childGroup = QgsLayerTree::toGroup( child );
972  if ( childGroup->customProperty( "embedded" ).toInt() )
973  {
974  // make sure to convert the path from relative to absolute
975  QString projectPath = readPath( childGroup->customProperty( "embedded_project" ).toString() );
976  childGroup->setCustomProperty( "embedded_project", projectPath );
977 
978  QgsLayerTreeGroup* newGroup = createEmbeddedGroup( childGroup->name(), projectPath, childGroup->customProperty( "embedded-invisible-layers" ).toStringList() );
979  if ( newGroup )
980  {
981  QList<QgsLayerTreeNode*> clonedChildren;
982  foreach ( QgsLayerTreeNode* newGroupChild, newGroup->children() )
983  clonedChildren << newGroupChild->clone();
984  delete newGroup;
985 
986  childGroup->insertChildNodes( 0, clonedChildren );
987  }
988  }
989  else
990  {
991  loadEmbeddedNodes( childGroup );
992  }
993  }
994  else if ( QgsLayerTree::isLayer( child ) )
995  {
996  if ( child->customProperty( "embedded" ).toInt() )
997  {
998  QList<QDomNode> brokenNodes;
999  QList< QPair< QgsVectorLayer*, QDomElement > > vectorLayerList;
1000  createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), child->customProperty( "embedded_project" ).toString(), brokenNodes, vectorLayerList );
1001  }
1002  }
1003 
1004  }
1005 }
1006 
1007 
1008 bool QgsProject::read( QDomNode & layerNode )
1009 {
1010  QList<QDomNode> brokenNodes;
1011  QList< QPair< QgsVectorLayer*, QDomElement > > vectorLayerList;
1012  return addLayer( layerNode.toElement(), brokenNodes, vectorLayerList );
1013 } // QgsProject::read( QDomNode & layerNode )
1014 
1015 
1016 
1017 bool QgsProject::write( QFileInfo const &file )
1018 {
1019  imp_->file.setFileName( file.filePath() );
1020 
1021  return write();
1022 } // QgsProject::write( QFileInfo const & file )
1023 
1024 
1026 {
1027  clearError();
1028 
1029  // Create backup file
1030  if ( QFile::exists( fileName() ) )
1031  {
1032  QString backup = fileName() + "~";
1033  if ( QFile::exists( backup ) )
1034  QFile::remove( backup );
1035  QFile::rename( fileName(), backup );
1036  }
1037 
1038  // if we have problems creating or otherwise writing to the project file,
1039  // let's find out up front before we go through all the hand-waving
1040  // necessary to create all the Dom objects
1041  if ( !imp_->file.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1042  {
1043  imp_->file.close(); // even though we got an error, let's make
1044  // sure it's closed anyway
1045 
1046  setError( tr( "Unable to save to file %1" ).arg( imp_->file.fileName() ) );
1047  return false;
1048  }
1049  QFileInfo myFileInfo( imp_->file );
1050  if ( !myFileInfo.isWritable() )
1051  {
1052  // even though we got an error, let's make
1053  // sure it's closed anyway
1054  imp_->file.close();
1055  setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." )
1056  .arg( imp_->file.fileName() ) );
1057  return false;
1058  }
1059 
1060 
1061 
1062  QDomImplementation DomImplementation;
1063  DomImplementation.setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
1064 
1065  QDomDocumentType documentType =
1066  DomImplementation.createDocumentType( "qgis", "http://mrcc.com/qgis.dtd",
1067  "SYSTEM" );
1068  QScopedPointer<QDomDocument> doc( new QDomDocument( documentType ) );
1069 
1070  QDomElement qgisNode = doc->createElement( "qgis" );
1071  qgisNode.setAttribute( "projectname", title() );
1072  qgisNode.setAttribute( "version", QString( "%1" ).arg( QGis::QGIS_VERSION ) );
1073 
1074  doc->appendChild( qgisNode );
1075 
1076  // title
1077  QDomElement titleNode = doc->createElement( "title" );
1078  qgisNode.appendChild( titleNode );
1079 
1080  QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
1081  titleNode.appendChild( titleText );
1082 
1083  // write layer tree - make sure it is without embedded subgroups
1084  QgsLayerTreeNode* clonedRoot = mRootGroup->clone();
1086  QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ) ); // convert absolute paths to relative paths if required
1087  clonedRoot->writeXML( qgisNode );
1088  delete clonedRoot;
1089 
1090  // let map canvas and legend write their information
1091  emit writeProject( *doc );
1092 
1093  // within top level node save list of layers
1094  const QMap<QString, QgsMapLayer*> & layers = QgsMapLayerRegistry::instance()->mapLayers();
1095 
1096  // Iterate over layers in zOrder
1097  // Call writeXML() on each
1098  QDomElement projectLayersNode = doc->createElement( "projectlayers" );
1099  projectLayersNode.setAttribute( "layercount", qulonglong( layers.size() ) );
1100 
1101  QMap<QString, QgsMapLayer*>::ConstIterator li = layers.constBegin();
1102  while ( li != layers.end() )
1103  {
1104  //QgsMapLayer *ml = QgsMapLayerRegistry::instance()->mapLayer(*li);
1105  QgsMapLayer* ml = li.value();
1106 
1107  if ( ml )
1108  {
1109  QString externalProjectFile = layerIsEmbedded( ml->id() );
1110  QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.find( ml->id() );
1111  if ( emIt == mEmbeddedLayers.constEnd() )
1112  {
1113  // general layer metadata
1114  QDomElement maplayerElem = doc->createElement( "maplayer" );
1115 
1116  ml->writeLayerXML( maplayerElem, *doc );
1117 
1118  emit writeMapLayer( ml, maplayerElem, *doc );
1119 
1120  projectLayersNode.appendChild( maplayerElem );
1121  }
1122  else //layer defined in an external project file
1123  {
1124  //only save embedded layer if not managed by a legend group
1125  if ( emIt.value().second )
1126  {
1127  QDomElement mapLayerElem = doc->createElement( "maplayer" );
1128  mapLayerElem.setAttribute( "embedded", 1 );
1129  mapLayerElem.setAttribute( "project", writePath( emIt.value().first ) );
1130  mapLayerElem.setAttribute( "id", ml->id() );
1131  projectLayersNode.appendChild( mapLayerElem );
1132  }
1133  }
1134  }
1135  li++;
1136  }
1137 
1138  qgisNode.appendChild( projectLayersNode );
1139 
1140  // now add the optional extra properties
1141 
1142  dump_( imp_->properties_ );
1143 
1144  QgsDebugMsg( QString( "there are %1 property scopes" ).arg( static_cast<int>( imp_->properties_.count() ) ) );
1145 
1146  if ( !imp_->properties_.isEmpty() ) // only worry about properties if we
1147  // actually have any properties
1148  {
1149  imp_->properties_.writeXML( "properties", qgisNode, *doc );
1150  }
1151 
1152  // now wrap it up and ship it to the project file
1153  doc->normalize(); // XXX I'm not entirely sure what this does
1154 
1155  //QString xml = doc->toString(4); // write to string with indentation of four characters
1156  // (yes, four is arbitrary)
1157 
1158  // const char * xmlString = xml; // debugger probe point
1159  // qDebug( "project file output:\n\n" + xml );
1160 
1161  QTextStream projectFileStream( &imp_->file );
1162 
1163  //projectFileStream << xml << endl;
1164  doc->save( projectFileStream, 2 ); // save as utf-8
1165  imp_->file.close();
1166 
1167  // check if the text stream had no error - if it does
1168  // the user will get a message so they can try to resolve the
1169  // situation e.g. by saving project to a volume with more space
1170  //
1171  if ( projectFileStream.pos() == -1 || imp_->file.error() != QFile::NoError )
1172  {
1173  setError( tr( "Unable to save to file %1. Your project "
1174  "may be corrupted on disk. Try clearing some space on the volume and "
1175  "check file permissions before pressing save again." )
1176  .arg( imp_->file.fileName() ) );
1177  return false;
1178  }
1179 
1180  dirty( false ); // reset to pristine state
1181 
1182  emit projectSaved();
1183 
1184  return true;
1185 } // QgsProject::write
1186 
1187 
1188 
1190 {
1191  clear();
1192 
1193  dirty( true );
1194 } // QgsProject::clearProperties()
1195 
1196 
1197 
1198 bool
1199 QgsProject::writeEntry( QString const &scope, const QString & key, bool value )
1200 {
1201  dirty( true );
1202 
1203  return addKey_( scope, key, &imp_->properties_, value );
1204 } // QgsProject::writeEntry ( ..., bool value )
1205 
1206 
1207 bool
1208 QgsProject::writeEntry( QString const &scope, const QString & key,
1209  double value )
1210 {
1211  dirty( true );
1212 
1213  return addKey_( scope, key, &imp_->properties_, value );
1214 } // QgsProject::writeEntry ( ..., double value )
1215 
1216 
1217 bool
1218 QgsProject::writeEntry( QString const &scope, const QString & key, int value )
1219 {
1220  dirty( true );
1221 
1222  return addKey_( scope, key, &imp_->properties_, value );
1223 } // QgsProject::writeEntry ( ..., int value )
1224 
1225 
1226 bool
1227 QgsProject::writeEntry( QString const &scope, const QString & key,
1228  const QString & value )
1229 {
1230  dirty( true );
1231 
1232  return addKey_( scope, key, &imp_->properties_, value );
1233 } // QgsProject::writeEntry ( ..., const QString & value )
1234 
1235 
1236 bool
1237 QgsProject::writeEntry( QString const &scope, const QString & key,
1238  const QStringList & value )
1239 {
1240  dirty( true );
1241 
1242  return addKey_( scope, key, &imp_->properties_, value );
1243 } // QgsProject::writeEntry ( ..., const QStringList & value )
1244 
1245 
1246 
1247 
1248 QStringList
1249 QgsProject::readListEntry( QString const & scope,
1250  const QString & key,
1251  QStringList def,
1252  bool * ok ) const
1253 {
1254  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1255 
1256  QVariant value;
1257 
1258  if ( property )
1259  {
1260  value = property->value();
1261  }
1262 
1263  bool valid = QVariant::StringList == value.type();
1264 
1265  if ( ok )
1266  {
1267  *ok = valid;
1268  }
1269 
1270  if ( valid )
1271  {
1272  return value.toStringList();
1273  }
1274 
1275  return def;
1276 } // QgsProject::readListEntry
1277 
1278 
1279 QString
1280 QgsProject::readEntry( QString const & scope,
1281  const QString & key,
1282  const QString & def,
1283  bool * ok ) const
1284 {
1285  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1286 
1287  QVariant value;
1288 
1289  if ( property )
1290  {
1291  value = property->value();
1292  }
1293 
1294  bool valid = value.canConvert( QVariant::String );
1295 
1296  if ( ok )
1297  {
1298  *ok = valid;
1299  }
1300 
1301  if ( valid )
1302  {
1303  return value.toString();
1304  }
1305 
1306  return QString( def );
1307 } // QgsProject::readEntry
1308 
1309 
1310 int
1311 QgsProject::readNumEntry( QString const &scope, const QString & key, int def,
1312  bool * ok ) const
1313 {
1314  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1315 
1316  QVariant value;
1317 
1318  if ( property )
1319  {
1320  value = property->value();
1321  }
1322 
1323  bool valid = value.canConvert( QVariant::String );
1324 
1325  if ( ok )
1326  {
1327  *ok = valid;
1328  }
1329 
1330  if ( valid )
1331  {
1332  return value.toInt();
1333  }
1334 
1335  return def;
1336 } // QgsProject::readNumEntry
1337 
1338 
1339 double
1340 QgsProject::readDoubleEntry( QString const &scope, const QString & key,
1341  double def,
1342  bool * ok ) const
1343 {
1344  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1345 
1346  QVariant value;
1347 
1348  if ( property )
1349  {
1350  value = property->value();
1351  }
1352 
1353  bool valid = value.canConvert( QVariant::Double );
1354 
1355  if ( ok )
1356  {
1357  *ok = valid;
1358  }
1359 
1360  if ( valid )
1361  {
1362  return value.toDouble();
1363  }
1364 
1365  return def;
1366 } // QgsProject::readDoubleEntry
1367 
1368 
1369 bool
1370 QgsProject::readBoolEntry( QString const &scope, const QString & key, bool def,
1371  bool * ok ) const
1372 {
1373  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1374 
1375  QVariant value;
1376 
1377  if ( property )
1378  {
1379  value = property->value();
1380  }
1381 
1382  bool valid = value.canConvert( QVariant::Bool );
1383 
1384  if ( ok )
1385  {
1386  *ok = valid;
1387  }
1388 
1389  if ( valid )
1390  {
1391  return value.toBool();
1392  }
1393 
1394  return def;
1395 } // QgsProject::readBoolEntry
1396 
1397 
1398 bool QgsProject::removeEntry( QString const &scope, const QString & key )
1399 {
1400  removeKey_( scope, key, imp_->properties_ );
1401 
1402  dirty( true );
1403 
1404  return ! findKey_( scope, key, imp_->properties_ );
1405 } // QgsProject::removeEntry
1406 
1407 
1408 
1409 QStringList QgsProject::entryList( QString const &scope, QString const &key ) const
1410 {
1411  QgsProperty * foundProperty = findKey_( scope, key, imp_->properties_ );
1412 
1413  QStringList entries;
1414 
1415  if ( foundProperty )
1416  {
1417  QgsPropertyKey * propertyKey = dynamic_cast<QgsPropertyKey*>( foundProperty );
1418 
1419  if ( propertyKey )
1420  { propertyKey->entryList( entries ); }
1421  }
1422 
1423  return entries;
1424 } // QgsProject::entryList
1425 
1426 
1427 QStringList QgsProject::subkeyList( QString const &scope, QString const &key ) const
1428 {
1429  QgsProperty * foundProperty = findKey_( scope, key, imp_->properties_ );
1430 
1431  QStringList entries;
1432 
1433  if ( foundProperty )
1434  {
1435  QgsPropertyKey * propertyKey = dynamic_cast<QgsPropertyKey*>( foundProperty );
1436 
1437  if ( propertyKey )
1438  { propertyKey->subkeyList( entries ); }
1439  }
1440 
1441  return entries;
1442 
1443 } // QgsProject::subkeyList
1444 
1445 
1446 
1448 {
1449  dump_( imp_->properties_ );
1450 } // QgsProject::dumpProperties
1451 
1452 
1453 // return the absolute path from a filename read from project file
1454 QString QgsProject::readPath( QString src ) const
1455 {
1456  if ( readBoolEntry( "Paths", "/Absolute", false ) )
1457  {
1458  return src;
1459  }
1460 
1461  // if this is a VSIFILE, remove the VSI prefix and append to final result
1462  QString vsiPrefix = qgsVsiPrefix( src );
1463  if ( ! vsiPrefix.isEmpty() )
1464  {
1465  // unfortunately qgsVsiPrefix returns prefix also for files like "/x/y/z.gz"
1466  // so we need to check if we really have the prefix
1467  if ( src.startsWith( "/vsi", Qt::CaseInsensitive ) )
1468  src.remove( 0, vsiPrefix.size() );
1469  else
1470  vsiPrefix.clear();
1471  }
1472 
1473  // relative path should always start with ./ or ../
1474  if ( !src.startsWith( "./" ) && !src.startsWith( "../" ) )
1475  {
1476 #if defined(Q_OS_WIN)
1477  if ( src.startsWith( "\\\\" ) ||
1478  src.startsWith( "//" ) ||
1479  ( src[0].isLetter() && src[1] == ':' ) )
1480  {
1481  // UNC or absolute path
1482  return vsiPrefix + src;
1483  }
1484 #else
1485  if ( src[0] == '/' )
1486  {
1487  // absolute path
1488  return vsiPrefix + src;
1489  }
1490 #endif
1491 
1492  // so this one isn't absolute, but also doesn't start // with ./ or ../.
1493  // That means that it was saved with an earlier version of "relative path support",
1494  // where the source file had to exist and only the project directory was stripped
1495  // from the filename.
1496  QString home = homePath();
1497  if ( home.isNull() )
1498  return vsiPrefix + src;
1499 
1500  QFileInfo fi( home + "/" + src );
1501 
1502  if ( !fi.exists() )
1503  {
1504  return vsiPrefix + src;
1505  }
1506  else
1507  {
1508  return vsiPrefix + fi.canonicalFilePath();
1509  }
1510  }
1511 
1512  QString srcPath = src;
1513  QString projPath = fileName();
1514 
1515  if ( projPath.isEmpty() )
1516  {
1517  return vsiPrefix + src;
1518  }
1519 
1520 #if defined(Q_OS_WIN)
1521  srcPath.replace( "\\", "/" );
1522  projPath.replace( "\\", "/" );
1523 
1524  bool uncPath = projPath.startsWith( "//" );
1525 #endif
1526 
1527  QStringList srcElems = srcPath.split( "/", QString::SkipEmptyParts );
1528  QStringList projElems = projPath.split( "/", QString::SkipEmptyParts );
1529 
1530 #if defined(Q_OS_WIN)
1531  if ( uncPath )
1532  {
1533  projElems.insert( 0, "" );
1534  projElems.insert( 0, "" );
1535  }
1536 #endif
1537 
1538  // remove project file element
1539  projElems.removeLast();
1540 
1541  // append source path elements
1542  projElems << srcElems;
1543  projElems.removeAll( "." );
1544 
1545  // resolve ..
1546  int pos;
1547  while (( pos = projElems.indexOf( ".." ) ) > 0 )
1548  {
1549  // remove preceding element and ..
1550  projElems.removeAt( pos - 1 );
1551  projElems.removeAt( pos - 1 );
1552  }
1553 
1554 #if !defined(Q_OS_WIN)
1555  // make path absolute
1556  projElems.prepend( "" );
1557 #endif
1558 
1559  return vsiPrefix + projElems.join( "/" );
1560 }
1561 
1562 // return the absolute or relative path to write it to the project file
1563 QString QgsProject::writePath( QString src, QString relativeBasePath ) const
1564 {
1565  if ( readBoolEntry( "Paths", "/Absolute", false ) || src.isEmpty() )
1566  {
1567  return src;
1568  }
1569 
1570  QFileInfo srcFileInfo( src );//QString srcPath = src;
1571  QFileInfo projFileInfo( fileName() );
1572  QString srcPath = srcFileInfo.canonicalFilePath();
1573  QString projPath = projFileInfo.canonicalFilePath();
1574 
1575  if ( !relativeBasePath.isNull() )
1576  {
1577  projPath = relativeBasePath;
1578  }
1579 
1580  if ( projPath.isEmpty() )
1581  {
1582  return src;
1583  }
1584 
1585  // if this is a VSIFILE, remove the VSI prefix and append to final result
1586  QString vsiPrefix = qgsVsiPrefix( src );
1587  if ( ! vsiPrefix.isEmpty() )
1588  {
1589  srcPath.remove( 0, vsiPrefix.size() );
1590  }
1591 
1592 #if defined( Q_OS_WIN )
1593  const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
1594 
1595  srcPath.replace( "\\", "/" );
1596 
1597  if ( srcPath.startsWith( "//" ) )
1598  {
1599  // keep UNC prefix
1600  srcPath = "\\\\" + srcPath.mid( 2 );
1601  }
1602 
1603  projPath.replace( "\\", "/" );
1604  if ( projPath.startsWith( "//" ) )
1605  {
1606  // keep UNC prefix
1607  projPath = "\\\\" + projPath.mid( 2 );
1608  }
1609 #else
1610  const Qt::CaseSensitivity cs = Qt::CaseSensitive;
1611 #endif
1612 
1613  QStringList projElems = projPath.split( "/", QString::SkipEmptyParts );
1614  QStringList srcElems = srcPath.split( "/", QString::SkipEmptyParts );
1615 
1616  // remove project file element
1617  projElems.removeLast();
1618 
1619  projElems.removeAll( "." );
1620  srcElems.removeAll( "." );
1621 
1622  // remove common part
1623  int n = 0;
1624  while ( srcElems.size() > 0 &&
1625  projElems.size() > 0 &&
1626  srcElems[0].compare( projElems[0], cs ) == 0 )
1627  {
1628  srcElems.removeFirst();
1629  projElems.removeFirst();
1630  n++;
1631  }
1632 
1633  if ( n == 0 )
1634  {
1635  // no common parts; might not even by a file
1636  return src;
1637  }
1638 
1639  if ( projElems.size() > 0 )
1640  {
1641  // go up to the common directory
1642  for ( int i = 0; i < projElems.size(); i++ )
1643  {
1644  srcElems.insert( 0, ".." );
1645  }
1646  }
1647  else
1648  {
1649  // let it start with . nevertheless,
1650  // so relative path always start with either ./ or ../
1651  srcElems.insert( 0, "." );
1652  }
1653 
1654  return vsiPrefix + srcElems.join( "/" );
1655 }
1656 
1657 void QgsProject::setError( QString errorMessage )
1658 {
1659  mErrorMessage = errorMessage;
1660 }
1661 
1662 QString QgsProject::error() const
1663 {
1664  return mErrorMessage;
1665 }
1666 
1668 {
1669  setError( QString() );
1670 }
1671 
1673 {
1674  delete mBadLayerHandler;
1675  mBadLayerHandler = handler;
1676 }
1677 
1678 QString QgsProject::layerIsEmbedded( const QString& id ) const
1679 {
1680  QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
1681  if ( it == mEmbeddedLayers.constEnd() )
1682  {
1683  return QString();
1684  }
1685  return it.value().first;
1686 }
1687 
1688 bool QgsProject::createEmbeddedLayer( const QString& layerId, const QString& projectFilePath, QList<QDomNode>& brokenNodes,
1689  QList< QPair< QgsVectorLayer*, QDomElement > >& vectorLayerList, bool saveFlag )
1690 {
1691  QgsDebugCall;
1692 
1693  static QString prevProjectFilePath;
1694  static QDomDocument projectDocument;
1695 
1696  if ( projectFilePath != prevProjectFilePath )
1697  {
1698  prevProjectFilePath.clear();
1699 
1700  QFile projectFile( projectFilePath );
1701  if ( !projectFile.open( QIODevice::ReadOnly ) )
1702  {
1703  return false;
1704  }
1705 
1706  if ( !projectDocument.setContent( &projectFile ) )
1707  {
1708  return false;
1709  }
1710 
1711  prevProjectFilePath = projectFilePath;
1712  }
1713 
1714  //does project store pathes absolute or relative?
1715  bool useAbsolutePathes = true;
1716 
1717  QDomElement propertiesElem = projectDocument.documentElement().firstChildElement( "properties" );
1718  if ( !propertiesElem.isNull() )
1719  {
1720  QDomElement absElem = propertiesElem.firstChildElement( "Paths" ).firstChildElement( "Absolute" );
1721  if ( !absElem.isNull() )
1722  {
1723  useAbsolutePathes = absElem.text().compare( "true", Qt::CaseInsensitive ) == 0;
1724  }
1725  }
1726 
1727  QDomElement projectLayersElem = projectDocument.documentElement().firstChildElement( "projectlayers" );
1728  if ( projectLayersElem.isNull() )
1729  {
1730  return false;
1731  }
1732 
1733  QDomNodeList mapLayerNodes = projectLayersElem.elementsByTagName( "maplayer" );
1734  for ( int i = 0; i < mapLayerNodes.size(); ++i )
1735  {
1736  //get layer id
1737  QDomElement mapLayerElem = mapLayerNodes.at( i ).toElement();
1738  QString id = mapLayerElem.firstChildElement( "id" ).text();
1739  if ( id == layerId )
1740  {
1741  //layer can be embedded only once
1742  if ( mapLayerElem.attribute( "embedded" ) == "1" )
1743  {
1744  return false;
1745  }
1746 
1747  mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
1748 
1749  //change datasource path from relative to absolute if necessary
1750  if ( !useAbsolutePathes )
1751  {
1752  QDomElement provider = mapLayerElem.firstChildElement( "provider" );
1753  if ( provider.text() == "spatialite" )
1754  {
1755  QDomElement dsElem = mapLayerElem.firstChildElement( "datasource" );
1756 
1757  QgsDataSourceURI uri( dsElem.text() );
1758 
1759  QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + "/" + uri.database() );
1760  if ( absoluteDs.exists() )
1761  {
1762  uri.setDatabase( absoluteDs.absoluteFilePath() );
1763  dsElem.removeChild( dsElem.childNodes().at( 0 ) );
1764  dsElem.appendChild( projectDocument.createTextNode( uri.uri() ) );
1765  }
1766  }
1767  else
1768  {
1769  QDomElement dsElem = mapLayerElem.firstChildElement( "datasource" );
1770  QString debug( QFileInfo( projectFilePath ).absolutePath() + "/" + dsElem.text() );
1771  QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + "/" + dsElem.text() );
1772  if ( absoluteDs.exists() )
1773  {
1774  dsElem.removeChild( dsElem.childNodes().at( 0 ) );
1775  dsElem.appendChild( projectDocument.createTextNode( absoluteDs.absoluteFilePath() ) );
1776  }
1777  }
1778  }
1779 
1780  if ( addLayer( mapLayerElem, brokenNodes, vectorLayerList ) )
1781  {
1782  return true;
1783  }
1784  else
1785  {
1786  mEmbeddedLayers.remove( layerId );
1787  return false;
1788  }
1789  }
1790  }
1791 
1792  return false;
1793 }
1794 
1795 
1796 QgsLayerTreeGroup* QgsProject::createEmbeddedGroup( const QString& groupName, const QString& projectFilePath, const QStringList &invisibleLayers )
1797 {
1798  //open project file, get layer ids in group, add the layers
1799  QFile projectFile( projectFilePath );
1800  if ( !projectFile.open( QIODevice::ReadOnly ) )
1801  {
1802  return 0;
1803  }
1804 
1805  QDomDocument projectDocument;
1806  if ( !projectDocument.setContent( &projectFile ) )
1807  {
1808  return 0;
1809  }
1810 
1811  //store identify disabled layers of the embedded project
1812  QSet<QString> embeddedIdentifyDisabledLayers;
1813  QDomElement disabledLayersElem = projectDocument.documentElement().firstChildElement( "properties" ).firstChildElement( "Identify" ).firstChildElement( "disabledLayers" );
1814  if ( !disabledLayersElem.isNull() )
1815  {
1816  QDomNodeList valueList = disabledLayersElem.elementsByTagName( "value" );
1817  for ( int i = 0; i < valueList.size(); ++i )
1818  {
1819  embeddedIdentifyDisabledLayers.insert( valueList.at( i ).toElement().text() );
1820  }
1821  }
1822 
1824 
1825  QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( "layer-tree-group" );
1826  if ( !layerTreeElem.isNull() )
1827  {
1828  root->readChildrenFromXML( layerTreeElem );
1829  }
1830  else
1831  {
1832  QgsLayerTreeUtils::readOldLegend( root, projectDocument.documentElement().firstChildElement( "legend" ) );
1833  }
1834 
1835  QgsLayerTreeGroup* group = root->findGroup( groupName );
1836  if ( !group || group->customProperty( "embedded" ).toBool() )
1837  {
1838  // embedded groups cannot be embedded again
1839  delete root;
1840  return 0;
1841  }
1842 
1843  // clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
1844  QgsLayerTreeGroup* newGroup = QgsLayerTree::toGroup( group->clone() );
1845  delete root;
1846  root = 0;
1847 
1848  newGroup->setCustomProperty( "embedded", 1 );
1849  newGroup->setCustomProperty( "embedded_project", projectFilePath );
1850 
1851  // set "embedded" to all children + load embedded layers
1852  mLayerTreeRegistryBridge->setEnabled( false );
1853  initializeEmbeddedSubtree( projectFilePath, newGroup );
1854  mLayerTreeRegistryBridge->setEnabled( true );
1855 
1856  // consider the layers might be identify disabled in its project
1857  foreach ( QString layerId, newGroup->findLayerIds() )
1858  {
1859  if ( embeddedIdentifyDisabledLayers.contains( layerId ) )
1860  {
1861  QStringList thisProjectIdentifyDisabledLayers = QgsProject::instance()->readListEntry( "Identify", "/disabledLayers" );
1862  thisProjectIdentifyDisabledLayers.append( layerId );
1863  QgsProject::instance()->writeEntry( "Identify", "/disabledLayers", thisProjectIdentifyDisabledLayers );
1864  }
1865 
1866  QgsLayerTreeLayer *layer = newGroup->findLayer( layerId );
1867  if ( layer )
1868  {
1869  layer->setVisible( invisibleLayers.contains( layerId ) ? Qt::Unchecked : Qt::Checked );
1870  }
1871  }
1872 
1873  return newGroup;
1874 }
1875 
1876 void QgsProject::initializeEmbeddedSubtree( const QString& projectFilePath, QgsLayerTreeGroup* group )
1877 {
1878  foreach ( QgsLayerTreeNode* child, group->children() )
1879  {
1880  // all nodes in the subtree will have "embedded" custom property set
1881  child->setCustomProperty( "embedded", 1 );
1882 
1883  if ( QgsLayerTree::isGroup( child ) )
1884  {
1885  initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ) );
1886  }
1887  else if ( QgsLayerTree::isLayer( child ) )
1888  {
1889  // load the layer into our project
1890  QList<QDomNode> brokenNodes;
1891  QList< QPair< QgsVectorLayer*, QDomElement > > vectorLayerList;
1892  createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, vectorLayerList, false );
1893  }
1894  }
1895 }
1896 
1897 void QgsProject::setSnapSettingsForLayer( const QString& layerId, bool enabled, QgsSnapper::SnappingType type, QgsTolerance::UnitType unit, double tolerance, bool avoidIntersection )
1898 {
1899  QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList;
1900  snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList );
1901  int idx = layerIdList.indexOf( layerId );
1902  if ( idx != -1 )
1903  {
1904  layerIdList.removeAt( idx );
1905  enabledList.removeAt( idx );
1906  snapTypeList.removeAt( idx );
1907  toleranceUnitList.removeAt( idx );
1908  toleranceList.removeAt( idx );
1909  avoidIntersectionList.removeOne( layerId );
1910  }
1911 
1912  layerIdList.append( layerId );
1913 
1914  //enabled
1915  enabledList.append( enabled ? "enabled" : "disabled" );
1916 
1917  //snap type
1918  QString typeString;
1919  if ( type == QgsSnapper::SnapToSegment )
1920  {
1921  typeString = "to_segment";
1922  }
1923  else if ( type == QgsSnapper::SnapToVertexAndSegment )
1924  {
1925  typeString = "to_vertex_and_segment";
1926  }
1927  else
1928  {
1929  typeString = "to_vertex";
1930  }
1931  snapTypeList.append( typeString );
1932 
1933  //units
1934  toleranceUnitList.append( QString::number( unit ) );
1935 
1936  //tolerance
1937  toleranceList.append( QString::number( tolerance ) );
1938 
1939  //avoid intersection
1940  if ( avoidIntersection )
1941  {
1942  avoidIntersectionList.append( layerId );
1943  }
1944 
1945  writeEntry( "Digitizing", "/LayerSnappingList", layerIdList );
1946  writeEntry( "Digitizing", "/LayerSnappingEnabledList", enabledList );
1947  writeEntry( "Digitizing", "/LayerSnappingToleranceList", toleranceList );
1948  writeEntry( "Digitizing", "/LayerSnappingToleranceUnitList", toleranceUnitList );
1949  writeEntry( "Digitizing", "/LayerSnapToList", snapTypeList );
1950  writeEntry( "Digitizing", "/AvoidIntersectionsList", avoidIntersectionList );
1951  emit snapSettingsChanged();
1952 }
1953 
1954 bool QgsProject::snapSettingsForLayer( const QString& layerId, bool& enabled, QgsSnapper::SnappingType &type, QgsTolerance::UnitType& units, double& tolerance,
1955  bool& avoidIntersection ) const
1956 {
1957  QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList;
1958  snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList );
1959  int idx = layerIdList.indexOf( layerId );
1960  if ( idx == -1 )
1961  {
1962  return false;
1963  }
1964 
1965  //make sure all lists are long enough
1966  int minListEntries = idx + 1;
1967  if ( layerIdList.size() < minListEntries || enabledList.size() < minListEntries || snapTypeList.size() < minListEntries ||
1968  toleranceUnitList.size() < minListEntries || toleranceList.size() < minListEntries )
1969  {
1970  return false;
1971  }
1972 
1973  //enabled
1974  enabled = enabledList.at( idx ) == "enabled";
1975 
1976  //snap type
1977  QString snapType = snapTypeList.at( idx );
1978  if ( snapType == "to_segment" )
1979  {
1981  }
1982  else if ( snapType == "to_vertex_and_segment" )
1983  {
1985  }
1986  else //to vertex
1987  {
1988  type = QgsSnapper::SnapToVertex;
1989  }
1990 
1991  //units
1992  if ( toleranceUnitList.at( idx ) == "1" )
1993  {
1994  units = QgsTolerance::Pixels;
1995  }
1996  else if ( toleranceUnitList.at( idx ) == "2" )
1997  {
1999  }
2000  else
2001  {
2002  units = QgsTolerance::LayerUnits;
2003  }
2004 
2005  //tolerance
2006  tolerance = toleranceList.at( idx ).toDouble();
2007 
2008  //avoid intersection
2009  avoidIntersection = ( avoidIntersectionList.indexOf( layerId ) != -1 );
2010 
2011  return true;
2012 }
2013 
2014 void QgsProject::snapSettings( QStringList& layerIdList, QStringList& enabledList, QStringList& snapTypeList, QStringList& toleranceUnitList, QStringList& toleranceList,
2015  QStringList& avoidIntersectionList ) const
2016 {
2017  layerIdList = readListEntry( "Digitizing", "/LayerSnappingList" );
2018  enabledList = readListEntry( "Digitizing", "/LayerSnappingEnabledList" );
2019  toleranceList = readListEntry( "Digitizing", "/LayerSnappingToleranceList" );
2020  toleranceUnitList = readListEntry( "Digitizing", "/LayerSnappingToleranceUnitList" );
2021  snapTypeList = readListEntry( "Digitizing", "/LayerSnapToList" );
2022  avoidIntersectionList = readListEntry( "Digitizing", "/AvoidIntersectionsList" );
2023 }
2024 
2026 {
2027  QgsProject::instance()->writeEntry( "Digitizing", "/TopologicalEditing", ( enabled ? 1 : 0 ) );
2028  emit snapSettingsChanged();
2029 }
2030 
2032 {
2033  return ( QgsProject::instance()->readNumEntry( "Digitizing", "/TopologicalEditing", 0 ) > 0 );
2034 }
2035 
2036 void QgsProjectBadLayerDefaultHandler::handleBadLayers( QList<QDomNode> /*layers*/, QDomDocument /*projectDom*/ )
2037 {
2038  // just ignore any bad layers
2039 }
2040 
2041 QString QgsProject::homePath() const
2042 {
2043  QFileInfo pfi( fileName() );
2044  if ( !pfi.exists() )
2045  return QString::null;
2046 
2047  return pfi.canonicalPath();
2048 }
2049 
2051 {
2052  return mRelationManager;
2053 }
2054 
2056 {
2057  return mRootGroup;
2058 }
virtual void handleBadLayers(QList< QDomNode > layers, QDomDocument projectDom) override
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.
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
Base class for all map layer types.
Definition: qgsmaplayer.h:49
static void removeInvalidLayers(QgsLayerTreeGroup *group)
Remove layer nodes that refer to invalid layers.
QString writePath(QString filename, QString relativeBasePath=QString::null) const
prepare a filename to save it to the project file
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.
QgsPropertyKey properties_
property hierarchy
Definition: qgsproject.cpp:301
QgsPropertyKey * addKey(const QString &keyName)
add the given property key
void setTopologicalEditing(bool enabled)
Convenience function to set topological editing.
bool writeLayerXML(QDomElement &layerElement, QDomDocument &document, QString relativeBasePath=QString::null)
stores state in Dom node
bool snapSettingsForLayer(const QString &layerId, bool &enabled, QgsSnapper::SnappingType &type, QgsTolerance::UnitType &units, double &tolerance, bool &avoidIntersection) const
Convenience function to query snap settings of a layer.
void 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.
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:33
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
void removeAllChildren()
Remove all child nodes. The nodes will be deleted.
Pixels unit of tolerance.
Definition: qgstolerance.h:40
virtual QgsLayerTreeNode * clone() const =0
Create a copy of the node. Returns new instance.
static QgsProperty * addKey_(QString const &scope, QString const &key, QgsPropertyKey *rootProperty, QVariant value)
add the given key and value
Definition: qgsproject.cpp:160
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:124
void oldProjectVersionWarning(QString)
emitted when an old project file is read.
void setDatabase(const QString &database)
Set database.
static QgsMapLayerRegistry * instance()
Definition: qgssingleton.h:23
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
QString qgsVsiPrefix(QString path)
Definition: qgis.cpp:242
Map (project) units.
Definition: qgstolerance.h:42
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:965
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.
void clear()
Remove any relation managed by this class.
QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group. No type checking is done - use isGroup() to find out whether this operation is ...
Definition: qgslayertree.h:46
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
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)
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
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.
static void replaceChildrenOfEmbeddedGroups(QgsLayerTreeGroup *group)
Remove subtree of embedded groups and replaces it with a custom property embedded-visible-layers.
void layerLoaded(int i, int n)
emitted when a layer from a projects was read
QString fileName() const
returns file name
Definition: qgsproject.cpp: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:98
bool removeEntry(const QString &scope, const QString &key)
remove the given key
static void updateEmbeddedGroupsProjectPath(QgsLayerTreeGroup *group)
Reads and writes project states.
Definition: qgsproject.h:67
QFile file
current physical project file
Definition: qgsproject.cpp:298
void setVisible(Qt::CheckState visible)
virtual QgsLayerTreeNode * clone() const override
Return a clone of the group. The children are cloned too.
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.
double readDoubleEntry(const QString &scope, const QString &key, double def=0, bool *ok=0) const
QgsProperty * find(QString &propertyName)
bool write()
#define QgsDebugCall
Definition: qgslogger.h:32
Class for storing the component parts of a PostgreSQL/RDBMS datasource URI.
void subkeyList(QStringList &entries) const
return keys that contain other keys
QgsLayerTreeGroup * createEmbeddedGroup(const QString &groupName, const QString &projectFilePath, const QStringList &invisibleLayers)
Create layer group instance defined in an arbitrary project file.
void clearError()
Clear error message.
void setError(QString errorMessage)
Set error message from read/write operation.
bool isDirty() const
the dirty flag is true if the project has been modified since the last write()
Definition: qgsproject.cpp:390
QString file
Definition: qgssvgcache.cpp:76
bool addLayer(const QDomElement &layerElem, QList< QDomNode > &brokenNodes, QList< QPair< QgsVectorLayer *, QDomElement > > &vectorLayerList)
Definition: qgsproject.cpp:747
void writeProject(QDomDocument &)
emitted when project is being written
virtual QString dump() const override
Return text representation of the tree. For debugging purposes only.
QString readEntry(const QString &scope, const QString &key, const QString &def=QString::null, bool *ok=0) const
Layer unit value.
Definition: qgstolerance.h:38
QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer. No type checking is done - use isLayer() to find out whether this operation is ...
Definition: qgslayertree.h:52
This class manages a set of relations between layers.
QStringList findLayerIds() const
Find layer IDs used in all layer nodes. Searches recursively the whole sub-tree.
QgsLayerTreeGroup * findGroup(const QString &name)
Find group node with specified name. Searches recursively the whole sub-tree.
static QgsProject * instance()
access to canonical QgsProject instance
Definition: qgsproject.cpp: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)
bool readXML(QDomNode &keyNode) override
restores property hierarchy to given Dom node
QStringList entryList(const QString &scope, const QString &key) const
return keys with values – do not return keys that contain other keys
QgsLayerTreeLayer * findLayer(const QString &layerId) const
Find layer node representing the map layer specified by its ID. Searches recursively the whole sub-tr...
bool readLayerXML(const QDomElement &layerElement)
sets state from Dom document
QString title
project title
Definition: qgsproject.cpp:304
const QString & title() const
returns title
Definition: qgsproject.cpp:384
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. ...
UnitType
Type of unit of tolerance value from settings.
Definition: qgstolerance.h:33
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:440
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:431
Layer tree node points to a map layer.
void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for the node.
void dump(int tabs=0) const override
dumps out the keys and values
void snapSettingsChanged()
virtual void handleBadLayers(QList< QDomNode > layers, QDomDocument projectDom)=0
void dirty(bool b)
Definition: qgsproject.cpp:396
#define tr(sourceText)