QGIS API Documentation  2.5.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties 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
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 
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 
1006 
1007 bool QgsProject::read( QDomNode & layerNode )
1008 {
1009  QList<QDomNode> brokenNodes;
1010  QList< QPair< QgsVectorLayer*, QDomElement > > vectorLayerList;
1011  return addLayer( layerNode.toElement(), brokenNodes, vectorLayerList );
1012 } // QgsProject::read( QDomNode & layerNode )
1013 
1014 
1015 
1016 bool QgsProject::write( QFileInfo const &file )
1017 {
1018  imp_->file.setFileName( file.filePath() );
1019 
1020  return write();
1021 } // QgsProject::write( QFileInfo const & file )
1022 
1023 
1025 {
1026  clearError();
1027 
1028  // Create backup file
1029  if ( QFile::exists( fileName() ) )
1030  {
1031  QString backup = fileName() + "~";
1032  if ( QFile::exists( backup ) )
1033  QFile::remove( backup );
1034  QFile::rename( fileName(), backup );
1035  }
1036 
1037  // if we have problems creating or otherwise writing to the project file,
1038  // let's find out up front before we go through all the hand-waving
1039  // necessary to create all the Dom objects
1040  if ( !imp_->file.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
1041  {
1042  imp_->file.close(); // even though we got an error, let's make
1043  // sure it's closed anyway
1044 
1045  setError( tr( "Unable to save to file %1" ).arg( imp_->file.fileName() ) );
1046  return false;
1047  }
1048  QFileInfo myFileInfo( imp_->file );
1049  if ( !myFileInfo.isWritable() )
1050  {
1051  // even though we got an error, let's make
1052  // sure it's closed anyway
1053  imp_->file.close();
1054  setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." )
1055  .arg( imp_->file.fileName() ) );
1056  return false;
1057  }
1058 
1059 
1060 
1061  QDomImplementation DomImplementation;
1062  DomImplementation.setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
1063 
1064  QDomDocumentType documentType =
1065  DomImplementation.createDocumentType( "qgis", "http://mrcc.com/qgis.dtd",
1066  "SYSTEM" );
1067  std::auto_ptr < QDomDocument > doc =
1068  std::auto_ptr < QDomDocument > ( new QDomDocument( documentType ) );
1069 
1070 
1071  QDomElement qgisNode = doc->createElement( "qgis" );
1072  qgisNode.setAttribute( "projectname", title() );
1073  qgisNode.setAttribute( "version", QString( "%1" ).arg( QGis::QGIS_VERSION ) );
1074 
1075  doc->appendChild( qgisNode );
1076 
1077  // title
1078  QDomElement titleNode = doc->createElement( "title" );
1079  qgisNode.appendChild( titleNode );
1080 
1081  QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
1082  titleNode.appendChild( titleText );
1083 
1084  // write layer tree - make sure it is without embedded subgroups
1085  QgsLayerTreeNode* clonedRoot = mRootGroup->clone();
1087  QgsLayerTreeUtils::updateEmbeddedGroupsProjectPath( QgsLayerTree::toGroup( clonedRoot ) ); // convert absolute paths to relative paths if required
1088  clonedRoot->writeXML( qgisNode );
1089  delete clonedRoot;
1090 
1091  // let map canvas and legend write their information
1092  emit writeProject( *doc );
1093 
1094  // within top level node save list of layers
1095  const QMap<QString, QgsMapLayer*> & layers = QgsMapLayerRegistry::instance()->mapLayers();
1096 
1097  // Iterate over layers in zOrder
1098  // Call writeXML() on each
1099  QDomElement projectLayersNode = doc->createElement( "projectlayers" );
1100  projectLayersNode.setAttribute( "layercount", qulonglong( layers.size() ) );
1101 
1102  QMap<QString, QgsMapLayer*>::ConstIterator li = layers.constBegin();
1103  while ( li != layers.end() )
1104  {
1105  //QgsMapLayer *ml = QgsMapLayerRegistry::instance()->mapLayer(*li);
1106  QgsMapLayer* ml = li.value();
1107 
1108  if ( ml )
1109  {
1110  QString externalProjectFile = layerIsEmbedded( ml->id() );
1111  QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.find( ml->id() );
1112  if ( emIt == mEmbeddedLayers.constEnd() )
1113  {
1114  // general layer metadata
1115  QDomElement maplayerElem = doc->createElement( "maplayer" );
1116 
1117  ml->writeLayerXML( maplayerElem, *doc );
1118 
1119  emit writeMapLayer( ml, maplayerElem, *doc );
1120 
1121  projectLayersNode.appendChild( maplayerElem );
1122  }
1123  else //layer defined in an external project file
1124  {
1125  //only save embedded layer if not managed by a legend group
1126  if ( emIt.value().second )
1127  {
1128  QDomElement mapLayerElem = doc->createElement( "maplayer" );
1129  mapLayerElem.setAttribute( "embedded", 1 );
1130  mapLayerElem.setAttribute( "project", writePath( emIt.value().first ) );
1131  mapLayerElem.setAttribute( "id", ml->id() );
1132  projectLayersNode.appendChild( mapLayerElem );
1133  }
1134  }
1135  }
1136  li++;
1137  }
1138 
1139  qgisNode.appendChild( projectLayersNode );
1140 
1141  // now add the optional extra properties
1142 
1143  dump_( imp_->properties_ );
1144 
1145  QgsDebugMsg( QString( "there are %1 property scopes" ).arg( static_cast<int>( imp_->properties_.count() ) ) );
1146 
1147  if ( !imp_->properties_.isEmpty() ) // only worry about properties if we
1148  // actually have any properties
1149  {
1150  imp_->properties_.writeXML( "properties", qgisNode, *doc );
1151  }
1152 
1153  // now wrap it up and ship it to the project file
1154  doc->normalize(); // XXX I'm not entirely sure what this does
1155 
1156  //QString xml = doc->toString(4); // write to string with indentation of four characters
1157  // (yes, four is arbitrary)
1158 
1159  // const char * xmlString = xml; // debugger probe point
1160  // qDebug( "project file output:\n\n" + xml );
1161 
1162  QTextStream projectFileStream( &imp_->file );
1163 
1164  //projectFileStream << xml << endl;
1165  doc->save( projectFileStream, 4 ); // save as utf-8
1166  imp_->file.close();
1167 
1168  // check if the text stream had no error - if it does
1169  // the user will get a message so they can try to resolve the
1170  // situation e.g. by saving project to a volume with more space
1171  //
1172  if ( projectFileStream.pos() == -1 || imp_->file.error() != QFile::NoError )
1173  {
1174  setError( tr( "Unable to save to file %1. Your project "
1175  "may be corrupted on disk. Try clearing some space on the volume and "
1176  "check file permissions before pressing save again." )
1177  .arg( imp_->file.fileName() ) );
1178  return false;
1179  }
1180 
1181  dirty( false ); // reset to pristine state
1182 
1183  emit projectSaved();
1184 
1185  return true;
1186 } // QgsProject::write
1187 
1188 
1189 
1191 {
1192  clear();
1193 
1194  dirty( true );
1195 } // QgsProject::clearProperties()
1196 
1197 
1198 
1199 bool
1200 QgsProject::writeEntry( QString const &scope, const QString & key, bool value )
1201 {
1202  dirty( true );
1203 
1204  return addKey_( scope, key, &imp_->properties_, value );
1205 } // QgsProject::writeEntry ( ..., bool value )
1206 
1207 
1208 bool
1209 QgsProject::writeEntry( QString const &scope, const QString & key,
1210  double value )
1211 {
1212  dirty( true );
1213 
1214  return addKey_( scope, key, &imp_->properties_, value );
1215 } // QgsProject::writeEntry ( ..., double value )
1216 
1217 
1218 bool
1219 QgsProject::writeEntry( QString const &scope, const QString & key, int value )
1220 {
1221  dirty( true );
1222 
1223  return addKey_( scope, key, &imp_->properties_, value );
1224 } // QgsProject::writeEntry ( ..., int value )
1225 
1226 
1227 bool
1228 QgsProject::writeEntry( QString const &scope, const QString & key,
1229  const QString & value )
1230 {
1231  dirty( true );
1232 
1233  return addKey_( scope, key, &imp_->properties_, value );
1234 } // QgsProject::writeEntry ( ..., const QString & value )
1235 
1236 
1237 bool
1238 QgsProject::writeEntry( QString const &scope, const QString & key,
1239  const QStringList & value )
1240 {
1241  dirty( true );
1242 
1243  return addKey_( scope, key, &imp_->properties_, value );
1244 } // QgsProject::writeEntry ( ..., const QStringList & value )
1245 
1246 
1247 
1248 
1249 QStringList
1250 QgsProject::readListEntry( QString const & scope,
1251  const QString & key,
1252  QStringList def,
1253  bool * ok ) const
1254 {
1255  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1256 
1257  QVariant value;
1258 
1259  if ( property )
1260  {
1261  value = property->value();
1262  }
1263 
1264  bool valid = QVariant::StringList == value.type();
1265 
1266  if ( ok )
1267  {
1268  *ok = valid;
1269  }
1270 
1271  if ( valid )
1272  {
1273  return value.toStringList();
1274  }
1275 
1276  return def;
1277 } // QgsProject::readListEntry
1278 
1279 
1280 QString
1281 QgsProject::readEntry( QString const & scope,
1282  const QString & key,
1283  const QString & def,
1284  bool * ok ) const
1285 {
1286  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1287 
1288  QVariant value;
1289 
1290  if ( property )
1291  {
1292  value = property->value();
1293  }
1294 
1295  bool valid = value.canConvert( QVariant::String );
1296 
1297  if ( ok )
1298  {
1299  *ok = valid;
1300  }
1301 
1302  if ( valid )
1303  {
1304  return value.toString();
1305  }
1306 
1307  return QString( def );
1308 } // QgsProject::readEntry
1309 
1310 
1311 int
1312 QgsProject::readNumEntry( QString const &scope, const QString & key, int def,
1313  bool * ok ) const
1314 {
1315  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1316 
1317  QVariant value;
1318 
1319  if ( property )
1320  {
1321  value = property->value();
1322  }
1323 
1324  bool valid = value.canConvert( QVariant::String );
1325 
1326  if ( ok )
1327  {
1328  *ok = valid;
1329  }
1330 
1331  if ( valid )
1332  {
1333  return value.toInt();
1334  }
1335 
1336  return def;
1337 } // QgsProject::readNumEntry
1338 
1339 
1340 double
1341 QgsProject::readDoubleEntry( QString const &scope, const QString & key,
1342  double def,
1343  bool * ok ) const
1344 {
1345  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1346 
1347  QVariant value;
1348 
1349  if ( property )
1350  {
1351  value = property->value();
1352  }
1353 
1354  bool valid = value.canConvert( QVariant::Double );
1355 
1356  if ( ok )
1357  {
1358  *ok = valid;
1359  }
1360 
1361  if ( valid )
1362  {
1363  return value.toDouble();
1364  }
1365 
1366  return def;
1367 } // QgsProject::readDoubleEntry
1368 
1369 
1370 bool
1371 QgsProject::readBoolEntry( QString const &scope, const QString & key, bool def,
1372  bool * ok ) const
1373 {
1374  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1375 
1376  QVariant value;
1377 
1378  if ( property )
1379  {
1380  value = property->value();
1381  }
1382 
1383  bool valid = value.canConvert( QVariant::Bool );
1384 
1385  if ( ok )
1386  {
1387  *ok = valid;
1388  }
1389 
1390  if ( valid )
1391  {
1392  return value.toBool();
1393  }
1394 
1395  return def;
1396 } // QgsProject::readBoolEntry
1397 
1398 
1399 bool QgsProject::removeEntry( QString const &scope, const QString & key )
1400 {
1401  removeKey_( scope, key, imp_->properties_ );
1402 
1403  dirty( true );
1404 
1405  return ! findKey_( scope, key, imp_->properties_ );
1406 } // QgsProject::removeEntry
1407 
1408 
1409 
1410 QStringList QgsProject::entryList( QString const &scope, QString const &key ) const
1411 {
1412  QgsProperty * foundProperty = findKey_( scope, key, imp_->properties_ );
1413 
1414  QStringList entries;
1415 
1416  if ( foundProperty )
1417  {
1418  QgsPropertyKey * propertyKey = dynamic_cast<QgsPropertyKey*>( foundProperty );
1419 
1420  if ( propertyKey )
1421  { propertyKey->entryList( entries ); }
1422  }
1423 
1424  return entries;
1425 } // QgsProject::entryList
1426 
1427 
1428 QStringList QgsProject::subkeyList( 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->subkeyList( entries ); }
1440  }
1441 
1442  return entries;
1443 
1444 } // QgsProject::subkeyList
1445 
1446 
1447 
1449 {
1450  dump_( imp_->properties_ );
1451 } // QgsProject::dumpProperties
1452 
1453 
1454 // return the absolute path from a filename read from project file
1455 QString QgsProject::readPath( QString src ) const
1456 {
1457  if ( readBoolEntry( "Paths", "/Absolute", false ) )
1458  {
1459  return src;
1460  }
1461 
1462  // if this is a VSIFILE, remove the VSI prefix and append to final result
1463  QString vsiPrefix = qgsVsiPrefix( src );
1464  if ( ! vsiPrefix.isEmpty() )
1465  {
1466  // unfortunately qgsVsiPrefix returns prefix also for files like "/x/y/z.gz"
1467  // so we need to check if we really have the prefix
1468  if ( src.startsWith( "/vsi", Qt::CaseInsensitive ) )
1469  src.remove( 0, vsiPrefix.size() );
1470  else
1471  vsiPrefix.clear();
1472  }
1473 
1474  // relative path should always start with ./ or ../
1475  if ( !src.startsWith( "./" ) && !src.startsWith( "../" ) )
1476  {
1477 #if defined(Q_OS_WIN)
1478  if ( src.startsWith( "\\\\" ) ||
1479  src.startsWith( "//" ) ||
1480  ( src[0].isLetter() && src[1] == ':' ) )
1481  {
1482  // UNC or absolute path
1483  return vsiPrefix + src;
1484  }
1485 #else
1486  if ( src[0] == '/' )
1487  {
1488  // absolute path
1489  return vsiPrefix + src;
1490  }
1491 #endif
1492 
1493  // so this one isn't absolute, but also doesn't start // with ./ or ../.
1494  // That means that it was saved with an earlier version of "relative path support",
1495  // where the source file had to exist and only the project directory was stripped
1496  // from the filename.
1497  QString home = homePath();
1498  if ( home.isNull() )
1499  return vsiPrefix + src;
1500 
1501  QFileInfo fi( home + "/" + src );
1502 
1503  if ( !fi.exists() )
1504  {
1505  return vsiPrefix + src;
1506  }
1507  else
1508  {
1509  return vsiPrefix + fi.canonicalFilePath();
1510  }
1511  }
1512 
1513  QString srcPath = src;
1514  QString projPath = fileName();
1515 
1516  if ( projPath.isEmpty() )
1517  {
1518  return vsiPrefix + src;
1519  }
1520 
1521 #if defined(Q_OS_WIN)
1522  srcPath.replace( "\\", "/" );
1523  projPath.replace( "\\", "/" );
1524 
1525  bool uncPath = projPath.startsWith( "//" );
1526 #endif
1527 
1528  QStringList srcElems = srcPath.split( "/", QString::SkipEmptyParts );
1529  QStringList projElems = projPath.split( "/", QString::SkipEmptyParts );
1530 
1531 #if defined(Q_OS_WIN)
1532  if ( uncPath )
1533  {
1534  projElems.insert( 0, "" );
1535  projElems.insert( 0, "" );
1536  }
1537 #endif
1538 
1539  // remove project file element
1540  projElems.removeLast();
1541 
1542  // append source path elements
1543  projElems << srcElems;
1544  projElems.removeAll( "." );
1545 
1546  // resolve ..
1547  int pos;
1548  while (( pos = projElems.indexOf( ".." ) ) > 0 )
1549  {
1550  // remove preceding element and ..
1551  projElems.removeAt( pos - 1 );
1552  projElems.removeAt( pos - 1 );
1553  }
1554 
1555 #if !defined(Q_OS_WIN)
1556  // make path absolute
1557  projElems.prepend( "" );
1558 #endif
1559 
1560  return vsiPrefix + projElems.join( "/" );
1561 }
1562 
1563 // return the absolute or relative path to write it to the project file
1564 QString QgsProject::writePath( QString src ) const
1565 {
1566  if ( readBoolEntry( "Paths", "/Absolute", false ) || src.isEmpty() )
1567  {
1568  return src;
1569  }
1570 
1571  QFileInfo srcFileInfo( src );//QString srcPath = src;
1572  QFileInfo projFileInfo( fileName() );
1573  QString srcPath = srcFileInfo.canonicalFilePath();
1574  QString projPath = projFileInfo.canonicalFilePath();
1575 
1576  if ( projPath.isEmpty() )
1577  {
1578  return src;
1579  }
1580 
1581  // if this is a VSIFILE, remove the VSI prefix and append to final result
1582  QString vsiPrefix = qgsVsiPrefix( src );
1583  if ( ! vsiPrefix.isEmpty() )
1584  {
1585  srcPath.remove( 0, vsiPrefix.size() );
1586  }
1587 
1588 #if defined( Q_OS_WIN )
1589  const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
1590 
1591  srcPath.replace( "\\", "/" );
1592 
1593  if ( srcPath.startsWith( "//" ) )
1594  {
1595  // keep UNC prefix
1596  srcPath = "\\\\" + srcPath.mid( 2 );
1597  }
1598 
1599  projPath.replace( "\\", "/" );
1600  if ( projPath.startsWith( "//" ) )
1601  {
1602  // keep UNC prefix
1603  projPath = "\\\\" + projPath.mid( 2 );
1604  }
1605 #else
1606  const Qt::CaseSensitivity cs = Qt::CaseSensitive;
1607 #endif
1608 
1609  QStringList projElems = projPath.split( "/", QString::SkipEmptyParts );
1610  QStringList srcElems = srcPath.split( "/", QString::SkipEmptyParts );
1611 
1612  // remove project file element
1613  projElems.removeLast();
1614 
1615  projElems.removeAll( "." );
1616  srcElems.removeAll( "." );
1617 
1618  // remove common part
1619  int n = 0;
1620  while ( srcElems.size() > 0 &&
1621  projElems.size() > 0 &&
1622  srcElems[0].compare( projElems[0], cs ) == 0 )
1623  {
1624  srcElems.removeFirst();
1625  projElems.removeFirst();
1626  n++;
1627  }
1628 
1629  if ( n == 0 )
1630  {
1631  // no common parts; might not even by a file
1632  return src;
1633  }
1634 
1635  if ( projElems.size() > 0 )
1636  {
1637  // go up to the common directory
1638  for ( int i = 0; i < projElems.size(); i++ )
1639  {
1640  srcElems.insert( 0, ".." );
1641  }
1642  }
1643  else
1644  {
1645  // let it start with . nevertheless,
1646  // so relative path always start with either ./ or ../
1647  srcElems.insert( 0, "." );
1648  }
1649 
1650  return vsiPrefix + srcElems.join( "/" );
1651 }
1652 
1653 void QgsProject::setError( QString errorMessage )
1654 {
1655  mErrorMessage = errorMessage;
1656 }
1657 
1658 QString QgsProject::error() const
1659 {
1660  return mErrorMessage;
1661 }
1662 
1664 {
1665  setError( QString() );
1666 }
1667 
1669 {
1670  delete mBadLayerHandler;
1671  mBadLayerHandler = handler;
1672 }
1673 
1674 QString QgsProject::layerIsEmbedded( const QString& id ) const
1675 {
1676  QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
1677  if ( it == mEmbeddedLayers.constEnd() )
1678  {
1679  return QString();
1680  }
1681  return it.value().first;
1682 }
1683 
1684 bool QgsProject::createEmbeddedLayer( const QString& layerId, const QString& projectFilePath, QList<QDomNode>& brokenNodes,
1685  QList< QPair< QgsVectorLayer*, QDomElement > >& vectorLayerList, bool saveFlag )
1686 {
1687  QFile projectFile( projectFilePath );
1688  if ( !projectFile.open( QIODevice::ReadOnly ) )
1689  {
1690  return false;
1691  }
1692 
1693  QDomDocument projectDocument;
1694  if ( !projectDocument.setContent( &projectFile ) )
1695  {
1696  return false;
1697  }
1698 
1699  //does project store pathes absolute or relative?
1700  bool useAbsolutePathes = true;
1701 
1702  QDomElement propertiesElem = projectDocument.documentElement().firstChildElement( "properties" );
1703  if ( !propertiesElem.isNull() )
1704  {
1705  QDomElement absElem = propertiesElem.firstChildElement( "Paths" ).firstChildElement( "Absolute" );
1706  if ( !absElem.isNull() )
1707  {
1708  useAbsolutePathes = absElem.text().compare( "true", Qt::CaseInsensitive ) == 0;
1709  }
1710  }
1711 
1712  QDomElement projectLayersElem = projectDocument.documentElement().firstChildElement( "projectlayers" );
1713  if ( projectLayersElem.isNull() )
1714  {
1715  return false;
1716  }
1717 
1718  QDomNodeList mapLayerNodes = projectLayersElem.elementsByTagName( "maplayer" );
1719  for ( int i = 0; i < mapLayerNodes.size(); ++i )
1720  {
1721  //get layer id
1722  QDomElement mapLayerElem = mapLayerNodes.at( i ).toElement();
1723  QString id = mapLayerElem.firstChildElement( "id" ).text();
1724  if ( id == layerId )
1725  {
1726  //layer can be embedded only once
1727  if ( mapLayerElem.attribute( "embedded" ) == "1" )
1728  {
1729  return false;
1730  }
1731 
1732  mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
1733 
1734  //change datasource path from relative to absolute if necessary
1735  if ( !useAbsolutePathes )
1736  {
1737  QDomElement provider = mapLayerElem.firstChildElement( "provider" );
1738  if ( provider.text() == "spatialite" )
1739  {
1740  QDomElement dsElem = mapLayerElem.firstChildElement( "datasource" );
1741 
1742  QgsDataSourceURI uri( dsElem.text() );
1743 
1744  QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + "/" + uri.database() );
1745  if ( absoluteDs.exists() )
1746  {
1747  uri.setDatabase( absoluteDs.absoluteFilePath() );
1748  dsElem.removeChild( dsElem.childNodes().at( 0 ) );
1749  dsElem.appendChild( projectDocument.createTextNode( uri.uri() ) );
1750  }
1751  }
1752  else
1753  {
1754  QDomElement dsElem = mapLayerElem.firstChildElement( "datasource" );
1755  QString debug( QFileInfo( projectFilePath ).absolutePath() + "/" + dsElem.text() );
1756  QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + "/" + dsElem.text() );
1757  if ( absoluteDs.exists() )
1758  {
1759  dsElem.removeChild( dsElem.childNodes().at( 0 ) );
1760  dsElem.appendChild( projectDocument.createTextNode( absoluteDs.absoluteFilePath() ) );
1761  }
1762  }
1763  }
1764 
1765  if ( addLayer( mapLayerElem, brokenNodes, vectorLayerList ) )
1766  {
1767  return true;
1768  }
1769  else
1770  {
1771  mEmbeddedLayers.remove( layerId );
1772  return false;
1773  }
1774  }
1775  }
1776 
1777  return false;
1778 }
1779 
1780 
1781 QgsLayerTreeGroup* QgsProject::createEmbeddedGroup( const QString& groupName, const QString& projectFilePath )
1782 {
1783  //open project file, get layer ids in group, add the layers
1784  QFile projectFile( projectFilePath );
1785  if ( !projectFile.open( QIODevice::ReadOnly ) )
1786  {
1787  return 0;
1788  }
1789 
1790  QDomDocument projectDocument;
1791  if ( !projectDocument.setContent( &projectFile ) )
1792  {
1793  return 0;
1794  }
1795 
1796  //store identify disabled layers of the embedded project
1797  QSet<QString> embeddedIdentifyDisabledLayers;
1798  QDomElement disabledLayersElem = projectDocument.documentElement().firstChildElement( "properties" ).firstChildElement( "Identify" ).firstChildElement( "disabledLayers" );
1799  if ( !disabledLayersElem.isNull() )
1800  {
1801  QDomNodeList valueList = disabledLayersElem.elementsByTagName( "value" );
1802  for ( int i = 0; i < valueList.size(); ++i )
1803  {
1804  embeddedIdentifyDisabledLayers.insert( valueList.at( i ).toElement().text() );
1805  }
1806  }
1807 
1809 
1810  QDomElement layerTreeElem = projectDocument.documentElement().firstChildElement( "layer-tree-group" );
1811  if ( !layerTreeElem.isNull() )
1812  {
1813  root->readChildrenFromXML( layerTreeElem );
1814  }
1815  else
1816  {
1817  QgsLayerTreeUtils::readOldLegend( root, projectDocument.documentElement().firstChildElement( "legend" ) );
1818  }
1819 
1820  QgsLayerTreeGroup* group = root->findGroup( groupName );
1821  if ( !group || group->customProperty( "embedded" ).toBool() )
1822  {
1823  // embedded groups cannot be embedded again
1824  delete root;
1825  return 0;
1826  }
1827 
1828  // clone the group sub-tree (it is used already in a tree, we cannot just tear it off)
1829  QgsLayerTreeGroup* newGroup = QgsLayerTree::toGroup( group->clone() );
1830  delete root;
1831  root = 0;
1832 
1833  newGroup->setCustomProperty( "embedded", 1 );
1834  newGroup->setCustomProperty( "embedded_project", projectFilePath );
1835 
1836  // set "embedded" to all children + load embedded layers
1838  initializeEmbeddedSubtree( projectFilePath, newGroup );
1840 
1841  // consider the layers might be identify disabled in its project
1842  foreach ( QString layerId, newGroup->findLayerIds() )
1843  {
1844  if ( embeddedIdentifyDisabledLayers.contains( layerId ) )
1845  {
1846  QStringList thisProjectIdentifyDisabledLayers = QgsProject::instance()->readListEntry( "Identify", "/disabledLayers" );
1847  thisProjectIdentifyDisabledLayers.append( layerId );
1848  QgsProject::instance()->writeEntry( "Identify", "/disabledLayers", thisProjectIdentifyDisabledLayers );
1849  }
1850  }
1851 
1852  return newGroup;
1853 }
1854 
1855 void QgsProject::initializeEmbeddedSubtree( const QString& projectFilePath, QgsLayerTreeGroup* group )
1856 {
1857  foreach ( QgsLayerTreeNode* child, group->children() )
1858  {
1859  // all nodes in the subtree will have "embedded" custom property set
1860  child->setCustomProperty( "embedded", 1 );
1861 
1862  if ( QgsLayerTree::isGroup( child ) )
1863  {
1864  initializeEmbeddedSubtree( projectFilePath, QgsLayerTree::toGroup( child ) );
1865  }
1866  else if ( QgsLayerTree::isLayer( child ) )
1867  {
1868  // load the layer into our project
1869  QList<QDomNode> brokenNodes;
1870  QList< QPair< QgsVectorLayer*, QDomElement > > vectorLayerList;
1871  createEmbeddedLayer( QgsLayerTree::toLayer( child )->layerId(), projectFilePath, brokenNodes, vectorLayerList, false );
1872  }
1873  }
1874 }
1875 
1876 void QgsProject::setSnapSettingsForLayer( const QString& layerId, bool enabled, QgsSnapper::SnappingType type, QgsTolerance::UnitType unit, double tolerance, bool avoidIntersection )
1877 {
1878  QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList;
1879  snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList );
1880  int idx = layerIdList.indexOf( layerId );
1881  if ( idx != -1 )
1882  {
1883  layerIdList.removeAt( idx );
1884  enabledList.removeAt( idx );
1885  snapTypeList.removeAt( idx );
1886  toleranceUnitList.removeAt( idx );
1887  toleranceList.removeAt( idx );
1888  avoidIntersectionList.removeOne( layerId );
1889  }
1890 
1891  layerIdList.append( layerId );
1892 
1893  //enabled
1894  enabledList.append( enabled ? "enabled" : "disabled" );
1895 
1896  //snap type
1897  QString typeString;
1898  if ( type == QgsSnapper::SnapToSegment )
1899  {
1900  typeString = "to_segment";
1901  }
1902  else if ( type == QgsSnapper::SnapToVertexAndSegment )
1903  {
1904  typeString = "to_vertex_and_segment";
1905  }
1906  else
1907  {
1908  typeString = "to_vertex";
1909  }
1910  snapTypeList.append( typeString );
1911 
1912  //units
1913  toleranceUnitList.append( unit == QgsTolerance::Pixels ? "1" : "0" );
1914 
1915  //tolerance
1916  toleranceList.append( QString::number( tolerance ) );
1917 
1918  //avoid intersection
1919  if ( avoidIntersection )
1920  {
1921  avoidIntersectionList.append( layerId );
1922  }
1923 
1924  writeEntry( "Digitizing", "/LayerSnappingList", layerIdList );
1925  writeEntry( "Digitizing", "/LayerSnappingEnabledList", enabledList );
1926  writeEntry( "Digitizing", "/LayerSnappingToleranceList", toleranceList );
1927  writeEntry( "Digitizing", "/LayerSnappingToleranceUnitList", toleranceUnitList );
1928  writeEntry( "Digitizing", "/LayerSnapToList", snapTypeList );
1929  writeEntry( "Digitizing", "/AvoidIntersectionsList", avoidIntersectionList );
1930  emit snapSettingsChanged();
1931 }
1932 
1933 bool QgsProject::snapSettingsForLayer( const QString& layerId, bool& enabled, QgsSnapper::SnappingType &type, QgsTolerance::UnitType& units, double& tolerance,
1934  bool& avoidIntersection ) const
1935 {
1936  QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList;
1937  snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList );
1938  int idx = layerIdList.indexOf( layerId );
1939  if ( idx == -1 )
1940  {
1941  return false;
1942  }
1943 
1944  //make sure all lists are long enough
1945  int minListEntries = idx + 1;
1946  if ( layerIdList.size() < minListEntries || enabledList.size() < minListEntries || snapTypeList.size() < minListEntries ||
1947  toleranceUnitList.size() < minListEntries || toleranceList.size() < minListEntries )
1948  {
1949  return false;
1950  }
1951 
1952  //enabled
1953  enabled = enabledList.at( idx ) == "enabled";
1954 
1955  //snap type
1956  QString snapType = snapTypeList.at( idx );
1957  if ( snapType == "to_segment" )
1958  {
1960  }
1961  else if ( snapType == "to_vertex_and_segment" )
1962  {
1964  }
1965  else //to vertex
1966  {
1967  type = QgsSnapper::SnapToVertex;
1968  }
1969 
1970  //units
1971  if ( toleranceUnitList.at( idx ) == "1" )
1972  {
1973  units = QgsTolerance::Pixels;
1974  }
1975  else
1976  {
1977  units = QgsTolerance::MapUnits;
1978  }
1979 
1980  //tolerance
1981  tolerance = toleranceList.at( idx ).toDouble();
1982 
1983  //avoid intersection
1984  avoidIntersection = ( avoidIntersectionList.indexOf( layerId ) != -1 );
1985 
1986  return true;
1987 }
1988 
1989 void QgsProject::snapSettings( QStringList& layerIdList, QStringList& enabledList, QStringList& snapTypeList, QStringList& toleranceUnitList, QStringList& toleranceList,
1990  QStringList& avoidIntersectionList ) const
1991 {
1992  layerIdList = readListEntry( "Digitizing", "/LayerSnappingList" );
1993  enabledList = readListEntry( "Digitizing", "/LayerSnappingEnabledList" );
1994  toleranceList = readListEntry( "Digitizing", "/LayerSnappingToleranceList" );
1995  toleranceUnitList = readListEntry( "Digitizing", "/LayerSnappingToleranceUnitList" );
1996  snapTypeList = readListEntry( "Digitizing", "/LayerSnapToList" );
1997  avoidIntersectionList = readListEntry( "Digitizing", "/AvoidIntersectionsList" );
1998 }
1999 
2001 {
2002  QgsProject::instance()->writeEntry( "Digitizing", "/TopologicalEditing", ( enabled ? 1 : 0 ) );
2003  emit snapSettingsChanged();
2004 }
2005 
2007 {
2008  return ( QgsProject::instance()->readNumEntry( "Digitizing", "/TopologicalEditing", 0 ) > 0 );
2009 }
2010 
2011 void QgsProjectBadLayerDefaultHandler::handleBadLayers( QList<QDomNode> /*layers*/, QDomDocument /*projectDom*/ )
2012 {
2013  // just ignore any bad layers
2014 }
2015 
2016 QString QgsProject::homePath() const
2017 {
2018  QFileInfo pfi( fileName() );
2019  if ( !pfi.exists() )
2020  return QString::null;
2021 
2022  return pfi.canonicalPath();
2023 }
2024 
2026 {
2027  return mRelationManager;
2028 }
2029 
2031 {
2032  return mRootGroup;
2033 }
QgsLayerTreeGroup * createEmbeddedGroup(const QString &groupName, const QString &projectFilePath)
Create layer group instance defined in an arbitrary project file.
QString mErrorMessage
Definition: qgsproject.h:421
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
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:428
QgsPropertyKey properties_
property hierarchy
Definition: qgsproject.cpp:301
QgsLayerTreeRegistryBridge * mLayerTreeRegistryBridge
Definition: qgsproject.h:437
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.
Pixels unit of tolerance.
Definition: qgstolerance.h:38
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
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:435
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
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.
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
static void updateEmbeddedGroupsProjectPath(QgsLayerTreeGroup *group)
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:423
virtual void handleBadLayers(QList< QDomNode > layers, QDomDocument projectDom)
double readDoubleEntry(const QString &scope, const QString &key, double def=0, bool *ok=0) const
Map unit value.
Definition: qgstolerance.h:36
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:417
std::auto_ptr< Imp > imp_
implementation handle
Definition: qgsproject.h:412
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:433
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:454
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:444
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)