QGIS API Documentation  2.0.1-Dufour
 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 "qgslogger.h"
24 #include "qgsrectangle.h"
25 #include "qgsvectorlayer.h"
26 #include "qgsrasterlayer.h"
27 #include "qgsmaplayerregistry.h"
28 #include "qgsexception.h"
29 #include "qgsprojectproperty.h"
31 #include "qgsprojectversion.h"
32 #include "qgspluginlayer.h"
33 #include "qgspluginlayerregistry.h"
34 #include "qgsdatasourceuri.h"
35 
36 #include <QApplication>
37 #include <QFileInfo>
38 #include <QDomNode>
39 #include <QObject>
40 #include <QTextStream>
41 
42 // canonical project instance
44 
53 static
54 QStringList makeKeyTokens_( QString const &scope, QString const &key )
55 {
56  // XXX - debugger probes
57  //const char * scope_str = scope.toLocal8Bit().data();
58  //const char * key_str = key.toLocal8Bit().data();
59 
60  QStringList keyTokens = QStringList( scope );
61  keyTokens += key.split( '/', QString::SkipEmptyParts );
62 
63  // be sure to include the canonical root node
64  keyTokens.push_front( "properties" );
65 
66  return keyTokens;
67 } // makeKeyTokens_
68 
69 
70 
71 
81 static
82 QgsProperty * findKey_( QString const & scope,
83  QString const & key,
84  QgsPropertyKey & rootProperty )
85 {
86  QgsPropertyKey * currentProperty = &rootProperty;
87  QgsProperty * nextProperty; // link to next property down hiearchy
88 
89  QStringList keySequence = makeKeyTokens_( scope, key );
90 
91  while ( ! keySequence.isEmpty() )
92  {
93  // if the current head of the sequence list matches the property name,
94  // then traverse down the property hierarchy
95  if ( keySequence.first() == currentProperty->name() )
96  {
97  // remove front key since we're traversing down a level
98  keySequence.pop_front();
99 
100  // if we have only one key name left, then return the key found
101  if ( 1 == keySequence.count() )
102  {
103  return currentProperty->find( keySequence.front() );
104 
105  }
106  // if we're out of keys then the current property is the one we
107  // want; i.e., we're in the rate case of being at the top-most
108  // property node
109  else if ( keySequence.isEmpty() )
110  {
111  return currentProperty;
112  }
113  else if (( nextProperty = currentProperty->find( keySequence.first() ) ) )
114  {
115  if ( nextProperty->isKey() )
116  {
117  currentProperty = dynamic_cast<QgsPropertyKey*>( nextProperty );
118  }
119  // it may be that this may be one of several property value
120  // nodes keyed by QDict string; if this is the last remaining
121  // key token and the next property is a value node, then
122  // that's the situation, so return the currentProperty
123  else if ( nextProperty->isValue() && ( 1 == keySequence.count() ) )
124  {
125  return currentProperty;
126  }
127  else // QgsPropertyValue not Key, so return null
128  {
129  return 0x0;
130  }
131  }
132  else // if the next key down isn't found
133  { // then the overall key sequence doesn't exist
134  return 0x0;
135  }
136  }
137  else
138  {
139  return 0x0;
140  }
141  }
142 
143  return 0x0;
144 } // findKey_
145 
146 
147 
155 static
156 QgsProperty * addKey_( QString const & scope,
157  QString const & key,
158  QgsPropertyKey * rootProperty,
159  QVariant value )
160 {
161  QStringList keySequence = makeKeyTokens_( scope, key );
162 
163  // cursor through property key/value hierarchy
164  QgsPropertyKey * currentProperty = rootProperty;
165 
166  QgsProperty * newProperty; // link to next property down hiearchy
167 
168  while ( ! keySequence.isEmpty() )
169  {
170  // if the current head of the sequence list matches the property name,
171  // then traverse down the property hierarchy
172  if ( keySequence.first() == currentProperty->name() )
173  {
174  // remove front key since we're traversing down a level
175  keySequence.pop_front();
176 
177  // if key sequence has one last element, then we use that as the
178  // name to store the value
179  if ( 1 == keySequence.count() )
180  {
181  currentProperty->setValue( keySequence.front(), value );
182  return currentProperty;
183  }
184  // we're at the top element if popping the keySequence element
185  // will leave it empty; in that case, just add the key
186  else if ( keySequence.isEmpty() )
187  {
188  currentProperty->setValue( value );
189 
190  return currentProperty;
191  }
192  else if (( newProperty = currentProperty->find( keySequence.first() ) ) )
193  {
194  currentProperty = dynamic_cast<QgsPropertyKey*>( newProperty );
195 
196  if ( currentProperty )
197  {
198  continue;
199  }
200  else // QgsPropertyValue not Key, so return null
201  {
202  return 0x0;
203  }
204  }
205  else // the next subkey doesn't exist, so add it
206  {
207  newProperty = currentProperty->addKey( keySequence.first() );
208 
209  if ( newProperty )
210  {
211  currentProperty = dynamic_cast<QgsPropertyKey*>( newProperty );
212  }
213  continue;
214  }
215  }
216  else
217  {
218  return 0x0;
219  }
220  }
221 
222  return 0x0;
223 
224 } // addKey_
225 
226 
227 
228 static
229 void removeKey_( QString const & scope,
230  QString const & key,
231  QgsPropertyKey & rootProperty )
232 {
233  QgsPropertyKey * currentProperty = &rootProperty;
234 
235  QgsProperty * nextProperty = NULL; // link to next property down hiearchy
236  QgsPropertyKey * previousQgsPropertyKey = NULL; // link to previous property up hiearchy
237 
238  QStringList keySequence = makeKeyTokens_( scope, key );
239 
240  while ( ! keySequence.isEmpty() )
241  {
242  // if the current head of the sequence list matches the property name,
243  // then traverse down the property hierarchy
244  if ( keySequence.first() == currentProperty->name() )
245  {
246  // remove front key since we're traversing down a level
247  keySequence.pop_front();
248 
249  // if we have only one key name left, then try to remove the key
250  // with that name
251  if ( 1 == keySequence.count() )
252  {
253  currentProperty->removeKey( keySequence.front() );
254  }
255  // if we're out of keys then the current property is the one we
256  // want to remove, but we can't delete it directly; we need to
257  // delete it from the parent property key container
258  else if ( keySequence.isEmpty() )
259  {
260  previousQgsPropertyKey->removeKey( currentProperty->name() );
261  }
262  else if (( nextProperty = currentProperty->find( keySequence.first() ) ) )
263  {
264  previousQgsPropertyKey = currentProperty;
265  currentProperty = dynamic_cast<QgsPropertyKey*>( nextProperty );
266 
267  if ( currentProperty )
268  {
269  continue;
270  }
271  else // QgsPropertyValue not Key, so return null
272  {
273  return;
274  }
275  }
276  else // if the next key down isn't found
277  { // then the overall key sequence doesn't exist
278  return;
279  }
280  }
281  else
282  {
283  return;
284  }
285  }
286 
287 } // void removeKey_
288 
289 
290 
292 {
294  QFile file;
295 
298 
300  QString title;
301 
303  bool dirty;
304 
305  Imp()
306  : title( "" ),
307  dirty( false )
308  { // top property node is the root
309  // "properties" that contains all plug-in
310  // and extra property keys and values
311  properties_.name() = "properties"; // root property node always this value
312  }
313 
316  void clear()
317  {
318  //QgsDebugMsg( "Clearing project properties Impl->clear();" );
319 
321  title = "";
322 
323  // reset some default project properties
324  // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
325  QgsProject::instance()->writeEntry( "PositionPrecision", "/Automatic", true );
326  QgsProject::instance()->writeEntry( "PositionPrecision", "/DecimalPlaces", 2 );
327  QgsProject::instance()->writeEntry( "Paths", "/Absolute", false );
328  }
329 
330 }; // struct QgsProject::Imp
331 
332 
333 
335  : imp_( new QgsProject::Imp ), mBadLayerHandler( new QgsProjectBadLayerDefaultHandler() )
336 {
337  // Set some default project properties
338  // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
339  writeEntry( "PositionPrecision", "/Automatic", true );
340  writeEntry( "PositionPrecision", "/DecimalPlaces", 2 );
341  writeEntry( "Paths", "/Absolute", false );
342  // XXX writeEntry() makes the project dirty, but it doesn't make sense
343  // for a new project to be dirty, so let's clean it up
344  dirty( false );
345 } // QgsProject ctor
346 
347 
348 
350 {
351  delete mBadLayerHandler;
352 
353  // note that std::auto_ptr automatically deletes imp_ when it's destroyed
354 } // QgsProject dtor
355 
356 
357 
359 {
360  if ( !QgsProject::theProject_ )
361  {
362  QgsProject::theProject_ = new QgsProject;
363  }
364 
366 } // QgsProject * instance()
367 
368 
369 
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 
721 bool QgsProject::addLayer( const QDomElement& layerElem, QList<QDomNode>& brokenNodes, QList< QPair< QgsVectorLayer*, QDomElement > >& vectorLayerList )
722 {
723  QString type = layerElem.attribute( "type" );
724  QgsDebugMsg( "Layer type is " + type );
725  QgsMapLayer *mapLayer = NULL;
726 
727  if ( type == "vector" )
728  {
729  mapLayer = new QgsVectorLayer;
730  }
731  else if ( type == "raster" )
732  {
733  mapLayer = new QgsRasterLayer;
734  }
735  else if ( type == "plugin" )
736  {
737  QString typeName = layerElem.attribute( "name" );
738  mapLayer = QgsPluginLayerRegistry::instance()->createLayer( typeName );
739  }
740 
741  if ( !mapLayer )
742  {
743  QgsDebugMsg( "Unable to create layer" );
744 
745  return false;
746  }
747 
748  Q_CHECK_PTR( mapLayer );
749 
750  // have the layer restore state that is stored in Dom node
751  if ( mapLayer->readLayerXML( layerElem ) && mapLayer->isValid() )
752  {
753  emit readMapLayer( mapLayer, layerElem );
754 
755  QList<QgsMapLayer *> myLayers;
756  myLayers << mapLayer;
757  QgsMapLayerRegistry::instance()->addMapLayers( myLayers );
758  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer*>( mapLayer );
759  if ( vLayer && vLayer->vectorJoins().size() > 0 )
760  {
761  vectorLayerList.push_back( qMakePair( vLayer, layerElem ) );
762  }
763  return true;
764  }
765  else
766  {
767  delete mapLayer;
768 
769  QgsDebugMsg( "Unable to load " + type + " layer" );
770  brokenNodes.push_back( layerElem );
771  return false;
772  }
773 }
774 
775 
779 bool QgsProject::read( QFileInfo const &file )
780 {
781  imp_->file.setFileName( file.filePath() );
782 
783  return read();
784 } // QgsProject::read
785 
786 
787 
792 {
793  clearError();
794 
795  std::auto_ptr< QDomDocument > doc =
796  std::auto_ptr < QDomDocument > ( new QDomDocument( "qgis" ) );
797 
798  if ( !imp_->file.open( QIODevice::ReadOnly | QIODevice::Text ) )
799  {
800  imp_->file.close();
801 
802  setError( tr( "Unable to open %1" ).arg( imp_->file.fileName() ) );
803 
804  return false;
805  }
806 
807  // location of problem associated with errorMsg
808  int line, column;
809  QString errorMsg;
810 
811  if ( !doc->setContent( &imp_->file, &errorMsg, &line, &column ) )
812  {
813  // want to make this class as GUI independent as possible; so commented out
814 #if 0
815  QMessageBox::critical( 0, tr( "Project File Read Error" ),
816  tr( "%1 at line %2 column %3" ).arg( errorMsg ).arg( line ).arg( column ) );
817 #endif
818 
819  QString errorString = tr( "Project file read error: %1 at line %2 column %3" )
820  .arg( errorMsg ).arg( line ).arg( column );
821 
822  QgsDebugMsg( errorString );
823 
824  imp_->file.close();
825 
826  setError( tr( "%1 for file %2" ).arg( errorString ).arg( imp_->file.fileName() ) );
827 
828  return false;
829  }
830 
831  imp_->file.close();
832 
833 
834  QgsDebugMsg( "Opened document " + imp_->file.fileName() );
835  QgsDebugMsg( "Project title: " + imp_->title );
836 
837  // get project version string, if any
838  QgsProjectVersion fileVersion = _getVersion( *doc );
839  QgsProjectVersion thisVersion( QGis::QGIS_VERSION );
840 
841  if ( thisVersion > fileVersion )
842  {
843  QgsLogger::warning( "Loading a file that was saved with an older "
844  "version of qgis (saved in " + fileVersion.text() +
845  ", loaded in " + QGis::QGIS_VERSION +
846  "). Problems may occur." );
847 
848  QgsProjectFileTransform projectFile( *doc, fileVersion );
849 
851  emit oldProjectVersionWarning( fileVersion.text() );
852  QgsDebugMsg( "Emitting oldProjectVersionWarning(oldVersion)." );
853 
854  projectFile.dump();
855 
856  projectFile.updateRevision( thisVersion );
857 
858  projectFile.dump();
859 
860  }
861 
862  // before we start loading everything, let's clear out the current set of
863  // properties first so that we don't have the properties from the previous
864  // project still hanging around
865 
866  imp_->clear();
867  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  // if we have problems creating or otherwise writing to the project file,
940  // let's find out up front before we go through all the hand-waving
941  // necessary to create all the Dom objects
942  if ( !imp_->file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
943  {
944  imp_->file.close(); // even though we got an error, let's make
945  // sure it's closed anyway
946 
947  setError( tr( "Unable to save to file %1" ).arg( imp_->file.fileName() ) );
948  return false;
949  }
950  QFileInfo myFileInfo( imp_->file );
951  if ( !myFileInfo.isWritable() )
952  {
953  // even though we got an error, let's make
954  // sure it's closed anyway
955  imp_->file.close();
956  setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." )
957  .arg( imp_->file.fileName() ) );
958  return false;
959  }
960 
961 
962 
963  QDomImplementation DomImplementation;
964  DomImplementation.setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
965 
966  QDomDocumentType documentType =
967  DomImplementation.createDocumentType( "qgis", "http://mrcc.com/qgis.dtd",
968  "SYSTEM" );
969  std::auto_ptr < QDomDocument > doc =
970  std::auto_ptr < QDomDocument > ( new QDomDocument( documentType ) );
971 
972 
973  QDomElement qgisNode = doc->createElement( "qgis" );
974  qgisNode.setAttribute( "projectname", title() );
975  qgisNode.setAttribute( "version", QString( "%1" ).arg( QGis::QGIS_VERSION ) );
976 
977  doc->appendChild( qgisNode );
978 
979  // title
980  QDomElement titleNode = doc->createElement( "title" );
981  qgisNode.appendChild( titleNode );
982 
983  QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
984  titleNode.appendChild( titleText );
985 
986  // let map canvas and legend write their information
987  emit writeProject( *doc );
988 
989  // within top level node save list of layers
990  const QMap<QString, QgsMapLayer*> & layers = QgsMapLayerRegistry::instance()->mapLayers();
991 
992  // Iterate over layers in zOrder
993  // Call writeXML() on each
994  QDomElement projectLayersNode = doc->createElement( "projectlayers" );
995  projectLayersNode.setAttribute( "layercount", qulonglong( layers.size() ) );
996 
997  QMap<QString, QgsMapLayer*>::ConstIterator li = layers.constBegin();
998  while ( li != layers.end() )
999  {
1000  //QgsMapLayer *ml = QgsMapLayerRegistry::instance()->mapLayer(*li);
1001  QgsMapLayer* ml = li.value();
1002 
1003  if ( ml )
1004  {
1005  QString externalProjectFile = layerIsEmbedded( ml->id() );
1006  QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.find( ml->id() );
1007  if ( emIt == mEmbeddedLayers.constEnd() )
1008  {
1009  // general layer metadata
1010  QDomElement maplayerElem = doc->createElement( "maplayer" );
1011 
1012  ml->writeLayerXML( maplayerElem, *doc );
1013 
1014  emit writeMapLayer( ml, maplayerElem, *doc );
1015 
1016  projectLayersNode.appendChild( maplayerElem );
1017  }
1018  else //layer defined in an external project file
1019  {
1020  //only save embedded layer if not managed by a legend group
1021  if ( emIt.value().second )
1022  {
1023  QDomElement mapLayerElem = doc->createElement( "maplayer" );
1024  mapLayerElem.setAttribute( "embedded", 1 );
1025  mapLayerElem.setAttribute( "project", writePath( emIt.value().first ) );
1026  mapLayerElem.setAttribute( "id", ml->id() );
1027  projectLayersNode.appendChild( mapLayerElem );
1028  }
1029  }
1030  }
1031  li++;
1032  }
1033 
1034  qgisNode.appendChild( projectLayersNode );
1035 
1036  // now add the optional extra properties
1037 
1038  dump_( imp_->properties_ );
1039 
1040  QgsDebugMsg( QString( "there are %1 property scopes" ).arg( static_cast<int>( imp_->properties_.count() ) ) );
1041 
1042  if ( !imp_->properties_.isEmpty() ) // only worry about properties if we
1043  // actually have any properties
1044  {
1045  imp_->properties_.writeXML( "properties", qgisNode, *doc );
1046  }
1047 
1048  // now wrap it up and ship it to the project file
1049  doc->normalize(); // XXX I'm not entirely sure what this does
1050 
1051  //QString xml = doc->toString(4); // write to string with indentation of four characters
1052  // (yes, four is arbitrary)
1053 
1054  // const char * xmlString = xml; // debugger probe point
1055  // qDebug( "project file output:\n\n" + xml );
1056 
1057  QTextStream projectFileStream( &imp_->file );
1058 
1059  //projectFileStream << xml << endl;
1060  doc->save( projectFileStream, 4 ); // save as utf-8
1061  imp_->file.close();
1062 
1063  // check if the text stream had no error - if it does
1064  // the user will get a message so they can try to resolve the
1065  // situation e.g. by saving project to a volume with more space
1066  //
1067  if ( projectFileStream.pos() == -1 || imp_->file.error() != QFile::NoError )
1068  {
1069  setError( tr( "Unable to save to file %1. Your project "
1070  "may be corrupted on disk. Try clearing some space on the volume and "
1071  "check file permissions before pressing save again." )
1072  .arg( imp_->file.fileName() ) );
1073  return false;
1074  }
1075 
1076  dirty( false ); // reset to pristine state
1077 
1078  emit projectSaved();
1079 
1080  return true;
1081 } // QgsProject::write
1082 
1083 
1084 
1086 {
1087  //QgsDebugMsg("entered.");
1088 
1089  imp_->clear();
1090 
1091  dirty( true );
1092 } // QgsProject::clearProperties()
1093 
1094 
1095 
1096 bool
1097 QgsProject::writeEntry( QString const &scope, const QString & key, bool value )
1098 {
1099  dirty( true );
1100 
1101  return addKey_( scope, key, &imp_->properties_, value );
1102 } // QgsProject::writeEntry ( ..., bool value )
1103 
1104 
1105 bool
1106 QgsProject::writeEntry( QString const &scope, const QString & key,
1107  double value )
1108 {
1109  dirty( true );
1110 
1111  return addKey_( scope, key, &imp_->properties_, value );
1112 } // QgsProject::writeEntry ( ..., double value )
1113 
1114 
1115 bool
1116 QgsProject::writeEntry( QString const &scope, const QString & key, int value )
1117 {
1118  dirty( true );
1119 
1120  return addKey_( scope, key, &imp_->properties_, value );
1121 } // QgsProject::writeEntry ( ..., int value )
1122 
1123 
1124 bool
1125 QgsProject::writeEntry( QString const &scope, const QString & key,
1126  const QString & value )
1127 {
1128  dirty( true );
1129 
1130  return addKey_( scope, key, &imp_->properties_, value );
1131 } // QgsProject::writeEntry ( ..., const QString & value )
1132 
1133 
1134 bool
1135 QgsProject::writeEntry( QString const &scope, const QString & key,
1136  const QStringList & value )
1137 {
1138  dirty( true );
1139 
1140  return addKey_( scope, key, &imp_->properties_, value );
1141 } // QgsProject::writeEntry ( ..., const QStringList & value )
1142 
1143 
1144 
1145 
1146 QStringList
1147 QgsProject::readListEntry( QString const & scope,
1148  const QString & key,
1149  QStringList def,
1150  bool * ok ) const
1151 {
1152  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1153 
1154  QVariant value;
1155 
1156  if ( property )
1157  {
1158  value = property->value();
1159  }
1160 
1161  bool valid = QVariant::StringList == value.type();
1162 
1163  if ( ok )
1164  {
1165  *ok = valid;
1166  }
1167 
1168  if ( valid )
1169  {
1170  return value.toStringList();
1171  }
1172 
1173  return def;
1174 } // QgsProject::readListEntry
1175 
1176 
1177 QString
1178 QgsProject::readEntry( QString const & scope,
1179  const QString & key,
1180  const QString & def,
1181  bool * ok ) const
1182 {
1183  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1184 
1185  QVariant value;
1186 
1187  if ( property )
1188  {
1189  value = property->value();
1190  }
1191 
1192  bool valid = value.canConvert( QVariant::String );
1193 
1194  if ( ok )
1195  {
1196  *ok = valid;
1197  }
1198 
1199  if ( valid )
1200  {
1201  return value.toString();
1202  }
1203 
1204  return QString( def );
1205 } // QgsProject::readEntry
1206 
1207 
1208 int
1209 QgsProject::readNumEntry( QString const &scope, const QString & key, int def,
1210  bool * ok ) const
1211 {
1212  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1213 
1214  QVariant value;
1215 
1216  if ( property )
1217  {
1218  value = property->value();
1219  }
1220 
1221  bool valid = value.canConvert( QVariant::String );
1222 
1223  if ( ok )
1224  {
1225  *ok = valid;
1226  }
1227 
1228  if ( valid )
1229  {
1230  return value.toInt();
1231  }
1232 
1233  return def;
1234 } // QgsProject::readNumEntry
1235 
1236 
1237 double
1238 QgsProject::readDoubleEntry( QString const &scope, const QString & key,
1239  double def,
1240  bool * ok ) const
1241 {
1242  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1243 
1244  QVariant value;
1245 
1246  if ( property )
1247  {
1248  value = property->value();
1249  }
1250 
1251  bool valid = value.canConvert( QVariant::Double );
1252 
1253  if ( ok )
1254  {
1255  *ok = valid;
1256  }
1257 
1258  if ( valid )
1259  {
1260  return value.toDouble();
1261  }
1262 
1263  return def;
1264 } // QgsProject::readDoubleEntry
1265 
1266 
1267 bool
1268 QgsProject::readBoolEntry( QString const &scope, const QString & key, bool def,
1269  bool * ok ) const
1270 {
1271  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1272 
1273  QVariant value;
1274 
1275  if ( property )
1276  {
1277  value = property->value();
1278  }
1279 
1280  bool valid = value.canConvert( QVariant::Bool );
1281 
1282  if ( ok )
1283  {
1284  *ok = valid;
1285  }
1286 
1287  if ( valid )
1288  {
1289  return value.toBool();
1290  }
1291 
1292  return def;
1293 } // QgsProject::readBoolEntry
1294 
1295 
1296 bool QgsProject::removeEntry( QString const &scope, const QString & key )
1297 {
1298  removeKey_( scope, key, imp_->properties_ );
1299 
1300  dirty( true );
1301 
1302  return ! findKey_( scope, key, imp_->properties_ );
1303 } // QgsProject::removeEntry
1304 
1305 
1306 
1307 QStringList QgsProject::entryList( QString const &scope, QString const &key ) const
1308 {
1309  QgsProperty * foundProperty = findKey_( scope, key, imp_->properties_ );
1310 
1311  QStringList entries;
1312 
1313  if ( foundProperty )
1314  {
1315  QgsPropertyKey * propertyKey = dynamic_cast<QgsPropertyKey*>( foundProperty );
1316 
1317  if ( propertyKey )
1318  { propertyKey->entryList( entries ); }
1319  }
1320 
1321  return entries;
1322 } // QgsProject::entryList
1323 
1324 
1325 QStringList
1326 QgsProject::subkeyList( QString const &scope, QString const &key ) const
1327 {
1328  QgsProperty * foundProperty = findKey_( scope, key, imp_->properties_ );
1329 
1330  QStringList entries;
1331 
1332  if ( foundProperty )
1333  {
1334  QgsPropertyKey * propertyKey = dynamic_cast<QgsPropertyKey*>( foundProperty );
1335 
1336  if ( propertyKey )
1337  { propertyKey->subkeyList( entries ); }
1338  }
1339 
1340  return entries;
1341 
1342 } // QgsProject::subkeyList
1343 
1344 
1345 
1347 {
1348  dump_( imp_->properties_ );
1349 } // QgsProject::dumpProperties
1350 
1351 
1352 // return the absolute path from a filename read from project file
1353 QString QgsProject::readPath( QString src ) const
1354 {
1355  if ( readBoolEntry( "Paths", "/Absolute", false ) )
1356  {
1357  return src;
1358  }
1359 
1360  // relative path should always start with ./ or ../
1361  if ( !src.startsWith( "./" ) && !src.startsWith( "../" ) )
1362  {
1363 #if defined(Q_OS_WIN)
1364  if ( src.startsWith( "\\\\" ) ||
1365  src.startsWith( "//" ) ||
1366  ( src[0].isLetter() && src[1] == ':' ) )
1367  {
1368  // UNC or absolute path
1369  return src;
1370  }
1371 #else
1372  if ( src[0] == '/' )
1373  {
1374  // absolute path
1375  return src;
1376  }
1377 #endif
1378 
1379  // so this one isn't absolute, but also doesn't start // with ./ or ../.
1380  // That means that it was saved with an earlier version of "relative path support",
1381  // where the source file had to exist and only the project directory was stripped
1382  // from the filename.
1383  QString home = homePath();
1384  if ( home.isNull() )
1385  return src;
1386 
1387  QFileInfo fi( home + "/" + src );
1388 
1389  if ( !fi.exists() )
1390  {
1391  return src;
1392  }
1393  else
1394  {
1395  return fi.canonicalFilePath();
1396  }
1397  }
1398 
1399  QString srcPath = src;
1400  QString projPath = fileName();
1401 
1402  if ( projPath.isEmpty() )
1403  {
1404  return src;
1405  }
1406 
1407 #if defined(Q_OS_WIN)
1408  srcPath.replace( "\\", "/" );
1409  projPath.replace( "\\", "/" );
1410 
1411  bool uncPath = projPath.startsWith( "//" );
1412 #endif
1413 
1414  QStringList srcElems = srcPath.split( "/", QString::SkipEmptyParts );
1415  QStringList projElems = projPath.split( "/", QString::SkipEmptyParts );
1416 
1417 #if defined(Q_OS_WIN)
1418  if ( uncPath )
1419  {
1420  projElems.insert( 0, "" );
1421  projElems.insert( 0, "" );
1422  }
1423 #endif
1424 
1425  // remove project file element
1426  projElems.removeLast();
1427 
1428  // append source path elements
1429  projElems << srcElems;
1430  projElems.removeAll( "." );
1431 
1432  // resolve ..
1433  int pos;
1434  while (( pos = projElems.indexOf( ".." ) ) > 0 )
1435  {
1436  // remove preceding element and ..
1437  projElems.removeAt( pos - 1 );
1438  projElems.removeAt( pos - 1 );
1439  }
1440 
1441 #if !defined(Q_OS_WIN)
1442  // make path absolute
1443  projElems.prepend( "" );
1444 #endif
1445 
1446  return projElems.join( "/" );
1447 }
1448 
1449 // return the absolute or relative path to write it to the project file
1450 QString QgsProject::writePath( QString src ) const
1451 {
1452  if ( readBoolEntry( "Paths", "/Absolute", false ) || src.isEmpty() )
1453  {
1454  return src;
1455  }
1456 
1457  QString srcPath = src;
1458  QString projPath = fileName();
1459 
1460  if ( projPath.isEmpty() )
1461  {
1462  return src;
1463  }
1464 
1465 #if defined( Q_OS_WIN )
1466  const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
1467 
1468  srcPath.replace( "\\", "/" );
1469 
1470  if ( srcPath.startsWith( "//" ) )
1471  {
1472  // keep UNC prefix
1473  srcPath = "\\\\" + srcPath.mid( 2 );
1474  }
1475 
1476  projPath.replace( "\\", "/" );
1477  if ( projPath.startsWith( "//" ) )
1478  {
1479  // keep UNC prefix
1480  projPath = "\\\\" + projPath.mid( 2 );
1481  }
1482 #else
1483  const Qt::CaseSensitivity cs = Qt::CaseSensitive;
1484 #endif
1485 
1486  QStringList projElems = projPath.split( "/", QString::SkipEmptyParts );
1487  QStringList srcElems = srcPath.split( "/", QString::SkipEmptyParts );
1488 
1489  // remove project file element
1490  projElems.removeLast();
1491 
1492  projElems.removeAll( "." );
1493  srcElems.removeAll( "." );
1494 
1495  // remove common part
1496  int n = 0;
1497  while ( srcElems.size() > 0 &&
1498  projElems.size() > 0 &&
1499  srcElems[0].compare( projElems[0], cs ) == 0 )
1500  {
1501  srcElems.removeFirst();
1502  projElems.removeFirst();
1503  n++;
1504  }
1505 
1506  if ( n == 0 )
1507  {
1508  // no common parts; might not even by a file
1509  return src;
1510  }
1511 
1512  if ( projElems.size() > 0 )
1513  {
1514  // go up to the common directory
1515  for ( int i = 0; i < projElems.size(); i++ )
1516  {
1517  srcElems.insert( 0, ".." );
1518  }
1519  }
1520  else
1521  {
1522  // let it start with . nevertheless,
1523  // so relative path always start with either ./ or ../
1524  srcElems.insert( 0, "." );
1525  }
1526 
1527  return srcElems.join( "/" );
1528 }
1529 
1530 void QgsProject::setError( QString errorMessage )
1531 {
1532  mErrorMessage = errorMessage;
1533 }
1534 
1535 QString QgsProject::error() const
1536 {
1537  return mErrorMessage;
1538 }
1539 
1541 {
1542  setError( QString() );
1543 }
1544 
1546 {
1547  delete mBadLayerHandler;
1548  mBadLayerHandler = handler;
1549 }
1550 
1551 QString QgsProject::layerIsEmbedded( const QString& id ) const
1552 {
1553  QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
1554  if ( it == mEmbeddedLayers.constEnd() )
1555  {
1556  return QString();
1557  }
1558  return it.value().first;
1559 }
1560 
1561 bool QgsProject::createEmbeddedLayer( const QString& layerId, const QString& projectFilePath, QList<QDomNode>& brokenNodes,
1562  QList< QPair< QgsVectorLayer*, QDomElement > >& vectorLayerList, bool saveFlag )
1563 {
1564  QFile projectFile( projectFilePath );
1565  if ( !projectFile.open( QIODevice::ReadOnly ) )
1566  {
1567  return false;
1568  }
1569 
1570  QDomDocument projectDocument;
1571  if ( !projectDocument.setContent( &projectFile ) )
1572  {
1573  return false;
1574  }
1575 
1576  //does project store pathes absolute or relative?
1577  bool useAbsolutePathes = true;
1578  QDomElement propertiesElem = projectDocument.documentElement().firstChildElement( "properties" );
1579  if ( !propertiesElem.isNull() )
1580  {
1581  QDomElement absElem = propertiesElem.firstChildElement( "Paths" ).firstChildElement( "Absolute" );
1582  if ( !absElem.isNull() )
1583  {
1584  useAbsolutePathes = absElem.text().compare( "true", Qt::CaseInsensitive ) == 0;
1585  }
1586  }
1587 
1588  QDomElement projectLayersElem = projectDocument.documentElement().firstChildElement( "projectlayers" );
1589  if ( projectLayersElem.isNull() )
1590  {
1591  return false;
1592  }
1593 
1594  QDomNodeList mapLayerNodes = projectLayersElem.elementsByTagName( "maplayer" );
1595  for ( int i = 0; i < mapLayerNodes.size(); ++i )
1596  {
1597  //get layer id
1598  QDomElement mapLayerElem = mapLayerNodes.at( i ).toElement();
1599  QString id = mapLayerElem.firstChildElement( "id" ).text();
1600  if ( id == layerId )
1601  {
1602  //layer can be embedded only once
1603  if ( mapLayerElem.attribute( "embedded" ) == "1" )
1604  {
1605  return false;
1606  }
1607 
1608  mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
1609 
1610  //change datasource path from relative to absolute if necessary
1611  if ( !useAbsolutePathes )
1612  {
1613  QDomElement provider = mapLayerElem.firstChildElement( "provider" );
1614  if ( provider.text() == "spatialite" )
1615  {
1616  QDomElement dsElem = mapLayerElem.firstChildElement( "datasource" );
1617 
1618  QgsDataSourceURI uri( dsElem.text() );
1619 
1620  QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + "/" + uri.database() );
1621  if ( absoluteDs.exists() )
1622  {
1623  uri.setDatabase( absoluteDs.absoluteFilePath() );
1624  dsElem.removeChild( dsElem.childNodes().at( 0 ) );
1625  dsElem.appendChild( projectDocument.createTextNode( uri.uri() ) );
1626  }
1627  }
1628  else
1629  {
1630  QDomElement dsElem = mapLayerElem.firstChildElement( "datasource" );
1631  QString debug( QFileInfo( projectFilePath ).absolutePath() + "/" + dsElem.text() );
1632  QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + "/" + dsElem.text() );
1633  if ( absoluteDs.exists() )
1634  {
1635  dsElem.removeChild( dsElem.childNodes().at( 0 ) );
1636  dsElem.appendChild( projectDocument.createTextNode( absoluteDs.absoluteFilePath() ) );
1637  }
1638  }
1639  }
1640 
1641  if ( addLayer( mapLayerElem, brokenNodes, vectorLayerList ) )
1642  {
1643  return true;
1644  }
1645  else
1646  {
1647  mEmbeddedLayers.remove( layerId );
1648  return false;
1649  }
1650  }
1651  }
1652 
1653  return false;
1654 }
1655 
1656 void QgsProject::setSnapSettingsForLayer( const QString& layerId, bool enabled, QgsSnapper::SnappingType type, QgsTolerance::UnitType unit, double tolerance, bool avoidIntersection )
1657 {
1658  QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList;
1659  snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList );
1660  int idx = layerIdList.indexOf( layerId );
1661  if ( idx != -1 )
1662  {
1663  layerIdList.removeAt( idx );
1664  enabledList.removeAt( idx );
1665  snapTypeList.removeAt( idx );
1666  toleranceUnitList.removeAt( idx );
1667  toleranceList.removeAt( idx );
1668  avoidIntersectionList.removeOne( layerId );
1669  }
1670 
1671  layerIdList.append( layerId );
1672 
1673  //enabled
1674  enabledList.append( enabled ? "enabled" : "disabled" );
1675 
1676  //snap type
1677  QString typeString;
1678  if ( type == QgsSnapper::SnapToSegment )
1679  {
1680  typeString = "to_segment";
1681  }
1682  else if ( type == QgsSnapper::SnapToVertexAndSegment )
1683  {
1684  typeString = "to_vertex_and_segment";
1685  }
1686  else
1687  {
1688  typeString = "to_vertex";
1689  }
1690  snapTypeList.append( typeString );
1691 
1692  //units
1693  toleranceUnitList.append( unit == QgsTolerance::Pixels ? "1" : "0" );
1694 
1695  //tolerance
1696  toleranceList.append( QString::number( tolerance ) );
1697 
1698  //avoid intersection
1699  if ( avoidIntersection )
1700  {
1701  avoidIntersectionList.append( layerId );
1702  }
1703 
1704  writeEntry( "Digitizing", "/LayerSnappingList", layerIdList );
1705  writeEntry( "Digitizing", "/LayerSnappingEnabledList", enabledList );
1706  writeEntry( "Digitizing", "/LayerSnappingToleranceList", toleranceList );
1707  writeEntry( "Digitizing", "/LayerSnappingToleranceUnitList", toleranceUnitList );
1708  writeEntry( "Digitizing", "/LayerSnapToList", snapTypeList );
1709  writeEntry( "Digitizing", "/AvoidIntersectionsList", avoidIntersectionList );
1710  emit snapSettingsChanged();
1711 }
1712 
1713 bool QgsProject::snapSettingsForLayer( const QString& layerId, bool& enabled, QgsSnapper::SnappingType &type, QgsTolerance::UnitType& units, double& tolerance,
1714  bool& avoidIntersection ) const
1715 {
1716  QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList;
1717  snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList );
1718  int idx = layerIdList.indexOf( layerId );
1719  if ( idx == -1 )
1720  {
1721  return false;
1722  }
1723 
1724  //make sure all lists are long enough
1725  int minListEntries = idx + 1;
1726  if ( layerIdList.size() < minListEntries || enabledList.size() < minListEntries || snapTypeList.size() < minListEntries ||
1727  toleranceUnitList.size() < minListEntries || toleranceList.size() < minListEntries )
1728  {
1729  return false;
1730  }
1731 
1732  //enabled
1733  enabled = enabledList.at( idx ) == "enabled";
1734 
1735  //snap type
1736  QString snapType = snapTypeList.at( idx );
1737  if ( snapType == "to_segment" )
1738  {
1740  }
1741  else if ( snapType == "to_vertex_and_segment" )
1742  {
1744  }
1745  else //to vertex
1746  {
1747  type = QgsSnapper::SnapToVertex;
1748  }
1749 
1750  //units
1751  if ( toleranceUnitList.at( idx ) == "1" )
1752  {
1753  units = QgsTolerance::Pixels;
1754  }
1755  else
1756  {
1757  units = QgsTolerance::MapUnits;
1758  }
1759 
1760  //tolerance
1761  tolerance = toleranceList.at( idx ).toDouble();
1762 
1763  //avoid intersection
1764  avoidIntersection = ( avoidIntersectionList.indexOf( layerId ) != -1 );
1765 
1766  return true;
1767 }
1768 
1769 void QgsProject::snapSettings( QStringList& layerIdList, QStringList& enabledList, QStringList& snapTypeList, QStringList& toleranceUnitList, QStringList& toleranceList,
1770  QStringList& avoidIntersectionList ) const
1771 {
1772  layerIdList = readListEntry( "Digitizing", "/LayerSnappingList" );
1773  enabledList = readListEntry( "Digitizing", "/LayerSnappingEnabledList" );
1774  toleranceList = readListEntry( "Digitizing", "/LayerSnappingToleranceList" );
1775  toleranceUnitList = readListEntry( "Digitizing", "/LayerSnappingToleranceUnitList" );
1776  snapTypeList = readListEntry( "Digitizing", "/LayerSnapToList" );
1777  avoidIntersectionList = readListEntry( "Digitizing", "/AvoidIntersectionsList" );
1778 }
1779 
1781 {
1782  QgsProject::instance()->writeEntry( "Digitizing", "/TopologicalEditing", ( enabled ? 1 : 0 ) );
1783  emit snapSettingsChanged();
1784 }
1785 
1787 {
1788  return ( QgsProject::instance()->readNumEntry( "Digitizing", "/TopologicalEditing", 0 ) > 0 );
1789 }
1790 
1791 void QgsProjectBadLayerDefaultHandler::handleBadLayers( QList<QDomNode> /*layers*/, QDomDocument /*projectDom*/ )
1792 {
1793  // just ignore any bad layers
1794 }
1795 
1796 QString QgsProject::homePath() const
1797 {
1798  QFileInfo pfi( fileName() );
1799  if ( !pfi.exists() )
1800  return QString::null;
1801 
1802  return pfi.canonicalPath();
1803 }