KDEGames

kgamerenderer.cpp
1 /***************************************************************************
2  * Copyright 2010-2012 Stefan Majewsky <[email protected]> *
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 <QCoreApplication>
27 #include <QDateTime>
28 #include <QFileInfo>
29 #include <QScopedPointer>
30 #include <QPainter>
31 #include <QVariant>
32 
33 //TODO: automatically schedule pre-rendering of animation frames
34 //TODO: multithreaded SVG loading?
35 
36 static const QString cacheName(QByteArray theme)
37 {
39  //e.g. "themes/foobar.desktop" -> "themes/foobar"
40  if (theme.endsWith(QByteArray(".desktop")))
41  theme.chop(8); //8 = strlen(".desktop")
42  return QStringLiteral("kgamerenderer-%1-%2")
43  .arg(appName).arg(QString::fromUtf8(theme));
44 }
45 
46 KGameRendererPrivate::KGameRendererPrivate(KgThemeProvider* provider, unsigned cacheSize, KGameRenderer* parent)
47  : m_parent(parent)
48  , m_provider(provider)
49  , m_currentTheme(nullptr) //will be loaded on first use
50  , m_frameSuffix(QStringLiteral("_%1"))
51  , m_sizePrefix(QStringLiteral("%1-%2-"))
52  , m_frameCountPrefix(QStringLiteral("fc-"))
53  , m_boundsPrefix(QStringLiteral("br-"))
54  //default cache size: 3 MiB = 3 << 20 bytes
55  , m_cacheSize((cacheSize == 0 ? 3 : cacheSize) << 20)
56  , m_strategies(KGameRenderer::UseDiskCache | KGameRenderer::UseRenderingThreads)
57  , m_frameBaseIndex(0)
58  , m_defaultPrimaryView(nullptr)
59  , m_rendererPool(&m_workerPool)
60  , m_imageCache(nullptr)
61 {
62  qRegisterMetaType<KGRInternal::Job*>();
63 
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 
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 
102 {
103  return d->m_defaultPrimaryView;
104 }
105 
107 {
108  d->m_defaultPrimaryView = view;
109 }
110 
112 {
113  return d->m_frameBaseIndex;
114 }
115 
117 {
118  d->m_frameBaseIndex = frameBaseIndex;
119 }
120 
122 {
123  return d->m_frameSuffix;
124 }
125 
127 {
128  d->m_frameSuffix = suffix.contains(QLatin1String("%1")) ? suffix : QStringLiteral("_%1");
129 }
130 
132 {
133  return d->m_strategies;
134 }
135 
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 = nullptr; //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  qCDebug(GAMES_LIB) << "Setting theme:" << theme->name();
167  if (!setTheme(theme))
168  {
169  const KgTheme* defaultTheme = m_provider->defaultTheme();
170  if (theme != defaultTheme)
171  {
172  qCDebug(GAMES_LIB) << "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  Q_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().toSecsSinceEpoch(),
203  theme->property("_k_themeDescTimestamp").value<qint64>()
204  );
205  QByteArray buffer;
206  if (!m_imageCache->find(QStringLiteral("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  qCDebug(GAMES_LIB) << "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(QStringLiteral("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  qCDebug(GAMES_LIB) << "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  qCDebug(GAMES_LIB) << "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 = nullptr;
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 
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), nullptr, &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(QStringLiteral( "-%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  for (KGameRendererClient* requester : requesters) {
530  requester->receivePixmap(pixmap);
531  }
532 }
533 
534 //BEGIN KGRInternal::Job/Worker
535 
536 KGRInternal::Worker::Worker(KGRInternal::Job* job, bool isSynchronous, KGameRendererPrivate* parent)
537  : m_job(job)
538  , m_synchronous(isSynchronous)
539  , m_parent(parent)
540 {
541 }
542 
543 static const uint transparentRgba = QColor(Qt::transparent).rgba();
544 
545 void KGRInternal::Worker::run()
546 {
547  QImage image(m_job->spec.size, QImage::Format_ARGB32_Premultiplied);
548  image.fill(transparentRgba);
549  QPainter* painter = nullptr;
550  QPaintDeviceColorProxy* proxy = nullptr;
551  //if no custom colors requested, paint directly onto image
552  if (m_job->spec.customColors.isEmpty())
553  {
554  painter = new QPainter(&image);
555  }
556  else
557  {
558  proxy = new QPaintDeviceColorProxy(&image, m_job->spec.customColors);
559  painter = new QPainter(proxy);
560  }
561 
562  //do renderering
563  QSvgRenderer* renderer = m_job->rendererPool->allocRenderer();
564  renderer->render(painter, m_job->elementKey);
565  m_job->rendererPool->freeRenderer(renderer);
566  delete painter;
567  delete proxy;
568 
569  //talk back to the main thread
570  m_job->result = image;
572  m_parent, "jobFinished", Qt::AutoConnection,
573  Q_ARG(KGRInternal::Job*, m_job), Q_ARG(bool, m_synchronous)
574  );
575  //NOTE: KGR::spritePixmap relies on Qt::DirectConnection when this method is run in the main thread.
576 }
577 
578 //END KGRInternal::Job/Worker
579 
580 //BEGIN KGRInternal::RendererPool
581 
582 KGRInternal::RendererPool::RendererPool(QThreadPool* threadPool)
583  : m_valid(Checked_Invalid) //don't try to allocate renderers until given a valid SVG file
584  , m_threadPool(threadPool)
585 {
586 }
587 
588 KGRInternal::RendererPool::~RendererPool()
589 {
590  //This deletes all renderers.
591  setPath(QString());
592 }
593 
594 void KGRInternal::RendererPool::setPath(const QString& graphicsPath, QSvgRenderer* renderer)
595 {
596  QMutexLocker locker(&m_mutex);
597  //delete all renderers
598  m_threadPool->waitForDone();
599  QHash<QSvgRenderer*, QThread*>::const_iterator it1 = m_hash.constBegin(), it2 = m_hash.constEnd();
600  for (; it1 != it2; ++it1)
601  {
602  Q_ASSERT(it1.value() == nullptr); //nobody may be using our renderers anymore now
603  delete it1.key();
604  }
605  m_hash.clear();
606  //set path
607  m_path = graphicsPath;
608  //existence of a renderer instance is evidence for the validity of the SVG file
609  if (renderer)
610  {
611  m_valid = Checked_Valid;
612  m_hash.insert(renderer, nullptr);
613  }
614  else
615  {
616  m_valid = Unchecked;
617  }
618 }
619 
620 bool KGRInternal::RendererPool::hasAvailableRenderers() const
621 {
622  //look for a renderer which is not associated with a thread
623  QMutexLocker locker(&m_mutex);
624  return m_hash.key(nullptr) != nullptr;
625 }
626 
627 QSvgRenderer* KGRInternal::RendererPool::allocRenderer()
628 {
630  //look for an available renderer
631  QMutexLocker locker(&m_mutex);
632  QSvgRenderer* renderer = m_hash.key(nullptr);
633  if (!renderer)
634  {
635  //instantiate a new renderer (only if the SVG file has not been found to be invalid yet)
636  if (m_valid == Checked_Invalid)
637  {
638  return nullptr;
639  }
640  renderer = new QSvgRenderer(m_path);
641  m_valid = renderer->isValid() ? Checked_Valid : Checked_Invalid;
642  }
643  //mark renderer as used
644  m_hash.insert(renderer, thread);
645  return renderer;
646 }
647 
648 void KGRInternal::RendererPool::freeRenderer(QSvgRenderer* renderer)
649 {
650  //mark renderer as available
651  QMutexLocker locker(&m_mutex);
652  m_hash.insert(renderer, nullptr);
653 }
654 
655 //END KGRInternal::RendererPool
656 
657 #include "moc_kgamerenderer.cpp"
658 #include "moc_kgamerenderer_p.cpp"
int toInt(bool *ok, int base) const const
Format_ARGB32_Premultiplied
void render(QPainter *painter)
A theme provider manages KgTheme instances, and maintains a selection of the currentTheme().
bool isValid() const const
void chop(int n)
QRectF boundsOnSprite(const QString &key, int frame=-1) const
void addTheme(KgTheme *theme)
Adds a theme to this instance.
T value() const const
QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags)
QThread * thread() const const
int frameCount(const QString &key) const
void setStrategyEnabled(Strategy strategy, bool enabled=true)
Enables/disables an optimization strategy for this renderer.
static void deleteCache(const QString &cacheName)
virtual ~KGameRenderer()
Deletes this KGameRenderer instance, as well as all clients using it.
KgThemeProvider * themeProvider() const
QString fromUtf8(const char *str, int size)
QString & insert(int position, QChar ch)
QVariant property(const char *name) const const
virtual void receivePixmap(const QPixmap &pixmap)=0
This method is called when the KGameRenderer has provided a new pixmap for this client (esp...
void fill(uint pixelValue)
void setDefaultPrimaryView(QGraphicsView *view)
Set the primary view which will be used by newly created KGameRenderedItem instances associated with ...
QGraphicsView * defaultPrimaryView() const
Cache-enabled rendering of SVG themes.
Definition: kgamerenderer.h:94
bool isEmpty() const const
QByteArray number(int n, int base)
Strategies strategies() const
const KgTheme * theme() const
QCoreApplication * instance()
void setParent(QObject *parent)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
An object that receives pixmaps from a KGameRenderer.
QRectF boundsOnElement(const QString &id) const const
Strategy
Describes the various strategies which KGameRenderer can use to speed up rendering.
void setFrameSuffix(const QString &suffix)
Sets the frame suffix.
void setFrameBaseIndex(int frameBaseIndex)
Sets the frame base index, i.e.
QThread * currentThread()
bool elementExists(const QString &id) const const
If set, pixmaps will be cached in a shared disk cache (using KSharedDataCache).
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool spriteExists(const QString &key) const
If set, pixmap requests from KGameRendererClients will be handled asynchronously if possible...
KGameRenderer(KgThemeProvider *prov, unsigned cacheSize=0)
Constructs a new KGameRenderer that renders prov->currentTheme().
A theme describes the visual appearance of a game.
Definition: kgtheme.h:72
QPixmap spritePixmap(const QString &key, const QSize &size, int frame=-1, const QHash< QColor, QColor > &customColors=(QHash< QColor, QColor >())) const
transparent
AutoConnection
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
bool endsWith(const QByteArray &ba) const const
Q_EMITQ_EMIT
QString frameSuffix() const
QRgb rgba() const const
int frameBaseIndex() const
QCA_EXPORT QString appName()
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Nov 30 2020 22:37:54 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.