Quantum GIS API Documentation  1.7.4
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 
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines