KDEGames

kgamerenderer.cpp
1/*
2 SPDX-FileCopyrightText: 2010-2012 Stefan Majewsky <majewsky@gmx.net>
3
4 SPDX-License-Identifier: LGPL-2.0-only
5*/
6
7#include "kgamerenderer.h"
8#include "kgamerenderer_p.h"
9
10// own
11#include "colorproxy_p.h"
12#include "kgamerendererclient.h"
13#include "kgametheme.h"
14#include "kgamethemeprovider.h"
15#include <kdegames_logging.h>
16// Qt
17#include <QDateTime>
18#include <QFileInfo>
19#include <QGuiApplication>
20#include <QPainter>
21#include <QVariant>
22
23// TODO: automatically schedule pre-rendering of animation frames
24// TODO: multithreaded SVG loading?
25
26static const QString cacheName(QByteArray theme)
27{
29 // e.g. "themes/foobar.desktop" -> "themes/foobar"
30 if (theme.endsWith(QByteArrayLiteral(".desktop")))
31 theme.chop(8); // 8 = strlen(".desktop")
32 return QStringLiteral("kgamerenderer-%1-%2").arg(appName, QString::fromUtf8(theme));
33}
34
35KGameRendererPrivate::KGameRendererPrivate(KGameThemeProvider *provider, unsigned cacheSize, KGameRenderer *parent)
36 : m_parent(parent)
37 , m_provider(provider)
38 , m_frameSuffix(QStringLiteral("_%1"))
39 , m_sizePrefix(QStringLiteral("%1-%2-"))
40 , m_frameCountPrefix(QStringLiteral("fc-"))
41 , m_boundsPrefix(QStringLiteral("br-"))
42 // default cache size: 3 MiB = 3 << 20 bytes
43 , m_cacheSize((cacheSize == 0 ? 3 : cacheSize) << 20)
44 , m_rendererPool(&m_workerPool)
45{
46 qRegisterMetaType<KGRInternal::Job *>();
47}
48
50 : d_ptr(new KGameRendererPrivate(provider, cacheSize, this))
51{
52 if (!provider->parent()) {
53 provider->setParent(this);
54 }
55 connect(provider, &KGameThemeProvider::currentThemeChanged, this, [this](const KGameTheme *theme) {
57 d->_k_setTheme(theme);
58 });
59}
60
61static KGameThemeProvider *providerForSingleTheme(KGameTheme *theme, QObject *parent)
62{
64 prov->addTheme(theme);
65 return prov;
66}
67
68KGameRenderer::KGameRenderer(KGameTheme *theme, unsigned cacheSize)
69 : d_ptr(new KGameRendererPrivate(providerForSingleTheme(theme, this), cacheSize, this))
70{
71}
72
74{
76
77 // cleanup clients
78 while (!d->m_clients.isEmpty()) {
79 delete d->m_clients.constBegin().key();
80 }
81 // cleanup own stuff
82 d->m_workerPool.waitForDone();
83 delete d->m_imageCache;
84}
85
87{
88 Q_D(const KGameRenderer);
89
90 return d->m_frameBaseIndex;
91}
92
93void KGameRenderer::setFrameBaseIndex(int frameBaseIndex)
94{
96
97 d->m_frameBaseIndex = frameBaseIndex;
98}
99
101{
102 Q_D(const KGameRenderer);
103
104 return d->m_frameSuffix;
105}
106
108{
110
111 d->m_frameSuffix = suffix.contains(QLatin1String("%1")) ? suffix : QStringLiteral("_%1");
112}
113
115{
116 Q_D(const KGameRenderer);
117
118 return d->m_strategies;
119}
120
122{
124
125 const bool oldEnabled = d->m_strategies & strategy;
126 if (enabled) {
127 d->m_strategies |= strategy;
128 } else {
129 d->m_strategies &= ~strategy;
130 }
131 if (strategy == KGameRenderer::UseDiskCache && oldEnabled != enabled) {
132 // reload theme
133 const KGameTheme *theme = d->m_currentTheme;
134 if (theme) {
135 d->m_currentTheme = nullptr; // or setTheme() will return immediately
136 d->_k_setTheme(theme);
137 }
138 }
139}
140
141void KGameRendererPrivate::_k_setTheme(const KGameTheme *theme)
142{
143 const KGameTheme *oldTheme = m_currentTheme;
144 if (oldTheme == theme) {
145 return;
146 }
147 qCDebug(KDEGAMES_LOG) << "Setting theme:" << theme->name();
148 if (!setTheme(theme)) {
149 const KGameTheme *defaultTheme = m_provider->defaultTheme();
150 if (theme != defaultTheme) {
151 qCDebug(KDEGAMES_LOG) << "Falling back to default theme:" << defaultTheme->name();
152 setTheme(defaultTheme);
153 m_provider->setCurrentTheme(defaultTheme);
154 }
155 }
156 // announce change to KGameRendererClients
157 QHash<KGameRendererClient *, QString>::iterator it1 = m_clients.begin(), it2 = m_clients.end();
158 for (; it1 != it2; ++it1) {
159 it1.value().clear(); // because the pixmap is outdated
160 it1.key()->d_ptr->fetchPixmap();
161 }
162 Q_EMIT m_parent->themeChanged(m_currentTheme);
163}
164
165bool KGameRendererPrivate::setTheme(const KGameTheme *theme)
166{
167 if (!theme) {
168 return false;
169 }
170 // open cache (and SVG file, if necessary)
171 if (m_strategies & KGameRenderer::UseDiskCache) {
172 std::unique_ptr<KImageCache> oldCache(m_imageCache);
173 const QString imageCacheName = cacheName(theme->identifier());
174 m_imageCache = new KImageCache(imageCacheName, m_cacheSize);
175 m_imageCache->setPixmapCaching(false); // see big comment in KGRPrivate class declaration
176 // check timestamp of cache vs. last write access to theme/SVG
177 const uint svgTimestamp =
178 qMax(QFileInfo(theme->graphicsPath()).lastModified().toSecsSinceEpoch(), theme->property("_k_themeDescTimestamp").value<qint64>());
179 QByteArray buffer;
180 if (!m_imageCache->find(QStringLiteral("kgr_timestamp"), &buffer))
181 buffer = "0";
182 const uint cacheTimestamp = buffer.toInt();
183 // try to instantiate renderer immediately if the cache does not exist or is outdated
184 // 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
185 // get evicted.
186 if (cacheTimestamp < svgTimestamp) {
187 qCDebug(KDEGAMES_LOG) << "Theme newer than cache, checking SVG";
188 std::unique_ptr<QSvgRenderer> renderer(new QSvgRenderer(theme->graphicsPath()));
189 if (renderer->isValid()) {
190 m_rendererPool.setPath(theme->graphicsPath(), renderer.release());
191 m_imageCache->clear();
192 m_imageCache->insert(QStringLiteral("kgr_timestamp"), QByteArray::number(svgTimestamp));
193 } else {
194 // The SVG file is broken, so we deny to change the theme without
195 // breaking the previous theme.
196 delete m_imageCache;
197 KSharedDataCache::deleteCache(imageCacheName);
198 m_imageCache = oldCache.release();
199 qCDebug(KDEGAMES_LOG) << "Theme change failed: SVG file broken";
200 return false;
201 }
202 }
203 // theme is cached - just delete the old renderer after making sure that no worker threads are using it anymore
204 else if (m_currentTheme != theme)
205 m_rendererPool.setPath(theme->graphicsPath());
206 } else // !(m_strategies & KGameRenderer::UseDiskCache) -> no cache is used
207 {
208 // load SVG file
209 std::unique_ptr<QSvgRenderer> renderer(new QSvgRenderer(theme->graphicsPath()));
210 if (renderer->isValid()) {
211 m_rendererPool.setPath(theme->graphicsPath(), renderer.release());
212 } else {
213 qCDebug(KDEGAMES_LOG) << "Theme change failed: SVG file broken";
214 return false;
215 }
216 // disconnect from disk cache (only needed if changing strategy)
217 delete m_imageCache;
218 m_imageCache = nullptr;
219 }
220 // clear in-process caches
221 m_pixmapCache.clear();
222 m_frameCountCache.clear();
223 m_boundsCache.clear();
224 // done
225 m_currentTheme = theme;
226 return true;
227}
228
229const KGameTheme *KGameRenderer::theme() const
230{
231 Q_D(const KGameRenderer);
232
233 // ensure that some theme is loaded
234 if (!d->m_currentTheme) {
235 const_cast<KGameRendererPrivate *>(d)->_k_setTheme(d->m_provider->currentTheme());
236 }
237 return d->m_currentTheme;
238}
239
240KGameThemeProvider *KGameRenderer::themeProvider() const
241{
242 Q_D(const KGameRenderer);
243
244 return d->m_provider;
245}
246
247QString KGameRendererPrivate::spriteFrameKey(const QString &key, int frame, bool normalizeFrameNo) const
248{
249 // fast path for non-animated sprites
250 if (frame < 0) {
251 return key;
252 }
253 // normalize frame number
254 if (normalizeFrameNo) {
255 const int frameCount = m_parent->frameCount(key);
256 if (frameCount <= 0) {
257 // non-animated sprite
258 return key;
259 } else {
260 frame = (frame - m_frameBaseIndex) % frameCount + m_frameBaseIndex;
261 }
262 }
263 return key + m_frameSuffix.arg(frame);
264}
265
267{
268 Q_D(const KGameRenderer);
269
270 // ensure that some theme is loaded
271 if (!d->m_currentTheme) {
272 const_cast<KGameRendererPrivate *>(d)->_k_setTheme(d->m_provider->currentTheme());
273 }
274 // look up in in-process cache
275 QHash<QString, int>::const_iterator it = d->m_frameCountCache.constFind(key);
276 if (it != d->m_frameCountCache.constEnd()) {
277 return it.value();
278 }
279 // look up in shared cache (if SVG is not yet loaded)
280 int count = -1;
281 bool countFound = false;
282 const QString cacheKey = d->m_frameCountPrefix + key;
283 if (d->m_rendererPool.hasAvailableRenderers() && (d->m_strategies & KGameRenderer::UseDiskCache)) {
284 QByteArray buffer;
285 if (d->m_imageCache->find(cacheKey, &buffer)) {
286 count = buffer.toInt();
287 countFound = true;
288 }
289 }
290 // determine from SVG
291 if (!countFound) {
292 QSvgRenderer *renderer = d->m_rendererPool.allocRenderer();
293 // look for animated sprite first
294 count = d->m_frameBaseIndex;
295 while (renderer->elementExists(d->spriteFrameKey(key, count, false))) {
296 ++count;
297 }
298 count -= d->m_frameBaseIndex;
299 // look for non-animated sprite instead
300 if (count == 0) {
301 if (!renderer->elementExists(key)) {
302 count = -1;
303 }
304 }
305 d->m_rendererPool.freeRenderer(renderer);
306 // save in shared cache for following requests
307 if (d->m_strategies & KGameRenderer::UseDiskCache) {
308 d->m_imageCache->insert(cacheKey, QByteArray::number(count));
309 }
310 }
311 d->m_frameCountCache.insert(key, count);
312 return count;
313}
314
315QRectF KGameRenderer::boundsOnSprite(const QString &key, int frame) const
316{
317 Q_D(const KGameRenderer);
318
319 const QString elementKey = d->spriteFrameKey(key, frame);
320 // ensure that some theme is loaded
321 if (!d->m_currentTheme) {
322 const_cast<KGameRendererPrivate *>(d)->_k_setTheme(d->m_provider->currentTheme());
323 }
324 // look up in in-process cache
325 QHash<QString, QRectF>::const_iterator it = d->m_boundsCache.constFind(elementKey);
326 if (it != d->m_boundsCache.constEnd()) {
327 return it.value();
328 }
329 // look up in shared cache (if SVG is not yet loaded)
330 QRectF bounds;
331 bool boundsFound = false;
332 const QString cacheKey = d->m_boundsPrefix + elementKey;
333 if (!d->m_rendererPool.hasAvailableRenderers() && (d->m_strategies & KGameRenderer::UseDiskCache)) {
334 QByteArray buffer;
335 if (d->m_imageCache->find(cacheKey, &buffer)) {
336 QDataStream stream(buffer);
337 stream >> bounds;
338 boundsFound = true;
339 }
340 }
341 // determine from SVG
342 if (!boundsFound) {
343 QSvgRenderer *renderer = d->m_rendererPool.allocRenderer();
344 bounds = renderer->boundsOnElement(elementKey);
345 d->m_rendererPool.freeRenderer(renderer);
346 // save in shared cache for following requests
347 if (d->m_strategies & KGameRenderer::UseDiskCache) {
348 QByteArray buffer;
349 {
350 QDataStream stream(&buffer, QIODevice::WriteOnly);
351 stream << bounds;
352 }
353 d->m_imageCache->insert(cacheKey, buffer);
354 }
355 }
356 d->m_boundsCache.insert(elementKey, bounds);
357 return bounds;
358}
359
361{
362 Q_D(const KGameRenderer);
363
364 return this->frameCount(key) >= 0;
365}
366
367QPixmap KGameRenderer::spritePixmap(const QString &key, QSize size, int frame, const QHash<QColor, QColor> &customColors) const
368{
369 Q_D(const KGameRenderer);
370
371 QPixmap result;
372 const_cast<KGameRendererPrivate *>(d)->requestPixmap(KGRInternal::ClientSpec(key, frame, size, customColors), nullptr, &result);
373 return result;
374}
375
376// Helper function for KGameRendererPrivate::requestPixmap.
377void KGameRendererPrivate::requestPixmap__propagateResult(const QPixmap &pixmap, KGameRendererClient *client, QPixmap *synchronousResult)
378{
379 if (client) {
380 client->receivePixmap(pixmap);
381 }
382 if (synchronousResult) {
383 *synchronousResult = pixmap;
384 }
385}
386
387void KGameRendererPrivate::requestPixmap(const KGRInternal::ClientSpec &spec, KGameRendererClient *client, QPixmap *synchronousResult)
388{
389 // 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
390 // KGameRendererClient::receivePixmap, the QPixmap* argument is then used to return the result. parse request
391 if (spec.size.isEmpty()) {
392 requestPixmap__propagateResult(QPixmap(), client, synchronousResult);
393 return;
394 }
395 const QString elementKey = spriteFrameKey(spec.spriteKey, spec.frame);
396 const qreal dpr = qApp->devicePixelRatio();
397 const QSize size = spec.size * dpr;
398 QString cacheKey = m_sizePrefix.arg(size.width()).arg(size.height()) + elementKey;
399 QHash<QColor, QColor>::const_iterator it1 = spec.customColors.constBegin(), it2 = spec.customColors.constEnd();
400 static const QString colorSuffix(QStringLiteral("-%1-%2"));
401 for (; it1 != it2; ++it1) {
402 cacheKey += colorSuffix.arg(it1.key().rgba()).arg(it1.value().rgba());
403 }
404 // check if update is needed
405 if (client) {
406 if (m_clients.value(client) == cacheKey) {
407 return;
408 }
409 m_clients[client] = cacheKey;
410 }
411 // ensure that some theme is loaded
412 if (!m_currentTheme) {
413 _k_setTheme(m_provider->currentTheme());
414 }
415 // try to serve from high-speed cache
416 QHash<QString, QPixmap>::const_iterator it = m_pixmapCache.constFind(cacheKey);
417 if (it != m_pixmapCache.constEnd()) {
418 QPixmap p = it.value();
419 p.setDevicePixelRatio(dpr);
420 requestPixmap__propagateResult(p, client, synchronousResult);
421 return;
422 }
423 // try to serve from low-speed cache
424 if (m_strategies & KGameRenderer::UseDiskCache) {
425 QPixmap pix;
426 if (m_imageCache->findPixmap(cacheKey, &pix)) {
427 pix.setDevicePixelRatio(dpr);
428 m_pixmapCache.insert(cacheKey, pix);
429 requestPixmap__propagateResult(pix, client, synchronousResult);
430 return;
431 }
432 }
433 // if asynchronous request, is such a rendering job already running?
434 if (client && m_pendingRequests.contains(cacheKey)) {
435 return;
436 }
437 // create job
438 KGRInternal::Job *job = new KGRInternal::Job;
439 job->rendererPool = &m_rendererPool;
440 job->cacheKey = cacheKey;
441 job->elementKey = elementKey;
442 job->spec = spec;
443 job->spec.size = size;
444 const bool synchronous = !client;
445 if (synchronous || !(m_strategies & KGameRenderer::UseRenderingThreads)) {
446 KGRInternal::Worker worker(job, true, this);
447 worker.run();
448 // if everything worked fine, result is in high-speed cache now
449 const QPixmap result = m_pixmapCache.value(cacheKey);
450 requestPixmap__propagateResult(result, client, synchronousResult);
451 } else {
452 m_workerPool.start(new KGRInternal::Worker(job, !client, this));
453 m_pendingRequests << cacheKey;
454 }
455}
456
457void KGameRendererPrivate::jobFinished(KGRInternal::Job *job, bool isSynchronous)
458{
459 // read job
460 const qreal dpr = qApp->devicePixelRatio();
461 const QString cacheKey = job->cacheKey;
462 QImage result = job->result;
463 result.setDevicePixelRatio(dpr);
464 delete job;
465 // check who wanted this pixmap
466 m_pendingRequests.removeAll(cacheKey);
467 const QList<KGameRendererClient *> requesters = m_clients.keys(cacheKey);
468 // put result into image cache
469 if (m_strategies & KGameRenderer::UseDiskCache) {
470 m_imageCache->insertImage(cacheKey, result);
471 // convert result to pixmap (and put into pixmap cache) only if it is needed now
472 // This optimization saves the image-pixmap conversion for intermediate sizes which occur during smooth resize events or window initializations.
473 if (!isSynchronous && requesters.isEmpty()) {
474 return;
475 }
476 }
477 const QPixmap pixmap = QPixmap::fromImage(result);
478 m_pixmapCache.insert(cacheKey, pixmap);
479 for (KGameRendererClient *requester : requesters) {
480 requester->receivePixmap(pixmap);
481 }
482}
483
484// BEGIN KGRInternal::Job/Worker
485
486KGRInternal::Worker::Worker(KGRInternal::Job *job, bool isSynchronous, KGameRendererPrivate *parent)
487 : m_job(job)
488 , m_synchronous(isSynchronous)
489 , m_parent(parent)
490{
491}
492
493static const uint transparentRgba = QColor(Qt::transparent).rgba();
494
495void KGRInternal::Worker::run()
496{
497 QImage image(m_job->spec.size, QImage::Format_ARGB32_Premultiplied);
498 // image might not be created if size is too large for needed memory, or invalid
499 if (image.isNull()) {
500 qCWarning(KDEGAMES_LOG) << "Could not create QImage. Key:" << m_job->spec.spriteKey << "Frame:" << m_job->spec.frame << "Size:" << m_job->spec.size;
501 } else {
502 image.fill(transparentRgba);
503 QPainter *painter = nullptr;
504 QPaintDeviceColorProxy *proxy = nullptr;
505 // if no custom colors requested, paint directly onto image
506 if (m_job->spec.customColors.isEmpty()) {
507 painter = new QPainter(&image);
508 } else {
509 proxy = new QPaintDeviceColorProxy(&image, m_job->spec.customColors);
510 painter = new QPainter(proxy);
511 }
512
513 // do renderering
514 QSvgRenderer *renderer = m_job->rendererPool->allocRenderer();
515 renderer->render(painter, m_job->elementKey);
516 m_job->rendererPool->freeRenderer(renderer);
517 delete painter;
518 delete proxy;
519 }
520 // talk back to the main thread
521 m_job->result = image;
522 QMetaObject::invokeMethod(m_parent, "jobFinished", Qt::AutoConnection, Q_ARG(KGRInternal::Job *, m_job), Q_ARG(bool, m_synchronous));
523 // NOTE: KGR::spritePixmap relies on Qt::DirectConnection when this method is run in the main thread.
524}
525
526// END KGRInternal::Job/Worker
527
528// BEGIN KGRInternal::RendererPool
529
530KGRInternal::RendererPool::RendererPool(QThreadPool *threadPool)
531 : m_valid(Checked_Invalid) // don't try to allocate renderers until given a valid SVG file
532 , m_threadPool(threadPool)
533{
534}
535
536KGRInternal::RendererPool::~RendererPool()
537{
538 // This deletes all renderers.
539 setPath(QString());
540}
541
542void KGRInternal::RendererPool::setPath(const QString &graphicsPath, QSvgRenderer *renderer)
543{
544 QMutexLocker locker(&m_mutex);
545 // delete all renderers
546 m_threadPool->waitForDone();
547 QHash<QSvgRenderer *, QThread *>::const_iterator it1 = m_hash.constBegin(), it2 = m_hash.constEnd();
548 for (; it1 != it2; ++it1) {
549 Q_ASSERT(it1.value() == nullptr); // nobody may be using our renderers anymore now
550 delete it1.key();
551 }
552 m_hash.clear();
553 // set path
554 m_path = graphicsPath;
555 // existence of a renderer instance is evidence for the validity of the SVG file
556 if (renderer) {
557 m_valid = Checked_Valid;
558 m_hash.insert(renderer, nullptr);
559 } else {
560 m_valid = Unchecked;
561 }
562}
563
564bool KGRInternal::RendererPool::hasAvailableRenderers() const
565{
566 // look for a renderer which is not associated with a thread
567 QMutexLocker locker(&m_mutex);
568 return m_hash.key(nullptr) != nullptr;
569}
570
571QSvgRenderer *KGRInternal::RendererPool::allocRenderer()
572{
574 // look for an available renderer
575 QMutexLocker locker(&m_mutex);
576 QSvgRenderer *renderer = m_hash.key(nullptr);
577 if (!renderer) {
578 // instantiate a new renderer (only if the SVG file has not been found to be invalid yet)
579 if (m_valid == Checked_Invalid) {
580 return nullptr;
581 }
582 renderer = new QSvgRenderer(m_path);
583 m_valid = renderer->isValid() ? Checked_Valid : Checked_Invalid;
584 }
585 // mark renderer as used
586 m_hash.insert(renderer, thread);
587 return renderer;
588}
589
590void KGRInternal::RendererPool::freeRenderer(QSvgRenderer *renderer)
591{
592 // mark renderer as available
593 QMutexLocker locker(&m_mutex);
594 m_hash.insert(renderer, nullptr);
595}
596
597// END KGRInternal::RendererPool
598
599#include "moc_kgamerenderer.cpp"
600#include "moc_kgamerenderer_p.cpp"
An object that receives pixmaps from a KGameRenderer.
virtual void receivePixmap(const QPixmap &pixmap)=0
This method is called when the KGameRenderer has provided a new pixmap for this client (esp.
Cache-enabled rendering of SVG themes.
int frameCount(const QString &key) const
void setStrategyEnabled(Strategy strategy, bool enabled=true)
Enables/disables an optimization strategy for this renderer.
Strategies strategies() const
QRectF boundsOnSprite(const QString &key, int frame=-1) const
QString frameSuffix() const
void setFrameSuffix(const QString &suffix)
Sets the frame suffix.
KGameRenderer(KGameThemeProvider *prov, unsigned cacheSize=0)
Constructs a new KGameRenderer that renders prov->currentTheme().
void setFrameBaseIndex(int frameBaseIndex)
Sets the frame base index, i.e.
Strategy
Describes the various strategies which KGameRenderer can use to speed up rendering.
@ UseDiskCache
If set, pixmaps will be cached in a shared disk cache (using KSharedDataCache).
@ UseRenderingThreads
If set, pixmap requests from KGameRendererClients will be handled asynchronously if possible.
~KGameRenderer() override
Deletes this KGameRenderer instance, as well as all clients using it.
bool spriteExists(const QString &key) const
QPixmap spritePixmap(const QString &key, QSize size, int frame=-1, const QHash< QColor, QColor > &customColors=(QHash< QColor, QColor >())) const
int frameBaseIndex() const
A theme provider manages KGameTheme instances, and maintains a selection of the currentTheme().
void currentThemeChanged(const KGameTheme *theme)
Emitted when the current theme changes.
void addTheme(KGameTheme *theme)
Adds a theme to this instance.
A theme describes the visual appearance of a game.
Definition kgametheme.h:60
static void deleteCache(const QString &cacheName)
QCA_EXPORT QString appName()
void chop(qsizetype n)
bool endsWith(QByteArrayView bv) const const
QByteArray & insert(qsizetype i, QByteArrayView data)
QByteArray number(double n, char format, int precision)
int toInt(bool *ok, int base) const const
QRgb rgba() const const
QCoreApplication * instance()
qint64 toSecsSinceEpoch() const const
QDateTime lastModified() const const
Format_ARGB32_Premultiplied
void setDevicePixelRatio(qreal scaleFactor)
bool isEmpty() const const
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QVariant property(const char *name) const const
QPixmap fromImage(QImage &&image, Qt::ImageConversionFlags flags)
void setDevicePixelRatio(qreal scaleFactor)
int height() const const
int width() const const
QString arg(Args &&... args) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromUtf8(QByteArrayView str)
QString & insert(qsizetype position, QChar ch)
qsizetype size() const const
QRectF boundsOnElement(const QString &id) const const
bool elementExists(const QString &id) const const
bool isValid() const const
void render(QPainter *painter)
Unchecked
AutoConnection
transparent
QThread * currentThread()
T value() const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:13:43 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.