|
QGIS API Documentation
master-59fd5e0
|
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 }