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