Quantum GIS API Documentation  1.7.5-Wroclaw
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
qgsproject.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsproject.cpp - description
3  -------------------
4  begin : July 23, 2004
5  copyright : (C) 2004 by Mark Coletti
6  email : mcoletti at gmail.com
7 ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgsproject.h"
19 
20 #include <deque>
21 #include <memory>
22 
23 #include "qgslogger.h"
24 #include "qgsrectangle.h"
25 #include "qgsvectorlayer.h"
26 #include "qgsrasterlayer.h"
27 #include "qgsmaplayerregistry.h"
28 #include "qgsexception.h"
29 #include "qgsprojectproperty.h"
31 #include "qgsprojectversion.h"
32 #include "qgspluginlayer.h"
33 #include "qgspluginlayerregistry.h"
34 
35 #include <QApplication>
36 #include <QFileInfo>
37 #include <QDomNode>
38 #include <QObject>
39 #include <QTextStream>
40 
41 
42 static const char *const ident_ = "$Id$";
43 
44 // canonical project instance
46 
55 static
56 QStringList makeKeyTokens_( QString const &scope, QString const &key )
57 {
58  // XXX - debugger probes
59  //const char * scope_str = scope.toLocal8Bit().data();
60  //const char * key_str = key.toLocal8Bit().data();
61 
62  QStringList keyTokens = QStringList( scope );
63  keyTokens += key.split( '/', QString::SkipEmptyParts );
64 
65  // be sure to include the canonical root node
66  keyTokens.push_front( "properties" );
67 
68  return keyTokens;
69 } // makeKeyTokens_
70 
71 
72 
73 
83 static
84 QgsProperty * findKey_( QString const & scope,
85  QString const & key,
86  QgsPropertyKey & rootProperty )
87 {
88  QgsPropertyKey * currentProperty = &rootProperty;
89  QgsProperty * nextProperty; // link to next property down hiearchy
90 
91  QStringList keySequence = makeKeyTokens_( scope, key );
92 
93  while ( ! keySequence.isEmpty() )
94  {
95  // if the current head of the sequence list matches the property name,
96  // then traverse down the property hierarchy
97  if ( keySequence.first() == currentProperty->name() )
98  {
99  // remove front key since we're traversing down a level
100  keySequence.pop_front();
101 
102  // if we have only one key name left, then return the key found
103  if ( 1 == keySequence.count() )
104  {
105  return currentProperty->find( keySequence.front() );
106 
107  }
108  // if we're out of keys then the current property is the one we
109  // want; i.e., we're in the rate case of being at the top-most
110  // property node
111  else if ( keySequence.isEmpty() )
112  {
113  return currentProperty;
114  }
115  else if (( nextProperty = currentProperty->find( keySequence.first() ) ) )
116  {
117  if ( nextProperty->isKey() )
118  {
119  currentProperty = dynamic_cast<QgsPropertyKey*>( nextProperty );
120  }
121  // it may be that this may be one of several property value
122  // nodes keyed by QDict string; if this is the last remaining
123  // key token and the next property is a value node, then
124  // that's the situation, so return the currentProperty
125  else if ( nextProperty->isValue() && ( 1 == keySequence.count() ) )
126  {
127  return currentProperty;
128  }
129  else // QgsPropertyValue not Key, so return null
130  {
131  return 0x0;
132  }
133  }
134  else // if the next key down isn't found
135  { // then the overall key sequence doesn't exist
136  return 0x0;
137  }
138  }
139  else
140  {
141  return 0x0;
142  }
143  }
144 
145  return 0x0;
146 } // findKey_
147 
148 
149 
157 static
158 QgsProperty * addKey_( QString const & scope,
159  QString const & key,
160  QgsPropertyKey * rootProperty,
161  QVariant value )
162 {
163  QStringList keySequence = makeKeyTokens_( scope, key );
164 
165  // cursor through property key/value hierarchy
166  QgsPropertyKey * currentProperty = rootProperty;
167 
168  QgsProperty * newProperty; // link to next property down hiearchy
169 
170  while ( ! keySequence.isEmpty() )
171  {
172  // if the current head of the sequence list matches the property name,
173  // then traverse down the property hierarchy
174  if ( keySequence.first() == currentProperty->name() )
175  {
176  // remove front key since we're traversing down a level
177  keySequence.pop_front();
178 
179  // if key sequence has one last element, then we use that as the
180  // name to store the value
181  if ( 1 == keySequence.count() )
182  {
183  currentProperty->setValue( keySequence.front(), value );
184  return currentProperty;
185  }
186  // we're at the top element if popping the keySequence element
187  // will leave it empty; in that case, just add the key
188  else if ( keySequence.isEmpty() )
189  {
190  currentProperty->setValue( value );
191 
192  return currentProperty;
193  }
194  else if (( newProperty = currentProperty->find( keySequence.first() ) ) )
195  {
196  currentProperty = dynamic_cast<QgsPropertyKey*>( newProperty );
197 
198  if ( currentProperty )
199  {
200  continue;
201  }
202  else // QgsPropertyValue not Key, so return null
203  {
204  return 0x0;
205  }
206  }
207  else // the next subkey doesn't exist, so add it
208  {
209  newProperty = currentProperty->addKey( keySequence.first() );
210 
211  if ( newProperty )
212  {
213  currentProperty = dynamic_cast<QgsPropertyKey*>( newProperty );
214  }
215  continue;
216  }
217  }
218  else
219  {
220  return 0x0;
221  }
222  }
223 
224  return 0x0;
225 
226 } // addKey_
227 
228 
229 
230 static
231 void removeKey_( QString const & scope,
232  QString const & key,
233  QgsPropertyKey & rootProperty )
234 {
235  QgsPropertyKey * currentProperty = &rootProperty;
236 
237  QgsProperty * nextProperty = NULL; // link to next property down hiearchy
238  QgsPropertyKey * previousQgsPropertyKey = NULL; // link to previous property up hiearchy
239 
240  QStringList keySequence = makeKeyTokens_( scope, key );
241 
242  while ( ! keySequence.isEmpty() )
243  {
244  // if the current head of the sequence list matches the property name,
245  // then traverse down the property hierarchy
246  if ( keySequence.first() == currentProperty->name() )
247  {
248  // remove front key since we're traversing down a level
249  keySequence.pop_front();
250 
251  // if we have only one key name left, then try to remove the key
252  // with that name
253  if ( 1 == keySequence.count() )
254  {
255  currentProperty->removeKey( keySequence.front() );
256  }
257  // if we're out of keys then the current property is the one we
258  // want to remove, but we can't delete it directly; we need to
259  // delete it from the parent property key container
260  else if ( keySequence.isEmpty() )
261  {
262  previousQgsPropertyKey->removeKey( currentProperty->name() );
263  }
264  else if (( nextProperty = currentProperty->find( keySequence.first() ) ) )
265  {
266  previousQgsPropertyKey = currentProperty;
267  currentProperty = dynamic_cast<QgsPropertyKey*>( nextProperty );
268 
269  if ( currentProperty )
270  {
271  continue;
272  }
273  else // QgsPropertyValue not Key, so return null
274  {
275  return;
276  }
277  }
278  else // if the next key down isn't found
279  { // then the overall key sequence doesn't exist
280  return;
281  }
282  }
283  else
284  {
285  return;
286  }
287  }
288 
289 } // void removeKey_
290 
291 
292 
294 {
296  QFile file;
297 
300 
302  QString title;
303 
305  bool dirty;
306 
307  Imp()
308  : title( "" ),
309  dirty( false )
310  { // top property node is the root
311  // "properties" that contains all plug-in
312  // and extra property keys and values
313  properties_.name() = "properties"; // root property node always this value
314  }
315 
318  void clear()
319  {
320  //QgsDebugMsg( "Clearing project properties Impl->clear();" );
321 
323  title = "";
324 
325  // reset some default project properties
326  // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
327  QgsProject::instance()->writeEntry( "PositionPrecision", "/Automatic", true );
328  QgsProject::instance()->writeEntry( "PositionPrecision", "/DecimalPlaces", 2 );
329  QgsProject::instance()->writeEntry( "Paths", "/Absolute", false );
330  }
331 
332 }; // struct QgsProject::Imp
333 
334 
335 
337  : imp_( new QgsProject::Imp ), mBadLayerHandler( new QgsProjectBadLayerDefaultHandler() )
338 {
339  // Set some default project properties
340  // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
341  writeEntry( "PositionPrecision", "/Automatic", true );
342  writeEntry( "PositionPrecision", "/DecimalPlaces", 2 );
343  writeEntry( "Paths", "/Absolute", false );
344  // XXX writeEntry() makes the project dirty, but it doesn't make sense
345  // for a new project to be dirty, so let's clean it up
346  dirty( false );
347 } // QgsProject ctor
348 
349 
350 
352 {
353  delete mBadLayerHandler;
354 
355  // note that std::auto_ptr automatically deletes imp_ when it's destroyed
356 } // QgsProject dtor
357 
358 
359 
361 {
362  if ( !QgsProject::theProject_ )
363  {
364  QgsProject::theProject_ = new QgsProject;
365  }
366 
368 } // QgsProject * instance()
369 
370 
371 
372 
373 void QgsProject::title( QString const &title )
374 {
375  imp_->title = title;
376 
377  dirty( true );
378 } // void QgsProject::title
379 
380 
381 QString const & QgsProject::title() const
382 {
383  return imp_->title;
384 } // QgsProject::title() const
385 
386 
388 {
389  return imp_->dirty;
390 } // bool QgsProject::isDirty()
391 
392 
393 void QgsProject::dirty( bool b )
394 {
395  imp_->dirty = b;
396 } // bool QgsProject::isDirty()
397 
398 
399 
400 void QgsProject::setFileName( QString const &name )
401 {
402  imp_->file.setFileName( name );
403 
404  dirty( true );
405 } // void QgsProject::setFileName( QString const & name )
406 
407 
408 
409 QString QgsProject::fileName() const
410 {
411  return imp_->file.fileName();
412 } // QString QgsProject::fileName() const
413 
414 
415 
417 static void dump_( QgsPropertyKey const & topQgsPropertyKey )
418 {
419  QgsDebugMsg( "current properties:" );
420 
421  topQgsPropertyKey.dump();
422 } // dump_
423 
424 
425 
426 
457 static
458 void
459 _getProperties( QDomDocument const &doc, QgsPropertyKey & project_properties )
460 {
461  QDomNodeList properties = doc.elementsByTagName( "properties" );
462 
463  if ( properties.count() > 1 )
464  {
465  QgsDebugMsg( "there appears to be more than one ``properties'' XML tag ... bailing" );
466  return;
467  }
468  else if ( properties.count() < 1 ) // no properties found, so we're done
469  {
470  return;
471  }
472 
473  // item(0) because there should only be ONE
474  // "properties" node
475  QDomNodeList scopes = properties.item( 0 ).childNodes();
476 
477  if ( scopes.count() < 1 )
478  {
479  QgsDebugMsg( "empty ``properties'' XML tag ... bailing" );
480  return;
481  }
482 
483  QDomNode propertyNode = properties.item( 0 );
484 
485  if ( ! project_properties.readXML( propertyNode ) )
486  {
487  QgsDebugMsg( "Project_properties.readXML() failed" );
488  }
489 
490 #if 0
491 // DEPRECATED as functionality has been shoved down to QgsProperyKey::readXML()
492  size_t i = 0;
493  while ( i < scopes.count() )
494  {
495  QDomNode curr_scope_node = scopes.item( i );
496 
497  qDebug( "found %d property node(s) for scope %s",
498  curr_scope_node.childNodes().count(),
499  curr_scope_node.nodeName().utf8().constData() );
500 
501  QString key( curr_scope_node.nodeName() );
502 
503  QgsPropertyKey * currentKey =
504  dynamic_cast<QgsPropertyKey*>( project_properties.find( key ) );
505 
506  if ( ! currentKey )
507  {
508  // if the property key doesn't yet exist, create an empty instance
509  // of that key
510 
511  currentKey = project_properties.addKey( key );
512 
513  if ( ! currentKey )
514  {
515  qDebug( "%s:%d unable to add key", __FILE__, __LINE__ );
516  }
517  }
518 
519  if ( ! currentKey->readXML( curr_scope_node ) )
520  {
521  qDebug( "%s:%d unable to read XML for property %s", __FILE__, __LINE__,
522  curr_scope_node.nodeName().utf8().constData() );
523  }
524 
525  ++i;
526  }
527 #endif
528 } // _getProperties
529 
530 
531 
532 
544 static void _getTitle( QDomDocument const &doc, QString & title )
545 {
546  QDomNodeList nl = doc.elementsByTagName( "title" );
547 
548  title = ""; // by default the title will be empty
549 
550  if ( !nl.count() )
551  {
552  QgsDebugMsg( "unable to find title element" );
553  return;
554  }
555 
556  QDomNode titleNode = nl.item( 0 ); // there should only be one, so zeroth element ok
557 
558  if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text
559  {
560  QgsDebugMsg( "unable to find title element" );
561  return;
562  }
563 
564  QDomNode titleTextNode = titleNode.firstChild(); // should only have one child
565 
566  if ( !titleTextNode.isText() )
567  {
568  QgsDebugMsg( "unable to find title element" );
569  return;
570  }
571 
572  QDomText titleText = titleTextNode.toText();
573 
574  title = titleText.data();
575 
576 } // _getTitle
577 
578 
583 static QgsProjectVersion _getVersion( QDomDocument const &doc )
584 {
585  QDomNodeList nl = doc.elementsByTagName( "qgis" );
586 
587  if ( !nl.count() )
588  {
589  QgsDebugMsg( " unable to find qgis element in project file" );
590  return QgsProjectVersion( 0, 0, 0, QString( "" ) );
591  }
592 
593  QDomNode qgisNode = nl.item( 0 ); // there should only be one, so zeroth element ok
594 
595  QDomElement qgisElement = qgisNode.toElement(); // qgis node should be element
596  QgsProjectVersion projectVersion( qgisElement.attribute( "version" ) );
597  return projectVersion;
598 } // _getVersion
599 
600 
601 
649 QPair< bool, QList<QDomNode> > QgsProject::_getMapLayers( QDomDocument const &doc )
650 {
651  // Layer order is set by the restoring the legend settings from project file.
652  // This is done on the 'readProject( ... )' signal
653 
654  QDomNodeList nl = doc.elementsByTagName( "maplayer" );
655 
656  // XXX what is this used for? QString layerCount( QString::number(nl.count()) );
657 
658  QString wk;
659 
660  QList<QDomNode> brokenNodes; // a list of Dom nodes corresponding to layers
661  // that we were unable to load; this could be
662  // because the layers were removed or
663  // re-located after the project was last saved
664 
665  // process the map layer nodes
666 
667  if ( 0 == nl.count() ) // if we have no layers to process, bail
668  {
669  return qMakePair( true, brokenNodes ); // Decided to return "true" since it's
670  // possible for there to be a project with no
671  // layers; but also, more imporantly, this
672  // would cause the tests/qgsproject to fail
673  // since the test suite doesn't currently
674  // support test layers
675  }
676 
677  bool returnStatus = true;
678 
679  emit layerLoaded( 0, nl.count() );
680 
681  //Collect vector layers with joins.
682  //They need to refresh join caches and symbology infos after all layers are loaded
683  QList< QPair< QgsVectorLayer*, QDomElement > > vLayerList;
684 
685  for ( int i = 0; i < nl.count(); i++ )
686  {
687  QDomNode node = nl.item( i );
688  QDomElement element = node.toElement();
689 
690  QString type = element.attribute( "type" );
691 
692  QgsDebugMsg( "Layer type is " + type );
693 
694  QgsMapLayer *mapLayer = NULL;
695 
696  if ( type == "vector" )
697  {
698  mapLayer = new QgsVectorLayer;
699  }
700  else if ( type == "raster" )
701  {
702  mapLayer = new QgsRasterLayer;
703  }
704  else if ( type == "plugin" )
705  {
706  QString typeName = element.attribute( "name" );
707  mapLayer = QgsPluginLayerRegistry::instance()->createLayer( typeName );
708  }
709 
710  Q_CHECK_PTR( mapLayer );
711 
712  if ( !mapLayer )
713  {
714  QgsDebugMsg( "Unable to create layer" );
715 
716  return qMakePair( false, brokenNodes );
717  }
718 
719  // have the layer restore state that is stored in Dom node
720  if ( mapLayer->readXML( node ) && mapLayer->isValid() )
721  {
722  mapLayer = QgsMapLayerRegistry::instance()->addMapLayer( mapLayer );
723  QgsVectorLayer* vLayer = qobject_cast<QgsVectorLayer*>( mapLayer );
724  if ( vLayer && vLayer->vectorJoins().size() > 0 )
725  {
726  vLayerList.push_back( qMakePair( vLayer, element ) );
727  }
728  }
729  else
730  {
731  delete mapLayer;
732 
733  QgsDebugMsg( "Unable to load " + type + " layer" );
734 
735  returnStatus = false; // flag that we had problems loading layers
736 
737  brokenNodes.push_back( node );
738  }
739  emit layerLoaded( i + 1, nl.count() );
740  }
741 
742  //Update field map of layers with joins and create join caches if necessary
743  //Needs to be done here once all dependent layers are loaded
744  QString errorMessage;
745  QList< QPair< QgsVectorLayer*, QDomElement > >::iterator vIt = vLayerList.begin();
746  for ( ; vIt != vLayerList.end(); ++vIt )
747  {
748  vIt->first->createJoinCaches();
749  vIt->first->updateFieldMap();
750  //for old symbology, it is necessary to read the symbology again after having the complete field map
751  if ( !vIt->first->isUsingRendererV2() )
752  {
753  vIt->first->readSymbology( vIt->second, errorMessage );
754  }
755  }
756 
757  return qMakePair( returnStatus, brokenNodes );
758 
759 } // _getMapLayers
760 
761 
762 
763 
767 bool QgsProject::read( QFileInfo const &file )
768 {
769  imp_->file.setFileName( file.filePath() );
770 
771  return read();
772 } // QgsProject::read
773 
774 
775 
780 {
781  clearError();
782 
783  std::auto_ptr< QDomDocument > doc =
784  std::auto_ptr < QDomDocument > ( new QDomDocument( "qgis" ) );
785 
786  if ( !imp_->file.open( QIODevice::ReadOnly ) )
787  {
788  imp_->file.close();
789 
790  setError( tr( "Unable to open %1" ).arg( imp_->file.fileName() ) );
791 
792  return false;
793  }
794 
795  // location of problem associated with errorMsg
796  int line, column;
797  QString errorMsg;
798 
799  if ( !doc->setContent( &imp_->file, &errorMsg, &line, &column ) )
800  {
801  // want to make this class as GUI independent as possible; so commented out
802 #if 0
803  QMessageBox::critical( 0, tr( "Project File Read Error" ),
804  tr( "%1 at line %2 column %3" ).arg( errorMsg ).arg( line ).arg( column ) );
805 #endif
806 
807  QString errorString = tr( "Project file read error: %1 at line %2 column %3" )
808  .arg( errorMsg ).arg( line ).arg( column );
809 
810  QgsDebugMsg( errorString );
811 
812  imp_->file.close();
813 
814  setError( tr( "%1 for file %2" ).arg( errorString ).arg( imp_->file.fileName() ) );
815 
816  return false;
817  }
818 
819  imp_->file.close();
820 
821 
822  QgsDebugMsg( "Opened document " + imp_->file.fileName() );
823  QgsDebugMsg( "Project title: " + imp_->title );
824 
825  // get project version string, if any
826  QgsProjectVersion fileVersion = _getVersion( *doc );
827  QgsProjectVersion thisVersion( QGis::QGIS_VERSION );
828 
829  if ( thisVersion > fileVersion )
830  {
831  QgsLogger::warning( "Loading a file that was saved with an older "
832  "version of qgis (saved in " + fileVersion.text() +
833  ", loaded in " + QGis::QGIS_VERSION +
834  "). Problems may occur." );
835 
836  QgsProjectFileTransform projectFile( *doc, fileVersion );
837 
839  emit oldProjectVersionWarning( fileVersion.text() );
840  QgsDebugMsg( "Emitting oldProjectVersionWarning(oldVersion)." );
841 
842  projectFile.dump();
843 
844  projectFile.updateRevision( thisVersion );
845 
846  projectFile.dump();
847 
848  }
849 
850  // before we start loading everything, let's clear out the current set of
851  // properties first so that we don't have the properties from the previous
852  // project still hanging around
853 
854  imp_->clear();
855 
856  // now get any properties
857  _getProperties( *doc, imp_->properties_ );
858 
859  QgsDebugMsg( QString::number( imp_->properties_.count() ) + " properties read" );
860 
861  dump_( imp_->properties_ );
862 
863 
864  // restore the canvas' area of interest
865 
866  // now get project title
867  _getTitle( *doc, imp_->title );
868 
869 
870  // get the map layers
871  QPair< bool, QList<QDomNode> > getMapLayersResults = _getMapLayers( *doc );
872 
873  // review the integrity of the retrieved map layers
874  bool clean = getMapLayersResults.first;
875 
876  if ( !clean )
877  {
878  QgsDebugMsg( "Unable to get map layers from project file." );
879 
880  if ( ! getMapLayersResults.second.isEmpty() )
881  {
882  QgsDebugMsg( "there are " + QString::number( getMapLayersResults.second.size() ) + " broken layers" );
883  }
884 
885  // we let a custom handler to decide what to do with missing layers
886  // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
887  mBadLayerHandler->handleBadLayers( getMapLayersResults.second, *doc );
888  }
889 
890  // read the project: used by map canvas and legend
891  emit readProject( *doc );
892 
893  // if all went well, we're allegedly in pristine state
894  if ( clean )
895  dirty( false );
896 
897  return true;
898 
899 } // QgsProject::read
900 
901 
902 
903 
904 
905 bool QgsProject::read( QDomNode & layerNode )
906 {
907  QString type = layerNode.toElement().attribute( "type" );
908 
909  QgsMapLayer *mapLayer = NULL;
910 
911  if ( type == "vector" )
912  {
913  mapLayer = new QgsVectorLayer;
914  }
915  else if ( type == "raster" )
916  {
917  mapLayer = new QgsRasterLayer;
918  }
919  else if ( type == "plugin" )
920  {
921  QString typeName = layerNode.toElement().attribute( "name" );
922  mapLayer = QgsPluginLayerRegistry::instance()->createLayer( typeName );
923  }
924  else
925  {
926  QgsDebugMsg( "bad layer type" );
927  return false;
928  }
929 
930  if ( !mapLayer )
931  {
932  QgsDebugMsg( "unable to create layer" );
933  return false;
934  }
935 
936  // have the layer restore state that is stored in Dom node
937  if ( mapLayer->readXML( layerNode ) )
938  {
939  mapLayer = QgsMapLayerRegistry::instance()->addMapLayer( mapLayer );
940  }
941  else
942  {
943  delete mapLayer;
944 
945  QgsDebugMsg( "unable to load " + type + " layer" );
946 
947  return false;
948  }
949 
950  return true;
951 } // QgsProject::read( QDomNode & layerNode )
952 
953 
954 
955 bool QgsProject::write( QFileInfo const &file )
956 {
957  imp_->file.setFileName( file.filePath() );
958 
959  return write();
960 } // QgsProject::write( QFileInfo const & file )
961 
962 
964 {
965  clearError();
966 
967  // if we have problems creating or otherwise writing to the project file,
968  // let's find out up front before we go through all the hand-waving
969  // necessary to create all the Dom objects
970  if ( !imp_->file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
971  {
972  imp_->file.close(); // even though we got an error, let's make
973  // sure it's closed anyway
974 
975  setError( tr( "Unable to save to file %1" ).arg( imp_->file.fileName() ) );
976  return false;
977  }
978  QFileInfo myFileInfo( imp_->file );
979  if ( !myFileInfo.isWritable() )
980  {
981  // even though we got an error, let's make
982  // sure it's closed anyway
983  imp_->file.close();
984  setError( tr( "%1 is not writeable. Please adjust permissions (if possible) and try again." )
985  .arg( imp_->file.fileName() ) );
986  return false;
987  }
988 
989  QDomImplementation DomImplementation;
990 
991  QDomDocumentType documentType =
992  DomImplementation.createDocumentType( "qgis", "http://mrcc.com/qgis.dtd",
993  "SYSTEM" );
994  std::auto_ptr < QDomDocument > doc =
995  std::auto_ptr < QDomDocument > ( new QDomDocument( documentType ) );
996 
997 
998  QDomElement qgisNode = doc->createElement( "qgis" );
999  qgisNode.setAttribute( "projectname", title() );
1000  qgisNode.setAttribute( "version", QString( "%1" ).arg( QGis::QGIS_VERSION ) );
1001 
1002  doc->appendChild( qgisNode );
1003 
1004  // title
1005  QDomElement titleNode = doc->createElement( "title" );
1006  qgisNode.appendChild( titleNode );
1007 
1008  QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
1009  titleNode.appendChild( titleText );
1010 
1011  // let map canvas and legend write their information
1012  emit writeProject( *doc );
1013 
1014  // within top level node save list of layers
1015  QMap<QString, QgsMapLayer*> & layers = QgsMapLayerRegistry::instance()->mapLayers();
1016 
1017  // Iterate over layers in zOrder
1018  // Call writeXML() on each
1019  QDomElement projectLayersNode = doc->createElement( "projectlayers" );
1020  projectLayersNode.setAttribute( "layercount", qulonglong( layers.size() ) );
1021 
1022  QMap<QString, QgsMapLayer*>::iterator li = layers.begin();
1023  while ( li != layers.end() )
1024  {
1025  //QgsMapLayer *ml = QgsMapLayerRegistry::instance()->mapLayer(*li);
1026  QgsMapLayer* ml = li.value();
1027 
1028  if ( ml )
1029  {
1030  ml->writeXML( projectLayersNode, *doc );
1031  }
1032  li++;
1033  }
1034 
1035  qgisNode.appendChild( projectLayersNode );
1036 
1037  // now add the optional extra properties
1038 
1039  dump_( imp_->properties_ );
1040 
1041  QgsDebugMsg( QString( "there are %1 property scopes" ).arg( static_cast<int>( imp_->properties_.count() ) ) );
1042 
1043  if ( !imp_->properties_.isEmpty() ) // only worry about properties if we
1044  // actually have any properties
1045  {
1046  imp_->properties_.writeXML( "properties", qgisNode, *doc );
1047  }
1048 
1049  // now wrap it up and ship it to the project file
1050  doc->normalize(); // XXX I'm not entirely sure what this does
1051 
1052  //QString xml = doc->toString(4); // write to string with indentation of four characters
1053  // (yes, four is arbitrary)
1054 
1055  // const char * xmlString = xml; // debugger probe point
1056  // qDebug( "project file output:\n\n" + xml );
1057 
1058  QTextStream projectFileStream( &imp_->file );
1059 
1060  //projectFileStream << xml << endl;
1061  doc->save( projectFileStream, 4 ); // save as utf-8
1062  imp_->file.close();
1063 
1064  // check if the text stream had no error - if it does
1065  // the user will get a message so they can try to resolve the
1066  // situation e.g. by saving project to a volume with more space
1067  //
1068  if ( projectFileStream.pos() == -1 || imp_->file.error() != QFile::NoError )
1069  {
1070  setError( tr( "Unable to save to file %1. Your project "
1071  "may be corrupted on disk. Try clearing some space on the volume and "
1072  "check file permissions before pressing save again." )
1073  .arg( imp_->file.fileName() ) );
1074  return false;
1075  }
1076 
1077  dirty( false ); // reset to pristine state
1078 
1079  return true;
1080 } // QgsProject::write
1081 
1082 
1083 
1085 {
1086  //QgsDebugMsg("entered.");
1087 
1088  imp_->clear();
1089 
1090  dirty( true );
1091 } // QgsProject::clearProperties()
1092 
1093 
1094 
1095 bool
1096 QgsProject::writeEntry( QString const &scope, const QString & key, bool value )
1097 {
1098  dirty( true );
1099 
1100  return addKey_( scope, key, &imp_->properties_, value );
1101 } // QgsProject::writeEntry ( ..., bool value )
1102 
1103 
1104 bool
1105 QgsProject::writeEntry( QString const &scope, const QString & key,
1106  double value )
1107 {
1108  dirty( true );
1109 
1110  return addKey_( scope, key, &imp_->properties_, value );
1111 } // QgsProject::writeEntry ( ..., double value )
1112 
1113 
1114 bool
1115 QgsProject::writeEntry( QString const &scope, const QString & key, int value )
1116 {
1117  dirty( true );
1118 
1119  return addKey_( scope, key, &imp_->properties_, value );
1120 } // QgsProject::writeEntry ( ..., int value )
1121 
1122 
1123 bool
1124 QgsProject::writeEntry( QString const &scope, const QString & key,
1125  const QString & value )
1126 {
1127  dirty( true );
1128 
1129  return addKey_( scope, key, &imp_->properties_, value );
1130 } // QgsProject::writeEntry ( ..., const QString & value )
1131 
1132 
1133 bool
1134 QgsProject::writeEntry( QString const &scope, const QString & key,
1135  const QStringList & value )
1136 {
1137  dirty( true );
1138 
1139  return addKey_( scope, key, &imp_->properties_, value );
1140 } // QgsProject::writeEntry ( ..., const QStringList & value )
1141 
1142 
1143 
1144 
1145 QStringList
1146 QgsProject::readListEntry( QString const & scope,
1147  const QString & key,
1148  bool * ok ) const
1149 {
1150  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1151 
1152  QVariant value;
1153 
1154  if ( property )
1155  {
1156  value = property->value();
1157  }
1158 
1159  bool valid = QVariant::StringList == value.type();
1160 
1161  if ( ok )
1162  {
1163  *ok = valid;
1164  }
1165 
1166  if ( valid )
1167  {
1168  return value.toStringList();
1169  }
1170 
1171  return QStringList();
1172 } // QgsProject::readListEntry
1173 
1174 
1175 QString
1176 QgsProject::readEntry( QString const & scope,
1177  const QString & key,
1178  const QString & def,
1179  bool * ok ) const
1180 {
1181  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1182 
1183  QVariant value;
1184 
1185  if ( property )
1186  {
1187  value = property->value();
1188  }
1189 
1190  bool valid = value.canConvert( QVariant::String );
1191 
1192  if ( ok )
1193  {
1194  *ok = valid;
1195  }
1196 
1197  if ( valid )
1198  {
1199  return value.toString();
1200  }
1201 
1202  return QString( def );
1203 } // QgsProject::readEntry
1204 
1205 
1206 int
1207 QgsProject::readNumEntry( QString const &scope, const QString & key, int def,
1208  bool * ok ) const
1209 {
1210  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1211 
1212  QVariant value;
1213 
1214  if ( property )
1215  {
1216  value = property->value();
1217  }
1218 
1219  bool valid = value.canConvert( QVariant::String );
1220 
1221  if ( ok )
1222  {
1223  *ok = valid;
1224  }
1225 
1226  if ( valid )
1227  {
1228  return value.toInt();
1229  }
1230 
1231  return def;
1232 } // QgsProject::readNumEntry
1233 
1234 
1235 double
1236 QgsProject::readDoubleEntry( QString const &scope, const QString & key,
1237  double def,
1238  bool * ok ) const
1239 {
1240  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1241 
1242  QVariant value;
1243 
1244  if ( property )
1245  {
1246  value = property->value();
1247  }
1248 
1249  bool valid = value.canConvert( QVariant::Double );
1250 
1251  if ( ok )
1252  {
1253  *ok = valid;
1254  }
1255 
1256  if ( valid )
1257  {
1258  return value.toDouble();
1259  }
1260 
1261  return def;
1262 } // QgsProject::readDoubleEntry
1263 
1264 
1265 bool
1266 QgsProject::readBoolEntry( QString const &scope, const QString & key, bool def,
1267  bool * ok ) const
1268 {
1269  QgsProperty * property = findKey_( scope, key, imp_->properties_ );
1270 
1271  QVariant value;
1272 
1273  if ( property )
1274  {
1275  value = property->value();
1276  }
1277 
1278  bool valid = value.canConvert( QVariant::Bool );
1279 
1280  if ( ok )
1281  {
1282  *ok = valid;
1283  }
1284 
1285  if ( valid )
1286  {
1287  return value.toBool();
1288  }
1289 
1290  return def;
1291 } // QgsProject::readBoolEntry
1292 
1293 
1294 bool QgsProject::removeEntry( QString const &scope, const QString & key )
1295 {
1296  removeKey_( scope, key, imp_->properties_ );
1297 
1298  dirty( true );
1299 
1300  return ! findKey_( scope, key, imp_->properties_ );
1301 } // QgsProject::removeEntry
1302 
1303 
1304 
1305 QStringList QgsProject::entryList( QString const &scope, QString const &key ) const
1306 {
1307  QgsProperty * foundProperty = findKey_( scope, key, imp_->properties_ );
1308 
1309  QStringList entries;
1310 
1311  if ( foundProperty )
1312  {
1313  QgsPropertyKey * propertyKey = dynamic_cast<QgsPropertyKey*>( foundProperty );
1314 
1315  if ( propertyKey )
1316  { propertyKey->entryList( entries ); }
1317  }
1318 
1319  return entries;
1320 } // QgsProject::entryList
1321 
1322 
1323 QStringList
1324 QgsProject::subkeyList( QString const &scope, QString const &key ) const
1325 {
1326  QgsProperty * foundProperty = findKey_( scope, key, imp_->properties_ );
1327 
1328  QStringList entries;
1329 
1330  if ( foundProperty )
1331  {
1332  QgsPropertyKey * propertyKey = dynamic_cast<QgsPropertyKey*>( foundProperty );
1333 
1334  if ( propertyKey )
1335  { propertyKey->subkeyList( entries ); }
1336  }
1337 
1338  return entries;
1339 
1340 } // QgsProject::subkeyList
1341 
1342 
1343 
1345 {
1346  dump_( imp_->properties_ );
1347 } // QgsProject::dumpProperties
1348 
1349 
1350 // return the absolute path from a filename read from project file
1351 QString QgsProject::readPath( QString src ) const
1352 {
1353  if ( readBoolEntry( "Paths", "/Absolute", false ) )
1354  {
1355  return src;
1356  }
1357 
1358  // relative path should always start with ./ or ../
1359  if ( !src.startsWith( "./" ) && !src.startsWith( "../" ) )
1360  {
1361 #if defined(Q_OS_WIN)
1362  if ( src.startsWith( "\\\\" ) ||
1363  src.startsWith( "//" ) ||
1364  ( src[0].isLetter() && src[1] == ':' ) )
1365  {
1366  // UNC or absolute path
1367  return src;
1368  }
1369 #else
1370  if ( src[0] == '/' )
1371  {
1372  // absolute path
1373  return src;
1374  }
1375 #endif
1376 
1377  // so this one isn't absolute, but also doesn't start // with ./ or ../.
1378  // That means that it was saved with an earlier version of "relative path support",
1379  // where the source file had to exist and only the project directory was stripped
1380  // from the filename.
1381  QFileInfo pfi( fileName() );
1382  if ( !pfi.exists() )
1383  return src;
1384 
1385  QFileInfo fi( pfi.canonicalPath() + "/" + src );
1386 
1387  if ( !fi.exists() )
1388  {
1389  return src;
1390  }
1391  else
1392  {
1393  return fi.canonicalFilePath();
1394  }
1395  }
1396 
1397  QString srcPath = src;
1398  QString projPath = fileName();
1399 
1400  if ( projPath.isEmpty() )
1401  {
1402  return src;
1403  }
1404 
1405 #if defined(Q_OS_WIN)
1406  srcPath.replace( "\\", "/" );
1407  projPath.replace( "\\", "/" );
1408 
1409  bool uncPath = projPath.startsWith( "//" );
1410 #endif
1411 
1412  QStringList srcElems = srcPath.split( "/", QString::SkipEmptyParts );
1413  QStringList projElems = projPath.split( "/", QString::SkipEmptyParts );
1414 
1415 #if defined(Q_OS_WIN)
1416  if ( uncPath )
1417  {
1418  projElems.insert( 0, "" );
1419  projElems.insert( 0, "" );
1420  }
1421 #endif
1422 
1423  // remove project file element
1424  projElems.removeLast();
1425 
1426  // append source path elements
1427  projElems << srcElems;
1428  projElems.removeAll( "." );
1429 
1430  // resolve ..
1431  int pos;
1432  while (( pos = projElems.indexOf( ".." ) ) > 0 )
1433  {
1434  // remove preceding element and ..
1435  projElems.removeAt( pos - 1 );
1436  projElems.removeAt( pos - 1 );
1437  }
1438 
1439 #if !defined(Q_OS_WIN)
1440  // make path absolute
1441  projElems.prepend( "" );
1442 #endif
1443 
1444  return projElems.join( "/" );
1445 }
1446 
1447 // return the absolute or relative path to write it to the project file
1448 QString QgsProject::writePath( QString src ) const
1449 {
1450  if ( readBoolEntry( "Paths", "/Absolute", false ) || src.isEmpty() )
1451  {
1452  return src;
1453  }
1454 
1455  QString srcPath = src;
1456  QString projPath = fileName();
1457 
1458  if ( projPath.isEmpty() )
1459  {
1460  return src;
1461  }
1462 
1463 #if defined( Q_OS_WIN )
1464  const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
1465 
1466  srcPath.replace( "\\", "/" );
1467 
1468  if ( srcPath.startsWith( "//" ) )
1469  {
1470  // keep UNC prefix
1471  srcPath = "\\\\" + srcPath.mid( 2 );
1472  }
1473 
1474  projPath.replace( "\\", "/" );
1475  if ( projPath.startsWith( "//" ) )
1476  {
1477  // keep UNC prefix
1478  projPath = "\\\\" + projPath.mid( 2 );
1479  }
1480 #else
1481  const Qt::CaseSensitivity cs = Qt::CaseSensitive;
1482 #endif
1483 
1484  QStringList projElems = projPath.split( "/", QString::SkipEmptyParts );
1485  QStringList srcElems = srcPath.split( "/", QString::SkipEmptyParts );
1486 
1487  // remove project file element
1488  projElems.removeLast();
1489 
1490  projElems.removeAll( "." );
1491  srcElems.removeAll( "." );
1492 
1493  // remove common part
1494  int n = 0;
1495  while ( srcElems.size() > 0 &&
1496  projElems.size() > 0 &&
1497  srcElems[0].compare( projElems[0], cs ) == 0 )
1498  {
1499  srcElems.removeFirst();
1500  projElems.removeFirst();
1501  n++;
1502  }
1503 
1504  if ( n == 0 )
1505  {
1506  // no common parts; might not even by a file
1507  return src;
1508  }
1509 
1510  if ( projElems.size() > 0 )
1511  {
1512  // go up to the common directory
1513  for ( int i = 0; i < projElems.size(); i++ )
1514  {
1515  srcElems.insert( 0, ".." );
1516  }
1517  }
1518  else
1519  {
1520  // let it start with . nevertheless,
1521  // so relative path always start with either ./ or ../
1522  srcElems.insert( 0, "." );
1523  }
1524 
1525  return srcElems.join( "/" );
1526 }
1527 
1528 void QgsProject::setError( QString errorMessage )
1529 {
1530  mErrorMessage = errorMessage;
1531 }
1532 
1533 QString QgsProject::error() const
1534 {
1535  return mErrorMessage;
1536 }
1537 
1539 {
1540  setError( QString() );
1541 }
1542 
1544 {
1545  delete mBadLayerHandler;
1546  mBadLayerHandler = handler;
1547 }
1548 
1549 void QgsProjectBadLayerDefaultHandler::handleBadLayers( QList<QDomNode> /*layers*/, QDomDocument /*projectDom*/ )
1550 {
1551  // just ignore any bad layers
1552 }