Quantum GIS API Documentation
1.7.4
|
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 00042 static const char *const ident_ = "$Id$"; 00043 00044 // canonical project instance 00045 QgsProject * QgsProject::theProject_; 00046 00055 static 00056 QStringList makeKeyTokens_( QString const &scope, QString const &key ) 00057 { 00058 // XXX - debugger probes 00059 //const char * scope_str = scope.toLocal8Bit().data(); 00060 //const char * key_str = key.toLocal8Bit().data(); 00061 00062 QStringList keyTokens = QStringList( scope ); 00063 keyTokens += key.split( '/', QString::SkipEmptyParts ); 00064 00065 // be sure to include the canonical root node 00066 keyTokens.push_front( "properties" ); 00067 00068 return keyTokens; 00069 } // makeKeyTokens_ 00070 00071 00072 00073 00083 static 00084 QgsProperty * findKey_( QString const & scope, 00085 QString const & key, 00086 QgsPropertyKey & rootProperty ) 00087 { 00088 QgsPropertyKey * currentProperty = &rootProperty; 00089 QgsProperty * nextProperty; // link to next property down hiearchy 00090 00091 QStringList keySequence = makeKeyTokens_( scope, key ); 00092 00093 while ( ! keySequence.isEmpty() ) 00094 { 00095 // if the current head of the sequence list matches the property name, 00096 // then traverse down the property hierarchy 00097 if ( keySequence.first() == currentProperty->name() ) 00098 { 00099 // remove front key since we're traversing down a level 00100 keySequence.pop_front(); 00101 00102 // if we have only one key name left, then return the key found 00103 if ( 1 == keySequence.count() ) 00104 { 00105 return currentProperty->find( keySequence.front() ); 00106 00107 } 00108 // if we're out of keys then the current property is the one we 00109 // want; i.e., we're in the rate case of being at the top-most 00110 // property node 00111 else if ( keySequence.isEmpty() ) 00112 { 00113 return currentProperty; 00114 } 00115 else if (( nextProperty = currentProperty->find( keySequence.first() ) ) ) 00116 { 00117 if ( nextProperty->isKey() ) 00118 { 00119 currentProperty = dynamic_cast<QgsPropertyKey*>( nextProperty ); 00120 } 00121 // it may be that this may be one of several property value 00122 // nodes keyed by QDict string; if this is the last remaining 00123 // key token and the next property is a value node, then 00124 // that's the situation, so return the currentProperty 00125 else if ( nextProperty->isValue() && ( 1 == keySequence.count() ) ) 00126 { 00127 return currentProperty; 00128 } 00129 else // QgsPropertyValue not Key, so return null 00130 { 00131 return 0x0; 00132 } 00133 } 00134 else // if the next key down isn't found 00135 { // then the overall key sequence doesn't exist 00136 return 0x0; 00137 } 00138 } 00139 else 00140 { 00141 return 0x0; 00142 } 00143 } 00144 00145 return 0x0; 00146 } // findKey_ 00147 00148 00149 00157 static 00158 QgsProperty * addKey_( QString const & scope, 00159 QString const & key, 00160 QgsPropertyKey * rootProperty, 00161 QVariant value ) 00162 { 00163 QStringList keySequence = makeKeyTokens_( scope, key ); 00164 00165 // cursor through property key/value hierarchy 00166 QgsPropertyKey * currentProperty = rootProperty; 00167 00168 QgsProperty * newProperty; // link to next property down hiearchy 00169 00170 while ( ! keySequence.isEmpty() ) 00171 { 00172 // if the current head of the sequence list matches the property name, 00173 // then traverse down the property hierarchy 00174 if ( keySequence.first() == currentProperty->name() ) 00175 { 00176 // remove front key since we're traversing down a level 00177 keySequence.pop_front(); 00178 00179 // if key sequence has one last element, then we use that as the 00180 // name to store the value 00181 if ( 1 == keySequence.count() ) 00182 { 00183 currentProperty->setValue( keySequence.front(), value ); 00184 return currentProperty; 00185 } 00186 // we're at the top element if popping the keySequence element 00187 // will leave it empty; in that case, just add the key 00188 else if ( keySequence.isEmpty() ) 00189 { 00190 currentProperty->setValue( value ); 00191 00192 return currentProperty; 00193 } 00194 else if (( newProperty = currentProperty->find( keySequence.first() ) ) ) 00195 { 00196 currentProperty = dynamic_cast<QgsPropertyKey*>( newProperty ); 00197 00198 if ( currentProperty ) 00199 { 00200 continue; 00201 } 00202 else // QgsPropertyValue not Key, so return null 00203 { 00204 return 0x0; 00205 } 00206 } 00207 else // the next subkey doesn't exist, so add it 00208 { 00209 newProperty = currentProperty->addKey( keySequence.first() ); 00210 00211 if ( newProperty ) 00212 { 00213 currentProperty = dynamic_cast<QgsPropertyKey*>( newProperty ); 00214 } 00215 continue; 00216 } 00217 } 00218 else 00219 { 00220 return 0x0; 00221 } 00222 } 00223 00224 return 0x0; 00225 00226 } // addKey_ 00227 00228 00229 00230 static 00231 void removeKey_( QString const & scope, 00232 QString const & key, 00233 QgsPropertyKey & rootProperty ) 00234 { 00235 QgsPropertyKey * currentProperty = &rootProperty; 00236 00237 QgsProperty * nextProperty = NULL; // link to next property down hiearchy 00238 QgsPropertyKey * previousQgsPropertyKey = NULL; // link to previous property up hiearchy 00239 00240 QStringList keySequence = makeKeyTokens_( scope, key ); 00241 00242 while ( ! keySequence.isEmpty() ) 00243 { 00244 // if the current head of the sequence list matches the property name, 00245 // then traverse down the property hierarchy 00246 if ( keySequence.first() == currentProperty->name() ) 00247 { 00248 // remove front key since we're traversing down a level 00249 keySequence.pop_front(); 00250 00251 // if we have only one key name left, then try to remove the key 00252 // with that name 00253 if ( 1 == keySequence.count() ) 00254 { 00255 currentProperty->removeKey( keySequence.front() ); 00256 } 00257 // if we're out of keys then the current property is the one we 00258 // want to remove, but we can't delete it directly; we need to 00259 // delete it from the parent property key container 00260 else if ( keySequence.isEmpty() ) 00261 { 00262 previousQgsPropertyKey->removeKey( currentProperty->name() ); 00263 } 00264 else if (( nextProperty = currentProperty->find( keySequence.first() ) ) ) 00265 { 00266 previousQgsPropertyKey = currentProperty; 00267 currentProperty = dynamic_cast<QgsPropertyKey*>( nextProperty ); 00268 00269 if ( currentProperty ) 00270 { 00271 continue; 00272 } 00273 else // QgsPropertyValue not Key, so return null 00274 { 00275 return; 00276 } 00277 } 00278 else // if the next key down isn't found 00279 { // then the overall key sequence doesn't exist 00280 return; 00281 } 00282 } 00283 else 00284 { 00285 return; 00286 } 00287 } 00288 00289 } // void removeKey_ 00290 00291 00292 00293 struct QgsProject::Imp 00294 { 00296 QFile file; 00297 00299 QgsPropertyKey properties_; 00300 00302 QString title; 00303 00305 bool dirty; 00306 00307 Imp() 00308 : title( "" ), 00309 dirty( false ) 00310 { // top property node is the root 00311 // "properties" that contains all plug-in 00312 // and extra property keys and values 00313 properties_.name() = "properties"; // root property node always this value 00314 } 00315 00318 void clear() 00319 { 00320 //QgsDebugMsg( "Clearing project properties Impl->clear();" ); 00321 00322 properties_.clearKeys(); 00323 title = ""; 00324 00325 // reset some default project properties 00326 // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE 00327 QgsProject::instance()->writeEntry( "PositionPrecision", "/Automatic", true ); 00328 QgsProject::instance()->writeEntry( "PositionPrecision", "/DecimalPlaces", 2 ); 00329 QgsProject::instance()->writeEntry( "Paths", "/Absolute", false ); 00330 } 00331 00332 }; // struct QgsProject::Imp 00333 00334 00335 00336 QgsProject::QgsProject() 00337 : imp_( new QgsProject::Imp ), mBadLayerHandler( new QgsProjectBadLayerDefaultHandler() ) 00338 { 00339 // Set some default project properties 00340 // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE 00341 writeEntry( "PositionPrecision", "/Automatic", true ); 00342 writeEntry( "PositionPrecision", "/DecimalPlaces", 2 ); 00343 writeEntry( "Paths", "/Absolute", false ); 00344 // XXX writeEntry() makes the project dirty, but it doesn't make sense 00345 // for a new project to be dirty, so let's clean it up 00346 dirty( false ); 00347 } // QgsProject ctor 00348 00349 00350 00351 QgsProject::~QgsProject() 00352 { 00353 delete mBadLayerHandler; 00354 00355 // note that std::auto_ptr automatically deletes imp_ when it's destroyed 00356 } // QgsProject dtor 00357 00358 00359 00360 QgsProject * QgsProject::instance() 00361 { 00362 if ( !QgsProject::theProject_ ) 00363 { 00364 QgsProject::theProject_ = new QgsProject; 00365 } 00366 00367 return QgsProject::theProject_; 00368 } // QgsProject * instance() 00369 00370 00371 00372 00373 void QgsProject::title( QString const &title ) 00374 { 00375 imp_->title = title; 00376 00377 dirty( true ); 00378 } // void QgsProject::title 00379 00380 00381 QString const & QgsProject::title() const 00382 { 00383 return imp_->title; 00384 } // QgsProject::title() const 00385 00386 00387 bool QgsProject::isDirty() const 00388 { 00389 return imp_->dirty; 00390 } // bool QgsProject::isDirty() 00391 00392 00393 void QgsProject::dirty( bool b ) 00394 { 00395 imp_->dirty = b; 00396 } // bool QgsProject::isDirty() 00397 00398 00399 00400 void QgsProject::setFileName( QString const &name ) 00401 { 00402 imp_->file.setFileName( name ); 00403 00404 dirty( true ); 00405 } // void QgsProject::setFileName( QString const & name ) 00406 00407 00408 00409 QString QgsProject::fileName() const 00410 { 00411 return imp_->file.fileName(); 00412 } // QString QgsProject::fileName() const 00413 00414 00415 00417 static void dump_( QgsPropertyKey const & topQgsPropertyKey ) 00418 { 00419 QgsDebugMsg( "current properties:" ); 00420 00421 topQgsPropertyKey.dump(); 00422 } // dump_ 00423 00424 00425 00426 00457 static 00458 void 00459 _getProperties( QDomDocument const &doc, QgsPropertyKey & project_properties ) 00460 { 00461 QDomNodeList properties = doc.elementsByTagName( "properties" ); 00462 00463 if ( properties.count() > 1 ) 00464 { 00465 QgsDebugMsg( "there appears to be more than one ``properties'' XML tag ... bailing" ); 00466 return; 00467 } 00468 else if ( properties.count() < 1 ) // no properties found, so we're done 00469 { 00470 return; 00471 } 00472 00473 // item(0) because there should only be ONE 00474 // "properties" node 00475 QDomNodeList scopes = properties.item( 0 ).childNodes(); 00476 00477 if ( scopes.count() < 1 ) 00478 { 00479 QgsDebugMsg( "empty ``properties'' XML tag ... bailing" ); 00480 return; 00481 } 00482 00483 QDomNode propertyNode = properties.item( 0 ); 00484 00485 if ( ! project_properties.readXML( propertyNode ) ) 00486 { 00487 QgsDebugMsg( "Project_properties.readXML() failed" ); 00488 } 00489 00490 #if 0 00491 // DEPRECATED as functionality has been shoved down to QgsProperyKey::readXML() 00492 size_t i = 0; 00493 while ( i < scopes.count() ) 00494 { 00495 QDomNode curr_scope_node = scopes.item( i ); 00496 00497 qDebug( "found %d property node(s) for scope %s", 00498 curr_scope_node.childNodes().count(), 00499 curr_scope_node.nodeName().utf8().constData() ); 00500 00501 QString key( curr_scope_node.nodeName() ); 00502 00503 QgsPropertyKey * currentKey = 00504 dynamic_cast<QgsPropertyKey*>( project_properties.find( key ) ); 00505 00506 if ( ! currentKey ) 00507 { 00508 // if the property key doesn't yet exist, create an empty instance 00509 // of that key 00510 00511 currentKey = project_properties.addKey( key ); 00512 00513 if ( ! currentKey ) 00514 { 00515 qDebug( "%s:%d unable to add key", __FILE__, __LINE__ ); 00516 } 00517 } 00518 00519 if ( ! currentKey->readXML( curr_scope_node ) ) 00520 { 00521 qDebug( "%s:%d unable to read XML for property %s", __FILE__, __LINE__, 00522 curr_scope_node.nodeName().utf8().constData() ); 00523 } 00524 00525 ++i; 00526 } 00527 #endif 00528 } // _getProperties 00529 00530 00531 00532 00544 static void _getTitle( QDomDocument const &doc, QString & title ) 00545 { 00546 QDomNodeList nl = doc.elementsByTagName( "title" ); 00547 00548 title = ""; // by default the title will be empty 00549 00550 if ( !nl.count() ) 00551 { 00552 QgsDebugMsg( "unable to find title element" ); 00553 return; 00554 } 00555 00556 QDomNode titleNode = nl.item( 0 ); // there should only be one, so zeroth element ok 00557 00558 if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text 00559 { 00560 QgsDebugMsg( "unable to find title element" ); 00561 return; 00562 } 00563 00564 QDomNode titleTextNode = titleNode.firstChild(); // should only have one child 00565 00566 if ( !titleTextNode.isText() ) 00567 { 00568 QgsDebugMsg( "unable to find title element" ); 00569 return; 00570 } 00571 00572 QDomText titleText = titleTextNode.toText(); 00573 00574 title = titleText.data(); 00575 00576 } // _getTitle 00577 00578 00583 static QgsProjectVersion _getVersion( QDomDocument const &doc ) 00584 { 00585 QDomNodeList nl = doc.elementsByTagName( "qgis" ); 00586 00587 if ( !nl.count() ) 00588 { 00589 QgsDebugMsg( " unable to find qgis element in project file" ); 00590 return QgsProjectVersion( 0, 0, 0, QString( "" ) ); 00591 } 00592 00593 QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element ok 00594 00595 QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element 00596 QgsProjectVersion projectVersion( qgisElement.attribute( "version" ) ); 00597 return projectVersion; 00598 } // _getVersion 00599 00600 00601 00649 QPair< bool, QList<QDomNode> > QgsProject::_getMapLayers( QDomDocument const &doc ) 00650 { 00651 // Layer order is set by the restoring the legend settings from project file. 00652 // This is done on the 'readProject( ... )' signal 00653 00654 QDomNodeList nl = doc.elementsByTagName( "maplayer" ); 00655 00656 // XXX what is this used for? QString layerCount( QString::number(nl.count()) ); 00657 00658 QString wk; 00659 00660 QList<QDomNode> brokenNodes; // a list of Dom nodes corresponding to layers 00661 // that we were unable to load; this could be 00662 // because the layers were removed or 00663 // re-located after the project was last saved 00664 00665 // process the map layer nodes 00666 00667 if ( 0 == nl.count() ) // if we have no layers to process, bail 00668 { 00669 return qMakePair( true, brokenNodes ); // Decided to return "true" since it's 00670 // possible for there to be a project with no 00671 // layers; but also, more imporantly, this 00672 // would cause the tests/qgsproject to fail 00673 // since the test suite doesn't currently 00674 // support test layers 00675 } 00676 00677 bool returnStatus = true; 00678 00679 emit layerLoaded( 0, nl.count() ); 00680 00681 //Collect vector layers with joins. 00682 //They need to refresh join caches and symbology infos after all layers are loaded 00683 QList< QPair< QgsVectorLayer*, QDomElement > > vLayerList; 00684 00685 for ( int i = 0; i < nl.count(); i++ ) 00686 { 00687 QDomNode node = nl.item( i ); 00688 QDomElement element = node.toElement(); 00689 00690 QString type = element.attribute( "type" ); 00691 00692 QgsDebugMsg( "Layer type is " + type ); 00693 00694 QgsMapLayer *mapLayer = NULL; 00695 00696 if ( type == "vector" ) 00697 { 00698 mapLayer = new QgsVectorLayer; 00699 } 00700 else if ( type == "raster" ) 00701 { 00702 mapLayer = new QgsRasterLayer; 00703 } 00704 else if ( type == "plugin" ) 00705 { 00706 QString typeName = element.attribute( "name" ); 00707 mapLayer = QgsPluginLayerRegistry::instance()->createLayer( typeName ); 00708 } 00709 00710 Q_CHECK_PTR( mapLayer ); 00711 00712 if ( !mapLayer ) 00713 { 00714 QgsDebugMsg( "Unable to create layer" ); 00715 00716 return qMakePair( false, brokenNodes ); 00717 } 00718 00719 // have the layer restore state that is stored in Dom node 00720 if ( mapLayer->readXML( node ) && mapLayer->isValid() ) 00721 { 00722 mapLayer = QgsMapLayerRegistry::instance()->addMapLayer( mapLayer ); 00723 QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer*>( mapLayer ); 00724 if ( vLayer && vLayer->vectorJoins().size() > 0 ) 00725 { 00726 vLayerList.push_back( qMakePair( vLayer, element ) ); 00727 } 00728 } 00729 else 00730 { 00731 delete mapLayer; 00732 00733 QgsDebugMsg( "Unable to load " + type + " layer" ); 00734 00735 returnStatus = false; // flag that we had problems loading layers 00736 00737 brokenNodes.push_back( node ); 00738 } 00739 emit layerLoaded( i + 1, nl.count() ); 00740 } 00741 00742 //Update field map of layers with joins and create join caches if necessary 00743 //Needs to be done here once all dependent layers are loaded 00744 QString errorMessage; 00745 QList< QPair< QgsVectorLayer*, QDomElement > >::iterator vIt = vLayerList.begin(); 00746 for ( ; vIt != vLayerList.end(); ++vIt ) 00747 { 00748 vIt->first->createJoinCaches(); 00749 vIt->first->updateFieldMap(); 00750 //for old symbology, it is necessary to read the symbology again after having the complete field map 00751 if ( !vIt->first->isUsingRendererV2() ) 00752 { 00753 vIt->first->readSymbology( vIt->second, errorMessage ); 00754 } 00755 } 00756 00757 return qMakePair( returnStatus, brokenNodes ); 00758 00759 } // _getMapLayers 00760 00761 00762 00763 00767 bool QgsProject::read( QFileInfo const &file ) 00768 { 00769 imp_->file.setFileName( file.filePath() ); 00770 00771 return read(); 00772 } // QgsProject::read 00773 00774 00775 00779 bool QgsProject::read() 00780 { 00781 clearError(); 00782 00783 std::auto_ptr< QDomDocument > doc = 00784 std::auto_ptr < QDomDocument > ( new QDomDocument( "qgis" ) ); 00785 00786 if ( !imp_->file.open( QIODevice::ReadOnly ) ) 00787 { 00788 imp_->file.close(); 00789 00790 setError( tr( "Unable to open %1" ).arg( imp_->file.fileName() ) ); 00791 00792 return false; 00793 } 00794 00795 // location of problem associated with errorMsg 00796 int line, column; 00797 QString errorMsg; 00798 00799 if ( !doc->setContent( &imp_->file, &errorMsg, &line, &column ) ) 00800 { 00801 // want to make this class as GUI independent as possible; so commented out 00802 #if 0 00803 QMessageBox::critical( 0, tr( "Project File Read Error" ), 00804 tr( "%1 at line %2 column %3" ).arg( errorMsg ).arg( line ).arg( column ) ); 00805 #endif 00806 00807 QString errorString = tr( "Project file read error: %1 at line %2 column %3" ) 00808 .arg( errorMsg ).arg( line ).arg( column ); 00809 00810 QgsDebugMsg( errorString ); 00811 00812 imp_->file.close(); 00813 00814 setError( tr( "%1 for file %2" ).arg( errorString ).arg( imp_->file.fileName() ) ); 00815 00816 return false; 00817 } 00818 00819 imp_->file.close(); 00820 00821 00822 QgsDebugMsg( "Opened document " + imp_->file.fileName() ); 00823 QgsDebugMsg( "Project title: " + imp_->title ); 00824 00825 // get project version string, if any 00826 QgsProjectVersion fileVersion = _getVersion( *doc ); 00827 QgsProjectVersion thisVersion( QGis::QGIS_VERSION ); 00828 00829 if ( thisVersion > fileVersion ) 00830 { 00831 QgsLogger::warning( "Loading a file that was saved with an older " 00832 "version of qgis (saved in " + fileVersion.text() + 00833 ", loaded in " + QGis::QGIS_VERSION + 00834 "). Problems may occur." ); 00835 00836 QgsProjectFileTransform projectFile( *doc, fileVersion ); 00837 00839 emit oldProjectVersionWarning( fileVersion.text() ); 00840 QgsDebugMsg( "Emitting oldProjectVersionWarning(oldVersion)." ); 00841 00842 projectFile.dump(); 00843 00844 projectFile.updateRevision( thisVersion ); 00845 00846 projectFile.dump(); 00847 00848 } 00849 00850 // before we start loading everything, let's clear out the current set of 00851 // properties first so that we don't have the properties from the previous 00852 // project still hanging around 00853 00854 imp_->clear(); 00855 00856 // now get any properties 00857 _getProperties( *doc, imp_->properties_ ); 00858 00859 QgsDebugMsg( QString::number( imp_->properties_.count() ) + " properties read" ); 00860 00861 dump_( imp_->properties_ ); 00862 00863 00864 // restore the canvas' area of interest 00865 00866 // now get project title 00867 _getTitle( *doc, imp_->title ); 00868 00869 00870 // get the map layers 00871 QPair< bool, QList<QDomNode> > getMapLayersResults = _getMapLayers( *doc ); 00872 00873 // review the integrity of the retrieved map layers 00874 bool clean = getMapLayersResults.first; 00875 00876 if ( !clean ) 00877 { 00878 QgsDebugMsg( "Unable to get map layers from project file." ); 00879 00880 if ( ! getMapLayersResults.second.isEmpty() ) 00881 { 00882 QgsDebugMsg( "there are " + QString::number( getMapLayersResults.second.size() ) + " broken layers" ); 00883 } 00884 00885 // we let a custom handler to decide what to do with missing layers 00886 // (default implementation ignores them, there's also a GUI handler that lets user choose correct path) 00887 mBadLayerHandler->handleBadLayers( getMapLayersResults.second, *doc ); 00888 } 00889 00890 // read the project: used by map canvas and legend 00891 emit readProject( *doc ); 00892 00893 // if all went well, we're allegedly in pristine state 00894 if ( clean ) 00895 dirty( false ); 00896 00897 return true; 00898 00899 } // QgsProject::read 00900 00901 00902 00903 00904 00905 bool QgsProject::read( QDomNode & layerNode ) 00906 { 00907 QString type = layerNode.toElement().attribute( "type" ); 00908 00909 QgsMapLayer *mapLayer = NULL; 00910 00911 if ( type == "vector" ) 00912 { 00913 mapLayer = new QgsVectorLayer; 00914 } 00915 else if ( type == "raster" ) 00916 { 00917 mapLayer = new QgsRasterLayer; 00918 } 00919 else if ( type == "plugin" ) 00920 { 00921 QString typeName = layerNode.toElement().attribute( "name" ); 00922 mapLayer = QgsPluginLayerRegistry::instance()->createLayer( typeName ); 00923 } 00924 else 00925 { 00926 QgsDebugMsg( "bad layer type" ); 00927 return false; 00928 } 00929 00930 if ( !mapLayer ) 00931 { 00932 QgsDebugMsg( "unable to create layer" ); 00933 return false; 00934 } 00935 00936 // have the layer restore state that is stored in Dom node 00937 if ( mapLayer->readXML( layerNode ) ) 00938 { 00939 mapLayer = QgsMapLayerRegistry::instance()->addMapLayer( mapLayer ); 00940 } 00941 else 00942 { 00943 delete mapLayer; 00944 00945 QgsDebugMsg( "unable to load " + type + " layer" ); 00946 00947 return false; 00948 } 00949 00950 return true; 00951 } // QgsProject::read( QDomNode & layerNode ) 00952 00953 00954 00955 bool QgsProject::write( QFileInfo const &file ) 00956 { 00957 imp_->file.setFileName( file.filePath() ); 00958 00959 return write(); 00960 } // QgsProject::write( QFileInfo const & file ) 00961 00962 00963 bool QgsProject::write() 00964 { 00965 clearError(); 00966 00967 // if we have problems creating or otherwise writing to the project file, 00968 // let's find out up front before we go through all the hand-waving 00969 // necessary to create all the Dom objects 00970 if ( !imp_->file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) ) 00971 { 00972 imp_->file.close(); // even though we got an error, let's make 00973 // sure it's closed anyway 00974 00975 setError( tr( "Unable to save to file %1" ).arg( imp_->file.fileName() ) ); 00976 return false; 00977 } 00978 QFileInfo myFileInfo( imp_->file ); 00979 if ( !myFileInfo.isWritable() ) 00980 { 00981 // even though we got an error, let's make 00982 // sure it's closed anyway 00983 imp_->file.close(); 00984 setError( tr( "%1 is not writeable. Please adjust permissions (if possible) and try again." ) 00985 .arg( imp_->file.fileName() ) ); 00986 return false; 00987 } 00988 00989 QDomImplementation DomImplementation; 00990 00991 QDomDocumentType documentType = 00992 DomImplementation.createDocumentType( "qgis", "http://mrcc.com/qgis.dtd", 00993 "SYSTEM" ); 00994 std::auto_ptr < QDomDocument > doc = 00995 std::auto_ptr < QDomDocument > ( new QDomDocument( documentType ) ); 00996 00997 00998 QDomElement qgisNode = doc->createElement( "qgis" ); 00999 qgisNode.setAttribute( "projectname", title() ); 01000 qgisNode.setAttribute( "version", QString( "%1" ).arg( QGis::QGIS_VERSION ) ); 01001 01002 doc->appendChild( qgisNode ); 01003 01004 // title 01005 QDomElement titleNode = doc->createElement( "title" ); 01006 qgisNode.appendChild( titleNode ); 01007 01008 QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE? 01009 titleNode.appendChild( titleText ); 01010 01011 // let map canvas and legend write their information 01012 emit writeProject( *doc ); 01013 01014 // within top level node save list of layers 01015 QMap<QString, QgsMapLayer*> & layers = QgsMapLayerRegistry::instance()->mapLayers(); 01016 01017 // Iterate over layers in zOrder 01018 // Call writeXML() on each 01019 QDomElement projectLayersNode = doc->createElement( "projectlayers" ); 01020 projectLayersNode.setAttribute( "layercount", qulonglong( layers.size() ) ); 01021 01022 QMap<QString, QgsMapLayer*>::iterator li = layers.begin(); 01023 while ( li != layers.end() ) 01024 { 01025 //QgsMapLayer *ml = QgsMapLayerRegistry::instance()->mapLayer(*li); 01026 QgsMapLayer* ml = li.value(); 01027 01028 if ( ml ) 01029 { 01030 ml->writeXML( projectLayersNode, *doc ); 01031 } 01032 li++; 01033 } 01034 01035 qgisNode.appendChild( projectLayersNode ); 01036 01037 // now add the optional extra properties 01038 01039 dump_( imp_->properties_ ); 01040 01041 QgsDebugMsg( QString( "there are %1 property scopes" ).arg( static_cast<int>( imp_->properties_.count() ) ) ); 01042 01043 if ( !imp_->properties_.isEmpty() ) // only worry about properties if we 01044 // actually have any properties 01045 { 01046 imp_->properties_.writeXML( "properties", qgisNode, *doc ); 01047 } 01048 01049 // now wrap it up and ship it to the project file 01050 doc->normalize(); // XXX I'm not entirely sure what this does 01051 01052 //QString xml = doc->toString(4); // write to string with indentation of four characters 01053 // (yes, four is arbitrary) 01054 01055 // const char * xmlString = xml; // debugger probe point 01056 // qDebug( "project file output:\n\n" + xml ); 01057 01058 QTextStream projectFileStream( &imp_->file ); 01059 01060 //projectFileStream << xml << endl; 01061 doc->save( projectFileStream, 4 ); // save as utf-8 01062 imp_->file.close(); 01063 01064 // check if the text stream had no error - if it does 01065 // the user will get a message so they can try to resolve the 01066 // situation e.g. by saving project to a volume with more space 01067 // 01068 if ( projectFileStream.pos() == -1 || imp_->file.error() != QFile::NoError ) 01069 { 01070 setError( tr( "Unable to save to file %1. Your project " 01071 "may be corrupted on disk. Try clearing some space on the volume and " 01072 "check file permissions before pressing save again." ) 01073 .arg( imp_->file.fileName() ) ); 01074 return false; 01075 } 01076 01077 dirty( false ); // reset to pristine state 01078 01079 return true; 01080 } // QgsProject::write 01081 01082 01083 01084 void QgsProject::clearProperties() 01085 { 01086 //QgsDebugMsg("entered."); 01087 01088 imp_->clear(); 01089 01090 dirty( true ); 01091 } // QgsProject::clearProperties() 01092 01093 01094 01095 bool 01096 QgsProject::writeEntry( QString const &scope, const QString & key, bool value ) 01097 { 01098 dirty( true ); 01099 01100 return addKey_( scope, key, &imp_->properties_, value ); 01101 } // QgsProject::writeEntry ( ..., bool value ) 01102 01103 01104 bool 01105 QgsProject::writeEntry( QString const &scope, const QString & key, 01106 double value ) 01107 { 01108 dirty( true ); 01109 01110 return addKey_( scope, key, &imp_->properties_, value ); 01111 } // QgsProject::writeEntry ( ..., double value ) 01112 01113 01114 bool 01115 QgsProject::writeEntry( QString const &scope, const QString & key, int value ) 01116 { 01117 dirty( true ); 01118 01119 return addKey_( scope, key, &imp_->properties_, value ); 01120 } // QgsProject::writeEntry ( ..., int value ) 01121 01122 01123 bool 01124 QgsProject::writeEntry( QString const &scope, const QString & key, 01125 const QString & value ) 01126 { 01127 dirty( true ); 01128 01129 return addKey_( scope, key, &imp_->properties_, value ); 01130 } // QgsProject::writeEntry ( ..., const QString & value ) 01131 01132 01133 bool 01134 QgsProject::writeEntry( QString const &scope, const QString & key, 01135 const QStringList & value ) 01136 { 01137 dirty( true ); 01138 01139 return addKey_( scope, key, &imp_->properties_, value ); 01140 } // QgsProject::writeEntry ( ..., const QStringList & value ) 01141 01142 01143 01144 01145 QStringList 01146 QgsProject::readListEntry( QString const & scope, 01147 const QString & key, 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 QStringList(); 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 QFileInfo pfi( fileName() ); 01382 if ( !pfi.exists() ) 01383 return src; 01384 01385 QFileInfo fi( pfi.canonicalPath() + "/" + 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 void QgsProjectBadLayerDefaultHandler::handleBadLayers( QList<QDomNode> /*layers*/, QDomDocument /*projectDom*/ ) 01550 { 01551 // just ignore any bad layers 01552 }