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