Quantum GIS API Documentation  1.8
src/core/qgsproject.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002                           qgsproject.cpp -  description
00003                              -------------------
00004   begin                : July 23, 2004
00005   copyright            : (C) 2004 by Mark Coletti
00006   email                : mcoletti at gmail.com
00007 ***************************************************************************/
00008 
00009 /***************************************************************************
00010  *                                                                         *
00011  *   This program is free software; you can redistribute it and/or modify  *
00012  *   it under the terms of the GNU General Public License as published by  *
00013  *   the Free Software Foundation; either version 2 of the License, or     *
00014  *   (at your option) any later version.                                   *
00015  *                                                                         *
00016  ***************************************************************************/
00017 
00018 #include "qgsproject.h"
00019 
00020 #include <deque>
00021 #include <memory>
00022 
00023 #include "qgslogger.h"
00024 #include "qgsrectangle.h"
00025 #include "qgsvectorlayer.h"
00026 #include "qgsrasterlayer.h"
00027 #include "qgsmaplayerregistry.h"
00028 #include "qgsexception.h"
00029 #include "qgsprojectproperty.h"
00030 #include "qgsprojectfiletransform.h"
00031 #include "qgsprojectversion.h"
00032 #include "qgspluginlayer.h"
00033 #include "qgspluginlayerregistry.h"
00034 
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines