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"