QGIS API Documentation  2.0.1-Dufour
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgssvgcache.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssvgcache.h
3  ------------------------------
4  begin : 2011
5  copyright : (C) 2011 by Marco Hugentobler
6  email : marco dot hugentobler at sourcepole dot ch
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 "qgssvgcache.h"
19 #include "qgis.h"
20 #include "qgslogger.h"
22 #include "qgsmessagelog.h"
23 #include <QApplication>
24 #include <QCoreApplication>
25 #include <QCursor>
26 #include <QDomDocument>
27 #include <QDomElement>
28 #include <QFile>
29 #include <QImage>
30 #include <QPainter>
31 #include <QPicture>
32 #include <QSvgRenderer>
33 #include <QFileInfo>
34 #include <QNetworkReply>
35 #include <QNetworkRequest>
36 
37 QgsSvgCacheEntry::QgsSvgCacheEntry(): file( QString() ), size( 0.0 ), outlineWidth( 0 ), widthScaleFactor( 1.0 ), rasterScaleFactor( 1.0 ), fill( Qt::black ),
38  outline( Qt::black ), image( 0 ), picture( 0 )
39 {
40 }
41 
42 QgsSvgCacheEntry::QgsSvgCacheEntry( const QString& f, double s, double ow, double wsf, double rsf, const QColor& fi, const QColor& ou ): file( f ), size( s ), outlineWidth( ow ),
43  widthScaleFactor( wsf ), rasterScaleFactor( rsf ), fill( fi ), outline( ou ), image( 0 ), picture( 0 )
44 {
45 }
46 
47 
49 {
50  delete image;
51  delete picture;
52 }
53 
55 {
56  return ( other.file == file && other.size == size && other.outlineWidth == outlineWidth && other.widthScaleFactor == widthScaleFactor
57  && other.rasterScaleFactor == rasterScaleFactor && other.fill == fill && other.outline == outline );
58 }
59 
61 {
62  int size = svgContent.size();
63  if ( picture )
64  {
65  size += picture->size();
66  }
67  if ( image )
68  {
69  size += ( image->width() * image->height() * 32 );
70  }
71  return size;
72 }
73 
74 QString file;
75 double size;
76 double outlineWidth;
79 QColor fill;
80 QColor outline;
81 
83 
85 {
86  if ( !mInstance )
87  {
88  mInstance = new QgsSvgCache();
89  }
90  return mInstance;
91 }
92 
93 QgsSvgCache::QgsSvgCache( QObject *parent )
94  : QObject( parent )
95  , mTotalSize( 0 )
96  , mLeastRecentEntry( 0 )
97  , mMostRecentEntry( 0 )
98 {
99 }
100 
102 {
103  QMultiHash< QString, QgsSvgCacheEntry* >::iterator it = mEntryLookup.begin();
104  for ( ; it != mEntryLookup.end(); ++it )
105  {
106  delete it.value();
107  }
108 }
109 
110 
111 const QImage& QgsSvgCache::svgAsImage( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
112  double widthScaleFactor, double rasterScaleFactor, bool& fitsInCache )
113 {
114  fitsInCache = true;
115  QgsSvgCacheEntry* currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
116 
117  //if current entry image is 0: cache image for entry
118  // checks to see if image will fit into cache
119  //update stats for memory usage
120  if ( !currentEntry->image )
121  {
122  QSvgRenderer r( currentEntry->svgContent );
123  double hwRatio = 1.0;
124  if ( r.viewBoxF().width() > 0 )
125  {
126  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
127  }
128  long cachedDataSize = 0;
129  cachedDataSize += currentEntry->svgContent.size();
130  cachedDataSize += ( int )( currentEntry->size * currentEntry->size * hwRatio * 32 );
131  if ( cachedDataSize > mMaximumSize / 2 )
132  {
133  fitsInCache = false;
134  delete currentEntry->image;
135  currentEntry->image = 0;
136  //currentEntry->image = new QImage( 0, 0 );
137 
138  // instead cache picture
139  if ( !currentEntry->picture )
140  {
141  cachePicture( currentEntry, false );
142  }
143  }
144  else
145  {
146  cacheImage( currentEntry );
147  }
149  }
150 
151  return *( currentEntry->image );
152 }
153 
154 const QPicture& QgsSvgCache::svgAsPicture( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
155  double widthScaleFactor, double rasterScaleFactor, bool forceVectorOutput )
156 {
157  QgsSvgCacheEntry* currentEntry = cacheEntry( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
158 
159  //if current entry picture is 0: cache picture for entry
160  //update stats for memory usage
161  if ( !currentEntry->picture )
162  {
163  cachePicture( currentEntry, forceVectorOutput );
165  }
166 
167  return *( currentEntry->picture );
168 }
169 
170 QgsSvgCacheEntry* QgsSvgCache::insertSVG( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
171  double widthScaleFactor, double rasterScaleFactor )
172 {
173  QgsSvgCacheEntry* entry = new QgsSvgCacheEntry( file, size, outlineWidth, widthScaleFactor, rasterScaleFactor, fill, outline );
174 
175  replaceParamsAndCacheSvg( entry );
176 
177  mEntryLookup.insert( file, entry );
178 
179  //insert to most recent place in entry list
180  if ( !mMostRecentEntry ) //inserting first entry
181  {
182  mLeastRecentEntry = entry;
183  mMostRecentEntry = entry;
184  entry->previousEntry = 0;
185  entry->nextEntry = 0;
186  }
187  else
188  {
190  entry->nextEntry = 0;
191  mMostRecentEntry->nextEntry = entry;
192  mMostRecentEntry = entry;
193  }
194 
196  return entry;
197 }
198 
199 void QgsSvgCache::containsParams( const QString& path, bool& hasFillParam, QColor& defaultFillColor, bool& hasOutlineParam, QColor& defaultOutlineColor,
200  bool& hasOutlineWidthParam, double& defaultOutlineWidth ) const
201 {
202  defaultFillColor = QColor( Qt::black );
203  defaultOutlineColor = QColor( Qt::black );
204  defaultOutlineWidth = 1.0;
205 
206  QDomDocument svgDoc;
207  if ( !svgDoc.setContent( getImageData( path ) ) )
208  {
209  return;
210  }
211 
212  QDomElement docElem = svgDoc.documentElement();
213  containsElemParams( docElem, hasFillParam, defaultFillColor, hasOutlineParam, defaultOutlineColor, hasOutlineWidthParam, defaultOutlineWidth );
214 }
215 
217 {
218  if ( !entry )
219  {
220  return;
221  }
222 
223  QDomDocument svgDoc;
224  if ( !svgDoc.setContent( getImageData( entry->file ) ) )
225  {
226  return;
227  }
228 
229  //replace fill color, outline color, outline with in all nodes
230  QDomElement docElem = svgDoc.documentElement();
231  replaceElemParams( docElem, entry->fill, entry->outline, entry->outlineWidth );
232 
233  entry->svgContent = svgDoc.toByteArray();
234  mTotalSize += entry->svgContent.size();
235 }
236 
237 QByteArray QgsSvgCache::getImageData( const QString &path ) const
238 {
239  // is it a path to local file?
240  QFile svgFile( path );
241  if ( svgFile.exists() )
242  {
243  if ( svgFile.open( QIODevice::ReadOnly ) )
244  {
245  return svgFile.readAll();
246  }
247  else
248  {
249  return QByteArray();
250  }
251  }
252 
253  // maybe it's a url...
254  if ( !path.contains( "://" ) ) // otherwise short, relative SVG paths might be considered URLs
255  {
256  return QByteArray();
257  }
258 
259  QUrl svgUrl( path );
260  if ( !svgUrl.isValid() )
261  {
262  return QByteArray();
263  }
264 
265  // check whether it's a url pointing to a local file
266  if ( svgUrl.scheme().compare( "file", Qt::CaseInsensitive ) == 0 )
267  {
268  svgFile.setFileName( svgUrl.toLocalFile() );
269  if ( svgFile.exists() )
270  {
271  if ( svgFile.open( QIODevice::ReadOnly ) )
272  {
273  return svgFile.readAll();
274  }
275  }
276 
277  // not found...
278  return QByteArray();
279  }
280 
281  // the url points to a remote resource, download it!
282  QNetworkReply *reply = 0;
283 
284  // The following code blocks until the file is downloaded...
285  // TODO: use signals to get reply finished notification, in this moment
286  // it's executed while rendering.
287  while ( 1 )
288  {
289  QgsDebugMsg( QString( "get svg: %1" ).arg( svgUrl.toString() ) );
290  QNetworkRequest request( svgUrl );
291  request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
292  request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
293 
294  reply = QgsNetworkAccessManager::instance()->get( request );
295  connect( reply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( downloadProgress( qint64, qint64 ) ) );
296 
297  //emit statusChanged( tr( "Downloading svg." ) );
298 
299  // wait until the image download finished
300  // TODO: connect to the reply->finished() signal
301  while ( !reply->isFinished() )
302  {
303  QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents, 500 );
304  }
305 
306  if ( reply->error() != QNetworkReply::NoError )
307  {
308  QgsMessageLog::logMessage( tr( "SVG request failed [error: %1 - url: %2]" ).arg( reply->errorString() ).arg( reply->url().toString() ), tr( "SVG" ) );
309 
310  reply->deleteLater();
311  return QByteArray();
312  }
313 
314  QVariant redirect = reply->attribute( QNetworkRequest::RedirectionTargetAttribute );
315  if ( redirect.isNull() )
316  {
317  // neither network error nor redirection
318  // TODO: cache the image
319  break;
320  }
321 
322  // do a new request to the redirect url
323  svgUrl = redirect.toUrl();
324  reply->deleteLater();
325  }
326 
327  QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
328  if ( !status.isNull() && status.toInt() >= 400 )
329  {
330  QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );
331  QgsMessageLog::logMessage( tr( "SVG request error [status: %1 - reason phrase: %2]" ).arg( status.toInt() ).arg( phrase.toString() ), tr( "SVG" ) );
332 
333  reply->deleteLater();
334  return QByteArray();
335  }
336 
337  QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
338  QgsDebugMsg( "contentType: " + contentType );
339  if ( !contentType.startsWith( "image/svg+xml", Qt::CaseInsensitive ) )
340  {
341  reply->deleteLater();
342  return QByteArray();
343  }
344 
345  // read the image data
346  QByteArray ba = reply->readAll();
347  reply->deleteLater();
348 
349  return ba;
350 }
351 
353 {
354  if ( !entry )
355  {
356  return;
357  }
358 
359  delete entry->image;
360  entry->image = 0;
361 
362  QSvgRenderer r( entry->svgContent );
363  double hwRatio = 1.0;
364  if ( r.viewBoxF().width() > 0 )
365  {
366  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
367  }
368  double wSize = entry->size;
369  int wImgSize = ( int )wSize;
370  if ( wImgSize < 1 )
371  {
372  wImgSize = 1;
373  }
374  double hSize = wSize * hwRatio;
375  int hImgSize = ( int )hSize;
376  if ( hImgSize < 1 )
377  {
378  hImgSize = 1;
379  }
380  // cast double image sizes to int for QImage
381  QImage* image = new QImage( wImgSize, hImgSize, QImage::Format_ARGB32_Premultiplied );
382  image->fill( 0 ); // transparent background
383 
384  QPainter p( image );
385  if ( r.viewBoxF().width() == r.viewBoxF().height() )
386  {
387  r.render( &p );
388  }
389  else
390  {
391  QSizeF s( r.viewBoxF().size() );
392  s.scale( wSize, hSize, Qt::KeepAspectRatio );
393  QRectF rect(( wImgSize - s.width() ) / 2, ( hImgSize - s.height() ) / 2, s.width(), s.height() );
394  r.render( &p, rect );
395  }
396 
397  entry->image = image;
398  mTotalSize += ( image->width() * image->height() * 32 );
399 }
400 
401 void QgsSvgCache::cachePicture( QgsSvgCacheEntry *entry, bool forceVectorOutput )
402 {
403  if ( !entry )
404  {
405  return;
406  }
407 
408  delete entry->picture;
409  entry->picture = 0;
410 
411  //correct QPictures dpi correction
412  QPicture* picture = new QPicture();
413  QRectF rect;
414  QSvgRenderer r( entry->svgContent );
415  double hwRatio = 1.0;
416  if ( r.viewBoxF().width() > 0 )
417  {
418  hwRatio = r.viewBoxF().height() / r.viewBoxF().width();
419  }
420  bool drawOnScreen = qgsDoubleNear( entry->rasterScaleFactor, 1.0, 0.1 );
421  if ( drawOnScreen && forceVectorOutput ) //forceVectorOutput always true in case of composer draw / composer preview
422  {
423  // fix to ensure rotated symbols scale with composer page (i.e. not map item) zoom
424  double wSize = entry->size;
425  double hSize = wSize * hwRatio;
426  QSizeF s( r.viewBoxF().size() );
427  s.scale( wSize, hSize, Qt::KeepAspectRatio );
428  rect = QRectF( -s.width() / 2.0, -s.height() / 2.0, s.width(), s.height() );
429  }
430  else
431  {
432  // output for print or image saving @ specific dpi
433  double scaledSize = entry->size / 25.4 / ( entry->rasterScaleFactor * entry->widthScaleFactor );
434  double wSize = scaledSize * picture->logicalDpiX();
435  double hSize = scaledSize * picture->logicalDpiY() * r.viewBoxF().height() / r.viewBoxF().width();
436  rect = QRectF( QPointF( -wSize / 2.0, -hSize / 2.0 ), QSizeF( wSize, hSize ) );
437  }
438 
439  QPainter p( picture );
440  r.render( &p, rect );
441  entry->picture = picture;
442  mTotalSize += entry->picture->size();
443 }
444 
445 QgsSvgCacheEntry* QgsSvgCache::cacheEntry( const QString& file, double size, const QColor& fill, const QColor& outline, double outlineWidth,
446  double widthScaleFactor, double rasterScaleFactor )
447 {
448  //search entries in mEntryLookup
449  QgsSvgCacheEntry* currentEntry = 0;
450  QList<QgsSvgCacheEntry*> entries = mEntryLookup.values( file );
451 
452  QList<QgsSvgCacheEntry*>::iterator entryIt = entries.begin();
453  for ( ; entryIt != entries.end(); ++entryIt )
454  {
455  QgsSvgCacheEntry* cacheEntry = *entryIt;
456  if ( cacheEntry->file == file && qgsDoubleNear( cacheEntry->size, size ) && cacheEntry->fill == fill && cacheEntry->outline == outline &&
457  cacheEntry->outlineWidth == outlineWidth && cacheEntry->widthScaleFactor == widthScaleFactor && cacheEntry->rasterScaleFactor == rasterScaleFactor )
458  {
459  currentEntry = cacheEntry;
460  break;
461  }
462  }
463 
464  //if not found: create new entry
465  //cache and replace params in svg content
466  if ( !currentEntry )
467  {
468  currentEntry = insertSVG( file, size, fill, outline, outlineWidth, widthScaleFactor, rasterScaleFactor );
469  }
470  else
471  {
472  takeEntryFromList( currentEntry );
473  if ( !mMostRecentEntry ) //list is empty
474  {
475  mMostRecentEntry = currentEntry;
476  mLeastRecentEntry = currentEntry;
477  }
478  else
479  {
480  mMostRecentEntry->nextEntry = currentEntry;
481  currentEntry->previousEntry = mMostRecentEntry;
482  currentEntry->nextEntry = 0;
483  mMostRecentEntry = currentEntry;
484  }
485  }
486 
487  //debugging
488  //printEntryList();
489 
490  return currentEntry;
491 }
492 
493 void QgsSvgCache::replaceElemParams( QDomElement& elem, const QColor& fill, const QColor& outline, double outlineWidth )
494 {
495  if ( elem.isNull() )
496  {
497  return;
498  }
499 
500  //go through attributes
501  QDomNamedNodeMap attributes = elem.attributes();
502  int nAttributes = attributes.count();
503  for ( int i = 0; i < nAttributes; ++i )
504  {
505  QDomAttr attribute = attributes.item( i ).toAttr();
506  //e.g. style="fill:param(fill);param(stroke)"
507  if ( attribute.name().compare( "style", Qt::CaseInsensitive ) == 0 )
508  {
509  //entries separated by ';'
510  QString newAttributeString;
511 
512  QStringList entryList = attribute.value().split( ';' );
513  QStringList::const_iterator entryIt = entryList.constBegin();
514  for ( ; entryIt != entryList.constEnd(); ++entryIt )
515  {
516  QStringList keyValueSplit = entryIt->split( ':' );
517  if ( keyValueSplit.size() < 2 )
518  {
519  continue;
520  }
521  QString key = keyValueSplit.at( 0 );
522  QString value = keyValueSplit.at( 1 );
523  if ( value.startsWith( "param(fill" ) )
524  {
525  value = fill.name();
526  }
527  else if ( value.startsWith( "param(outline)" ) )
528  {
529  value = outline.name();
530  }
531  else if ( value.startsWith( "param(outline-width)" ) )
532  {
533  value = QString::number( outlineWidth );
534  }
535 
536  if ( entryIt != entryList.constBegin() )
537  {
538  newAttributeString.append( ";" );
539  }
540  newAttributeString.append( key + ":" + value );
541  }
542  elem.setAttribute( attribute.name(), newAttributeString );
543  }
544  else
545  {
546  QString value = attribute.value();
547  if ( value.startsWith( "param(fill)" ) )
548  {
549  elem.setAttribute( attribute.name(), fill.name() );
550  }
551  else if ( value.startsWith( "param(outline)" ) )
552  {
553  elem.setAttribute( attribute.name(), outline.name() );
554  }
555  else if ( value.startsWith( "param(outline-width)" ) )
556  {
557  elem.setAttribute( attribute.name(), QString::number( outlineWidth ) );
558  }
559  }
560  }
561 
562  QDomNodeList childList = elem.childNodes();
563  int nChildren = childList.count();
564  for ( int i = 0; i < nChildren; ++i )
565  {
566  QDomElement childElem = childList.at( i ).toElement();
567  replaceElemParams( childElem, fill, outline, outlineWidth );
568  }
569 }
570 
571 void QgsSvgCache::containsElemParams( const QDomElement& elem, bool& hasFillParam, QColor& defaultFill, bool& hasOutlineParam, QColor& defaultOutline,
572  bool& hasOutlineWidthParam, double& defaultOutlineWidth ) const
573 {
574  if ( elem.isNull() )
575  {
576  return;
577  }
578 
579  //we already have all the information, no need to go deeper
580  if ( hasFillParam && hasOutlineParam && hasOutlineWidthParam )
581  {
582  return;
583  }
584 
585  //check this elements attribute
586  QDomNamedNodeMap attributes = elem.attributes();
587  int nAttributes = attributes.count();
588 
589  QStringList valueSplit;
590  for ( int i = 0; i < nAttributes; ++i )
591  {
592  QDomAttr attribute = attributes.item( i ).toAttr();
593  if ( attribute.name().compare( "style", Qt::CaseInsensitive ) == 0 )
594  {
595  //entries separated by ';'
596  QStringList entryList = attribute.value().split( ';' );
597  QStringList::const_iterator entryIt = entryList.constBegin();
598  for ( ; entryIt != entryList.constEnd(); ++entryIt )
599  {
600  QStringList keyValueSplit = entryIt->split( ':' );
601  if ( keyValueSplit.size() < 2 )
602  {
603  continue;
604  }
605  QString key = keyValueSplit.at( 0 );
606  QString value = keyValueSplit.at( 1 );
607  valueSplit = value.split( " " );
608  if ( !hasFillParam && value.startsWith( "param(fill)" ) )
609  {
610  hasFillParam = true;
611  if ( valueSplit.size() > 1 )
612  {
613  defaultFill = QColor( valueSplit.at( 1 ) );
614  }
615  }
616  else if ( !hasOutlineParam && value.startsWith( "param(outline)" ) )
617  {
618  hasOutlineParam = true;
619  if ( valueSplit.size() > 1 )
620  {
621  defaultOutline = QColor( valueSplit.at( 1 ) );
622  }
623  }
624  else if ( !hasOutlineWidthParam && value.startsWith( "param(outline-width)" ) )
625  {
626  hasOutlineWidthParam = true;
627  if ( valueSplit.size() > 1 )
628  {
629  defaultOutlineWidth = valueSplit.at( 1 ).toDouble();
630  }
631  }
632  }
633  }
634  else
635  {
636  QString value = attribute.value();
637  valueSplit = value.split( " " );
638  if ( !hasFillParam && value.startsWith( "param(fill)" ) )
639  {
640  hasFillParam = true;
641  if ( valueSplit.size() > 1 )
642  {
643  defaultFill = QColor( valueSplit.at( 1 ) );
644  }
645  }
646  else if ( !hasOutlineParam && value.startsWith( "param(outline)" ) )
647  {
648  hasOutlineParam = true;
649  if ( valueSplit.size() > 1 )
650  {
651  defaultOutline = QColor( valueSplit.at( 1 ) );
652  }
653  }
654  else if ( !hasOutlineWidthParam && value.startsWith( "param(outline-width)" ) )
655  {
656  hasOutlineWidthParam = true;
657  if ( valueSplit.size() > 1 )
658  {
659  defaultOutlineWidth = valueSplit.at( 1 ).toDouble();
660  }
661  }
662  }
663  }
664 
665  //pass it further to child items
666  QDomNodeList childList = elem.childNodes();
667  int nChildren = childList.count();
668  for ( int i = 0; i < nChildren; ++i )
669  {
670  QDomElement childElem = childList.at( i ).toElement();
671  containsElemParams( childElem, hasFillParam, defaultFill, hasOutlineParam, defaultOutline, hasOutlineWidthParam, defaultOutlineWidth );
672  }
673 }
674 
676 {
677  delete entry;
678  mEntryLookup.remove( s , entry );
679 }
680 
682 {
683  QgsDebugMsg( "****************svg cache entry list*************************" );
684  QgsDebugMsg( "Cache size: " + QString::number( mTotalSize ) );
686  while ( entry )
687  {
688  QgsDebugMsg( "***Entry:" );
689  QgsDebugMsg( "File:" + entry->file );
690  QgsDebugMsg( "Size:" + QString::number( entry->size ) );
691  QgsDebugMsg( "Width scale factor" + QString::number( entry->widthScaleFactor ) );
692  QgsDebugMsg( "Raster scale factor" + QString::number( entry->rasterScaleFactor ) );
693  entry = entry->nextEntry;
694  }
695 }
696 
698 {
699  //only one entry in cache
701  {
702  return;
703  }
705  while ( entry && ( mTotalSize > mMaximumSize ) )
706  {
707  QgsSvgCacheEntry* bkEntry = entry;
708  entry = entry->nextEntry;
709 
710  takeEntryFromList( bkEntry );
711  mEntryLookup.remove( bkEntry->file, bkEntry );
712  mTotalSize -= bkEntry->dataSize();
713  delete bkEntry;
714  }
715 }
716 
718 {
719  if ( !entry )
720  {
721  return;
722  }
723 
724  if ( entry->previousEntry )
725  {
726  entry->previousEntry->nextEntry = entry->nextEntry;
727  }
728  else
729  {
730  mLeastRecentEntry = entry->nextEntry;
731  }
732  if ( entry->nextEntry )
733  {
734  entry->nextEntry->previousEntry = entry->previousEntry;
735  }
736  else
737  {
739  }
740 }
741 
742 void QgsSvgCache::downloadProgress( qint64 bytesReceived, qint64 bytesTotal )
743 {
744  QString msg = tr( "%1 of %2 bytes of svg image downloaded." ).arg( bytesReceived ).arg( bytesTotal < 0 ? QString( "unknown number of" ) : QString::number( bytesTotal ) );
745  QgsDebugMsg( msg );
746  emit statusChanged( msg );
747 }