LibKmahjongg

kmahjonggtileset.cpp
1/*
2 SPDX-FileCopyrightText: 1997 Mathias Mueller <in5y158@public.uni-hamburg.de>
3 SPDX-FileCopyrightText: 2006 Mauricio Piacentini <mauricio@tabuleiro.com>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6*/
7
8// own
9#include "kmahjonggtileset.h"
10
11// STL
12#include <cstdlib>
13
14// Qt
15#include <QFile>
16#include <QGuiApplication>
17#include <QPainter>
18#include <QPixmapCache>
19#include <QStandardPaths>
20#include <QSvgRenderer>
21
22// KF
23#include <KConfig>
24#include <KConfigGroup>
25#include <KLocalizedString>
26
27// LibKMahjongg
28#include "libkmahjongg_debug.h"
29
30class KMahjonggTilesetMetricsData
31{
32public:
33 short lvloffx = 0; // used for 3D indentation, x value
34 short lvloffy = 0; // used for 3D indentation, y value
35 short w = 0; // tile width ( +border +shadow)
36 short h = 0; // tile height ( +border +shadow)
37 short fw = 0; // face width
38 short fh = 0; // face height
39
40 KMahjonggTilesetMetricsData() = default;
41};
42
43class KMahjonggTilesetPrivate
44{
45public:
46 KMahjonggTilesetPrivate() = default;
47
48 void updateScaleInfo(short tilew, short tileh);
49 void buildElementIdTable();
50 QString pixmapCacheNameFromElementId(const QString &elementid, short width, short height) const;
51 QPixmap renderElement(short width, short height, const QString &elementid) const;
52
53public:
54 QList<QString> elementIdTable;
55
56 QString name;
57 QString description;
58 QString license;
59 QString copyrightText;
60 QString version;
61 QString website;
62 QString bugReportUrl;
63 QString authorName;
64 QString authorEmailAddress;
65
66 KMahjonggTilesetMetricsData originaldata;
67 KMahjonggTilesetMetricsData scaleddata;
68 QString filename; // cache the last file loaded to save reloading it
69 QString graphicspath;
70
71 mutable QSvgRenderer svg; // render() is non-const
72 bool isSVG = false;
73 bool graphicsLoaded = false;
74};
75
76// ---------------------------------------------------------
77
78KMahjonggTileset::KMahjonggTileset()
79 : d_ptr(new KMahjonggTilesetPrivate)
80{
82
83 d->buildElementIdTable();
84}
85
86// ---------------------------------------------------------
87
88KMahjonggTileset::~KMahjonggTileset() = default;
89
90void KMahjonggTilesetPrivate::updateScaleInfo(short tilew, short tileh)
91{
92 scaleddata.w = tilew;
93 scaleddata.h = tileh;
94 const double ratio = (static_cast<qreal>(scaleddata.w)) / (static_cast<qreal>(originaldata.w));
95 scaleddata.lvloffx = static_cast<short>(originaldata.lvloffx * ratio);
96 scaleddata.lvloffy = static_cast<short>(originaldata.lvloffy * ratio);
97 scaleddata.fw = static_cast<short>(originaldata.fw * ratio);
98 scaleddata.fh = static_cast<short>(originaldata.fh * ratio);
99}
100
101QSize KMahjonggTileset::preferredTileSize(QSize boardsize, int horizontalCells, int verticalCells) const
102{
103 Q_D(const KMahjonggTileset);
104
105 // calculate our best tile size to fit the boardsize passed to us
106 qreal newtilew, newtileh, aspectratio;
107 qreal bw = boardsize.width();
108 qreal bh = boardsize.height();
109
110 // use tileface for calculation, with one complete tile in the sum for extra margin
111 qreal fullh = (d->originaldata.fh * verticalCells) + d->originaldata.h;
112 qreal fullw = (d->originaldata.fw * horizontalCells) + d->originaldata.w;
113 qreal floatw = d->originaldata.w;
114 qreal floath = d->originaldata.h;
115
116 if ((fullw / fullh) > (bw / bh)) {
117 // space will be left on height, use width as limit
118 aspectratio = bw / fullw;
119 } else {
120 aspectratio = bh / fullh;
121 }
122 newtilew = aspectratio * floatw;
123 newtileh = aspectratio * floath;
124 return QSize(static_cast<short>(newtilew), static_cast<short>(newtileh));
125}
126
127bool KMahjonggTileset::loadDefault()
128{
130
131 const QString tilesetPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kmahjongglib/tilesets/default.desktop"));
132 qCDebug(LIBKMAHJONGG_LOG) << "Inside LoadDefault(), located path at" << tilesetPath;
133 if (tilesetPath.isEmpty()) {
134 return false;
135 }
136 return loadTileset(tilesetPath);
137}
138
139QString KMahjonggTileset::name() const
140{
141 Q_D(const KMahjonggTileset);
142
143 return d->name;
144}
145
146QString KMahjonggTileset::description() const
147{
148 Q_D(const KMahjonggTileset);
149
150 return d->description;
151}
152
153QString KMahjonggTileset::license() const
154{
155 Q_D(const KMahjonggTileset);
156
157 return d->license;
158}
159
160QString KMahjonggTileset::copyrightText() const
161{
162 Q_D(const KMahjonggTileset);
163
164 return d->copyrightText;
165}
166
167QString KMahjonggTileset::version() const
168{
169 Q_D(const KMahjonggTileset);
170
171 return d->version;
172}
173
174QString KMahjonggTileset::website() const
175{
176 Q_D(const KMahjonggTileset);
177
178 return d->website;
179}
180
181QString KMahjonggTileset::bugReportUrl() const
182{
183 Q_D(const KMahjonggTileset);
184
185 return d->bugReportUrl;
186}
187
188QString KMahjonggTileset::authorName() const
189{
190 Q_D(const KMahjonggTileset);
191
192 return d->authorName;
193}
194
195QString KMahjonggTileset::authorEmailAddress() const
196{
197 Q_D(const KMahjonggTileset);
198
199 return d->authorEmailAddress;
200}
201
202short KMahjonggTileset::width() const
203{
204 Q_D(const KMahjonggTileset);
205
206 return d->scaleddata.w;
207}
208
209short KMahjonggTileset::height() const
210{
211 Q_D(const KMahjonggTileset);
212
213 return d->scaleddata.h;
214}
215
216short KMahjonggTileset::levelOffsetX() const
217{
218 Q_D(const KMahjonggTileset);
219
220 return d->scaleddata.lvloffx;
221}
222
223short KMahjonggTileset::levelOffsetY() const
224{
225 Q_D(const KMahjonggTileset);
226
227 return d->scaleddata.lvloffy;
228}
229
230short KMahjonggTileset::qWidth() const
231{
232 Q_D(const KMahjonggTileset);
233
234 return static_cast<short>(d->scaleddata.fw / 2.0);
235}
236
237short KMahjonggTileset::qHeight() const
238{
239 Q_D(const KMahjonggTileset);
240
241 return static_cast<short>(d->scaleddata.fh / 2.0);
242}
243
244QString KMahjonggTileset::path() const
245{
246 Q_D(const KMahjonggTileset);
247
248 return d->filename;
249}
250
251#define kTilesetVersionFormat 1
252
253// ---------------------------------------------------------
254bool KMahjonggTileset::loadTileset(const QString &tilesetPath)
255{
257
258 // qCDebug(LIBKMAHJONGG_LOG) << "Attempting to load .desktop at" << tilesetPath;
259
260 // verify if it is a valid file first and if we can open it
261 QFile tilesetfile(tilesetPath);
262 if (!tilesetfile.open(QIODevice::ReadOnly)) {
263 d->name.clear();
264 d->description.clear();
265 d->license.clear();
266 d->copyrightText.clear();
267 d->version.clear();
268 d->website.clear();
269 d->bugReportUrl.clear();
270 d->authorName.clear();
271 d->authorEmailAddress.clear();
272 return false;
273 }
274 tilesetfile.close();
275
276 KConfig tileconfig(tilesetPath, KConfig::SimpleConfig);
277 KConfigGroup group = tileconfig.group(QStringLiteral("KMahjonggTileset"));
278
279 d->name = group.readEntry("Name"); // Returns translated data
280 d->description = group.readEntry("Description");
281 d->license = group.readEntry("License");
282 d->copyrightText = group.readEntry("Copyright");
283 d->version = group.readEntry("Version");
284 d->website = group.readEntry("Website");
285 d->bugReportUrl = group.readEntry("BugReportUrl");
286 d->authorName = group.readEntry("Author");
287 d->authorEmailAddress = group.readEntry("AuthorEmail");
288
289 // Version control
290 int tileversion = group.readEntry("VersionFormat", 0);
291 // Format is increased when we have incompatible changes, meaning that older clients are not able to use the remaining information safely
292 if (tileversion > kTilesetVersionFormat) {
293 return false;
294 }
295
296 QString graphName = group.readEntry("FileName");
297
298 d->graphicspath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kmahjongglib/tilesets/") + graphName);
299 // qCDebug(LIBKMAHJONGG_LOG) << "Using tileset at" << d->graphicspath;
300
301 // only SVG for now
302 d->isSVG = true;
303 if (d->graphicspath.isEmpty()) {
304 return false;
305 }
306
307 d->originaldata.w = group.readEntry("TileWidth", 30);
308 d->originaldata.h = group.readEntry("TileHeight", 50);
309 d->originaldata.fw = group.readEntry("TileFaceWidth", 30);
310 d->originaldata.fh = group.readEntry("TileFaceHeight", 50);
311 d->originaldata.lvloffx = group.readEntry("LevelOffsetX", 10);
312 d->originaldata.lvloffy = group.readEntry("LevelOffsetY", 10);
313
314 // client application needs to call loadGraphics()
315 d->graphicsLoaded = false;
316 d->filename = tilesetPath;
317
318 return true;
319}
320
321// ---------------------------------------------------------
322bool KMahjonggTileset::loadGraphics()
323{
325
326 if (d->graphicsLoaded) {
327 return true;
328 }
329 if (d->isSVG) {
330 // really?
331 d->svg.load(d->graphicspath);
332 if (d->svg.isValid()) {
333 // invalidate our global cache
335 d->graphicsLoaded = true;
336 reloadTileset(QSize(d->originaldata.w, d->originaldata.h));
337 } else {
338 return false;
339 }
340 } else {
341 // TODO add support for png??
342 return false;
343 }
344
345 return true;
346}
347
348// ---------------------------------------------------------
349bool KMahjonggTileset::reloadTileset(QSize newTilesize)
350{
352
353 if (QSize(d->scaleddata.w, d->scaleddata.h) == newTilesize) {
354 return false;
355 }
356
357 if (d->isSVG) {
358 if (d->svg.isValid()) {
359 d->updateScaleInfo(newTilesize.width(), newTilesize.height());
360 // rendering will be done when needed, automatically using the global cache
361 } else {
362 return false;
363 }
364 } else {
365 // TODO add support for png???
366 return false;
367 }
368
369 return true;
370}
371
372void KMahjonggTilesetPrivate::buildElementIdTable()
373{
374 constexpr int tileCount = 4;
375 constexpr int characterCount = 9;
376 constexpr int bambooCount = 9;
377 constexpr int rodCount = 9;
378 constexpr int seasonCount = 4;
379 constexpr int windCount = 4;
380 constexpr int dragonCount = 3;
381 constexpr int flowerCount = 4;
382
383 elementIdTable.reserve(2 * tileCount + characterCount + bambooCount + rodCount + seasonCount + windCount + dragonCount + flowerCount);
384
385 // Build a list for faster lookup of element ids, mapped to the enumeration used by GameData and BoardWidget
386 // Unselected tiles
387 for (short idx = 1; idx <= tileCount; idx++) {
388 elementIdTable.append(QStringLiteral("TILE_%1").arg(idx));
389 }
390 // Selected tiles
391 for (short idx = 1; idx <= tileCount; idx++) {
392 elementIdTable.append(QStringLiteral("TILE_%1_SEL").arg(idx));
393 }
394 // now faces
395 for (short idx = 1; idx <= characterCount; idx++) {
396 elementIdTable.append(QStringLiteral("CHARACTER_%1").arg(idx));
397 }
398 for (short idx = 1; idx <= bambooCount; idx++) {
399 elementIdTable.append(QStringLiteral("BAMBOO_%1").arg(idx));
400 }
401 for (short idx = 1; idx <= rodCount; idx++) {
402 elementIdTable.append(QStringLiteral("ROD_%1").arg(idx));
403 }
404 for (short idx = 1; idx <= seasonCount; idx++) {
405 elementIdTable.append(QStringLiteral("SEASON_%1").arg(idx));
406 }
407 for (short idx = 1; idx <= windCount; idx++) {
408 elementIdTable.append(QStringLiteral("WIND_%1").arg(idx));
409 }
410 for (short idx = 1; idx <= dragonCount; idx++) {
411 elementIdTable.append(QStringLiteral("DRAGON_%1").arg(idx));
412 }
413 for (short idx = 1; idx <= flowerCount; idx++) {
414 elementIdTable.append(QStringLiteral("FLOWER_%1").arg(idx));
415 }
416}
417
418QString KMahjonggTilesetPrivate::pixmapCacheNameFromElementId(const QString &elementid, short width, short height) const
419{
420 return name + elementid + QStringLiteral("W%1H%2").arg(width).arg(height);
421}
422
423QPixmap KMahjonggTilesetPrivate::renderElement(short width, short height, const QString &elementid) const
424{
425 // qCDebug(LIBKMAHJONGG_LOG) << "render element" << elementid << width << height;
426 QPixmap qiRend(width, height);
427 qiRend.fill(Qt::transparent);
428
429 if (svg.isValid()) {
430 QPainter p(&qiRend);
431 svg.render(&p, elementid);
432 }
433 return qiRend;
434}
435
436QPixmap KMahjonggTileset::selectedTile(int num) const
437{
438 Q_D(const KMahjonggTileset);
439
440 QPixmap pm;
441
442 const qreal dpr = qApp->devicePixelRatio();
443 // use tile size
444 const short width = d->scaleddata.w * dpr;
445 const short height = d->scaleddata.h * dpr;
446 QString elemId = d->elementIdTable.at(num + 4); // selected offset in our idtable;
447 // using raw pixmap size with cache id, as the rendering is done dpr-ignorant
448 const QString pixmapCacheName = d->pixmapCacheNameFromElementId(elemId, width, height);
449 if (!QPixmapCache::find(pixmapCacheName, &pm)) {
450 pm = d->renderElement(width, height, elemId);
451 pm.setDevicePixelRatio(dpr);
452 QPixmapCache::insert(pixmapCacheName, pm);
453 }
454 return pm;
455}
456
457QPixmap KMahjonggTileset::unselectedTile(int num) const
458{
459 Q_D(const KMahjonggTileset);
460
461 QPixmap pm;
462
463 const qreal dpr = qApp->devicePixelRatio();
464 // use tile size
465 const short width = d->scaleddata.w * dpr;
466 const short height = d->scaleddata.h * dpr;
467 QString elemId = d->elementIdTable.at(num);
468 // using raw pixmap size with cache id, as the rendering is done dpr-ignorant
469 const QString pixmapCacheName = d->pixmapCacheNameFromElementId(elemId, width, height);
470 if (!QPixmapCache::find(pixmapCacheName, &pm)) {
471 pm = d->renderElement(width, height, elemId);
472 pm.setDevicePixelRatio(dpr);
473 QPixmapCache::insert(pixmapCacheName, pm);
474 }
475 return pm;
476}
477
478QPixmap KMahjonggTileset::tileface(int num) const
479{
480 Q_D(const KMahjonggTileset);
481
482 QPixmap pm;
483 if ((num + 8) >= d->elementIdTable.count()) {
484 // qCDebug(LIBKMAHJONGG_LOG) << "Client asked for invalid tileface id";
485 return pm;
486 }
487
488 const qreal dpr = qApp->devicePixelRatio();
489 // use face size
490 const short width = d->scaleddata.fw * dpr;
491 const short height = d->scaleddata.fh * dpr;
492 QString elemId = d->elementIdTable.at(num + 8); // tileface offset in our idtable;
493 // using raw pixmap size with cache id, as the rendering is done dpr-ignorant
494 const QString pixmapCacheName = d->pixmapCacheNameFromElementId(elemId, width, height);
495 if (!QPixmapCache::find(pixmapCacheName, &pm)) {
496 pm = d->renderElement(width, height, elemId);
497 pm.setDevicePixelRatio(dpr);
498 QPixmapCache::insert(pixmapCacheName, pm);
499 }
500 return pm;
501}
KConfigGroup group(const QString &group)
QString name() const
QString readEntry(const char *key, const char *aDefault=nullptr) const
void append(QList< T > &&value)
void reserve(qsizetype size)
qreal devicePixelRatio() const const
void setDevicePixelRatio(qreal scaleFactor)
bool find(const Key &key, QPixmap *pixmap)
Key insert(const QPixmap &pixmap)
int height() const const
int width() const const
QString locate(StandardLocation type, const QString &fileName, LocateOptions options)
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
bool isEmpty() const const
bool isValid() const const
void render(QPainter *painter)
transparent
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:34 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.