QGIS API Documentation  master-6227475
src/core/qgsproject.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002                           qgsproject.cpp -  description
00003                              -------------------
00004   begin                : July 23, 2004
00005   copyright            : (C) 2004 by Mark Coletti
00006   email                : mcoletti at gmail.com
00007 ***************************************************************************/
00008 
00009 /***************************************************************************
00010  *                                                                         *
00011  *   This program is free software; you can redistribute it and/or modify  *
00012  *   it under the terms of the GNU General Public License as published by  *
00013  *   the Free Software Foundation; either version 2 of the License, or     *
00014  *   (at your option) any later version.                                   *
00015  *                                                                         *
00016  ***************************************************************************/
00017 
00018 #include "qgsproject.h"
00019 
00020 #include <deque>
00021 #include <memory>
00022 
00023 #include "qgslogger.h"
00024 #include "qgsrectangle.h"
00025 #include "qgsvectorlayer.h"
00026 #include "qgsrasterlayer.h"
00027 #include "qgsmaplayerregistry.h"
00028 #include "qgsexception.h"
00029 #include "qgsprojectproperty.h"
00030 #include "qgsprojectfiletransform.h"
00031 #include "qgsprojectversion.h"
00032 #include "qgspluginlayer.h"
00033 #include "qgspluginlayerregistry.h"
00034 #include "qgsdatasourceuri.h"
00035 
00036 #include <QApplication>
00037 #include <QFileInfo>
00038 #include <QDomNode>
00039 #include <QObject>
00040 #include <QTextStream>
00041 
00042 // canonical project instance
00043 QgsProject * QgsProject::theProject_;
00044 
00053 static
00054 QStringList makeKeyTokens_( QString const &scope, QString const &key )
00055 {
00056   // XXX - debugger probes
00057   //const char * scope_str = scope.toLocal8Bit().data();
00058   //const char * key_str   = key.toLocal8Bit().data();
00059 
00060   QStringList keyTokens = QStringList( scope );
00061   keyTokens += key.split( '/', QString::SkipEmptyParts );
00062 
00063   // be sure to include the canonical root node
00064   keyTokens.push_front( "properties" );
00065 
00066   return keyTokens;
00067 } // makeKeyTokens_
00068 
00069 
00070 
00071 
00081 static
00082 QgsProperty * findKey_( QString const & scope,
00083                         QString const & key,
00084                         QgsPropertyKey &   rootProperty )
00085 {
00086   QgsPropertyKey * currentProperty = &rootProperty;
00087   QgsProperty * nextProperty;           // link to next property down hiearchy
00088 
00089   QStringList keySequence = makeKeyTokens_( scope, key );
00090 
00091   while ( ! keySequence.isEmpty() )
00092   {
00093     // if the current head of the sequence list matches the property name,
00094     // then traverse down the property hierarchy
00095     if ( keySequence.first() == currentProperty->name() )
00096     {
00097       // remove front key since we're traversing down a level
00098       keySequence.pop_front();
00099 
00100       // if we have only one key name left, then return the key found
00101       if ( 1 == keySequence.count() )
00102       {
00103         return currentProperty->find( keySequence.front() );
00104 
00105       }
00106       // if we're out of keys then the current property is the one we
00107       // want; i.e., we're in the rate case of being at the top-most
00108       // property node
00109       else if ( keySequence.isEmpty() )
00110       {
00111         return currentProperty;
00112       }
00113       else if (( nextProperty = currentProperty->find( keySequence.first() ) ) )
00114       {
00115         if ( nextProperty->isKey() )
00116         {
00117           currentProperty = dynamic_cast<QgsPropertyKey*>( nextProperty );
00118         }
00119         // it may be that this may be one of several property value
00120         // nodes keyed by QDict string; if this is the last remaining
00121         // key token and the next property is a value node, then
00122         // that's the situation, so return the currentProperty
00123         else if ( nextProperty->isValue() && ( 1 == keySequence.count() ) )
00124         {
00125           return currentProperty;
00126         }
00127         else            // QgsPropertyValue not Key, so return null
00128         {
00129           return 0x0;
00130         }
00131       }
00132       else                // if the next key down isn't found
00133       {                   // then the overall key sequence doesn't exist
00134         return 0x0;
00135       }
00136     }
00137     else
00138     {
00139       return 0x0;
00140     }
00141   }
00142 
00143   return 0x0;
00144 } // findKey_
00145 
00146 
00147 
00155 static
00156 QgsProperty * addKey_( QString const & scope,
00157                        QString const & key,
00158                        QgsPropertyKey * rootProperty,
00159                        QVariant value )
00160 {
00161   QStringList keySequence = makeKeyTokens_( scope, key );
00162 
00163   // cursor through property key/value hierarchy
00164   QgsPropertyKey * currentProperty = rootProperty;
00165 
00166   QgsProperty * newProperty; // link to next property down hiearchy
00167 
00168   while ( ! keySequence.isEmpty() )
00169   {
00170     // if the current head of the sequence list matches the property name,
00171     // then traverse down the property hierarchy
00172     if ( keySequence.first() == currentProperty->name() )
00173     {
00174       // remove front key since we're traversing down a level
00175       keySequence.pop_front();
00176 
00177       // if key sequence has one last element, then we use that as the
00178       // name to store the value
00179       if ( 1 == keySequence.count() )
00180       {
00181         currentProperty->setValue( keySequence.front(), value );
00182         return currentProperty;
00183       }
00184       // we're at the top element if popping the keySequence element
00185       // will leave it empty; in that case, just add the key
00186       else if ( keySequence.isEmpty() )
00187       {
00188         currentProperty->setValue( value );
00189 
00190         return currentProperty;
00191       }
00192       else if (( newProperty = currentProperty->find( keySequence.first() ) ) )
00193       {
00194         currentProperty = dynamic_cast<QgsPropertyKey*>( newProperty );
00195 
00196         if ( currentProperty )
00197         {
00198           continue;
00199         }
00200         else            // QgsPropertyValue not Key, so return null
00201         {
00202           return 0x0;
00203         }
00204       }
00205       else                // the next subkey doesn't exist, so add it
00206       {
00207         newProperty = currentProperty->addKey( keySequence.first() );
00208 
00209         if ( newProperty )
00210         {
00211           currentProperty = dynamic_cast<QgsPropertyKey*>( newProperty );
00212         }
00213         continue;
00214       }
00215     }
00216     else
00217     {
00218       return 0x0;
00219     }
00220   }
00221 
00222   return 0x0;
00223 
00224 } // addKey_
00225 
00226 
00227 
00228 static
00229 void removeKey_( QString const & scope,
00230                  QString const & key,
00231                  QgsPropertyKey & rootProperty )
00232 {
00233   QgsPropertyKey * currentProperty = &rootProperty;
00234 
00235   QgsProperty * nextProperty = NULL;   // link to next property down hiearchy
00236   QgsPropertyKey * previousQgsPropertyKey = NULL; // link to previous property up hiearchy
00237 
00238   QStringList keySequence = makeKeyTokens_( scope, key );
00239 
00240   while ( ! keySequence.isEmpty() )
00241   {
00242     // if the current head of the sequence list matches the property name,
00243     // then traverse down the property hierarchy
00244     if ( keySequence.first() == currentProperty->name() )
00245     {
00246       // remove front key since we're traversing down a level
00247       keySequence.pop_front();
00248 
00249       // if we have only one key name left, then try to remove the key
00250       // with that name
00251       if ( 1 == keySequence.count() )
00252       {
00253         currentProperty->removeKey( keySequence.front() );
00254       }
00255       // if we're out of keys then the current property is the one we
00256       // want to remove, but we can't delete it directly; we need to
00257       // delete it from the parent property key container
00258       else if ( keySequence.isEmpty() )
00259       {
00260         previousQgsPropertyKey->removeKey( currentProperty->name() );
00261       }
00262       else if (( nextProperty = currentProperty->find( keySequence.first() ) ) )
00263       {
00264         previousQgsPropertyKey = currentProperty;
00265         currentProperty = dynamic_cast<QgsPropertyKey*>( nextProperty );
00266 
00267         if ( currentProperty )
00268         {
00269           continue;
00270         }
00271         else            // QgsPropertyValue not Key, so return null
00272         {
00273           return;
00274         }
00275       }
00276       else                // if the next key down isn't found
00277       {                   // then the overall key sequence doesn't exist
00278         return;
00279       }
00280     }
00281     else
00282     {
00283       return;
00284     }
00285   }
00286 
00287 } // void removeKey_
00288 
00289 
00290 
00291 struct QgsProject::Imp
00292 {
00294   QFile file;
00295 
00297   QgsPropertyKey properties_;
00298 
00300   QString title;
00301 
00303   bool dirty;
00304 
00305   Imp()
00306       : title( "" ),
00307       dirty( false )
00308   {                             // top property node is the root
00309     // "properties" that contains all plug-in
00310     // and extra property keys and values
00311     properties_.name() = "properties"; // root property node always this value
00312   }
00313 
00316   void clear()
00317   {
00318     //QgsDebugMsg( "Clearing project properties Impl->clear();" );
00319 
00320     properties_.clearKeys();
00321     title = "";
00322 
00323     // reset some default project properties
00324     // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
00325     QgsProject::instance()->writeEntry( "PositionPrecision", "/Automatic", true );
00326     QgsProject::instance()->writeEntry( "PositionPrecision", "/DecimalPlaces", 2 );
00327     QgsProject::instance()->writeEntry( "Paths", "/Absolute", false );
00328   }
00329 
00330 };  // struct QgsProject::Imp
00331 
00332 
00333 
00334 QgsProject::QgsProject()
00335     : imp_( new QgsProject::Imp ), mBadLayerHandler( new QgsProjectBadLayerDefaultHandler() )
00336 {
00337   // Set some default project properties
00338   // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
00339   writeEntry( "PositionPrecision", "/Automatic", true );
00340   writeEntry( "PositionPrecision", "/DecimalPlaces", 2 );
00341   writeEntry( "Paths", "/Absolute", false );
00342   // XXX writeEntry() makes the project dirty, but it doesn't make sense
00343   // for a new project to be dirty, so let's clean it up
00344   dirty( false );
00345 } // QgsProject ctor
00346 
00347 
00348 
00349 QgsProject::~QgsProject()
00350 {
00351   delete mBadLayerHandler;
00352 
00353   // note that std::auto_ptr automatically deletes imp_ when it's destroyed
00354 } // QgsProject dtor
00355 
00356 
00357 
00358 QgsProject * QgsProject::instance()
00359 {
00360   if ( !QgsProject::theProject_ )
00361   {
00362     QgsProject::theProject_ = new QgsProject;
00363   }
00364 
00365   return QgsProject::theProject_;
00366 } // QgsProject * instance()
00367 
00368 
00369 
00370 
00371 void QgsProject::title( QString const &title )
00372 {
00373   imp_->title = title;
00374 
00375   dirty( true );
00376 } // void QgsProject::title
00377 
00378 
00379 QString const & QgsProject::title() const
00380 {
00381   return imp_->title;
00382 } // QgsProject::title() const
00383 
00384 
00385 bool QgsProject::isDirty() const
00386 {
00387   return imp_->dirty;
00388 } // bool QgsProject::isDirty()
00389 
00390 
00391 void QgsProject::dirty( bool b )
00392 {
00393   imp_->dirty = b;
00394 } // bool QgsProject::isDirty()
00395 
00396 
00397 
00398 void QgsProject::setFileName( QString const &name )
00399 {
00400   imp_->file.setFileName( name );
00401 
00402   dirty( true );
00403 } // void QgsProject::setFileName( QString const & name )
00404 
00405 
00406 
00407 QString QgsProject::fileName() const
00408 {
00409   return imp_->file.fileName();
00410 } // QString QgsProject::fileName() const
00411 
00412 
00413 
00415 static void dump_( QgsPropertyKey const & topQgsPropertyKey )
00416 {
00417   QgsDebugMsg( "current properties:" );
00418 
00419   topQgsPropertyKey.dump();
00420 } // dump_
00421 
00422 
00423 
00424 
00455 static
00456 void
00457 _getProperties( QDomDocument const &doc, QgsPropertyKey & project_properties )
00458 {
00459   QDomNodeList properties = doc.elementsByTagName( "properties" );
00460 
00461   if ( properties.count() > 1 )
00462   {
00463     QgsDebugMsg( "there appears to be more than one ``properties'' XML tag ... bailing" );
00464     return;
00465   }
00466   else if ( properties.count() < 1 )  // no properties found, so we're done
00467   {
00468     return;
00469   }
00470 
00471   // item(0) because there should only be ONE
00472   // "properties" node
00473   QDomNodeList scopes = properties.item( 0 ).childNodes();
00474 
00475   if ( scopes.count() < 1 )
00476   {
00477     QgsDebugMsg( "empty ``properties'' XML tag ... bailing" );
00478     return;
00479   }
00480 
00481   QDomNode propertyNode = properties.item( 0 );
00482 
00483   if ( ! project_properties.readXML( propertyNode ) )
00484   {
00485     QgsDebugMsg( "Project_properties.readXML() failed" );
00486   }
00487 
00488 #if 0
00489 // DEPRECATED as functionality has been shoved down to QgsProperyKey::readXML()
00490   size_t i = 0;
00491   while ( i < scopes.count() )
00492   {
00493     QDomNode curr_scope_node = scopes.item( i );
00494 
00495     qDebug( "found %d property node(s) for scope %s",
00496             curr_scope_node.childNodes().count(),
00497             curr_scope_node.nodeName().utf8().constData() );
00498 
00499     QString key( curr_scope_node.nodeName() );
00500 
00501     QgsPropertyKey * currentKey =
00502       dynamic_cast<QgsPropertyKey*>( project_properties.find( key ) );
00503 
00504     if ( ! currentKey )
00505     {
00506       // if the property key doesn't yet exist, create an empty instance
00507       // of that key
00508 
00509       currentKey = project_properties.addKey( key );
00510 
00511       if ( ! currentKey )
00512       {
00513         qDebug( "%s:%d unable to add key", __FILE__, __LINE__ );
00514       }
00515     }
00516 
00517     if ( ! currentKey->readXML( curr_scope_node ) )
00518     {
00519       qDebug( "%s:%d unable to read XML for property %s", __FILE__, __LINE__,
00520               curr_scope_node.nodeName().utf8().constData() );
00521     }
00522 
00523     ++i;
00524   }
00525 #endif
00526 } // _getProperties
00527 
00528 
00529 
00530 
00542 static void _getTitle( QDomDocument const &doc, QString & title )
00543 {
00544   QDomNodeList nl = doc.elementsByTagName( "title" );
00545 
00546   title = "";                 // by default the title will be empty
00547 
00548   if ( !nl.count() )
00549   {
00550     QgsDebugMsg( "unable to find title element" );
00551     return;
00552   }
00553 
00554   QDomNode titleNode = nl.item( 0 );  // there should only be one, so zeroth element ok
00555 
00556   if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text
00557   {
00558     QgsDebugMsg( "unable to find title element" );
00559     return;
00560   }
00561 
00562   QDomNode titleTextNode = titleNode.firstChild();  // should only have one child
00563 
00564   if ( !titleTextNode.isText() )
00565   {
00566     QgsDebugMsg( "unable to find title element" );
00567     return;
00568   }
00569 
00570   QDomText titleText = titleTextNode.toText();
00571 
00572   title = titleText.data();
00573 
00574 } // _getTitle
00575 
00576 
00581 static QgsProjectVersion _getVersion( QDomDocument const &doc )
00582 {
00583   QDomNodeList nl = doc.elementsByTagName( "qgis" );
00584 
00585   if ( !nl.count() )
00586   {
00587     QgsDebugMsg( " unable to find qgis element in project file" );
00588     return QgsProjectVersion( 0, 0, 0, QString( "" ) );
00589   }
00590 
00591   QDomNode qgisNode = nl.item( 0 );  // there should only be one, so zeroth element ok
00592 
00593   QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
00594   QgsProjectVersion projectVersion( qgisElement.attribute( "version" ) );
00595   return projectVersion;
00596 } // _getVersion
00597 
00598 
00599 
00647 QPair< bool, QList<QDomNode> > QgsProject::_getMapLayers( QDomDocument const &doc )
00648 {
00649   // Layer order is set by the restoring the legend settings from project file.
00650   // This is done on the 'readProject( ... )' signal
00651 
00652   QDomNodeList nl = doc.elementsByTagName( "maplayer" );
00653 
00654   // XXX what is this used for? QString layerCount( QString::number(nl.count()) );
00655 
00656   QString wk;
00657 
00658   QList<QDomNode> brokenNodes; // a list of Dom nodes corresponding to layers
00659   // that we were unable to load; this could be
00660   // because the layers were removed or
00661   // re-located after the project was last saved
00662 
00663   // process the map layer nodes
00664 
00665   if ( 0 == nl.count() )      // if we have no layers to process, bail
00666   {
00667     return qMakePair( true, brokenNodes ); // Decided to return "true" since it's
00668     // possible for there to be a project with no
00669     // layers; but also, more imporantly, this
00670     // would cause the tests/qgsproject to fail
00671     // since the test suite doesn't currently
00672     // support test layers
00673   }
00674 
00675   bool returnStatus = true;
00676 
00677   emit layerLoaded( 0, nl.count() );
00678 
00679   //Collect vector layers with joins.
00680   //They need to refresh join caches and symbology infos after all layers are loaded
00681   QList< QPair< QgsVectorLayer*, QDomElement > > vLayerList;
00682 
00683   for ( int i = 0; i < nl.count(); i++ )
00684   {
00685     QDomNode node = nl.item( i );
00686     QDomElement element = node.toElement();
00687 
00688     QString name = node.namedItem( "layername" ).toElement().text();
00689     if ( !name.isNull() )
00690       emit loadingLayer( tr( "Loading layer %1" ).arg( name ) );
00691 
00692     if ( element.attribute( "embedded" ) == "1" )
00693     {
00694       createEmbeddedLayer( element.attribute( "id" ), readPath( element.attribute( "project" ) ), brokenNodes, vLayerList );
00695       continue;
00696     }
00697     else
00698     {
00699       if ( !addLayer( element, brokenNodes, vLayerList ) )
00700       {
00701         returnStatus = false;
00702       }
00703     }
00704     emit layerLoaded( i + 1, nl.count() );
00705   }
00706 
00707   //Update field map of layers with joins and create join caches if necessary
00708   //Needs to be done here once all dependent layers are loaded
00709   QList< QPair< QgsVectorLayer*, QDomElement > >::iterator vIt = vLayerList.begin();
00710   for ( ; vIt != vLayerList.end(); ++vIt )
00711   {
00712     vIt->first->createJoinCaches();
00713     vIt->first->updateFields();
00714   }
00715 
00716   return qMakePair( returnStatus, brokenNodes );
00717 
00718 } // _getMapLayers
00719 
00720 
00721 bool QgsProject::addLayer( const QDomElement& layerElem, QList<QDomNode>& brokenNodes, QList< QPair< QgsVectorLayer*, QDomElement > >& vectorLayerList )
00722 {
00723   QString type = layerElem.attribute( "type" );
00724   QgsDebugMsg( "Layer type is " + type );
00725   QgsMapLayer *mapLayer = NULL;
00726 
00727   if ( type == "vector" )
00728   {
00729     mapLayer = new QgsVectorLayer;
00730   }
00731   else if ( type == "raster" )
00732   {
00733     mapLayer = new QgsRasterLayer;
00734   }
00735   else if ( type == "plugin" )
00736   {
00737     QString typeName = layerElem.attribute( "name" );
00738     mapLayer = QgsPluginLayerRegistry::instance()->createLayer( typeName );
00739   }
00740 
00741   if ( !mapLayer )
00742   {
00743     QgsDebugMsg( "Unable to create layer" );
00744 
00745     return false;
00746   }
00747 
00748   Q_CHECK_PTR( mapLayer );
00749 
00750   // have the layer restore state that is stored in Dom node
00751   if ( mapLayer->readLayerXML( layerElem ) && mapLayer->isValid() )
00752   {
00753     emit readMapLayer( mapLayer, layerElem );
00754 
00755     QList<QgsMapLayer *> myLayers;
00756     myLayers << mapLayer;
00757     QgsMapLayerRegistry::instance()->addMapLayers( myLayers );
00758     QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer*>( mapLayer );
00759     if ( vLayer && vLayer->vectorJoins().size() > 0 )
00760     {
00761       vectorLayerList.push_back( qMakePair( vLayer, layerElem ) );
00762     }
00763     return true;
00764   }
00765   else
00766   {
00767     delete mapLayer;
00768 
00769     QgsDebugMsg( "Unable to load " + type + " layer" );
00770     brokenNodes.push_back( layerElem );
00771     return false;
00772   }
00773 }
00774 
00775 
00779 bool QgsProject::read( QFileInfo const &file )
00780 {
00781   imp_->file.setFileName( file.filePath() );
00782 
00783   return read();
00784 } // QgsProject::read
00785 
00786 
00787 
00791 bool QgsProject::read()
00792 {
00793   clearError();
00794 
00795   std::auto_ptr< QDomDocument > doc =
00796     std::auto_ptr < QDomDocument > ( new QDomDocument( "qgis" ) );
00797 
00798   if ( !imp_->file.open( QIODevice::ReadOnly | QIODevice::Text ) )
00799   {
00800     imp_->file.close();
00801 
00802     setError( tr( "Unable to open %1" ).arg( imp_->file.fileName() ) );
00803 
00804     return false;
00805   }
00806 
00807   // location of problem associated with errorMsg
00808   int line, column;
00809   QString errorMsg;
00810 
00811   if ( !doc->setContent( &imp_->file, &errorMsg, &line, &column ) )
00812   {
00813     // want to make this class as GUI independent as possible; so commented out
00814 #if 0
00815     QMessageBox::critical( 0, tr( "Project File Read Error" ),
00816                            tr( "%1 at line %2 column %3" ).arg( errorMsg ).arg( line ).arg( column ) );
00817 #endif
00818 
00819     QString errorString = tr( "Project file read error: %1 at line %2 column %3" )
00820                           .arg( errorMsg ).arg( line ).arg( column );
00821 
00822     QgsDebugMsg( errorString );
00823 
00824     imp_->file.close();
00825 
00826     setError( tr( "%1 for file %2" ).arg( errorString ).arg( imp_->file.fileName() ) );
00827 
00828     return false;
00829   }
00830 
00831   imp_->file.close();
00832 
00833 
00834   QgsDebugMsg( "Opened document " + imp_->file.fileName() );
00835   QgsDebugMsg( "Project title: " + imp_->title );
00836 
00837   // get project version string, if any
00838   QgsProjectVersion fileVersion =  _getVersion( *doc );
00839   QgsProjectVersion thisVersion( QGis::QGIS_VERSION );
00840 
00841   if ( thisVersion > fileVersion )
00842   {
00843     QgsLogger::warning( "Loading a file that was saved with an older "
00844                         "version of qgis (saved in " + fileVersion.text() +
00845                         ", loaded in " + QGis::QGIS_VERSION +
00846                         "). Problems may occur." );
00847 
00848     QgsProjectFileTransform projectFile( *doc, fileVersion );
00849 
00851     emit oldProjectVersionWarning( fileVersion.text() );
00852     QgsDebugMsg( "Emitting oldProjectVersionWarning(oldVersion)." );
00853 
00854     projectFile.dump();
00855 
00856     projectFile.updateRevision( thisVersion );
00857 
00858     projectFile.dump();
00859 
00860   }
00861 
00862   // before we start loading everything, let's clear out the current set of
00863   // properties first so that we don't have the properties from the previous
00864   // project still hanging around
00865 
00866   imp_->clear();
00867   mEmbeddedLayers.clear();
00868 
00869   // now get any properties
00870   _getProperties( *doc, imp_->properties_ );
00871 
00872   QgsDebugMsg( QString::number( imp_->properties_.count() ) + " properties read" );
00873 
00874   dump_( imp_->properties_ );
00875 
00876 
00877   // restore the canvas' area of interest
00878 
00879   // now get project title
00880   _getTitle( *doc, imp_->title );
00881 
00882 
00883   // get the map layers
00884   QPair< bool, QList<QDomNode> > getMapLayersResults =  _getMapLayers( *doc );
00885 
00886   // review the integrity of the retrieved map layers
00887   bool clean = getMapLayersResults.first;
00888 
00889   if ( !clean )
00890   {
00891     QgsDebugMsg( "Unable to get map layers from project file." );
00892 
00893     if ( ! getMapLayersResults.second.isEmpty() )
00894     {
00895       QgsDebugMsg( "there are " + QString::number( getMapLayersResults.second.size() ) + " broken layers" );
00896     }
00897 
00898     // we let a custom handler to decide what to do with missing layers
00899     // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
00900     mBadLayerHandler->handleBadLayers( getMapLayersResults.second, *doc );
00901   }
00902 
00903   // read the project: used by map canvas and legend
00904   emit readProject( *doc );
00905 
00906   // if all went well, we're allegedly in pristine state
00907   if ( clean )
00908     dirty( false );
00909 
00910   return true;
00911 
00912 } // QgsProject::read
00913 
00914 
00915 
00916 
00917 
00918 bool QgsProject::read( QDomNode & layerNode )
00919 {
00920   QList<QDomNode> brokenNodes;
00921   QList< QPair< QgsVectorLayer*, QDomElement > > vectorLayerList;
00922   return addLayer( layerNode.toElement(), brokenNodes, vectorLayerList );
00923 } // QgsProject::read( QDomNode & layerNode )
00924 
00925 
00926 
00927 bool QgsProject::write( QFileInfo const &file )
00928 {
00929   imp_->file.setFileName( file.filePath() );
00930 
00931   return write();
00932 } // QgsProject::write( QFileInfo const & file )
00933 
00934 
00935 bool QgsProject::write()
00936 {
00937   clearError();
00938 
00939   // if we have problems creating or otherwise writing to the project file,
00940   // let's find out up front before we go through all the hand-waving
00941   // necessary to create all the Dom objects
00942   if ( !imp_->file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
00943   {
00944     imp_->file.close();         // even though we got an error, let's make
00945     // sure it's closed anyway
00946 
00947     setError( tr( "Unable to save to file %1" ).arg( imp_->file.fileName() ) );
00948     return false;
00949   }
00950   QFileInfo myFileInfo( imp_->file );
00951   if ( !myFileInfo.isWritable() )
00952   {
00953     // even though we got an error, let's make
00954     // sure it's closed anyway
00955     imp_->file.close();
00956     setError( tr( "%1 is not writable. Please adjust permissions (if possible) and try again." )
00957               .arg( imp_->file.fileName() ) );
00958     return false;
00959   }
00960 
00961   QDomImplementation DomImplementation;
00962   DomImplementation.setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
00963 
00964   QDomDocumentType documentType =
00965     DomImplementation.createDocumentType( "qgis", "http://mrcc.com/qgis.dtd",
00966                                           "SYSTEM" );
00967   std::auto_ptr < QDomDocument > doc =
00968     std::auto_ptr < QDomDocument > ( new QDomDocument( documentType ) );
00969 
00970 
00971   QDomElement qgisNode = doc->createElement( "qgis" );
00972   qgisNode.setAttribute( "projectname", title() );
00973   qgisNode.setAttribute( "version", QString( "%1" ).arg( QGis::QGIS_VERSION ) );
00974 
00975   doc->appendChild( qgisNode );
00976 
00977   // title
00978   QDomElement titleNode = doc->createElement( "title" );
00979   qgisNode.appendChild( titleNode );
00980 
00981   QDomText titleText = doc->createTextNode( title() );  // XXX why have title TWICE?
00982   titleNode.appendChild( titleText );
00983 
00984   // let map canvas and legend write their information
00985   emit writeProject( *doc );
00986 
00987   // within top level node save list of layers
00988   const QMap<QString, QgsMapLayer*> & layers = QgsMapLayerRegistry::instance()->mapLayers();
00989 
00990   // Iterate over layers in zOrder
00991   // Call writeXML() on each
00992   QDomElement projectLayersNode = doc->createElement( "projectlayers" );
00993   projectLayersNode.setAttribute( "layercount", qulonglong( layers.size() ) );
00994 
00995   QMap<QString, QgsMapLayer*>::ConstIterator li = layers.constBegin();
00996   while ( li != layers.end() )
00997   {
00998     //QgsMapLayer *ml = QgsMapLayerRegistry::instance()->mapLayer(*li);
00999     QgsMapLayer* ml = li.value();
01000 
01001     if ( ml )
01002     {
01003       QString externalProjectFile = layerIsEmbedded( ml->id() );
01004       QHash< QString, QPair< QString, bool> >::const_iterator emIt = mEmbeddedLayers.find( ml->id() );
01005       if ( emIt == mEmbeddedLayers.constEnd() )
01006       {
01007         // general layer metadata
01008         QDomElement maplayerElem = doc->createElement( "maplayer" );
01009 
01010         ml->writeLayerXML( maplayerElem, *doc );
01011 
01012         emit writeMapLayer( ml, maplayerElem, *doc );
01013 
01014         projectLayersNode.appendChild( maplayerElem );
01015       }
01016       else //layer defined in an external project file
01017       {
01018         //only save embedded layer if not managed by a legend group
01019         if ( emIt.value().second )
01020         {
01021           QDomElement mapLayerElem = doc->createElement( "maplayer" );
01022           mapLayerElem.setAttribute( "embedded", 1 );
01023           mapLayerElem.setAttribute( "project", writePath( emIt.value().first ) );
01024           mapLayerElem.setAttribute( "id", ml->id() );
01025           projectLayersNode.appendChild( mapLayerElem );
01026         }
01027       }
01028     }
01029     li++;
01030   }
01031 
01032   qgisNode.appendChild( projectLayersNode );
01033 
01034   // now add the optional extra properties
01035 
01036   dump_( imp_->properties_ );
01037 
01038   QgsDebugMsg( QString( "there are %1 property scopes" ).arg( static_cast<int>( imp_->properties_.count() ) ) );
01039 
01040   if ( !imp_->properties_.isEmpty() ) // only worry about properties if we
01041     // actually have any properties
01042   {
01043     imp_->properties_.writeXML( "properties", qgisNode, *doc );
01044   }
01045 
01046   // now wrap it up and ship it to the project file
01047   doc->normalize();             // XXX I'm not entirely sure what this does
01048 
01049   //QString xml = doc->toString(4); // write to string with indentation of four characters
01050   // (yes, four is arbitrary)
01051 
01052   // const char * xmlString = xml; // debugger probe point
01053   // qDebug( "project file output:\n\n" + xml );
01054 
01055   QTextStream projectFileStream( &imp_->file );
01056 
01057   //projectFileStream << xml << endl;
01058   doc->save( projectFileStream, 4 );  // save as utf-8
01059   imp_->file.close();
01060 
01061   // check if the text stream had no error - if it does
01062   // the user will get a message so they can try to resolve the
01063   // situation e.g. by saving project to a volume with more space
01064   //
01065   if ( projectFileStream.pos() == -1  || imp_->file.error() != QFile::NoError )
01066   {
01067     setError( tr( "Unable to save to file %1. Your project "
01068                   "may be corrupted on disk. Try clearing some space on the volume and "
01069                   "check file permissions before pressing save again." )
01070               .arg( imp_->file.fileName() ) );
01071     return false;
01072   }
01073 
01074   dirty( false );               // reset to pristine state
01075 
01076   emit projectSaved();
01077 
01078   return true;
01079 } // QgsProject::write
01080 
01081 
01082 
01083 void QgsProject::clearProperties()
01084 {
01085   //QgsDebugMsg("entered.");
01086 
01087   imp_->clear();
01088 
01089   dirty( true );
01090 } // QgsProject::clearProperties()
01091 
01092 
01093 
01094 bool
01095 QgsProject::writeEntry( QString const &scope, const QString & key, bool value )
01096 {
01097   dirty( true );
01098 
01099   return addKey_( scope, key, &imp_->properties_, value );
01100 } // QgsProject::writeEntry ( ..., bool value )
01101 
01102 
01103 bool
01104 QgsProject::writeEntry( QString const &scope, const QString & key,
01105                         double value )
01106 {
01107   dirty( true );
01108 
01109   return addKey_( scope, key, &imp_->properties_, value );
01110 } // QgsProject::writeEntry ( ..., double value )
01111 
01112 
01113 bool
01114 QgsProject::writeEntry( QString const &scope, const QString & key, int value )
01115 {
01116   dirty( true );
01117 
01118   return addKey_( scope, key, &imp_->properties_, value );
01119 } // QgsProject::writeEntry ( ..., int value )
01120 
01121 
01122 bool
01123 QgsProject::writeEntry( QString const &scope, const QString & key,
01124                         const QString & value )
01125 {
01126   dirty( true );
01127 
01128   return addKey_( scope, key, &imp_->properties_, value );
01129 } // QgsProject::writeEntry ( ..., const QString & value )
01130 
01131 
01132 bool
01133 QgsProject::writeEntry( QString const &scope, const QString & key,
01134                         const QStringList & value )
01135 {
01136   dirty( true );
01137 
01138   return addKey_( scope, key, &imp_->properties_, value );
01139 } // QgsProject::writeEntry ( ..., const QStringList & value )
01140 
01141 
01142 
01143 
01144 QStringList
01145 QgsProject::readListEntry( QString const & scope,
01146                            const QString & key,
01147                            QStringList def,
01148                            bool * ok ) const
01149 {
01150   QgsProperty * property = findKey_( scope, key, imp_->properties_ );
01151 
01152   QVariant value;
01153 
01154   if ( property )
01155   {
01156     value = property->value();
01157   }
01158 
01159   bool valid = QVariant::StringList == value.type();
01160 
01161   if ( ok )
01162   {
01163     *ok = valid;
01164   }
01165 
01166   if ( valid )
01167   {
01168     return value.toStringList();
01169   }
01170 
01171   return def;
01172 } // QgsProject::readListEntry
01173 
01174 
01175 QString
01176 QgsProject::readEntry( QString const & scope,
01177                        const QString & key,
01178                        const QString & def,
01179                        bool * ok ) const
01180 {
01181   QgsProperty * property = findKey_( scope, key, imp_->properties_ );
01182 
01183   QVariant value;
01184 
01185   if ( property )
01186   {
01187     value = property->value();
01188   }
01189 
01190   bool valid = value.canConvert( QVariant::String );
01191 
01192   if ( ok )
01193   {
01194     *ok = valid;
01195   }
01196 
01197   if ( valid )
01198   {
01199     return value.toString();
01200   }
01201 
01202   return QString( def );
01203 } // QgsProject::readEntry
01204 
01205 
01206 int
01207 QgsProject::readNumEntry( QString const &scope, const QString & key, int def,
01208                           bool * ok ) const
01209 {
01210   QgsProperty * property = findKey_( scope, key, imp_->properties_ );
01211 
01212   QVariant value;
01213 
01214   if ( property )
01215   {
01216     value = property->value();
01217   }
01218 
01219   bool valid = value.canConvert( QVariant::String );
01220 
01221   if ( ok )
01222   {
01223     *ok = valid;
01224   }
01225 
01226   if ( valid )
01227   {
01228     return value.toInt();
01229   }
01230 
01231   return def;
01232 } // QgsProject::readNumEntry
01233 
01234 
01235 double
01236 QgsProject::readDoubleEntry( QString const &scope, const QString & key,
01237                              double def,
01238                              bool * ok ) const
01239 {
01240   QgsProperty * property = findKey_( scope, key, imp_->properties_ );
01241 
01242   QVariant value;
01243 
01244   if ( property )
01245   {
01246     value = property->value();
01247   }
01248 
01249   bool valid = value.canConvert( QVariant::Double );
01250 
01251   if ( ok )
01252   {
01253     *ok = valid;
01254   }
01255 
01256   if ( valid )
01257   {
01258     return value.toDouble();
01259   }
01260 
01261   return def;
01262 } // QgsProject::readDoubleEntry
01263 
01264 
01265 bool
01266 QgsProject::readBoolEntry( QString const &scope, const QString & key, bool def,
01267                            bool * ok ) const
01268 {
01269   QgsProperty * property = findKey_( scope, key, imp_->properties_ );
01270 
01271   QVariant value;
01272 
01273   if ( property )
01274   {
01275     value = property->value();
01276   }
01277 
01278   bool valid = value.canConvert( QVariant::Bool );
01279 
01280   if ( ok )
01281   {
01282     *ok = valid;
01283   }
01284 
01285   if ( valid )
01286   {
01287     return value.toBool();
01288   }
01289 
01290   return def;
01291 } // QgsProject::readBoolEntry
01292 
01293 
01294 bool QgsProject::removeEntry( QString const &scope, const QString & key )
01295 {
01296   removeKey_( scope, key, imp_->properties_ );
01297 
01298   dirty( true );
01299 
01300   return ! findKey_( scope, key, imp_->properties_ );
01301 } // QgsProject::removeEntry
01302 
01303 
01304 
01305 QStringList QgsProject::entryList( QString const &scope, QString const &key ) const
01306 {
01307   QgsProperty * foundProperty = findKey_( scope, key, imp_->properties_ );
01308 
01309   QStringList entries;
01310 
01311   if ( foundProperty )
01312   {
01313     QgsPropertyKey * propertyKey = dynamic_cast<QgsPropertyKey*>( foundProperty );
01314 
01315     if ( propertyKey )
01316       { propertyKey->entryList( entries ); }
01317   }
01318 
01319   return entries;
01320 } // QgsProject::entryList
01321 
01322 
01323 QStringList
01324 QgsProject::subkeyList( QString const &scope, QString const &key ) const
01325 {
01326   QgsProperty * foundProperty = findKey_( scope, key, imp_->properties_ );
01327 
01328   QStringList entries;
01329 
01330   if ( foundProperty )
01331   {
01332     QgsPropertyKey * propertyKey = dynamic_cast<QgsPropertyKey*>( foundProperty );
01333 
01334     if ( propertyKey )
01335       { propertyKey->subkeyList( entries ); }
01336   }
01337 
01338   return entries;
01339 
01340 } // QgsProject::subkeyList
01341 
01342 
01343 
01344 void QgsProject::dumpProperties() const
01345 {
01346   dump_( imp_->properties_ );
01347 } // QgsProject::dumpProperties
01348 
01349 
01350 // return the absolute path from a filename read from project file
01351 QString QgsProject::readPath( QString src ) const
01352 {
01353   if ( readBoolEntry( "Paths", "/Absolute", false ) )
01354   {
01355     return src;
01356   }
01357 
01358   // relative path should always start with ./ or ../
01359   if ( !src.startsWith( "./" ) && !src.startsWith( "../" ) )
01360   {
01361 #if defined(Q_OS_WIN)
01362     if ( src.startsWith( "\\\\" ) ||
01363          src.startsWith( "//" ) ||
01364          ( src[0].isLetter() && src[1] == ':' ) )
01365     {
01366       // UNC or absolute path
01367       return src;
01368     }
01369 #else
01370     if ( src[0] == '/' )
01371     {
01372       // absolute path
01373       return src;
01374     }
01375 #endif
01376 
01377     // so this one isn't absolute, but also doesn't start // with ./ or ../.
01378     // That means that it was saved with an earlier version of "relative path support",
01379     // where the source file had to exist and only the project directory was stripped
01380     // from the filename.
01381     QString home = homePath();
01382     if ( home.isNull() )
01383       return src;
01384 
01385     QFileInfo fi( home + "/" + src );
01386 
01387     if ( !fi.exists() )
01388     {
01389       return src;
01390     }
01391     else
01392     {
01393       return fi.canonicalFilePath();
01394     }
01395   }
01396 
01397   QString srcPath = src;
01398   QString projPath = fileName();
01399 
01400   if ( projPath.isEmpty() )
01401   {
01402     return src;
01403   }
01404 
01405 #if defined(Q_OS_WIN)
01406   srcPath.replace( "\\", "/" );
01407   projPath.replace( "\\", "/" );
01408 
01409   bool uncPath = projPath.startsWith( "//" );
01410 #endif
01411 
01412   QStringList srcElems = srcPath.split( "/", QString::SkipEmptyParts );
01413   QStringList projElems = projPath.split( "/", QString::SkipEmptyParts );
01414 
01415 #if defined(Q_OS_WIN)
01416   if ( uncPath )
01417   {
01418     projElems.insert( 0, "" );
01419     projElems.insert( 0, "" );
01420   }
01421 #endif
01422 
01423   // remove project file element
01424   projElems.removeLast();
01425 
01426   // append source path elements
01427   projElems << srcElems;
01428   projElems.removeAll( "." );
01429 
01430   // resolve ..
01431   int pos;
01432   while (( pos = projElems.indexOf( ".." ) ) > 0 )
01433   {
01434     // remove preceding element and ..
01435     projElems.removeAt( pos - 1 );
01436     projElems.removeAt( pos - 1 );
01437   }
01438 
01439 #if !defined(Q_OS_WIN)
01440   // make path absolute
01441   projElems.prepend( "" );
01442 #endif
01443 
01444   return projElems.join( "/" );
01445 }
01446 
01447 // return the absolute or relative path to write it to the project file
01448 QString QgsProject::writePath( QString src ) const
01449 {
01450   if ( readBoolEntry( "Paths", "/Absolute", false ) || src.isEmpty() )
01451   {
01452     return src;
01453   }
01454 
01455   QString srcPath = src;
01456   QString projPath = fileName();
01457 
01458   if ( projPath.isEmpty() )
01459   {
01460     return src;
01461   }
01462 
01463 #if defined( Q_OS_WIN )
01464   const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
01465 
01466   srcPath.replace( "\\", "/" );
01467 
01468   if ( srcPath.startsWith( "//" ) )
01469   {
01470     // keep UNC prefix
01471     srcPath = "\\\\" + srcPath.mid( 2 );
01472   }
01473 
01474   projPath.replace( "\\", "/" );
01475   if ( projPath.startsWith( "//" ) )
01476   {
01477     // keep UNC prefix
01478     projPath = "\\\\" + projPath.mid( 2 );
01479   }
01480 #else
01481   const Qt::CaseSensitivity cs = Qt::CaseSensitive;
01482 #endif
01483 
01484   QStringList projElems = projPath.split( "/", QString::SkipEmptyParts );
01485   QStringList srcElems = srcPath.split( "/", QString::SkipEmptyParts );
01486 
01487   // remove project file element
01488   projElems.removeLast();
01489 
01490   projElems.removeAll( "." );
01491   srcElems.removeAll( "." );
01492 
01493   // remove common part
01494   int n = 0;
01495   while ( srcElems.size() > 0 &&
01496           projElems.size() > 0 &&
01497           srcElems[0].compare( projElems[0], cs ) == 0 )
01498   {
01499     srcElems.removeFirst();
01500     projElems.removeFirst();
01501     n++;
01502   }
01503 
01504   if ( n == 0 )
01505   {
01506     // no common parts; might not even by a file
01507     return src;
01508   }
01509 
01510   if ( projElems.size() > 0 )
01511   {
01512     // go up to the common directory
01513     for ( int i = 0; i < projElems.size(); i++ )
01514     {
01515       srcElems.insert( 0, ".." );
01516     }
01517   }
01518   else
01519   {
01520     // let it start with . nevertheless,
01521     // so relative path always start with either ./ or ../
01522     srcElems.insert( 0, "." );
01523   }
01524 
01525   return srcElems.join( "/" );
01526 }
01527 
01528 void QgsProject::setError( QString errorMessage )
01529 {
01530   mErrorMessage = errorMessage;
01531 }
01532 
01533 QString QgsProject::error() const
01534 {
01535   return mErrorMessage;
01536 }
01537 
01538 void QgsProject::clearError()
01539 {
01540   setError( QString() );
01541 }
01542 
01543 void QgsProject::setBadLayerHandler( QgsProjectBadLayerHandler* handler )
01544 {
01545   delete mBadLayerHandler;
01546   mBadLayerHandler = handler;
01547 }
01548 
01549 QString QgsProject::layerIsEmbedded( const QString& id ) const
01550 {
01551   QHash< QString, QPair< QString, bool > >::const_iterator it = mEmbeddedLayers.find( id );
01552   if ( it == mEmbeddedLayers.constEnd() )
01553   {
01554     return QString();
01555   }
01556   return it.value().first;
01557 }
01558 
01559 bool QgsProject::createEmbeddedLayer( const QString& layerId, const QString& projectFilePath, QList<QDomNode>& brokenNodes,
01560                                       QList< QPair< QgsVectorLayer*, QDomElement > >& vectorLayerList, bool saveFlag )
01561 {
01562   QFile projectFile( projectFilePath );
01563   if ( !projectFile.open( QIODevice::ReadOnly ) )
01564   {
01565     return false;
01566   }
01567 
01568   QDomDocument projectDocument;
01569   if ( !projectDocument.setContent( &projectFile ) )
01570   {
01571     return false;
01572   }
01573 
01574   //does project store pathes absolute or relative?
01575   bool useAbsolutePathes = true;
01576   QDomElement propertiesElem = projectDocument.documentElement().firstChildElement( "properties" );
01577   if ( !propertiesElem.isNull() )
01578   {
01579     QDomElement absElem = propertiesElem.firstChildElement( "Paths" ).firstChildElement( "Absolute" );
01580     if ( !absElem.isNull() )
01581     {
01582       useAbsolutePathes = absElem.text().compare( "true", Qt::CaseInsensitive ) == 0;
01583     }
01584   }
01585 
01586   QDomElement projectLayersElem = projectDocument.documentElement().firstChildElement( "projectlayers" );
01587   if ( projectLayersElem.isNull() )
01588   {
01589     return false;
01590   }
01591 
01592   QDomNodeList mapLayerNodes = projectLayersElem.elementsByTagName( "maplayer" );
01593   for ( int i = 0; i < mapLayerNodes.size(); ++i )
01594   {
01595     //get layer id
01596     QDomElement mapLayerElem = mapLayerNodes.at( i ).toElement();
01597     QString id = mapLayerElem.firstChildElement( "id" ).text();
01598     if ( id == layerId )
01599     {
01600       //layer can be embedded only once
01601       if ( mapLayerElem.attribute( "embedded" ) == "1" )
01602       {
01603         return false;
01604       }
01605 
01606       mEmbeddedLayers.insert( layerId, qMakePair( projectFilePath, saveFlag ) );
01607 
01608       //change datasource path from relative to absolute if necessary
01609       if ( !useAbsolutePathes )
01610       {
01611         QDomElement provider = mapLayerElem.firstChildElement( "provider" );
01612         if ( provider.text() == "spatialite" )
01613         {
01614           QDomElement dsElem = mapLayerElem.firstChildElement( "datasource" );
01615 
01616           QgsDataSourceURI uri( dsElem.text() );
01617 
01618           QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + "/" + uri.database() );
01619           if ( absoluteDs.exists() )
01620           {
01621             uri.setDatabase( absoluteDs.absoluteFilePath() );
01622             dsElem.removeChild( dsElem.childNodes().at( 0 ) );
01623             dsElem.appendChild( projectDocument.createTextNode( uri.uri() ) );
01624           }
01625         }
01626         else
01627         {
01628           QDomElement dsElem = mapLayerElem.firstChildElement( "datasource" );
01629           QString debug( QFileInfo( projectFilePath ).absolutePath() + "/" + dsElem.text() );
01630           QFileInfo absoluteDs( QFileInfo( projectFilePath ).absolutePath() + "/" + dsElem.text() );
01631           if ( absoluteDs.exists() )
01632           {
01633             dsElem.removeChild( dsElem.childNodes().at( 0 ) );
01634             dsElem.appendChild( projectDocument.createTextNode( absoluteDs.absoluteFilePath() ) );
01635           }
01636         }
01637       }
01638 
01639       if ( addLayer( mapLayerElem, brokenNodes, vectorLayerList ) )
01640       {
01641         return true;
01642       }
01643       else
01644       {
01645         mEmbeddedLayers.remove( layerId );
01646         return false;
01647       }
01648     }
01649   }
01650 
01651   return false;
01652 }
01653 
01654 void QgsProject::setSnapSettingsForLayer( const QString& layerId, bool enabled, QgsSnapper::SnappingType  type, QgsTolerance::UnitType unit, double tolerance, bool avoidIntersection )
01655 {
01656   QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList;
01657   snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList );
01658   int idx = layerIdList.indexOf( layerId );
01659   if ( idx != -1 )
01660   {
01661     layerIdList.removeAt( idx );
01662     enabledList.removeAt( idx );
01663     snapTypeList.removeAt( idx );
01664     toleranceUnitList.removeAt( idx );
01665     toleranceList.removeAt( idx );
01666     avoidIntersectionList.removeOne( layerId );
01667   }
01668 
01669   layerIdList.append( layerId );
01670 
01671   //enabled
01672   enabledList.append( enabled ? "enabled" : "disabled" );
01673 
01674   //snap type
01675   QString typeString;
01676   if ( type == QgsSnapper::SnapToSegment )
01677   {
01678     typeString = "to_segment";
01679   }
01680   else if ( type == QgsSnapper::SnapToVertexAndSegment )
01681   {
01682     typeString = "to_vertex_and_segment";
01683   }
01684   else
01685   {
01686     typeString = "to_vertex";
01687   }
01688   snapTypeList.append( typeString );
01689 
01690   //units
01691   toleranceUnitList.append( unit == QgsTolerance::Pixels ? "1" : "0" );
01692 
01693   //tolerance
01694   toleranceList.append( QString::number( tolerance ) );
01695 
01696   //avoid intersection
01697   if ( avoidIntersection )
01698   {
01699     avoidIntersectionList.append( layerId );
01700   }
01701 
01702   writeEntry( "Digitizing", "/LayerSnappingList", layerIdList );
01703   writeEntry( "Digitizing", "/LayerSnappingEnabledList", enabledList );
01704   writeEntry( "Digitizing", "/LayerSnappingToleranceList", toleranceList );
01705   writeEntry( "Digitizing", "/LayerSnappingToleranceUnitList", toleranceUnitList );
01706   writeEntry( "Digitizing", "/LayerSnapToList", snapTypeList );
01707   writeEntry( "Digitizing", "/AvoidIntersectionsList", avoidIntersectionList );
01708   emit snapSettingsChanged();
01709 }
01710 
01711 bool QgsProject::snapSettingsForLayer( const QString& layerId, bool& enabled, QgsSnapper::SnappingType &type, QgsTolerance::UnitType& units, double& tolerance,
01712                                        bool& avoidIntersection ) const
01713 {
01714   QStringList layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList;
01715   snapSettings( layerIdList, enabledList, snapTypeList, toleranceUnitList, toleranceList, avoidIntersectionList );
01716   int idx = layerIdList.indexOf( layerId );
01717   if ( idx == -1 )
01718   {
01719     return false;
01720   }
01721 
01722   //make sure all lists are long enough
01723   int minListEntries = idx + 1;
01724   if ( layerIdList.size() < minListEntries || enabledList.size() < minListEntries || snapTypeList.size() < minListEntries ||
01725        toleranceUnitList.size() < minListEntries || toleranceList.size() < minListEntries )
01726   {
01727     return false;
01728   }
01729 
01730   //enabled
01731   enabled = enabledList.at( idx ) == "enabled";
01732 
01733   //snap type
01734   QString snapType = snapTypeList.at( idx );
01735   if ( snapType == "to_segment" )
01736   {
01737     type = QgsSnapper::SnapToSegment;
01738   }
01739   else if ( snapType == "to_vertex_and_segment" )
01740   {
01741     type = QgsSnapper::SnapToVertexAndSegment;
01742   }
01743   else //to vertex
01744   {
01745     type = QgsSnapper::SnapToVertex;
01746   }
01747 
01748   //units
01749   if ( toleranceUnitList.at( idx ) == "1" )
01750   {
01751     units = QgsTolerance::Pixels;
01752   }
01753   else
01754   {
01755     units = QgsTolerance::MapUnits;
01756   }
01757 
01758   //tolerance
01759   tolerance = toleranceList.at( idx ).toDouble();
01760 
01761   //avoid intersection
01762   avoidIntersection = ( avoidIntersectionList.indexOf( layerId ) != -1 );
01763 
01764   return true;
01765 }
01766 
01767 void QgsProject::snapSettings( QStringList& layerIdList, QStringList& enabledList, QStringList& snapTypeList, QStringList& toleranceUnitList, QStringList& toleranceList,
01768                                QStringList& avoidIntersectionList ) const
01769 {
01770   layerIdList = readListEntry( "Digitizing", "/LayerSnappingList" );
01771   enabledList = readListEntry( "Digitizing", "/LayerSnappingEnabledList" );
01772   toleranceList = readListEntry( "Digitizing", "/LayerSnappingToleranceList" );
01773   toleranceUnitList = readListEntry( "Digitizing", "/LayerSnappingToleranceUnitList" );
01774   snapTypeList = readListEntry( "Digitizing", "/LayerSnapToList" );
01775   avoidIntersectionList = readListEntry( "Digitizing", "/AvoidIntersectionsList" );
01776 }
01777 
01778 void QgsProject::setTopologicalEditing( bool enabled )
01779 {
01780   QgsProject::instance()->writeEntry( "Digitizing", "/TopologicalEditing", ( enabled ? 1 : 0 ) );
01781   emit snapSettingsChanged();
01782 }
01783 
01784 bool QgsProject::topologicalEditing() const
01785 {
01786   return ( QgsProject::instance()->readNumEntry( "Digitizing", "/TopologicalEditing", 0 ) > 0 );
01787 }
01788 
01789 void QgsProjectBadLayerDefaultHandler::handleBadLayers( QList<QDomNode> /*layers*/, QDomDocument /*projectDom*/ )
01790 {
01791   // just ignore any bad layers
01792 }
01793 
01794 QString QgsProject::homePath() const
01795 {
01796   QFileInfo pfi( fileName() );
01797   if ( !pfi.exists() )
01798     return QString::null;
01799 
01800   return pfi.canonicalPath();
01801 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines