File indexing completed on 2024-04-21 04:05:21
0001 /* 0002 This file is part of the KDE games library 0003 SPDX-FileCopyrightText: 2008 Andreas Pakulat <apaku@gmx.de> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only 0006 */ 0007 0008 #include "cardcache.h" 0009 #include "cardcache_p.h" 0010 0011 #include <QDateTime> 0012 #include <QFileInfo> 0013 #include <QMutexLocker> 0014 #include <QPainter> 0015 #include <QPixmap> 0016 #include <QSizeF> 0017 #include <QSvgRenderer> 0018 0019 0020 #include "carddeckinfo.h" 0021 0022 #include "lskat_debug.h" 0023 0024 #define DECKLIST_LENGTH 53 0025 0026 KCardInfo fullDeckList[DECKLIST_LENGTH] = { 0027 KCardInfo(KCardInfo::Club, KCardInfo::Ace), 0028 KCardInfo(KCardInfo::Heart, KCardInfo::Ace), 0029 KCardInfo(KCardInfo::Diamond, KCardInfo::Ace), 0030 KCardInfo(KCardInfo::Spade, KCardInfo::Ace), 0031 KCardInfo(KCardInfo::Club, KCardInfo::King), 0032 KCardInfo(KCardInfo::Heart, KCardInfo::King), 0033 KCardInfo(KCardInfo::Diamond, KCardInfo::King), 0034 KCardInfo(KCardInfo::Spade, KCardInfo::King), 0035 KCardInfo(KCardInfo::Club, KCardInfo::Queen), 0036 KCardInfo(KCardInfo::Heart, KCardInfo::Queen), 0037 KCardInfo(KCardInfo::Diamond, KCardInfo::Queen), 0038 KCardInfo(KCardInfo::Spade, KCardInfo::Queen), 0039 KCardInfo(KCardInfo::Club, KCardInfo::Jack), 0040 KCardInfo(KCardInfo::Heart, KCardInfo::Jack), 0041 KCardInfo(KCardInfo::Diamond, KCardInfo::Jack), 0042 KCardInfo(KCardInfo::Spade, KCardInfo::Jack), 0043 KCardInfo(KCardInfo::Club, KCardInfo::Ten), 0044 KCardInfo(KCardInfo::Heart, KCardInfo::Ten), 0045 KCardInfo(KCardInfo::Diamond, KCardInfo::Ten), 0046 KCardInfo(KCardInfo::Spade, KCardInfo::Ten), 0047 KCardInfo(KCardInfo::Club, KCardInfo::Nine), 0048 KCardInfo(KCardInfo::Heart, KCardInfo::Nine), 0049 KCardInfo(KCardInfo::Diamond, KCardInfo::Nine), 0050 KCardInfo(KCardInfo::Spade, KCardInfo::Nine), 0051 KCardInfo(KCardInfo::Club, KCardInfo::Eight), 0052 KCardInfo(KCardInfo::Heart, KCardInfo::Eight), 0053 KCardInfo(KCardInfo::Diamond, KCardInfo::Eight), 0054 KCardInfo(KCardInfo::Spade, KCardInfo::Eight), 0055 KCardInfo(KCardInfo::Club, KCardInfo::Seven), 0056 KCardInfo(KCardInfo::Heart, KCardInfo::Seven), 0057 KCardInfo(KCardInfo::Diamond, KCardInfo::Seven), 0058 KCardInfo(KCardInfo::Spade, KCardInfo::Seven), 0059 KCardInfo(KCardInfo::Club, KCardInfo::Six), 0060 KCardInfo(KCardInfo::Heart, KCardInfo::Six), 0061 KCardInfo(KCardInfo::Diamond, KCardInfo::Six), 0062 KCardInfo(KCardInfo::Spade, KCardInfo::Six), 0063 KCardInfo(KCardInfo::Club, KCardInfo::Five), 0064 KCardInfo(KCardInfo::Heart, KCardInfo::Five), 0065 KCardInfo(KCardInfo::Diamond, KCardInfo::Five), 0066 KCardInfo(KCardInfo::Spade, KCardInfo::Five), 0067 KCardInfo(KCardInfo::Club, KCardInfo::Four), 0068 KCardInfo(KCardInfo::Heart, KCardInfo::Four), 0069 KCardInfo(KCardInfo::Diamond, KCardInfo::Four), 0070 KCardInfo(KCardInfo::Spade, KCardInfo::Four), 0071 KCardInfo(KCardInfo::Club, KCardInfo::Three), 0072 KCardInfo(KCardInfo::Heart, KCardInfo::Three), 0073 KCardInfo(KCardInfo::Diamond, KCardInfo::Three), 0074 KCardInfo(KCardInfo::Spade, KCardInfo::Three), 0075 KCardInfo(KCardInfo::Club, KCardInfo::Two), 0076 KCardInfo(KCardInfo::Heart, KCardInfo::Two), 0077 KCardInfo(KCardInfo::Diamond, KCardInfo::Two), 0078 KCardInfo(KCardInfo::Spade, KCardInfo::Two), 0079 KCardInfo(KCardInfo::None, KCardInfo::Joker) 0080 }; 0081 0082 KCardInfo::KCardInfo(KCardInfo::Suit s, KCardInfo::Card c) 0083 : m_suit(s), m_card(c) 0084 { 0085 } 0086 0087 KCardInfo::Card KCardInfo::card() const 0088 { 0089 return m_card; 0090 } 0091 0092 KCardInfo::Suit KCardInfo::suit() const 0093 { 0094 return m_suit; 0095 } 0096 0097 void KCardInfo::setCard(KCardInfo::Card c) 0098 { 0099 m_card = c; 0100 } 0101 0102 void KCardInfo::setSuit(KCardInfo::Suit s) 0103 { 0104 m_suit = s; 0105 } 0106 0107 bool KCardInfo::operator==(KCardInfo c) const 0108 { 0109 return (c.card() == card() && c.suit() == suit()); 0110 } 0111 0112 QString KCardInfo::svgName() const 0113 { 0114 QString s; 0115 if (card() == KCardInfo::Ace) 0116 s += QLatin1String("1_"); 0117 if (card() == KCardInfo::King) 0118 s += QLatin1String("king_"); 0119 if (card() == KCardInfo::Queen) 0120 s += QLatin1String("queen_"); 0121 if (card() == KCardInfo::Jack) 0122 s += QLatin1String("jack_"); 0123 if (card() == KCardInfo::Ten) 0124 s += QLatin1String("10_"); 0125 if (card() == KCardInfo::Nine) 0126 s += QLatin1String("9_"); 0127 if (card() == KCardInfo::Eight) 0128 s += QLatin1String("8_"); 0129 if (card() == KCardInfo::Seven) 0130 s += QLatin1String("7_"); 0131 if (card() == KCardInfo::Six) 0132 s += QLatin1String("6_"); 0133 if (card() == KCardInfo::Five) 0134 s += QLatin1String("5_"); 0135 if (card() == KCardInfo::Four) 0136 s += QLatin1String("4_"); 0137 if (card() == KCardInfo::Three) 0138 s += QLatin1String("3_"); 0139 if (card() == KCardInfo::Two) 0140 s += QLatin1String("2_"); 0141 if (suit() == KCardInfo::Club) 0142 s += QLatin1String("club"); 0143 if (suit() == KCardInfo::Spade) 0144 s += QLatin1String("spade"); 0145 if (suit() == KCardInfo::Diamond) 0146 s += QLatin1String("diamond"); 0147 if (suit() == KCardInfo::Heart) 0148 s += QLatin1String("heart"); 0149 return s; 0150 } 0151 0152 QPixmap doRender(const QString &element, QSvgRenderer *r, QSize s) 0153 { 0154 QPixmap pix = QPixmap(s); 0155 pix.fill(Qt::transparent); 0156 QPainter p(&pix); 0157 r->render(&p, element); 0158 p.end(); 0159 return pix; 0160 } 0161 0162 QString keyForPixmap(const QString &theme, const QString &element, QSize s) 0163 { 0164 return theme + QLatin1Char('_') + element + QLatin1Char('_') 0165 + QString::number(s.width()) + QLatin1Char('_') 0166 + QString::number(s.height()); 0167 } 0168 0169 QSvgRenderer *KCardCachePrivate::renderer() 0170 { 0171 if (!svgRenderer) 0172 { 0173 qCDebug(LSKAT_LOG) << "Loading front SVG renderer"; 0174 svgRenderer = new QSvgRenderer(CardDeckInfo::svgFilePath(deckName)); 0175 } 0176 return svgRenderer; 0177 } 0178 0179 void KCardCachePrivate::ensureNonNullPixmap(QPixmap &pix) 0180 { 0181 if (pix.isNull()) 0182 { 0183 qCWarning(LSKAT_LOG) << "Could not produce a non-null pixmap, creating a red cross"; 0184 pix = QPixmap(size); 0185 QPainter p(&pix); 0186 p.fillRect(QRect(0, 0, pix.width(), pix.height()), QBrush(Qt::white)); 0187 QPen pen = p.pen(); 0188 pen.setWidth(4); 0189 pen.setColor(QColor(Qt::red)); 0190 p.setPen(pen); 0191 p.drawLine(QPoint(2, 2), QPoint(pix.width()-2, pix.height()-2)); 0192 p.drawLine(QPoint(pix.width()-2, 2), QPoint(2, pix.height()-2)); 0193 p.end(); 0194 } 0195 } 0196 0197 QPixmap KCardCachePrivate::renderSvg(const QString &element) 0198 { 0199 qCDebug(LSKAT_LOG) << "Rendering" << element << "in main thread."; 0200 QMutexLocker l(rendererMutex); 0201 return doRender(element, renderer(), size); 0202 } 0203 0204 void KCardCachePrivate::submitRendering(const QString &key, const QPixmap &pixmap) 0205 { 0206 qCDebug(LSKAT_LOG) << "Received render of" << key << "from rendering thread."; 0207 QMutexLocker l(cacheMutex); 0208 cache->insertPixmap(key, pixmap); 0209 } 0210 0211 LoadThread::LoadThread(KCardCachePrivate *d_) 0212 : d(d_), doKill(false), killMutex(new QMutex) 0213 { 0214 } 0215 0216 LoadThread::~LoadThread() 0217 { 0218 delete killMutex; 0219 } 0220 0221 void LoadThread::setSize(const QSize &s) 0222 { 0223 size = s; 0224 } 0225 0226 void LoadThread::setDeckName(const QString &frontTheme_) 0227 { 0228 frontTheme = frontTheme_; 0229 } 0230 0231 void LoadThread::setElementsToLoad(const QStringList &elements_) 0232 { 0233 elementsToRender = elements_; 0234 } 0235 0236 void LoadThread::kill() 0237 { 0238 QMutexLocker l(killMutex); 0239 doKill = true; 0240 } 0241 0242 void LoadThread::run() 0243 { 0244 { 0245 // Load the renderer even if we don't have any elements to render. 0246 QMutexLocker l(d->rendererMutex); 0247 d->renderer(); 0248 } 0249 0250 for (const QString &element : std::as_const(elementsToRender)) 0251 { 0252 { 0253 QMutexLocker l(killMutex); 0254 if (doKill) 0255 return; 0256 } 0257 QPixmap img(size); 0258 img.fill(Qt::transparent); 0259 QPainter p(&img); 0260 { 0261 QMutexLocker l(d->rendererMutex); 0262 d->renderer()->render(&p, element); 0263 } 0264 p.end(); 0265 0266 QString key = keyForPixmap(frontTheme, element, size); 0267 Q_EMIT renderingDone(key, img); 0268 } 0269 } 0270 0271 KCardCache::KCardCache() 0272 : d(new KCardCachePrivate) 0273 { 0274 d->cache = nullptr; 0275 d->cacheMutex = new QMutex(); 0276 d->rendererMutex = new QMutex(); 0277 d->svgRenderer = nullptr; 0278 d->loadThread = nullptr; 0279 } 0280 0281 KCardCache::~KCardCache() 0282 { 0283 if (d->loadThread && d->loadThread->isRunning()) 0284 { 0285 d->loadThread->kill(); 0286 } 0287 delete d->loadThread; 0288 delete d->cache; 0289 delete d->cacheMutex; 0290 delete d->rendererMutex; 0291 delete d->svgRenderer; 0292 delete d; 0293 } 0294 0295 QPixmap KCardCache::backside() const 0296 { 0297 QPixmap pix; 0298 if (d->deckName.isEmpty() || d->size.isEmpty()) 0299 return pix; 0300 QString element = QStringLiteral("back"); 0301 QString key = keyForPixmap(d->deckName, element, d->size); 0302 0303 { 0304 QMutexLocker l(d->cacheMutex); 0305 if (d->cache && (!d->cache->findPixmap(key, &pix) || pix.isNull())) 0306 { 0307 pix = d->renderSvg(element); 0308 d->cache->insertPixmap(key, pix); 0309 } 0310 } 0311 // Make sure we never return an invalid pixmap 0312 d->ensureNonNullPixmap(pix); 0313 return pix; 0314 } 0315 0316 QPixmap KCardCache::frontside(KCardInfo info) const 0317 { 0318 QPixmap pix; 0319 if (d->deckName.isEmpty() || d->size.isEmpty()) 0320 return pix; 0321 QString key = keyForPixmap(d->deckName, info.svgName(), d->size); 0322 0323 { 0324 QMutexLocker l(d->cacheMutex); 0325 if (d->cache && (!d->cache->findPixmap(key, &pix) || pix.isNull())) 0326 { 0327 pix = d->renderSvg(info.svgName()); 0328 d->cache->insertPixmap(key, pix); 0329 } 0330 } 0331 // Make sure we never return an invalid pixmap 0332 d->ensureNonNullPixmap(pix); 0333 return pix; 0334 } 0335 0336 void KCardCache::setSize(const QSize &s) 0337 { 0338 if (s != d->size) 0339 d->size = s; 0340 } 0341 0342 QSize KCardCache::size() const 0343 { 0344 return d->size; 0345 } 0346 0347 void KCardCache::setDeckName(const QString &theme) 0348 { 0349 { 0350 QMutexLocker l(d->cacheMutex); 0351 delete d->cache; 0352 // The default size is arbitrary: it reflects the old KPixmapCache default 0353 // and it seems to match the real maximum size for the decks 0354 d->cache = new KImageCache(QStringLiteral("kdegames-cards_%1").arg(theme), 3*(1024<<10)); 0355 QDateTime dt = QFileInfo(CardDeckInfo::svgFilePath(theme)).lastModified(); 0356 if (d->cache->lastModifiedTime() < dt) 0357 { 0358 d->cache->clear(); 0359 } 0360 } 0361 { 0362 QMutexLocker l(d->rendererMutex); 0363 delete d->svgRenderer; 0364 d->svgRenderer = nullptr; 0365 } 0366 d->deckName = theme; 0367 } 0368 0369 QString KCardCache::deckName() const 0370 { 0371 return d->deckName; 0372 } 0373 0374 void KCardCache::loadTheme(LoadInfos infos) 0375 { 0376 if (d->loadThread && d->loadThread->isRunning()) 0377 { 0378 d->loadThread->kill(); 0379 d->loadThread->wait(); 0380 } 0381 delete d->loadThread; 0382 0383 // We have to compile the list of elements to load here, because we can not 0384 // check the contents of the KImageCache from outside the GUI thread. 0385 QStringList elements; 0386 QPixmap pix; 0387 if (infos &KCardCache::LoadFrontSide) 0388 { 0389 int numCards; 0390 if (infos &KCardCache::Load53Cards) 0391 numCards = 53; 0392 else if (infos &KCardCache::Load52Cards) 0393 numCards = 52; 0394 else 0395 numCards = 32; 0396 0397 for (int i = 0; i < numCards; i++) 0398 { 0399 QString element = fullDeckList[i].svgName(); 0400 QString key = keyForPixmap(d->deckName, element, d->size); 0401 { 0402 QMutexLocker l(d->cacheMutex); 0403 if (d->cache && !d->cache->findPixmap(key, &pix)) 0404 elements << element; 0405 } 0406 } 0407 } 0408 0409 d->loadThread = new LoadThread(d); 0410 d->loadThread->setDeckName(d->deckName); 0411 d->loadThread->setSize(d->size); 0412 d->loadThread->setElementsToLoad(elements); 0413 d->connect(d->loadThread, &LoadThread::renderingDone, d, &KCardCachePrivate::submitRendering, Qt::QueuedConnection); 0414 d->loadThread->start(QThread::IdlePriority); 0415 } 0416 0417 void KCardCache::invalidateCache() 0418 { 0419 QMutexLocker l(d->cacheMutex); 0420 if (d->cache) 0421 d->cache->clear(); 0422 } 0423 0424 #include "moc_cardcache_p.cpp"