File indexing completed on 2024-04-21 07:52:37
0001 /* 0002 SPDX-FileCopyrightText: 1997 Mathias Mueller <in5y158@public.uni-hamburg.de> 0003 SPDX-FileCopyrightText: 2006 Mauricio Piacentini <mauricio@tabuleiro.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 // own 0009 #include "kmahjonggtileset.h" 0010 0011 // STL 0012 #include <cstdlib> 0013 0014 // Qt 0015 #include <QFile> 0016 #include <QGuiApplication> 0017 #include <QPainter> 0018 #include <QPixmapCache> 0019 #include <QStandardPaths> 0020 #include <QSvgRenderer> 0021 0022 // KF 0023 #include <KConfig> 0024 #include <KConfigGroup> 0025 #include <KLocalizedString> 0026 0027 // LibKMahjongg 0028 #include "libkmahjongg_debug.h" 0029 0030 class KMahjonggTilesetMetricsData 0031 { 0032 public: 0033 short lvloffx = 0; // used for 3D indentation, x value 0034 short lvloffy = 0; // used for 3D indentation, y value 0035 short w = 0; // tile width ( +border +shadow) 0036 short h = 0; // tile height ( +border +shadow) 0037 short fw = 0; // face width 0038 short fh = 0; // face height 0039 0040 KMahjonggTilesetMetricsData() = default; 0041 }; 0042 0043 class KMahjonggTilesetPrivate 0044 { 0045 public: 0046 KMahjonggTilesetPrivate() = default; 0047 0048 void updateScaleInfo(short tilew, short tileh); 0049 void buildElementIdTable(); 0050 QString pixmapCacheNameFromElementId(const QString &elementid, short width, short height) const; 0051 QPixmap renderElement(short width, short height, const QString &elementid) const; 0052 0053 public: 0054 QList<QString> elementIdTable; 0055 0056 QString name; 0057 QString description; 0058 QString license; 0059 QString copyrightText; 0060 QString version; 0061 QString website; 0062 QString bugReportUrl; 0063 QString authorName; 0064 QString authorEmailAddress; 0065 0066 KMahjonggTilesetMetricsData originaldata; 0067 KMahjonggTilesetMetricsData scaleddata; 0068 QString filename; // cache the last file loaded to save reloading it 0069 QString graphicspath; 0070 0071 mutable QSvgRenderer svg; // render() is non-const 0072 bool isSVG = false; 0073 bool graphicsLoaded = false; 0074 }; 0075 0076 // --------------------------------------------------------- 0077 0078 KMahjonggTileset::KMahjonggTileset() 0079 : d_ptr(new KMahjonggTilesetPrivate) 0080 { 0081 Q_D(KMahjonggTileset); 0082 0083 d->buildElementIdTable(); 0084 } 0085 0086 // --------------------------------------------------------- 0087 0088 KMahjonggTileset::~KMahjonggTileset() = default; 0089 0090 void KMahjonggTilesetPrivate::updateScaleInfo(short tilew, short tileh) 0091 { 0092 scaleddata.w = tilew; 0093 scaleddata.h = tileh; 0094 const double ratio = (static_cast<qreal>(scaleddata.w)) / (static_cast<qreal>(originaldata.w)); 0095 scaleddata.lvloffx = static_cast<short>(originaldata.lvloffx * ratio); 0096 scaleddata.lvloffy = static_cast<short>(originaldata.lvloffy * ratio); 0097 scaleddata.fw = static_cast<short>(originaldata.fw * ratio); 0098 scaleddata.fh = static_cast<short>(originaldata.fh * ratio); 0099 } 0100 0101 QSize KMahjonggTileset::preferredTileSize(QSize boardsize, int horizontalCells, int verticalCells) const 0102 { 0103 Q_D(const KMahjonggTileset); 0104 0105 // calculate our best tile size to fit the boardsize passed to us 0106 qreal newtilew, newtileh, aspectratio; 0107 qreal bw = boardsize.width(); 0108 qreal bh = boardsize.height(); 0109 0110 // use tileface for calculation, with one complete tile in the sum for extra margin 0111 qreal fullh = (d->originaldata.fh * verticalCells) + d->originaldata.h; 0112 qreal fullw = (d->originaldata.fw * horizontalCells) + d->originaldata.w; 0113 qreal floatw = d->originaldata.w; 0114 qreal floath = d->originaldata.h; 0115 0116 if ((fullw / fullh) > (bw / bh)) { 0117 // space will be left on height, use width as limit 0118 aspectratio = bw / fullw; 0119 } else { 0120 aspectratio = bh / fullh; 0121 } 0122 newtilew = aspectratio * floatw; 0123 newtileh = aspectratio * floath; 0124 return QSize(static_cast<short>(newtilew), static_cast<short>(newtileh)); 0125 } 0126 0127 bool KMahjonggTileset::loadDefault() 0128 { 0129 Q_D(KMahjonggTileset); 0130 0131 const QString tilesetPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kmahjongglib/tilesets/default.desktop")); 0132 qCDebug(LIBKMAHJONGG_LOG) << "Inside LoadDefault(), located path at" << tilesetPath; 0133 if (tilesetPath.isEmpty()) { 0134 return false; 0135 } 0136 return loadTileset(tilesetPath); 0137 } 0138 0139 QString KMahjonggTileset::name() const 0140 { 0141 Q_D(const KMahjonggTileset); 0142 0143 return d->name; 0144 } 0145 0146 QString KMahjonggTileset::description() const 0147 { 0148 Q_D(const KMahjonggTileset); 0149 0150 return d->description; 0151 } 0152 0153 QString KMahjonggTileset::license() const 0154 { 0155 Q_D(const KMahjonggTileset); 0156 0157 return d->license; 0158 } 0159 0160 QString KMahjonggTileset::copyrightText() const 0161 { 0162 Q_D(const KMahjonggTileset); 0163 0164 return d->copyrightText; 0165 } 0166 0167 QString KMahjonggTileset::version() const 0168 { 0169 Q_D(const KMahjonggTileset); 0170 0171 return d->version; 0172 } 0173 0174 QString KMahjonggTileset::website() const 0175 { 0176 Q_D(const KMahjonggTileset); 0177 0178 return d->website; 0179 } 0180 0181 QString KMahjonggTileset::bugReportUrl() const 0182 { 0183 Q_D(const KMahjonggTileset); 0184 0185 return d->bugReportUrl; 0186 } 0187 0188 QString KMahjonggTileset::authorName() const 0189 { 0190 Q_D(const KMahjonggTileset); 0191 0192 return d->authorName; 0193 } 0194 0195 QString KMahjonggTileset::authorEmailAddress() const 0196 { 0197 Q_D(const KMahjonggTileset); 0198 0199 return d->authorEmailAddress; 0200 } 0201 0202 short KMahjonggTileset::width() const 0203 { 0204 Q_D(const KMahjonggTileset); 0205 0206 return d->scaleddata.w; 0207 } 0208 0209 short KMahjonggTileset::height() const 0210 { 0211 Q_D(const KMahjonggTileset); 0212 0213 return d->scaleddata.h; 0214 } 0215 0216 short KMahjonggTileset::levelOffsetX() const 0217 { 0218 Q_D(const KMahjonggTileset); 0219 0220 return d->scaleddata.lvloffx; 0221 } 0222 0223 short KMahjonggTileset::levelOffsetY() const 0224 { 0225 Q_D(const KMahjonggTileset); 0226 0227 return d->scaleddata.lvloffy; 0228 } 0229 0230 short KMahjonggTileset::qWidth() const 0231 { 0232 Q_D(const KMahjonggTileset); 0233 0234 return static_cast<short>(d->scaleddata.fw / 2.0); 0235 } 0236 0237 short KMahjonggTileset::qHeight() const 0238 { 0239 Q_D(const KMahjonggTileset); 0240 0241 return static_cast<short>(d->scaleddata.fh / 2.0); 0242 } 0243 0244 QString KMahjonggTileset::path() const 0245 { 0246 Q_D(const KMahjonggTileset); 0247 0248 return d->filename; 0249 } 0250 0251 #define kTilesetVersionFormat 1 0252 0253 // --------------------------------------------------------- 0254 bool KMahjonggTileset::loadTileset(const QString &tilesetPath) 0255 { 0256 Q_D(KMahjonggTileset); 0257 0258 // qCDebug(LIBKMAHJONGG_LOG) << "Attempting to load .desktop at" << tilesetPath; 0259 0260 // verify if it is a valid file first and if we can open it 0261 QFile tilesetfile(tilesetPath); 0262 if (!tilesetfile.open(QIODevice::ReadOnly)) { 0263 d->name.clear(); 0264 d->description.clear(); 0265 d->license.clear(); 0266 d->copyrightText.clear(); 0267 d->version.clear(); 0268 d->website.clear(); 0269 d->bugReportUrl.clear(); 0270 d->authorName.clear(); 0271 d->authorEmailAddress.clear(); 0272 return false; 0273 } 0274 tilesetfile.close(); 0275 0276 KConfig tileconfig(tilesetPath, KConfig::SimpleConfig); 0277 KConfigGroup group = tileconfig.group(QStringLiteral("KMahjonggTileset")); 0278 0279 d->name = group.readEntry("Name"); // Returns translated data 0280 d->description = group.readEntry("Description"); 0281 d->license = group.readEntry("License"); 0282 d->copyrightText = group.readEntry("Copyright"); 0283 d->version = group.readEntry("Version"); 0284 d->website = group.readEntry("Website"); 0285 d->bugReportUrl = group.readEntry("BugReportUrl"); 0286 d->authorName = group.readEntry("Author"); 0287 d->authorEmailAddress = group.readEntry("AuthorEmail"); 0288 0289 // Version control 0290 int tileversion = group.readEntry("VersionFormat", 0); 0291 // Format is increased when we have incompatible changes, meaning that older clients are not able to use the remaining information safely 0292 if (tileversion > kTilesetVersionFormat) { 0293 return false; 0294 } 0295 0296 QString graphName = group.readEntry("FileName"); 0297 0298 d->graphicspath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kmahjongglib/tilesets/") + graphName); 0299 // qCDebug(LIBKMAHJONGG_LOG) << "Using tileset at" << d->graphicspath; 0300 0301 // only SVG for now 0302 d->isSVG = true; 0303 if (d->graphicspath.isEmpty()) { 0304 return false; 0305 } 0306 0307 d->originaldata.w = group.readEntry("TileWidth", 30); 0308 d->originaldata.h = group.readEntry("TileHeight", 50); 0309 d->originaldata.fw = group.readEntry("TileFaceWidth", 30); 0310 d->originaldata.fh = group.readEntry("TileFaceHeight", 50); 0311 d->originaldata.lvloffx = group.readEntry("LevelOffsetX", 10); 0312 d->originaldata.lvloffy = group.readEntry("LevelOffsetY", 10); 0313 0314 // client application needs to call loadGraphics() 0315 d->graphicsLoaded = false; 0316 d->filename = tilesetPath; 0317 0318 return true; 0319 } 0320 0321 // --------------------------------------------------------- 0322 bool KMahjonggTileset::loadGraphics() 0323 { 0324 Q_D(KMahjonggTileset); 0325 0326 if (d->graphicsLoaded) { 0327 return true; 0328 } 0329 if (d->isSVG) { 0330 // really? 0331 d->svg.load(d->graphicspath); 0332 if (d->svg.isValid()) { 0333 // invalidate our global cache 0334 QPixmapCache::clear(); 0335 d->graphicsLoaded = true; 0336 reloadTileset(QSize(d->originaldata.w, d->originaldata.h)); 0337 } else { 0338 return false; 0339 } 0340 } else { 0341 // TODO add support for png?? 0342 return false; 0343 } 0344 0345 return true; 0346 } 0347 0348 // --------------------------------------------------------- 0349 bool KMahjonggTileset::reloadTileset(QSize newTilesize) 0350 { 0351 Q_D(KMahjonggTileset); 0352 0353 if (QSize(d->scaleddata.w, d->scaleddata.h) == newTilesize) { 0354 return false; 0355 } 0356 0357 if (d->isSVG) { 0358 if (d->svg.isValid()) { 0359 d->updateScaleInfo(newTilesize.width(), newTilesize.height()); 0360 // rendering will be done when needed, automatically using the global cache 0361 } else { 0362 return false; 0363 } 0364 } else { 0365 // TODO add support for png??? 0366 return false; 0367 } 0368 0369 return true; 0370 } 0371 0372 void KMahjonggTilesetPrivate::buildElementIdTable() 0373 { 0374 constexpr int tileCount = 4; 0375 constexpr int characterCount = 9; 0376 constexpr int bambooCount = 9; 0377 constexpr int rodCount = 9; 0378 constexpr int seasonCount = 4; 0379 constexpr int windCount = 4; 0380 constexpr int dragonCount = 3; 0381 constexpr int flowerCount = 4; 0382 0383 elementIdTable.reserve(2 * tileCount + characterCount + bambooCount + rodCount + seasonCount + windCount + dragonCount + flowerCount); 0384 0385 // Build a list for faster lookup of element ids, mapped to the enumeration used by GameData and BoardWidget 0386 // Unselected tiles 0387 for (short idx = 1; idx <= tileCount; idx++) { 0388 elementIdTable.append(QStringLiteral("TILE_%1").arg(idx)); 0389 } 0390 // Selected tiles 0391 for (short idx = 1; idx <= tileCount; idx++) { 0392 elementIdTable.append(QStringLiteral("TILE_%1_SEL").arg(idx)); 0393 } 0394 // now faces 0395 for (short idx = 1; idx <= characterCount; idx++) { 0396 elementIdTable.append(QStringLiteral("CHARACTER_%1").arg(idx)); 0397 } 0398 for (short idx = 1; idx <= bambooCount; idx++) { 0399 elementIdTable.append(QStringLiteral("BAMBOO_%1").arg(idx)); 0400 } 0401 for (short idx = 1; idx <= rodCount; idx++) { 0402 elementIdTable.append(QStringLiteral("ROD_%1").arg(idx)); 0403 } 0404 for (short idx = 1; idx <= seasonCount; idx++) { 0405 elementIdTable.append(QStringLiteral("SEASON_%1").arg(idx)); 0406 } 0407 for (short idx = 1; idx <= windCount; idx++) { 0408 elementIdTable.append(QStringLiteral("WIND_%1").arg(idx)); 0409 } 0410 for (short idx = 1; idx <= dragonCount; idx++) { 0411 elementIdTable.append(QStringLiteral("DRAGON_%1").arg(idx)); 0412 } 0413 for (short idx = 1; idx <= flowerCount; idx++) { 0414 elementIdTable.append(QStringLiteral("FLOWER_%1").arg(idx)); 0415 } 0416 } 0417 0418 QString KMahjonggTilesetPrivate::pixmapCacheNameFromElementId(const QString &elementid, short width, short height) const 0419 { 0420 return name + elementid + QStringLiteral("W%1H%2").arg(width).arg(height); 0421 } 0422 0423 QPixmap KMahjonggTilesetPrivate::renderElement(short width, short height, const QString &elementid) const 0424 { 0425 // qCDebug(LIBKMAHJONGG_LOG) << "render element" << elementid << width << height; 0426 QPixmap qiRend(width, height); 0427 qiRend.fill(Qt::transparent); 0428 0429 if (svg.isValid()) { 0430 QPainter p(&qiRend); 0431 svg.render(&p, elementid); 0432 } 0433 return qiRend; 0434 } 0435 0436 QPixmap KMahjonggTileset::selectedTile(int num) const 0437 { 0438 Q_D(const KMahjonggTileset); 0439 0440 QPixmap pm; 0441 0442 const qreal dpr = qApp->devicePixelRatio(); 0443 // use tile size 0444 const short width = d->scaleddata.w * dpr; 0445 const short height = d->scaleddata.h * dpr; 0446 QString elemId = d->elementIdTable.at(num + 4); // selected offset in our idtable; 0447 // using raw pixmap size with cache id, as the rendering is done dpr-ignorant 0448 const QString pixmapCacheName = d->pixmapCacheNameFromElementId(elemId, width, height); 0449 if (!QPixmapCache::find(pixmapCacheName, &pm)) { 0450 pm = d->renderElement(width, height, elemId); 0451 pm.setDevicePixelRatio(dpr); 0452 QPixmapCache::insert(pixmapCacheName, pm); 0453 } 0454 return pm; 0455 } 0456 0457 QPixmap KMahjonggTileset::unselectedTile(int num) const 0458 { 0459 Q_D(const KMahjonggTileset); 0460 0461 QPixmap pm; 0462 0463 const qreal dpr = qApp->devicePixelRatio(); 0464 // use tile size 0465 const short width = d->scaleddata.w * dpr; 0466 const short height = d->scaleddata.h * dpr; 0467 QString elemId = d->elementIdTable.at(num); 0468 // using raw pixmap size with cache id, as the rendering is done dpr-ignorant 0469 const QString pixmapCacheName = d->pixmapCacheNameFromElementId(elemId, width, height); 0470 if (!QPixmapCache::find(pixmapCacheName, &pm)) { 0471 pm = d->renderElement(width, height, elemId); 0472 pm.setDevicePixelRatio(dpr); 0473 QPixmapCache::insert(pixmapCacheName, pm); 0474 } 0475 return pm; 0476 } 0477 0478 QPixmap KMahjonggTileset::tileface(int num) const 0479 { 0480 Q_D(const KMahjonggTileset); 0481 0482 QPixmap pm; 0483 if ((num + 8) >= d->elementIdTable.count()) { 0484 // qCDebug(LIBKMAHJONGG_LOG) << "Client asked for invalid tileface id"; 0485 return pm; 0486 } 0487 0488 const qreal dpr = qApp->devicePixelRatio(); 0489 // use face size 0490 const short width = d->scaleddata.fw * dpr; 0491 const short height = d->scaleddata.fh * dpr; 0492 QString elemId = d->elementIdTable.at(num + 8); // tileface offset in our idtable; 0493 // using raw pixmap size with cache id, as the rendering is done dpr-ignorant 0494 const QString pixmapCacheName = d->pixmapCacheNameFromElementId(elemId, width, height); 0495 if (!QPixmapCache::find(pixmapCacheName, &pm)) { 0496 pm = d->renderElement(width, height, elemId); 0497 pm.setDevicePixelRatio(dpr); 0498 QPixmapCache::insert(pixmapCacheName, pm); 0499 } 0500 return pm; 0501 }