QGIS API Documentation  2.2.0-Valmiera
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsmaplayer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmaplayer.cpp - description
3  -------------------
4  begin : Fri Jun 28 2002
5  copyright : (C) 2002 by Gary E.Sherman
6  email : sherman at mrcc.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 
19 #include <QDateTime>
20 #include <QDomNode>
21 #include <QFileInfo>
22 #include <QSettings> // TODO: get rid of it [MD]
23 #include <QDir>
24 #include <QFile>
25 #include <QDomDocument>
26 #include <QDomElement>
27 #include <QDomImplementation>
28 #include <QTextStream>
29 #include <QUrl>
30 
31 #include <sqlite3.h>
32 
33 #include "qgslogger.h"
34 #include "qgsrectangle.h"
35 #include "qgsmaplayer.h"
37 #include "qgsapplication.h"
38 #include "qgsproject.h"
40 #include "qgsdatasourceuri.h"
41 #include "qgsvectorlayer.h"
42 #include "qgsproviderregistry.h"
43 
45  QString lyrname,
46  QString source ) :
47  mValid( false ), // assume the layer is invalid
48  mDataSource( source ),
49  mLayerOrigName( lyrname ), // store the original name
50  mID( "" ),
51  mLayerType( type ),
52  mBlendMode( QPainter::CompositionMode_SourceOver ) // Default to normal blending
53 {
55 
56  // Set the display name = internal name
57  QgsDebugMsg( "original name: '" + mLayerOrigName + "'" );
59  QgsDebugMsg( "display name: '" + mLayerName + "'" );
60 
61  // Generate the unique ID of this layer
62  QDateTime dt = QDateTime::currentDateTime();
63  mID = lyrname + dt.toString( "yyyyMMddhhmmsszzz" );
64  // Tidy the ID up to avoid characters that may cause problems
65  // elsewhere (e.g in some parts of XML). Replaces every non-word
66  // character (word characters are the alphabet, numbers and
67  // underscore) with an underscore.
68  // Note that the first backslashe in the regular expression is
69  // there for the compiler, so the pattern is actually \W
70  mID.replace( QRegExp( "[\\W]" ), "_" );
71 
72  //set some generous defaults for scale based visibility
73  mMinScale = 0;
74  mMaxScale = 100000000;
75  mScaleBasedVisibility = false;
76  mpCacheImage = 0;
77 }
78 
80 {
81  delete mCRS;
82  if ( mpCacheImage )
83  {
84  delete mpCacheImage;
85  }
86 }
87 
89 {
90  return mLayerType;
91 }
92 
94 QString QgsMapLayer::id() const
95 {
96  return mID;
97 }
98 
100 void QgsMapLayer::setLayerName( const QString & name )
101 {
102  QgsDebugMsg( "new original name: '" + name + "'" );
103  QString newName = capitaliseLayerName( name );
104  QgsDebugMsg( "new display name: '" + name + "'" );
105  if ( name == mLayerOrigName && newName == mLayerName ) return;
106  mLayerOrigName = name; // store the new original name
107  mLayerName = newName;
108  emit layerNameChanged();
109 }
110 
112 QString const & QgsMapLayer::name() const
113 {
114  QgsDebugMsgLevel( "returning name '" + mLayerName + "'", 3 );
115  return mLayerName;
116 }
117 
119 {
120  // Redo this every time we're asked for it, as we don't know if
121  // dataSource has changed.
122  QString safeName = QgsDataSourceURI::removePassword( mDataSource );
123  return safeName;
124 }
125 
126 QString const & QgsMapLayer::source() const
127 {
128  return mDataSource;
129 }
130 
132 {
133  return mExtent;
134 }
135 
137 void QgsMapLayer::setBlendMode( const QPainter::CompositionMode &blendMode )
138 {
140  emit blendModeChanged( blendMode );
141 }
142 
144 QPainter::CompositionMode QgsMapLayer::blendMode() const
145 {
146  return mBlendMode;
147 }
148 
149 bool QgsMapLayer::draw( QgsRenderContext& rendererContext )
150 {
151  Q_UNUSED( rendererContext );
152  return false;
153 }
154 
156 {
157  Q_UNUSED( rendererContext );
158 }
159 
160 bool QgsMapLayer::readLayerXML( const QDomElement& layerElement )
161 {
163  CUSTOM_CRS_VALIDATION savedValidation;
164  bool layerError;
165 
166  QDomNode mnl;
167  QDomElement mne;
168 
169  // read provider
170  QString provider;
171  mnl = layerElement.namedItem( "provider" );
172  mne = mnl.toElement();
173  provider = mne.text();
174 
175  // set data source
176  mnl = layerElement.namedItem( "datasource" );
177  mne = mnl.toElement();
178  mDataSource = mne.text();
179 
180  // TODO: this should go to providers
181  if ( provider == "spatialite" )
182  {
184  uri.setDatabase( QgsProject::instance()->readPath( uri.database() ) );
185  mDataSource = uri.uri();
186  }
187  else if ( provider == "ogr" )
188  {
189  QStringList theURIParts = mDataSource.split( "|" );
190  theURIParts[0] = QgsProject::instance()->readPath( theURIParts[0] );
191  mDataSource = theURIParts.join( "|" );
192  }
193  else if ( provider == "delimitedtext" )
194  {
195  QUrl urlSource = QUrl::fromEncoded( mDataSource.toAscii() );
196 
197  if ( !mDataSource.startsWith( "file:" ) )
198  {
199  QUrl file = QUrl::fromLocalFile( mDataSource.left( mDataSource.indexOf( "?" ) ) );
200  urlSource.setScheme( "file" );
201  urlSource.setPath( file.path() );
202  }
203 
204  QUrl urlDest = QUrl::fromLocalFile( QgsProject::instance()->readPath( urlSource.toLocalFile() ) );
205  urlDest.setQueryItems( urlSource.queryItems() );
206  mDataSource = QString::fromAscii( urlDest.toEncoded() );
207  }
208  else if ( provider == "wms" )
209  {
210  // >>> BACKWARD COMPATIBILITY < 1.9
211  // For project file backward compatibility we must support old format:
212  // 1. mode: <url>
213  // example: http://example.org/wms?
214  // 2. mode: tiled=<width>;<height>;<resolution>;<resolution>...,ignoreUrl=GetMap;GetFeatureInfo,featureCount=<count>,username=<name>,password=<password>,url=<url>
215  // example: tiled=256;256;0.703;0.351,url=http://example.org/tilecache?
216  // example: featureCount=10,http://example.org/wms?
217  // example: ignoreUrl=GetMap;GetFeatureInfo,username=cimrman,password=jara,url=http://example.org/wms?
218  // This is modified version of old QgsWmsProvider::parseUri
219  // The new format has always params crs,format,layers,styles and that params
220  // should not appear in old format url -> use them to identify version
221  if ( !mDataSource.contains( "crs=" ) && !mDataSource.contains( "format=" ) )
222  {
223  QgsDebugMsg( "Old WMS URI format detected -> converting to new format" );
224  QgsDataSourceURI uri;
225  if ( !mDataSource.startsWith( "http:" ) )
226  {
227  QStringList parts = mDataSource.split( "," );
228  QStringListIterator iter( parts );
229  while ( iter.hasNext() )
230  {
231  QString item = iter.next();
232  if ( item.startsWith( "username=" ) )
233  {
234  uri.setParam( "username", item.mid( 9 ) );
235  }
236  else if ( item.startsWith( "password=" ) )
237  {
238  uri.setParam( "password", item.mid( 9 ) );
239  }
240  else if ( item.startsWith( "tiled=" ) )
241  {
242  // in < 1.9 tiled= may apper in to variants:
243  // tiled=width;height - non tiled mode, specifies max width and max height
244  // tiled=width;height;resolutions-1;resolution2;... - tile mode
245 
246  QStringList params = item.mid( 6 ).split( ";" );
247 
248  if ( params.size() == 2 ) // non tiled mode
249  {
250  uri.setParam( "maxWidth", params.takeFirst() );
251  uri.setParam( "maxHeight", params.takeFirst() );
252  }
253  else if ( params.size() > 2 ) // tiled mode
254  {
255  // resolutions are no more needed and size limit is not used for tiles
256  // we have to tell to the provider however that it is tiled
257  uri.setParam( "tileMatrixSet", "" );
258  }
259  }
260  else if ( item.startsWith( "featureCount=" ) )
261  {
262  uri.setParam( "featureCount", item.mid( 13 ) );
263  }
264  else if ( item.startsWith( "url=" ) )
265  {
266  uri.setParam( "url", item.mid( 4 ) );
267  }
268  else if ( item.startsWith( "ignoreUrl=" ) )
269  {
270  uri.setParam( "ignoreUrl", item.mid( 10 ).split( ";" ) );
271  }
272  }
273  }
274  else
275  {
276  uri.setParam( "url", mDataSource );
277  }
278  mDataSource = uri.encodedUri();
279  // At this point, the URI is obviously incomplete, we add additional params
280  // in QgsRasterLayer::readXml
281  }
282  // <<< BACKWARD COMPATIBILITY < 1.9
283  }
284  else
285  {
287  }
288 
289  // Set the CRS from project file, asking the user if necessary.
290  // Make it the saved CRS to have WMS layer projected correctly.
291  // We will still overwrite whatever GDAL etc picks up anyway
292  // further down this function.
293  mnl = layerElement.namedItem( "layername" );
294  mne = mnl.toElement();
295 
296  QDomNode srsNode = layerElement.namedItem( "srs" );
297  mCRS->readXML( srsNode );
298  mCRS->setValidationHint( tr( "Specify CRS for layer %1" ).arg( mne.text() ) );
299  mCRS->validate();
300  savedCRS = *mCRS;
301 
302  // Do not validate any projections in children, they will be overwritten anyway.
303  // No need to ask the user for a projections when it is overwritten, is there?
306 
307  // now let the children grab what they need from the Dom node.
308  layerError = !readXml( layerElement );
309 
310  // overwrite CRS with what we read from project file before the raster/vector
311  // file readnig functions changed it. They will if projections is specfied in the file.
312  // FIXME: is this necessary?
314  *mCRS = savedCRS;
315 
316  // Abort if any error in layer, such as not found.
317  if ( layerError )
318  {
319  return false;
320  }
321 
322  // the internal name is just the data source basename
323  //QFileInfo dataSourceFileInfo( mDataSource );
324  //internalName = dataSourceFileInfo.baseName();
325 
326  // set ID
327  mnl = layerElement.namedItem( "id" );
328  if ( ! mnl.isNull() )
329  {
330  mne = mnl.toElement();
331  if ( ! mne.isNull() && mne.text().length() > 10 ) // should be at least 17 (yyyyMMddhhmmsszzz)
332  {
333  mID = mne.text();
334  }
335  }
336 
337  // use scale dependent visibility flag
338  toggleScaleBasedVisibility( layerElement.attribute( "hasScaleBasedVisibilityFlag" ).toInt() == 1 );
339  setMinimumScale( layerElement.attribute( "minimumScale" ).toFloat() );
340  setMaximumScale( layerElement.attribute( "maximumScale" ).toFloat() );
341 
342  // set name
343  mnl = layerElement.namedItem( "layername" );
344  mne = mnl.toElement();
345  setLayerName( mne.text() );
346 
347  //title
348  QDomElement titleElem = layerElement.firstChildElement( "title" );
349  if ( !titleElem.isNull() )
350  {
351  mTitle = titleElem.text();
352  }
353 
354  //abstract
355  QDomElement abstractElem = layerElement.firstChildElement( "abstract" );
356  if ( !abstractElem.isNull() )
357  {
358  mAbstract = abstractElem.text();
359  }
360 
361  //keywordList
362  QDomElement keywordListElem = layerElement.firstChildElement( "keywordList" );
363  if ( !keywordListElem.isNull() )
364  {
365  QStringList kwdList;
366  for ( QDomNode n = keywordListElem.firstChild(); !n.isNull(); n = n.nextSibling() )
367  {
368  kwdList << n.toElement().text();
369  }
370  mKeywordList = kwdList.join( ", " );
371  }
372 
373  //metadataUrl
374  QDomElement dataUrlElem = layerElement.firstChildElement( "dataUrl" );
375  if ( !dataUrlElem.isNull() )
376  {
377  mDataUrl = dataUrlElem.text();
378  mDataUrlFormat = dataUrlElem.attribute( "format", "" );
379  }
380 
381  //attribution
382  QDomElement attribElem = layerElement.firstChildElement( "attribution" );
383  if ( !attribElem.isNull() )
384  {
385  mAttribution = attribElem.text();
386  mAttributionUrl = attribElem.attribute( "href", "" );
387  }
388 
389  //metadataUrl
390  QDomElement metaUrlElem = layerElement.firstChildElement( "metadataUrl" );
391  if ( !metaUrlElem.isNull() )
392  {
393  mMetadataUrl = metaUrlElem.text();
394  mMetadataUrlType = metaUrlElem.attribute( "type", "" );
395  mMetadataUrlFormat = metaUrlElem.attribute( "format", "" );
396  }
397 
398 #if 0
399  //read transparency level
400  QDomNode transparencyNode = layer_node.namedItem( "transparencyLevelInt" );
401  if ( ! transparencyNode.isNull() )
402  {
403  // set transparency level only if it's in project
404  // (otherwise it sets the layer transparent)
405  QDomElement myElement = transparencyNode.toElement();
406  setTransparency( myElement.text().toInt() );
407  }
408 #endif
409 
410  readCustomProperties( layerElement );
411 
412  return true;
413 } // bool QgsMapLayer::readLayerXML
414 
415 
416 bool QgsMapLayer::readXml( const QDomNode& layer_node )
417 {
418  Q_UNUSED( layer_node );
419  // NOP by default; children will over-ride with behavior specific to them
420 
421  return true;
422 } // void QgsMapLayer::readXml
423 
424 
425 
426 bool QgsMapLayer::writeLayerXML( QDomElement& layerElement, QDomDocument& document )
427 {
428  // use scale dependent visibility flag
429  layerElement.setAttribute( "hasScaleBasedVisibilityFlag", hasScaleBasedVisibility() ? 1 : 0 );
430  layerElement.setAttribute( "minimumScale", QString::number( minimumScale() ) );
431  layerElement.setAttribute( "maximumScale", QString::number( maximumScale() ) );
432 
433  // ID
434  QDomElement layerId = document.createElement( "id" );
435  QDomText layerIdText = document.createTextNode( id() );
436  layerId.appendChild( layerIdText );
437 
438  layerElement.appendChild( layerId );
439 
440  // data source
441  QDomElement dataSource = document.createElement( "datasource" );
442 
443  QString src = source();
444 
445  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( this );
446  // TODO: what about postgres, mysql and others, they should not go through writePath()
447  if ( vlayer && vlayer->providerType() == "spatialite" )
448  {
449  QgsDataSourceURI uri( src );
450  QString database = QgsProject::instance()->writePath( uri.database() );
451  uri.setConnection( uri.host(), uri.port(), database, uri.username(), uri.password() );
452  src = uri.uri();
453  }
454  else if ( vlayer && vlayer->providerType() == "ogr" )
455  {
456  QStringList theURIParts = src.split( "|" );
457  theURIParts[0] = QgsProject::instance()->writePath( theURIParts[0] );
458  src = theURIParts.join( "|" );
459  }
460  else if ( vlayer && vlayer->providerType() == "delimitedtext" )
461  {
462  QUrl urlSource = QUrl::fromEncoded( src.toAscii() );
463  QUrl urlDest = QUrl::fromLocalFile( QgsProject::instance()->writePath( urlSource.toLocalFile() ) );
464  urlDest.setQueryItems( urlSource.queryItems() );
465  src = QString::fromAscii( urlDest.toEncoded() );
466  }
467  else
468  {
469  src = QgsProject::instance()->writePath( src );
470  }
471 
472  QDomText dataSourceText = document.createTextNode( src );
473  dataSource.appendChild( dataSourceText );
474 
475  layerElement.appendChild( dataSource );
476 
477 
478  // layer name
479  QDomElement layerName = document.createElement( "layername" );
480  QDomText layerNameText = document.createTextNode( originalName() );
481  layerName.appendChild( layerNameText );
482 
483  // layer title
484  QDomElement layerTitle = document.createElement( "title" ) ;
485  QDomText layerTitleText = document.createTextNode( title() );
486  layerTitle.appendChild( layerTitleText );
487 
488  // layer abstract
489  QDomElement layerAbstract = document.createElement( "abstract" );
490  QDomText layerAbstractText = document.createTextNode( abstract() );
491  layerAbstract.appendChild( layerAbstractText );
492 
493  layerElement.appendChild( layerName );
494  layerElement.appendChild( layerTitle );
495  layerElement.appendChild( layerAbstract );
496 
497  // layer keyword list
498  QStringList keywordStringList = keywordList().split( "," );
499  if ( keywordStringList.size() > 0 )
500  {
501  QDomElement layerKeywordList = document.createElement( "keywordList" );
502  for ( int i = 0; i < keywordStringList.size(); ++i )
503  {
504  QDomElement layerKeywordValue = document.createElement( "value" );
505  QDomText layerKeywordText = document.createTextNode( keywordStringList.at( i ).trimmed() );
506  layerKeywordValue.appendChild( layerKeywordText );
507  layerKeywordList.appendChild( layerKeywordValue );
508  }
509  layerElement.appendChild( layerKeywordList );
510  }
511 
512  // layer metadataUrl
513  QString aDataUrl = dataUrl();
514  if ( !aDataUrl.isEmpty() )
515  {
516  QDomElement layerDataUrl = document.createElement( "dataUrl" ) ;
517  QDomText layerDataUrlText = document.createTextNode( aDataUrl );
518  layerDataUrl.appendChild( layerDataUrlText );
519  layerDataUrl.setAttribute( "format", dataUrlFormat() );
520  layerElement.appendChild( layerDataUrl );
521  }
522 
523  // layer attribution
524  QString aAttribution = attribution();
525  if ( !aAttribution.isEmpty() )
526  {
527  QDomElement layerAttribution = document.createElement( "attribution" ) ;
528  QDomText layerAttributionText = document.createTextNode( aAttribution );
529  layerAttribution.appendChild( layerAttributionText );
530  layerAttribution.setAttribute( "href", attributionUrl() );
531  layerElement.appendChild( layerAttribution );
532  }
533 
534  // layer metadataUrl
535  QString aMetadataUrl = metadataUrl();
536  if ( !aMetadataUrl.isEmpty() )
537  {
538  QDomElement layerMetadataUrl = document.createElement( "metadataUrl" ) ;
539  QDomText layerMetadataUrlText = document.createTextNode( aMetadataUrl );
540  layerMetadataUrl.appendChild( layerMetadataUrlText );
541  layerMetadataUrl.setAttribute( "type", metadataUrlType() );
542  layerMetadataUrl.setAttribute( "format", metadataUrlFormat() );
543  layerElement.appendChild( layerMetadataUrl );
544  }
545 
546  // timestamp if supported
547  if ( timestamp() > QDateTime() )
548  {
549  QDomElement stamp = document.createElement( "timestamp" );
550  QDomText stampText = document.createTextNode( timestamp().toString( Qt::ISODate ) );
551  stamp.appendChild( stampText );
552  layerElement.appendChild( stamp );
553  }
554 
555  layerElement.appendChild( layerName );
556 
557  // zorder
558  // This is no longer stored in the project file. It is superfluous since the layers
559  // are written and read in the proper order.
560 
561  // spatial reference system id
562  QDomElement mySrsElement = document.createElement( "srs" );
563  mCRS->writeXML( mySrsElement, document );
564  layerElement.appendChild( mySrsElement );
565 
566 #if 0
567  // <transparencyLevelInt>
568  QDomElement transparencyLevelIntElement = document.createElement( "transparencyLevelInt" );
569  QDomText transparencyLevelIntText = document.createTextNode( QString::number( getTransparency() ) );
570  transparencyLevelIntElement.appendChild( transparencyLevelIntText );
571  maplayer.appendChild( transparencyLevelIntElement );
572 #endif
573 
574  // now append layer node to map layer node
575 
576  writeCustomProperties( layerElement, document );
577 
578  return writeXml( layerElement, document );
579 
580 } // bool QgsMapLayer::writeXML
581 
582 
583 bool QgsMapLayer::writeXml( QDomNode & layer_node, QDomDocument & document )
584 {
585  Q_UNUSED( layer_node );
586  Q_UNUSED( document );
587  // NOP by default; children will over-ride with behavior specific to them
588 
589  return true;
590 } // void QgsMapLayer::writeXml
591 
592 
593 
594 
596 {
597  return mValid;
598 }
599 
600 
602 {
603  QgsDebugMsg( "called" );
604  // TODO: emit a signal - it will be used to update legend
605 }
606 
607 
609 {
610  return QString();
611 }
612 
614 {
615  return QString();
616 }
617 
618 void QgsMapLayer::connectNotify( const char * signal )
619 {
620  Q_UNUSED( signal );
621  QgsDebugMsgLevel( "QgsMapLayer connected to " + QString( signal ), 3 );
622 } // QgsMapLayer::connectNotify
623 
624 
625 
626 void QgsMapLayer::toggleScaleBasedVisibility( bool theVisibilityFlag )
627 {
628  mScaleBasedVisibility = theVisibilityFlag;
629 }
630 
632 {
633  return mScaleBasedVisibility;
634 }
635 
636 void QgsMapLayer::setMinimumScale( float theMinScale )
637 {
638  mMinScale = theMinScale;
639 }
640 
642 {
643  return mMinScale;
644 }
645 
646 
647 void QgsMapLayer::setMaximumScale( float theMaxScale )
648 {
649  mMaxScale = theMaxScale;
650 }
651 
653 {
654  return mMaxScale;
655 }
656 
657 
658 QStringList QgsMapLayer::subLayers() const
659 {
660  return QStringList(); // Empty
661 }
662 
663 void QgsMapLayer::setLayerOrder( const QStringList &layers )
664 {
665  Q_UNUSED( layers );
666  // NOOP
667 }
668 
669 void QgsMapLayer::setSubLayerVisibility( QString name, bool vis )
670 {
671  Q_UNUSED( name );
672  Q_UNUSED( vis );
673  // NOOP
674 }
675 
677 {
678  return *mCRS;
679 }
680 
681 void QgsMapLayer::setCrs( const QgsCoordinateReferenceSystem& srs, bool emitSignal )
682 {
683  *mCRS = srs;
684 
685  if ( !mCRS->isValid() )
686  {
687  mCRS->setValidationHint( tr( "Specify CRS for layer %1" ).arg( name() ) );
688  mCRS->validate();
689  }
690 
691  if ( emitSignal )
692  emit layerCrsChanged();
693 }
694 
695 QString QgsMapLayer::capitaliseLayerName( const QString& name )
696 {
697  // Capitalise the first letter of the layer name if requested
698  QSettings settings;
699  bool capitaliseLayerName =
700  settings.value( "/qgis/capitaliseLayerName", QVariant( false ) ).toBool();
701 
702  QString layerName( name );
703 
704  if ( capitaliseLayerName )
705  layerName = layerName.left( 1 ).toUpper() + layerName.mid( 1 );
706 
707  return layerName;
708 }
709 
711 {
712  QString myURI = publicSource();
713 
714  // if file is using the VSIFILE mechanism, remove the prefix
715  if ( myURI.startsWith( "/vsigzip/", Qt::CaseInsensitive ) )
716  {
717  myURI.remove( 0, 9 );
718  }
719  else if ( myURI.startsWith( "/vsizip/", Qt::CaseInsensitive ) &&
720  myURI.endsWith( ".zip", Qt::CaseInsensitive ) )
721  {
722  // ideally we should look for .qml file inside zip file
723  myURI.remove( 0, 8 );
724  }
725  else if ( myURI.startsWith( "/vsitar/", Qt::CaseInsensitive ) &&
726  ( myURI.endsWith( ".tar", Qt::CaseInsensitive ) ||
727  myURI.endsWith( ".tar.gz", Qt::CaseInsensitive ) ||
728  myURI.endsWith( ".tgz", Qt::CaseInsensitive ) ) )
729  {
730  // ideally we should look for .qml file inside tar file
731  myURI.remove( 0, 8 );
732  }
733 
734  QFileInfo myFileInfo( myURI );
735  QString key;
736 
737  if ( myFileInfo.exists() )
738  {
739  // if file is using the /vsizip/ or /vsigzip/ mechanism, cleanup the name
740  if ( myURI.endsWith( ".gz", Qt::CaseInsensitive ) )
741  myURI.chop( 3 );
742  else if ( myURI.endsWith( ".zip", Qt::CaseInsensitive ) )
743  myURI.chop( 4 );
744  else if ( myURI.endsWith( ".tar", Qt::CaseInsensitive ) )
745  myURI.chop( 4 );
746  else if ( myURI.endsWith( ".tar.gz", Qt::CaseInsensitive ) )
747  myURI.chop( 7 );
748  else if ( myURI.endsWith( ".tgz", Qt::CaseInsensitive ) )
749  myURI.chop( 4 );
750  else if ( myURI.endsWith( ".gz", Qt::CaseInsensitive ) )
751  myURI.chop( 3 );
752  myFileInfo.setFile( myURI );
753  // get the file name for our .qml style file
754  key = myFileInfo.path() + QDir::separator() + myFileInfo.completeBaseName() + ".qml";
755  }
756  else
757  {
758  key = publicSource();
759  }
760 
761  return key;
762 }
763 
764 QString QgsMapLayer::loadDefaultStyle( bool & theResultFlag )
765 {
766  return loadNamedStyle( styleURI(), theResultFlag );
767 }
768 
769 bool QgsMapLayer::loadNamedStyleFromDb( const QString &db, const QString &theURI, QString &qml )
770 {
771  QgsDebugMsg( QString( "db = %1 uri = %2" ).arg( db ).arg( theURI ) );
772 
773  bool theResultFlag = false;
774 
775  // read from database
776  sqlite3 *myDatabase;
777  sqlite3_stmt *myPreparedStatement;
778  const char *myTail;
779  int myResult;
780 
781  QgsDebugMsg( QString( "Trying to load style for \"%1\" from \"%2\"" ).arg( theURI ).arg( db ) );
782 
783  if ( !QFile( db ).exists() )
784  return false;
785 
786  myResult = sqlite3_open_v2( db.toUtf8().data(), &myDatabase, SQLITE_OPEN_READONLY, NULL );
787  if ( myResult != SQLITE_OK )
788  {
789  return false;
790  }
791 
792  QString mySql = "select qml from tbl_styles where style=?";
793  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8().data(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
794  if ( myResult == SQLITE_OK )
795  {
796  QByteArray param = theURI.toUtf8();
797 
798  if ( sqlite3_bind_text( myPreparedStatement, 1, param.data(), param.length(), SQLITE_STATIC ) == SQLITE_OK &&
799  sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
800  {
801  qml = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
802  theResultFlag = true;
803  }
804 
805  sqlite3_finalize( myPreparedStatement );
806  }
807 
808  sqlite3_close( myDatabase );
809 
810  return theResultFlag;
811 }
812 
813 QString QgsMapLayer::loadNamedStyle( const QString &theURI, bool &theResultFlag )
814 {
815  QgsDebugMsg( QString( "uri = %1 myURI = %2" ).arg( theURI ).arg( publicSource() ) );
816 
817  theResultFlag = false;
818 
819  QDomDocument myDocument( "qgis" );
820 
821  // location of problem associated with errorMsg
822  int line, column;
823  QString myErrorMessage;
824 
825  QFile myFile( theURI );
826  if ( myFile.open( QFile::ReadOnly ) )
827  {
828  // read file
829  theResultFlag = myDocument.setContent( &myFile, &myErrorMessage, &line, &column );
830  if ( !theResultFlag )
831  myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column );
832  myFile.close();
833  }
834  else
835  {
836  QFileInfo project( QgsProject::instance()->fileName() );
837  QgsDebugMsg( QString( "project fileName: %1" ).arg( project.absoluteFilePath() ) );
838 
839  QString qml;
840  if ( loadNamedStyleFromDb( QDir( QgsApplication::qgisSettingsDirPath() ).absoluteFilePath( "qgis.qmldb" ), theURI, qml ) ||
841  ( project.exists() && loadNamedStyleFromDb( project.absoluteDir().absoluteFilePath( project.baseName() + ".qmldb" ), theURI, qml ) ) ||
842  loadNamedStyleFromDb( QDir( QgsApplication::pkgDataPath() ).absoluteFilePath( "resources/qgis.qmldb" ), theURI, qml ) )
843  {
844  theResultFlag = myDocument.setContent( qml, &myErrorMessage, &line, &column );
845  if ( !theResultFlag )
846  {
847  myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column );
848  }
849  }
850  else
851  {
852  myErrorMessage = tr( "style not found in database" );
853  }
854  }
855 
856  if ( !theResultFlag )
857  {
858  return myErrorMessage;
859  }
860 
861  // get style file version string, if any
862  QgsProjectVersion fileVersion( myDocument.firstChildElement( "qgis" ).attribute( "version" ) );
863  QgsProjectVersion thisVersion( QGis::QGIS_VERSION );
864 
865  if ( thisVersion > fileVersion )
866  {
867  QgsLogger::warning( "Loading a style file that was saved with an older "
868  "version of qgis (saved in " + fileVersion.text() +
869  ", loaded in " + QGis::QGIS_VERSION +
870  "). Problems may occur." );
871 
872  QgsProjectFileTransform styleFile( myDocument, fileVersion );
873  // styleFile.dump();
874  styleFile.updateRevision( thisVersion );
875  // styleFile.dump();
876  }
877 
878  // now get the layer node out and pass it over to the layer
879  // to deserialise...
880  QDomElement myRoot = myDocument.firstChildElement( "qgis" );
881  if ( myRoot.isNull() )
882  {
883  myErrorMessage = tr( "Error: qgis element could not be found in %1" ).arg( theURI );
884  theResultFlag = false;
885  return myErrorMessage;
886  }
887 
888  // use scale dependent visibility flag
889  toggleScaleBasedVisibility( myRoot.attribute( "hasScaleBasedVisibilityFlag" ).toInt() == 1 );
890  setMinimumScale( myRoot.attribute( "minimumScale" ).toFloat() );
891  setMaximumScale( myRoot.attribute( "maximumScale" ).toFloat() );
892 
893 #if 0
894  //read transparency level
895  QDomNode transparencyNode = myRoot.namedItem( "transparencyLevelInt" );
896  if ( ! transparencyNode.isNull() )
897  {
898  // set transparency level only if it's in project
899  // (otherwise it sets the layer transparent)
900  QDomElement myElement = transparencyNode.toElement();
901  setTransparency( myElement.text().toInt() );
902  }
903 #endif
904 
905  QString errorMsg;
906  theResultFlag = readSymbology( myRoot, errorMsg );
907  if ( !theResultFlag )
908  {
909  myErrorMessage = tr( "Loading style file %1 failed because:\n%2" ).arg( theURI ).arg( errorMsg );
910  return myErrorMessage;
911  }
912 
913  return "";
914 }
915 
916 void QgsMapLayer::exportNamedStyle( QDomDocument &doc, QString &errorMsg )
917 {
918  QDomImplementation DomImplementation;
919  QDomDocumentType documentType = DomImplementation.createDocumentType( "qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
920  QDomDocument myDocument( documentType );
921 
922  QDomElement myRootNode = myDocument.createElement( "qgis" );
923  myRootNode.setAttribute( "version", QString( "%1" ).arg( QGis::QGIS_VERSION ) );
924  myDocument.appendChild( myRootNode );
925 
926  myRootNode.setAttribute( "hasScaleBasedVisibilityFlag", hasScaleBasedVisibility() ? 1 : 0 );
927  myRootNode.setAttribute( "minimumScale", QString::number( minimumScale() ) );
928  myRootNode.setAttribute( "maximumScale", QString::number( maximumScale() ) );
929 
930 #if 0
931  // <transparencyLevelInt>
932  QDomElement transparencyLevelIntElement = myDocument.createElement( "transparencyLevelInt" );
933  QDomText transparencyLevelIntText = myDocument.createTextNode( QString::number( getTransparency() ) );
934  transparencyLevelIntElement.appendChild( transparencyLevelIntText );
935  myRootNode.appendChild( transparencyLevelIntElement );
936 #endif
937 
938  if ( !writeSymbology( myRootNode, myDocument, errorMsg ) )
939  {
940  errorMsg = QObject::tr( "Could not save symbology because:\n%1" ).arg( errorMsg );
941  return;
942  }
943  doc = myDocument;
944 }
945 
946 QString QgsMapLayer::saveDefaultStyle( bool & theResultFlag )
947 {
948  return saveNamedStyle( styleURI(), theResultFlag );
949 }
950 
951 QString QgsMapLayer::saveNamedStyle( const QString &theURI, bool &theResultFlag )
952 {
953  QString myErrorMessage;
954  QDomDocument myDocument;
955  exportNamedStyle( myDocument, myErrorMessage );
956 
957  // check if the uri is a file or ends with .qml,
958  // which indicates that it should become one
959  // everything else goes to the database
960  QString filename;
961 
962  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( this );
963  if ( vlayer && vlayer->providerType() == "ogr" )
964  {
965  QStringList theURIParts = theURI.split( "|" );
966  filename = theURIParts[0];
967  }
968  else if ( vlayer && vlayer->providerType() == "delimitedtext" )
969  {
970  filename = QUrl::fromEncoded( theURI.toAscii() ).toLocalFile();
971  }
972  else
973  {
974  filename = theURI;
975  }
976 
977  QFileInfo myFileInfo( filename );
978  if ( myFileInfo.exists() || filename.endsWith( ".qml", Qt::CaseInsensitive ) )
979  {
980  QFileInfo myDirInfo( myFileInfo.path() ); //excludes file name
981  if ( !myDirInfo.isWritable() )
982  {
983  return tr( "The directory containing your dataset needs to be writable!" );
984  }
985 
986  // now construct the file name for our .qml style file
987  QString myFileName = myFileInfo.path() + QDir::separator() + myFileInfo.completeBaseName() + ".qml";
988 
989  QFile myFile( myFileName );
990  if ( myFile.open( QFile::WriteOnly | QFile::Truncate ) )
991  {
992  QTextStream myFileStream( &myFile );
993  // save as utf-8 with 2 spaces for indents
994  myDocument.save( myFileStream, 2 );
995  myFile.close();
996  theResultFlag = true;
997  return tr( "Created default style file as %1" ).arg( myFileName );
998  }
999  else
1000  {
1001  theResultFlag = false;
1002  return tr( "ERROR: Failed to created default style file as %1. Check file permissions and retry." ).arg( myFileName );
1003  }
1004  }
1005  else
1006  {
1007  QString qml = myDocument.toString();
1008 
1009  // read from database
1010  sqlite3 *myDatabase;
1011  sqlite3_stmt *myPreparedStatement;
1012  const char *myTail;
1013  int myResult;
1014 
1015  myResult = sqlite3_open( QDir( QgsApplication::qgisSettingsDirPath() ).absoluteFilePath( "qgis.qmldb" ).toUtf8().data(), &myDatabase );
1016  if ( myResult != SQLITE_OK )
1017  {
1018  return tr( "User database could not be opened." );
1019  }
1020 
1021  QByteArray param0 = theURI.toUtf8();
1022  QByteArray param1 = qml.toUtf8();
1023 
1024  QString mySql = "create table if not exists tbl_styles(style varchar primary key,qml varchar)";
1025  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8().data(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
1026  if ( myResult == SQLITE_OK )
1027  {
1028  if ( sqlite3_step( myPreparedStatement ) != SQLITE_DONE )
1029  {
1030  sqlite3_finalize( myPreparedStatement );
1031  sqlite3_close( myDatabase );
1032  theResultFlag = false;
1033  return tr( "The style table could not be created." );
1034  }
1035  }
1036 
1037  sqlite3_finalize( myPreparedStatement );
1038 
1039  mySql = "insert into tbl_styles(style,qml) values (?,?)";
1040  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8().data(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
1041  if ( myResult == SQLITE_OK )
1042  {
1043  if ( sqlite3_bind_text( myPreparedStatement, 1, param0.data(), param0.length(), SQLITE_STATIC ) == SQLITE_OK &&
1044  sqlite3_bind_text( myPreparedStatement, 2, param1.data(), param1.length(), SQLITE_STATIC ) == SQLITE_OK &&
1045  sqlite3_step( myPreparedStatement ) == SQLITE_DONE )
1046  {
1047  theResultFlag = true;
1048  myErrorMessage = tr( "The style %1 was saved to database" ).arg( theURI );
1049  }
1050  }
1051 
1052  sqlite3_finalize( myPreparedStatement );
1053 
1054  if ( !theResultFlag )
1055  {
1056  QString mySql = "update tbl_styles set qml=? where style=?";
1057  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8().data(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
1058  if ( myResult == SQLITE_OK )
1059  {
1060  if ( sqlite3_bind_text( myPreparedStatement, 2, param0.data(), param0.length(), SQLITE_STATIC ) == SQLITE_OK &&
1061  sqlite3_bind_text( myPreparedStatement, 1, param1.data(), param1.length(), SQLITE_STATIC ) == SQLITE_OK &&
1062  sqlite3_step( myPreparedStatement ) == SQLITE_DONE )
1063  {
1064  theResultFlag = true;
1065  myErrorMessage = tr( "The style %1 was updated in the database." ).arg( theURI );
1066  }
1067  else
1068  {
1069  theResultFlag = false;
1070  myErrorMessage = tr( "The style %1 could not be updated in the database." ).arg( theURI );
1071  }
1072  }
1073  else
1074  {
1075  theResultFlag = false;
1076  myErrorMessage = tr( "The style %1 could not be inserted into database." ).arg( theURI );
1077  }
1078 
1079  sqlite3_finalize( myPreparedStatement );
1080  }
1081 
1082  sqlite3_close( myDatabase );
1083  }
1084 
1085  return myErrorMessage;
1086 }
1087 
1088 void QgsMapLayer::exportSldStyle( QDomDocument &doc, QString &errorMsg )
1089 {
1090  QDomDocument myDocument = QDomDocument();
1091 
1092  QDomNode header = myDocument.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" );
1093  myDocument.appendChild( header );
1094 
1095  // Create the root element
1096  QDomElement root = myDocument.createElementNS( "http://www.opengis.net/sld", "StyledLayerDescriptor" );
1097  root.setAttribute( "version", "1.1.0" );
1098  root.setAttribute( "xsi:schemaLocation", "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd" );
1099  root.setAttribute( "xmlns:ogc", "http://www.opengis.net/ogc" );
1100  root.setAttribute( "xmlns:se", "http://www.opengis.net/se" );
1101  root.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" );
1102  root.setAttribute( "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance" );
1103  myDocument.appendChild( root );
1104 
1105  // Create the NamedLayer element
1106  QDomElement namedLayerNode = myDocument.createElement( "NamedLayer" );
1107  root.appendChild( namedLayerNode );
1108 
1109  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( this );
1110  if ( !vlayer )
1111  {
1112  errorMsg = tr( "Could not save symbology because:\n%1" )
1113  .arg( "Non-vector layers not supported yet" );
1114  return;
1115  }
1116 
1117  if ( !vlayer->writeSld( namedLayerNode, myDocument, errorMsg ) )
1118  {
1119  errorMsg = tr( "Could not save symbology because:\n%1" ).arg( errorMsg );
1120  return;
1121  }
1122 
1123  doc = myDocument;
1124 }
1125 
1126 QString QgsMapLayer::saveSldStyle( const QString &theURI, bool &theResultFlag )
1127 {
1128  QString errorMsg;
1129  QDomDocument myDocument;
1130  exportSldStyle( myDocument, errorMsg );
1131  if ( !errorMsg.isNull() )
1132  {
1133  theResultFlag = false;
1134  return errorMsg;
1135  }
1136  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( this );
1137 
1138  // check if the uri is a file or ends with .sld,
1139  // which indicates that it should become one
1140  QString filename;
1141  if ( vlayer->providerType() == "ogr" )
1142  {
1143  QStringList theURIParts = theURI.split( "|" );
1144  filename = theURIParts[0];
1145  }
1146  else if ( vlayer->providerType() == "delimitedtext" )
1147  {
1148  filename = QUrl::fromEncoded( theURI.toAscii() ).toLocalFile();
1149  }
1150  else
1151  {
1152  filename = theURI;
1153  }
1154 
1155  QFileInfo myFileInfo( filename );
1156  if ( myFileInfo.exists() || filename.endsWith( ".sld", Qt::CaseInsensitive ) )
1157  {
1158  QFileInfo myDirInfo( myFileInfo.path() ); //excludes file name
1159  if ( !myDirInfo.isWritable() )
1160  {
1161  return tr( "The directory containing your dataset needs to be writable!" );
1162  }
1163 
1164  // now construct the file name for our .sld style file
1165  QString myFileName = myFileInfo.path() + QDir::separator() + myFileInfo.completeBaseName() + ".sld";
1166 
1167  QFile myFile( myFileName );
1168  if ( myFile.open( QFile::WriteOnly | QFile::Truncate ) )
1169  {
1170  QTextStream myFileStream( &myFile );
1171  // save as utf-8 with 2 spaces for indents
1172  myDocument.save( myFileStream, 2 );
1173  myFile.close();
1174  theResultFlag = true;
1175  return tr( "Created default style file as %1" ).arg( myFileName );
1176  }
1177  }
1178 
1179  theResultFlag = false;
1180  return tr( "ERROR: Failed to created SLD style file as %1. Check file permissions and retry." ).arg( filename );
1181 }
1182 
1183 QString QgsMapLayer::loadSldStyle( const QString &theURI, bool &theResultFlag )
1184 {
1185  QgsDebugMsg( "Entered." );
1186 
1187  theResultFlag = false;
1188 
1189  QDomDocument myDocument;
1190 
1191  // location of problem associated with errorMsg
1192  int line, column;
1193  QString myErrorMessage;
1194 
1195  QFile myFile( theURI );
1196  if ( myFile.open( QFile::ReadOnly ) )
1197  {
1198  // read file
1199  theResultFlag = myDocument.setContent( &myFile, true, &myErrorMessage, &line, &column );
1200  if ( !theResultFlag )
1201  myErrorMessage = tr( "%1 at line %2 column %3" ).arg( myErrorMessage ).arg( line ).arg( column );
1202  myFile.close();
1203  }
1204  else
1205  {
1206  myErrorMessage = tr( "Unable to open file %1" ).arg( theURI );
1207  }
1208 
1209  if ( !theResultFlag )
1210  {
1211  return myErrorMessage;
1212  }
1213 
1214  // check for root SLD element
1215  QDomElement myRoot = myDocument.firstChildElement( "StyledLayerDescriptor" );
1216  if ( myRoot.isNull() )
1217  {
1218  myErrorMessage = QString( "Error: StyledLayerDescriptor element not found in %1" ).arg( theURI );
1219  theResultFlag = false;
1220  return myErrorMessage;
1221  }
1222 
1223  // now get the style node out and pass it over to the layer
1224  // to deserialise...
1225  QDomElement namedLayerElem = myRoot.firstChildElement( "NamedLayer" );
1226  if ( namedLayerElem.isNull() )
1227  {
1228  myErrorMessage = QString( "Info: NamedLayer element not found." );
1229  theResultFlag = false;
1230  return myErrorMessage;
1231  }
1232 
1233  QString errorMsg;
1234  theResultFlag = readSld( namedLayerElem, errorMsg );
1235  if ( !theResultFlag )
1236  {
1237  myErrorMessage = tr( "Loading style file %1 failed because:\n%2" ).arg( theURI ).arg( errorMsg );
1238  return myErrorMessage;
1239  }
1240 
1241  return "";
1242 }
1243 
1244 
1246 {
1247  return &mUndoStack;
1248 }
1249 
1250 
1251 void QgsMapLayer::setCustomProperty( const QString& key, const QVariant& value )
1252 {
1253  mCustomProperties[key] = value;
1254 }
1255 
1256 QVariant QgsMapLayer::customProperty( const QString& value, const QVariant& defaultValue ) const
1257 {
1258  return mCustomProperties.value( value, defaultValue );
1259 }
1260 
1261 void QgsMapLayer::removeCustomProperty( const QString& key )
1262 {
1263  mCustomProperties.remove( key );
1264 }
1265 
1266 void QgsMapLayer::readCustomProperties( const QDomNode& layerNode, const QString& keyStartsWith )
1267 {
1268  QDomNode propsNode = layerNode.namedItem( "customproperties" );
1269  if ( propsNode.isNull() ) // no properties stored...
1270  return;
1271 
1272  if ( !keyStartsWith.isEmpty() )
1273  {
1274  //remove old keys
1275  QStringList keysToRemove;
1276  QMap<QString, QVariant>::const_iterator pIt = mCustomProperties.constBegin();
1277  for ( ; pIt != mCustomProperties.constEnd(); ++pIt )
1278  {
1279  if ( pIt.key().startsWith( keyStartsWith ) )
1280  {
1281  keysToRemove.push_back( pIt.key() );
1282  }
1283  }
1284 
1285  QStringList::const_iterator sIt = keysToRemove.constBegin();
1286  for ( ; sIt != keysToRemove.constEnd(); ++sIt )
1287  {
1288  mCustomProperties.remove( *sIt );
1289  }
1290  }
1291  else
1292  {
1293  mCustomProperties.clear();
1294  }
1295 
1296  QDomNodeList nodes = propsNode.childNodes();
1297 
1298  for ( int i = 0; i < nodes.size(); i++ )
1299  {
1300  QDomNode propNode = nodes.at( i );
1301  if ( propNode.isNull() || propNode.nodeName() != "property" )
1302  continue;
1303  QDomElement propElement = propNode.toElement();
1304 
1305  QString key = propElement.attribute( "key" );
1306  if ( key.isEmpty() || key.startsWith( keyStartsWith ) )
1307  {
1308  QString value = propElement.attribute( "value" );
1309  mCustomProperties[key] = QVariant( value );
1310  }
1311  }
1312 
1313 }
1314 
1315 void QgsMapLayer::writeCustomProperties( QDomNode & layerNode, QDomDocument & doc ) const
1316 {
1317  //remove already existing <customproperties> tags
1318  QDomNodeList propertyList = layerNode.toElement().elementsByTagName( "customproperties" );
1319  for ( int i = 0; i < propertyList.size(); ++i )
1320  {
1321  layerNode.removeChild( propertyList.at( i ) );
1322  }
1323 
1324  QDomElement propsElement = doc.createElement( "customproperties" );
1325 
1326  for ( QMap<QString, QVariant>::const_iterator it = mCustomProperties.constBegin(); it != mCustomProperties.constEnd(); ++it )
1327  {
1328  QDomElement propElement = doc.createElement( "property" );
1329  propElement.setAttribute( "key", it.key() );
1330  propElement.setAttribute( "value", it.value().toString() );
1331  propsElement.appendChild( propElement );
1332  }
1333 
1334  layerNode.appendChild( propsElement );
1335 }
1336 
1337 void QgsMapLayer::setCacheImage( QImage * thepImage )
1338 {
1339  QgsDebugMsg( "cache Image set!" );
1340  if ( mpCacheImage == thepImage )
1341  return;
1342 
1343  if ( mpCacheImage )
1344  {
1346  delete mpCacheImage;
1347  }
1348  mpCacheImage = thepImage;
1349 }
1350 
1352 {
1353  return false;
1354 }
1355 
1356 void QgsMapLayer::setValid( bool valid )
1357 {
1358  mValid = valid;
1359 }
1360 
1362 {
1363  setCacheImage( 0 );
1364 }
1365 
1367 {
1368  return QString();
1369 }
1370 
1372 {
1373  mExtent = r;
1374 }