1 | /*************************************************************************** |
2 | * Copyright 2010-2012 Stefan Majewsky <majewsky@gmx.net> * |
3 | * * |
4 | * This program is free software; you can redistribute it and/or modify * |
5 | * it under the terms of the GNU Library General Public License * |
6 | * version 2 as published by the Free Software Foundation * |
7 | * * |
8 | * This program is distributed in the hope that it will be useful, * |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
11 | * GNU Library General Public License for more details. * |
12 | * * |
13 | * You should have received a copy of the GNU Library General Public * |
14 | * License along with this program; if not, write to the * |
15 | * Free Software Foundation, Inc., * |
16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * |
17 | ***************************************************************************/ |
18 | |
19 | #include "kgamerenderer.h" |
20 | #include "kgamerenderer_p.h" |
21 | #include "kgamerendererclient.h" |
22 | #include "colorproxy_p.h" |
23 | #include "kgtheme.h" |
24 | #include "kgthemeprovider.h" |
25 | |
26 | #include <QtCore/QCoreApplication> |
27 | #include <QtCore/QDateTime> |
28 | #include <QtCore/QFileInfo> |
29 | #include <QtCore/QScopedPointer> |
30 | #include <QtGui/QPainter> |
31 | #include <QVariant> |
32 | #include <KDebug> |
33 | |
34 | //TODO: automatically schedule pre-rendering of animation frames |
35 | //TODO: multithreaded SVG loading? |
36 | |
37 | static const QString cacheName(QByteArray theme) |
38 | { |
39 | const QString appName = QCoreApplication::instance()->applicationName(); |
40 | //e.g. "themes/foobar.desktop" -> "themes/foobar" |
41 | if (theme.endsWith(QByteArray(".desktop" ))) |
42 | theme.truncate(theme.length() - 8); //8 = strlen(".desktop") |
43 | return QString::fromLatin1("kgamerenderer-%1-%2" ) |
44 | .arg(appName).arg(QString::fromUtf8(theme)); |
45 | } |
46 | |
47 | KGameRendererPrivate::KGameRendererPrivate(KgThemeProvider* provider, unsigned cacheSize, KGameRenderer* parent) |
48 | : m_parent(parent) |
49 | , m_provider(provider) |
50 | , m_currentTheme(0) //will be loaded on first use |
51 | , m_frameSuffix(QString::fromLatin1("_%1" )) |
52 | , m_sizePrefix(QString::fromLatin1("%1-%2-" )) |
53 | , m_frameCountPrefix(QString::fromLatin1("fc-" )) |
54 | , m_boundsPrefix(QString::fromLatin1("br-" )) |
55 | //default cache size: 3 MiB = 3 << 20 bytes |
56 | , m_cacheSize((cacheSize == 0 ? 3 : cacheSize) << 20) |
57 | , m_strategies(KGameRenderer::UseDiskCache | KGameRenderer::UseRenderingThreads) |
58 | , m_frameBaseIndex(0) |
59 | , m_defaultPrimaryView(0) |
60 | , m_rendererPool(&m_workerPool) |
61 | , m_imageCache(0) |
62 | { |
63 | qRegisterMetaType<KGRInternal::Job*>(); |
64 | } |
65 | |
66 | KGameRenderer::KGameRenderer(KgThemeProvider* provider, unsigned cacheSize) |
67 | : d(new KGameRendererPrivate(provider, cacheSize, this)) |
68 | { |
69 | if (!provider->parent()) |
70 | { |
71 | provider->setParent(this); |
72 | } |
73 | connect(provider, SIGNAL(currentThemeChanged(const KgTheme*)), SLOT(_k_setTheme(const KgTheme*))); |
74 | } |
75 | |
76 | static KgThemeProvider* providerForSingleTheme(KgTheme* theme, QObject* parent) |
77 | { |
78 | KgThemeProvider* prov = new KgThemeProvider(QByteArray(), parent); |
79 | prov->addTheme(theme); |
80 | return prov; |
81 | } |
82 | |
83 | KGameRenderer::KGameRenderer(KgTheme* theme, unsigned cacheSize) |
84 | : d(new KGameRendererPrivate(providerForSingleTheme(theme, this), cacheSize, this)) |
85 | { |
86 | } |
87 | |
88 | KGameRenderer::~KGameRenderer() |
89 | { |
90 | //cleanup clients |
91 | while (!d->m_clients.isEmpty()) |
92 | { |
93 | delete d->m_clients.constBegin().key(); |
94 | } |
95 | //cleanup own stuff |
96 | d->m_workerPool.waitForDone(); |
97 | delete d->m_imageCache; |
98 | delete d; |
99 | } |
100 | |
101 | QGraphicsView* KGameRenderer::defaultPrimaryView() const |
102 | { |
103 | return d->m_defaultPrimaryView; |
104 | } |
105 | |
106 | void KGameRenderer::setDefaultPrimaryView(QGraphicsView* view) |
107 | { |
108 | d->m_defaultPrimaryView = view; |
109 | } |
110 | |
111 | int KGameRenderer::frameBaseIndex() const |
112 | { |
113 | return d->m_frameBaseIndex; |
114 | } |
115 | |
116 | void KGameRenderer::setFrameBaseIndex(int frameBaseIndex) |
117 | { |
118 | d->m_frameBaseIndex = frameBaseIndex; |
119 | } |
120 | |
121 | QString KGameRenderer::frameSuffix() const |
122 | { |
123 | return d->m_frameSuffix; |
124 | } |
125 | |
126 | void KGameRenderer::setFrameSuffix(const QString& suffix) |
127 | { |
128 | d->m_frameSuffix = suffix.contains(QLatin1String("%1" )) ? suffix : QLatin1String("_%1" ); |
129 | } |
130 | |
131 | KGameRenderer::Strategies KGameRenderer::strategies() const |
132 | { |
133 | return d->m_strategies; |
134 | } |
135 | |
136 | void KGameRenderer::setStrategyEnabled(KGameRenderer::Strategy strategy, bool enabled) |
137 | { |
138 | const bool oldEnabled = d->m_strategies & strategy; |
139 | if (enabled) |
140 | { |
141 | d->m_strategies |= strategy; |
142 | } |
143 | else |
144 | { |
145 | d->m_strategies &= ~strategy; |
146 | } |
147 | if (strategy == KGameRenderer::UseDiskCache && oldEnabled != enabled) |
148 | { |
149 | //reload theme |
150 | const KgTheme* theme = d->m_currentTheme; |
151 | if (theme) |
152 | { |
153 | d->m_currentTheme = 0; //or setTheme() will return immediately |
154 | d->_k_setTheme(theme); |
155 | } |
156 | } |
157 | } |
158 | |
159 | void KGameRendererPrivate::_k_setTheme(const KgTheme* theme) |
160 | { |
161 | const KgTheme* oldTheme = m_currentTheme; |
162 | if (oldTheme == theme) |
163 | { |
164 | return; |
165 | } |
166 | kDebug(11000) << "Setting theme:" << theme->name(); |
167 | if (!setTheme(theme)) |
168 | { |
169 | const KgTheme* defaultTheme = m_provider->defaultTheme(); |
170 | if (theme != defaultTheme) |
171 | { |
172 | kDebug(11000) << "Falling back to default theme:" << defaultTheme->name(); |
173 | setTheme(defaultTheme); |
174 | m_provider->setCurrentTheme(defaultTheme); |
175 | } |
176 | } |
177 | //announce change to KGameRendererClients |
178 | QHash<KGameRendererClient*, QString>::iterator it1 = m_clients.begin(), it2 = m_clients.end(); |
179 | for (; it1 != it2; ++it1) |
180 | { |
181 | it1.value().clear(); //because the pixmap is outdated |
182 | it1.key()->d->fetchPixmap(); |
183 | } |
184 | emit m_parent->themeChanged(m_currentTheme); |
185 | } |
186 | |
187 | bool KGameRendererPrivate::setTheme(const KgTheme* theme) |
188 | { |
189 | if (!theme) |
190 | { |
191 | return false; |
192 | } |
193 | //open cache (and SVG file, if necessary) |
194 | if (m_strategies & KGameRenderer::UseDiskCache) |
195 | { |
196 | QScopedPointer<KImageCache> oldCache(m_imageCache); |
197 | const QString imageCacheName = cacheName(theme->identifier()); |
198 | m_imageCache = new KImageCache(imageCacheName, m_cacheSize); |
199 | m_imageCache->setPixmapCaching(false); //see big comment in KGRPrivate class declaration |
200 | //check timestamp of cache vs. last write access to theme/SVG |
201 | const uint svgTimestamp = qMax( |
202 | QFileInfo(theme->graphicsPath()).lastModified().toTime_t(), |
203 | theme->property("_k_themeDescTimestamp" ).value<uint>() |
204 | ); |
205 | QByteArray buffer; |
206 | if (!m_imageCache->find(QString::fromLatin1("kgr_timestamp" ), &buffer)) |
207 | buffer = "0" ; |
208 | const uint cacheTimestamp = buffer.toInt(); |
209 | //try to instantiate renderer immediately if the cache does not exist or is outdated |
210 | //FIXME: This logic breaks if the cache evicts the "kgr_timestamp" key. We need additional API in KSharedDataCache to make sure that this key does not get evicted. |
211 | if (cacheTimestamp < svgTimestamp) |
212 | { |
213 | kDebug(11000) << "Theme newer than cache, checking SVG" ; |
214 | QScopedPointer<QSvgRenderer> renderer(new QSvgRenderer(theme->graphicsPath())); |
215 | if (renderer->isValid()) |
216 | { |
217 | m_rendererPool.setPath(theme->graphicsPath(), renderer.take()); |
218 | m_imageCache->clear(); |
219 | m_imageCache->insert(QString::fromLatin1("kgr_timestamp" ), QByteArray::number(svgTimestamp)); |
220 | } |
221 | else |
222 | { |
223 | //The SVG file is broken, so we deny to change the theme without |
224 | //breaking the previous theme. |
225 | delete m_imageCache; |
226 | KSharedDataCache::deleteCache(imageCacheName); |
227 | m_imageCache = oldCache.take(); |
228 | kDebug(11000) << "Theme change failed: SVG file broken" ; |
229 | return false; |
230 | } |
231 | } |
232 | //theme is cached - just delete the old renderer after making sure that no worker threads are using it anymore |
233 | else if (m_currentTheme != theme) |
234 | m_rendererPool.setPath(theme->graphicsPath()); |
235 | } |
236 | else // !(m_strategies & KGameRenderer::UseDiskCache) -> no cache is used |
237 | { |
238 | //load SVG file |
239 | QScopedPointer<QSvgRenderer> renderer(new QSvgRenderer(theme->graphicsPath())); |
240 | if (renderer->isValid()) |
241 | { |
242 | m_rendererPool.setPath(theme->graphicsPath(), renderer.take()); |
243 | } |
244 | else |
245 | { |
246 | kDebug(11000) << "Theme change failed: SVG file broken" ; |
247 | return false; |
248 | } |
249 | //disconnect from disk cache (only needed if changing strategy) |
250 | delete m_imageCache; |
251 | m_imageCache = 0; |
252 | } |
253 | //clear in-process caches |
254 | m_pixmapCache.clear(); |
255 | m_frameCountCache.clear(); |
256 | m_boundsCache.clear(); |
257 | //done |
258 | m_currentTheme = theme; |
259 | return true; |
260 | } |
261 | |
262 | const KgTheme* KGameRenderer::theme() const |
263 | { |
264 | //ensure that some theme is loaded |
265 | if (!d->m_currentTheme) |
266 | { |
267 | d->_k_setTheme(d->m_provider->currentTheme()); |
268 | } |
269 | return d->m_currentTheme; |
270 | } |
271 | |
272 | KgThemeProvider* KGameRenderer::themeProvider() const |
273 | { |
274 | return d->m_provider; |
275 | } |
276 | |
277 | QString KGameRendererPrivate::spriteFrameKey(const QString& key, int frame, bool normalizeFrameNo) const |
278 | { |
279 | //fast path for non-animated sprites |
280 | if (frame < 0) |
281 | { |
282 | return key; |
283 | } |
284 | //normalize frame number |
285 | if (normalizeFrameNo) |
286 | { |
287 | const int frameCount = m_parent->frameCount(key); |
288 | if (frameCount <= 0) |
289 | { |
290 | //non-animated sprite |
291 | return key; |
292 | } |
293 | else |
294 | { |
295 | frame = (frame - m_frameBaseIndex) % frameCount + m_frameBaseIndex; |
296 | } |
297 | } |
298 | return key + m_frameSuffix.arg(frame); |
299 | } |
300 | |
301 | int KGameRenderer::frameCount(const QString& key) const |
302 | { |
303 | //ensure that some theme is loaded |
304 | if (!d->m_currentTheme) |
305 | { |
306 | d->_k_setTheme(d->m_provider->currentTheme()); |
307 | } |
308 | //look up in in-process cache |
309 | QHash<QString, int>::const_iterator it = d->m_frameCountCache.constFind(key); |
310 | if (it != d->m_frameCountCache.constEnd()) |
311 | { |
312 | return it.value(); |
313 | } |
314 | //look up in shared cache (if SVG is not yet loaded) |
315 | int count = -1; |
316 | bool countFound = false; |
317 | const QString cacheKey = d->m_frameCountPrefix + key; |
318 | if (d->m_rendererPool.hasAvailableRenderers() && (d->m_strategies & KGameRenderer::UseDiskCache)) |
319 | { |
320 | QByteArray buffer; |
321 | if (d->m_imageCache->find(cacheKey, &buffer)) |
322 | { |
323 | count = buffer.toInt(); |
324 | countFound = true; |
325 | } |
326 | } |
327 | //determine from SVG |
328 | if (!countFound) |
329 | { |
330 | QSvgRenderer* renderer = d->m_rendererPool.allocRenderer(); |
331 | //look for animated sprite first |
332 | count = d->m_frameBaseIndex; |
333 | while (renderer->elementExists(d->spriteFrameKey(key, count, false))) |
334 | { |
335 | ++count; |
336 | } |
337 | count -= d->m_frameBaseIndex; |
338 | //look for non-animated sprite instead |
339 | if (count == 0) |
340 | { |
341 | if (!renderer->elementExists(key)) |
342 | { |
343 | count = -1; |
344 | } |
345 | } |
346 | d->m_rendererPool.freeRenderer(renderer); |
347 | //save in shared cache for following requests |
348 | if (d->m_strategies & KGameRenderer::UseDiskCache) |
349 | { |
350 | d->m_imageCache->insert(cacheKey, QByteArray::number(count)); |
351 | } |
352 | } |
353 | d->m_frameCountCache.insert(key, count); |
354 | return count; |
355 | } |
356 | |
357 | QRectF KGameRenderer::boundsOnSprite(const QString& key, int frame) const |
358 | { |
359 | const QString elementKey = d->spriteFrameKey(key, frame); |
360 | //ensure that some theme is loaded |
361 | if (!d->m_currentTheme) |
362 | { |
363 | d->_k_setTheme(d->m_provider->currentTheme()); |
364 | } |
365 | //look up in in-process cache |
366 | QHash<QString, QRectF>::const_iterator it = d->m_boundsCache.constFind(elementKey); |
367 | if (it != d->m_boundsCache.constEnd()) |
368 | { |
369 | return it.value(); |
370 | } |
371 | //look up in shared cache (if SVG is not yet loaded) |
372 | QRectF bounds; |
373 | bool boundsFound = false; |
374 | const QString cacheKey = d->m_boundsPrefix + elementKey; |
375 | if (!d->m_rendererPool.hasAvailableRenderers() && (d->m_strategies & KGameRenderer::UseDiskCache)) |
376 | { |
377 | QByteArray buffer; |
378 | if (d->m_imageCache->find(cacheKey, &buffer)) |
379 | { |
380 | QDataStream stream(buffer); |
381 | stream >> bounds; |
382 | boundsFound = true; |
383 | } |
384 | } |
385 | //determine from SVG |
386 | if (!boundsFound) |
387 | { |
388 | QSvgRenderer* renderer = d->m_rendererPool.allocRenderer(); |
389 | bounds = renderer->boundsOnElement(elementKey); |
390 | d->m_rendererPool.freeRenderer(renderer); |
391 | //save in shared cache for following requests |
392 | if (d->m_strategies & KGameRenderer::UseDiskCache) |
393 | { |
394 | QByteArray buffer; |
395 | { |
396 | QDataStream stream(&buffer, QIODevice::WriteOnly); |
397 | stream << bounds; |
398 | } |
399 | d->m_imageCache->insert(cacheKey, buffer); |
400 | } |
401 | } |
402 | d->m_boundsCache.insert(elementKey, bounds); |
403 | return bounds; |
404 | } |
405 | |
406 | bool KGameRenderer::spriteExists(const QString& key) const |
407 | { |
408 | return this->frameCount(key) >= 0; |
409 | } |
410 | |
411 | QPixmap KGameRenderer::spritePixmap(const QString& key, const QSize& size, int frame, const QHash<QColor, QColor>& customColors) const |
412 | { |
413 | QPixmap result; |
414 | d->requestPixmap(KGRInternal::ClientSpec(key, frame, size, customColors), 0, &result); |
415 | return result; |
416 | } |
417 | |
418 | //Helper function for KGameRendererPrivate::requestPixmap. |
419 | void KGameRendererPrivate::requestPixmap__propagateResult(const QPixmap& pixmap, KGameRendererClient* client, QPixmap* synchronousResult) |
420 | { |
421 | if (client) |
422 | { |
423 | client->receivePixmap(pixmap); |
424 | } |
425 | if (synchronousResult) |
426 | { |
427 | *synchronousResult = pixmap; |
428 | } |
429 | } |
430 | |
431 | void KGameRendererPrivate::requestPixmap(const KGRInternal::ClientSpec& spec, KGameRendererClient* client, QPixmap* synchronousResult) |
432 | { |
433 | //NOTE: If client == 0, the request is synchronous and must be finished when this method returns. This behavior is used by KGR::spritePixmap(). Instead of KGameRendererClient::receivePixmap, the QPixmap* argument is then used to return the result. |
434 | //parse request |
435 | if (spec.size.isEmpty()) |
436 | { |
437 | requestPixmap__propagateResult(QPixmap(), client, synchronousResult); |
438 | return; |
439 | } |
440 | const QString elementKey = spriteFrameKey(spec.spriteKey, spec.frame); |
441 | QString cacheKey = m_sizePrefix.arg(spec.size.width()).arg(spec.size.height()) + elementKey; |
442 | QHash<QColor, QColor>::const_iterator it1 = spec.customColors.constBegin(), it2 = spec.customColors.constEnd(); |
443 | static const QString colorSuffix(QLatin1String( "-%1-%2" )); |
444 | for (; it1 != it2; ++it1) |
445 | { |
446 | cacheKey += colorSuffix.arg(it1.key().rgba()).arg(it1.value().rgba()); |
447 | } |
448 | //check if update is needed |
449 | if (client) |
450 | { |
451 | if (m_clients.value(client) == cacheKey) |
452 | { |
453 | return; |
454 | } |
455 | m_clients[client] = cacheKey; |
456 | } |
457 | //ensure that some theme is loaded |
458 | if (!m_currentTheme) |
459 | { |
460 | _k_setTheme(m_provider->currentTheme()); |
461 | } |
462 | //try to serve from high-speed cache |
463 | QHash<QString, QPixmap>::const_iterator it = m_pixmapCache.constFind(cacheKey); |
464 | if (it != m_pixmapCache.constEnd()) |
465 | { |
466 | requestPixmap__propagateResult(it.value(), client, synchronousResult); |
467 | return; |
468 | } |
469 | //try to serve from low-speed cache |
470 | if (m_strategies & KGameRenderer::UseDiskCache) |
471 | { |
472 | QPixmap pix; |
473 | if (m_imageCache->findPixmap(cacheKey, &pix)) |
474 | { |
475 | m_pixmapCache.insert(cacheKey, pix); |
476 | requestPixmap__propagateResult(pix, client, synchronousResult); |
477 | return; |
478 | } |
479 | } |
480 | //if asynchronous request, is such a rendering job already running? |
481 | if (client && m_pendingRequests.contains(cacheKey)) |
482 | { |
483 | return; |
484 | } |
485 | //create job |
486 | KGRInternal::Job* job = new KGRInternal::Job; |
487 | job->rendererPool = &m_rendererPool; |
488 | job->cacheKey = cacheKey; |
489 | job->elementKey = elementKey; |
490 | job->spec = spec; |
491 | const bool synchronous = !client; |
492 | if (synchronous || !(m_strategies & KGameRenderer::UseRenderingThreads)) |
493 | { |
494 | KGRInternal::Worker worker(job, true, this); |
495 | worker.run(); |
496 | //if everything worked fine, result is in high-speed cache now |
497 | const QPixmap result = m_pixmapCache.value(cacheKey); |
498 | requestPixmap__propagateResult(result, client, synchronousResult); |
499 | } |
500 | else |
501 | { |
502 | m_workerPool.start(new KGRInternal::Worker(job, !client, this)); |
503 | m_pendingRequests << cacheKey; |
504 | } |
505 | } |
506 | |
507 | void KGameRendererPrivate::jobFinished(KGRInternal::Job* job, bool isSynchronous) |
508 | { |
509 | //read job |
510 | const QString cacheKey = job->cacheKey; |
511 | const QImage result = job->result; |
512 | delete job; |
513 | //check who wanted this pixmap |
514 | m_pendingRequests.removeAll(cacheKey); |
515 | const QList<KGameRendererClient*> requesters = m_clients.keys(cacheKey); |
516 | //put result into image cache |
517 | if (m_strategies & KGameRenderer::UseDiskCache) |
518 | { |
519 | m_imageCache->insertImage(cacheKey, result); |
520 | //convert result to pixmap (and put into pixmap cache) only if it is needed now |
521 | //This optimization saves the image-pixmap conversion for intermediate sizes which occur during smooth resize events or window initializations. |
522 | if (!isSynchronous && requesters.isEmpty()) |
523 | { |
524 | return; |
525 | } |
526 | } |
527 | const QPixmap pixmap = QPixmap::fromImage(result); |
528 | m_pixmapCache.insert(cacheKey, pixmap); |
529 | foreach (KGameRendererClient* requester, requesters) |
530 | { |
531 | requester->receivePixmap(pixmap); |
532 | } |
533 | } |
534 | |
535 | //BEGIN KGRInternal::Job/Worker |
536 | |
537 | KGRInternal::Worker::Worker(KGRInternal::Job* job, bool isSynchronous, KGameRendererPrivate* parent) |
538 | : m_job(job) |
539 | , m_synchronous(isSynchronous) |
540 | , m_parent(parent) |
541 | { |
542 | } |
543 | |
544 | static const uint transparentRgba = QColor(Qt::transparent).rgba(); |
545 | |
546 | void KGRInternal::Worker::run() |
547 | { |
548 | QImage image(m_job->spec.size, QImage::Format_ARGB32_Premultiplied); |
549 | image.fill(transparentRgba); |
550 | QPainter* painter = 0; |
551 | QPaintDeviceColorProxy* proxy = 0; |
552 | //if no custom colors requested, paint directly onto image |
553 | if (m_job->spec.customColors.isEmpty()) |
554 | { |
555 | painter = new QPainter(&image); |
556 | } |
557 | else |
558 | { |
559 | proxy = new QPaintDeviceColorProxy(&image, m_job->spec.customColors); |
560 | painter = new QPainter(proxy); |
561 | } |
562 | |
563 | //do renderering |
564 | QSvgRenderer* renderer = m_job->rendererPool->allocRenderer(); |
565 | renderer->render(painter, m_job->elementKey); |
566 | m_job->rendererPool->freeRenderer(renderer); |
567 | delete painter; |
568 | delete proxy; |
569 | |
570 | //talk back to the main thread |
571 | m_job->result = image; |
572 | QMetaObject::invokeMethod( |
573 | m_parent, "jobFinished" , Qt::AutoConnection, |
574 | Q_ARG(KGRInternal::Job*, m_job), Q_ARG(bool, m_synchronous) |
575 | ); |
576 | //NOTE: KGR::spritePixmap relies on Qt::DirectConnection when this method is run in the main thread. |
577 | } |
578 | |
579 | //END KGRInternal::Job/Worker |
580 | |
581 | //BEGIN KGRInternal::RendererPool |
582 | |
583 | KGRInternal::RendererPool::RendererPool(QThreadPool* threadPool) |
584 | : m_valid(Checked_Invalid) //don't try to allocate renderers until given a valid SVG file |
585 | , m_threadPool(threadPool) |
586 | { |
587 | } |
588 | |
589 | KGRInternal::RendererPool::~RendererPool() |
590 | { |
591 | //This deletes all renderers. |
592 | setPath(QString()); |
593 | } |
594 | |
595 | void KGRInternal::RendererPool::setPath(const QString& graphicsPath, QSvgRenderer* renderer) |
596 | { |
597 | QMutexLocker locker(&m_mutex); |
598 | //delete all renderers |
599 | m_threadPool->waitForDone(); |
600 | QHash<QSvgRenderer*, QThread*>::const_iterator it1 = m_hash.constBegin(), it2 = m_hash.constEnd(); |
601 | for (; it1 != it2; ++it1) |
602 | { |
603 | Q_ASSERT(it1.value() == 0); //nobody may be using our renderers anymore now |
604 | delete it1.key(); |
605 | } |
606 | m_hash.clear(); |
607 | //set path |
608 | m_path = graphicsPath; |
609 | //existence of a renderer instance is evidence for the validity of the SVG file |
610 | if (renderer) |
611 | { |
612 | m_valid = Checked_Valid; |
613 | m_hash.insert(renderer, 0); |
614 | } |
615 | else |
616 | { |
617 | m_valid = Unchecked; |
618 | } |
619 | } |
620 | |
621 | bool KGRInternal::RendererPool::hasAvailableRenderers() const |
622 | { |
623 | //look for a renderer which is not associated with a thread |
624 | QMutexLocker locker(&m_mutex); |
625 | return m_hash.key(0) != 0; |
626 | } |
627 | |
628 | QSvgRenderer* KGRInternal::RendererPool::allocRenderer() |
629 | { |
630 | QThread* thread = QThread::currentThread(); |
631 | //look for an available renderer |
632 | QMutexLocker locker(&m_mutex); |
633 | QSvgRenderer* renderer = m_hash.key(0); |
634 | if (!renderer) |
635 | { |
636 | //instantiate a new renderer (only if the SVG file has not been found to be invalid yet) |
637 | if (m_valid == Checked_Invalid) |
638 | { |
639 | return 0; |
640 | } |
641 | renderer = new QSvgRenderer(m_path); |
642 | m_valid = renderer->isValid() ? Checked_Valid : Checked_Invalid; |
643 | } |
644 | //mark renderer as used |
645 | m_hash.insert(renderer, thread); |
646 | return renderer; |
647 | } |
648 | |
649 | void KGRInternal::RendererPool::freeRenderer(QSvgRenderer* renderer) |
650 | { |
651 | //mark renderer as available |
652 | QMutexLocker locker(&m_mutex); |
653 | m_hash.insert(renderer, 0); |
654 | } |
655 | |
656 | //END KGRInternal::RendererPool |
657 | |
658 | #include "kgamerenderer.moc" |
659 | #include "kgamerenderer_p.moc" |
660 | |