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