1/*
2 Copyright (C) 1997 Mathias Mueller <in5y158@public.uni-hamburg.de>
3 Copyright (C) 2006 Mauricio Piacentini <mauricio@tabuleiro.com>
4
5 Libkmahjongg is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18*/
19
20#include "kmahjonggtileset.h"
21
22#include <klocale.h>
23#include <kconfig.h>
24#include <kconfiggroup.h>
25#include <qimage.h>
26#include <kstandarddirs.h>
27#include <qsvgrenderer.h>
28#include <QPainter>
29#include <QPixmapCache>
30#include <QFile>
31#include <KDebug>
32#include <QMap>
33
34#include <stdlib.h>
35
36class KMahjonggTilesetMetricsData
37{
38 public:
39 short lvloffx; // used for 3D indentation, x value
40 short lvloffy; // used for 3D indentation, y value
41 short w; // tile width ( +border +shadow)
42 short h; // tile height ( +border +shadow)
43 short fw; // face width
44 short fh; // face height
45
46 KMahjonggTilesetMetricsData()
47 : lvloffx(0), lvloffy(0), w(0), h(0), fw(0), fh(0)
48 {}
49};
50
51class KMahjonggTilesetPrivate
52{
53 public:
54 KMahjonggTilesetPrivate() : isSVG(false), graphicsLoaded(false) {}
55 QList<QString> elementIdTable;
56 QMap<QString, QString> authorproperties;
57
58 KMahjonggTilesetMetricsData originaldata;
59 KMahjonggTilesetMetricsData scaleddata;
60 QString filename; // cache the last file loaded to save reloading it
61 QString graphicspath;
62
63 QSvgRenderer svg;
64 bool isSVG;
65 bool graphicsLoaded;
66};
67
68// ---------------------------------------------------------
69
70KMahjonggTileset::KMahjonggTileset()
71 : d(new KMahjonggTilesetPrivate)
72{
73 buildElementIdTable();
74
75 static bool _inited = false;
76 if (_inited)
77 return;
78 KGlobal::dirs()->addResourceType("kmahjonggtileset", "data", QString::fromLatin1("kmahjongglib/tilesets/"));
79
80 KGlobal::locale()->insertCatalog( QLatin1String( "libkmahjongglib" ));
81 _inited = true;
82}
83
84// ---------------------------------------------------------
85
86KMahjonggTileset::~KMahjonggTileset() {
87 delete d;
88}
89
90void KMahjonggTileset::updateScaleInfo(short tilew, short tileh)
91{
92 d->scaleddata.w = tilew;
93 d->scaleddata.h = tileh;
94 double ratio = ((qreal) d->scaleddata.w) / ((qreal) d->originaldata.w);
95 d->scaleddata.lvloffx = (short) (d->originaldata.lvloffx * ratio);
96 d->scaleddata.lvloffy = (short) (d->originaldata.lvloffy * ratio);
97 d->scaleddata.fw = (short) (d->originaldata.fw * ratio);
98 d->scaleddata.fh = (short) (d->originaldata.fh * ratio);
99}
100
101QSize KMahjonggTileset::preferredTileSize(const QSize & boardsize, int horizontalCells, int verticalCells)
102{
103 //calculate our best tile size to fit the boardsize passed to us
104 qreal newtilew, newtileh, aspectratio;
105 qreal bw = boardsize.width();
106 qreal bh = boardsize.height();
107
108 //use tileface for calculation, with one complete tile in the sum for extra margin
109 qreal fullh = (d->originaldata.fh * verticalCells) + d->originaldata.h;
110 qreal fullw = (d->originaldata.fw * horizontalCells) + d->originaldata.w;
111 qreal floatw = d->originaldata.w;
112 qreal floath = d->originaldata.h;
113
114 if ((fullw/fullh)>(bw/bh)) {
115 //space will be left on height, use width as limit
116 aspectratio = bw/fullw;
117 } else {
118 aspectratio = bh/fullh;
119 }
120 newtilew = aspectratio * floatw;
121 newtileh = aspectratio * floath;
122 return QSize((short)newtilew, (short)newtileh);
123}
124
125bool KMahjonggTileset::loadDefault()
126{
127 QString idx = QLatin1String( "default.desktop" );
128
129 QString tilesetPath = KStandardDirs::locate("kmahjonggtileset", idx);
130 kDebug() << "Inside LoadDefault(), located path at" << tilesetPath;
131 if (tilesetPath.isEmpty()) {
132 return false;
133 }
134 return loadTileset(tilesetPath);
135}
136
137QString KMahjonggTileset::authorProperty(const QString &key) const
138{
139 return d->authorproperties[key];
140}
141
142short KMahjonggTileset::width() const
143{
144 return d->scaleddata.w;
145}
146
147short KMahjonggTileset::height() const
148{
149 return d->scaleddata.h;
150}
151
152short KMahjonggTileset::levelOffsetX() const
153{
154 return d->scaleddata.lvloffx;
155}
156
157short KMahjonggTileset::levelOffsetY() const
158{
159 return d->scaleddata.lvloffy;
160}
161
162short KMahjonggTileset::qWidth() const
163{
164 return (short) (d->scaleddata.fw / 2.0);
165}
166
167short KMahjonggTileset::qHeight() const
168{
169 return (short) (d->scaleddata.fh / 2.0);
170}
171
172QString KMahjonggTileset::path() const
173{
174 return d->filename;
175}
176
177#define kTilesetVersionFormat 1
178
179// ---------------------------------------------------------
180bool KMahjonggTileset::loadTileset( const QString & tilesetPath)
181{
182
183 QImage qiTiles;
184 kDebug() << "Attempting to load .desktop at" << tilesetPath;
185
186 //clear our properties map
187 d->authorproperties.clear();
188
189 // verify if it is a valid file first and if we can open it
190 QFile tilesetfile(tilesetPath);
191 if (!tilesetfile.open(QIODevice::ReadOnly)) {
192 return (false);
193 }
194 tilesetfile.close();
195
196 KConfig tileconfig(tilesetPath, KConfig::SimpleConfig);
197 KConfigGroup group = tileconfig.group("KMahjonggTileset");
198
199 d->authorproperties.insert(QLatin1String( "Name" ), group.readEntry("Name"));// Returns translated data
200 d->authorproperties.insert(QLatin1String( "Author" ), group.readEntry("Author"));
201 d->authorproperties.insert(QLatin1String( "Description" ), group.readEntry("Description"));
202 d->authorproperties.insert(QLatin1String( "AuthorEmail" ), group.readEntry("AuthorEmail"));
203
204 //Version control
205 int tileversion = group.readEntry("VersionFormat",0);
206 //Format is increased when we have incompatible changes, meaning that older clients are not able to use the remaining information safely
207 if (tileversion > kTilesetVersionFormat) {
208 return false;
209 }
210
211 QString graphName = group.readEntry("FileName");
212
213 d->graphicspath = KStandardDirs::locate("kmahjonggtileset", graphName);
214 kDebug() << "Using tileset at" << d->graphicspath;
215 //d->filename = graphicsPath;
216
217 //only SVG for now
218 d->isSVG = true;
219 if (d->graphicspath.isEmpty()) return (false);
220
221 d->originaldata.w = group.readEntry("TileWidth", 30);
222 d->originaldata.h = group.readEntry("TileHeight", 50);
223 d->originaldata.fw = group.readEntry("TileFaceWidth", 30);
224 d->originaldata.fh = group.readEntry("TileFaceHeight", 50);
225 d->originaldata.lvloffx = group.readEntry("LevelOffsetX", 10);
226 d->originaldata.lvloffy = group.readEntry("LevelOffsetY", 10);
227
228 //client application needs to call loadGraphics()
229 d->graphicsLoaded = false;
230 d->filename = tilesetPath;
231
232 /* if (d->isSVG) {
233 //really?
234 d->svg.load(graphicsPath);
235 if (d->svg.isValid()) {
236 d->filename = tilesetPath;
237 //invalidate our global cache
238 QPixmapCache::clear();
239
240 d->isSVG = true;
241 reloadTileset(QSize(d->originaldata.w, d->originaldata.h));
242 } else {
243 return( false );
244 }
245 } else {
246 //TODO add support for png??
247 return false;
248 }*/
249
250 return( true );
251}
252
253// ---------------------------------------------------------
254bool KMahjonggTileset::loadGraphics()
255{
256 if (d->graphicsLoaded == true) return (true) ;
257 if (d->isSVG) {
258 //really?
259 d->svg.load(d->graphicspath);
260 if (d->svg.isValid()) {
261 //invalidate our global cache
262 QPixmapCache::clear();
263 d->graphicsLoaded = true;
264 reloadTileset(QSize(d->originaldata.w, d->originaldata.h));
265 } else {
266 return( false );
267 }
268 } else {
269 //TODO add support for png??
270 return false;
271 }
272
273 return( true );
274}
275
276// ---------------------------------------------------------
277bool KMahjonggTileset::reloadTileset( const QSize & newTilesize)
278{
279 QString tilesetPath = d->filename;
280
281 if (QSize(d->scaleddata.w, d->scaleddata.h) == newTilesize) return false;
282
283 if (d->isSVG) {
284 if (d->svg.isValid()) {
285 updateScaleInfo(newTilesize.width(), newTilesize.height());
286 //rendering will be done when needed, automatically using the global cache
287 } else {
288 return( false );
289 }
290 } else {
291 //TODO add support for png???
292 return false;
293 }
294
295 return( true );
296}
297
298void KMahjonggTileset::buildElementIdTable() {
299 //Build a list for faster lookup of element ids, mapped to the enumeration used by GameData and BoardWidget
300 //Unselected tiles
301 for (short idx=1; idx<=4; idx++) {
302 d->elementIdTable.append(QString::fromLatin1( "TILE_%1").arg(idx));
303 }
304 //Selected tiles
305 for (short idx=1; idx<=4; idx++) {
306 d->elementIdTable.append(QString::fromLatin1( "TILE_%1_SEL").arg(idx));
307 }
308 //now faces
309 for (short idx=1; idx<=9; idx++) {
310 d->elementIdTable.append(QString::fromLatin1( "CHARACTER_%1").arg(idx));
311 }
312 for (short idx=1; idx<=9; idx++) {
313 d->elementIdTable.append(QString::fromLatin1( "BAMBOO_%1").arg(idx));
314 }
315 for (short idx=1; idx<=9; idx++) {
316 d->elementIdTable.append(QString::fromLatin1( "ROD_%1").arg(idx));
317 }
318 for (short idx=1; idx<=4; idx++) {
319 d->elementIdTable.append(QString::fromLatin1( "SEASON_%1").arg(idx));
320 }
321 for (short idx=1; idx<=4; idx++) {
322 d->elementIdTable.append(QString::fromLatin1( "WIND_%1").arg(idx));
323 }
324 for (short idx=1; idx<=3; idx++) {
325 d->elementIdTable.append(QString::fromLatin1( "DRAGON_%1").arg(idx));
326 }
327 for (short idx=1; idx<=4; idx++) {
328 d->elementIdTable.append(QString::fromLatin1( "FLOWER_%1").arg(idx));
329 }
330}
331
332QString KMahjonggTileset::pixmapCacheNameFromElementId(const QString & elementid) {
333 return authorProperty(QLatin1String( "Name" ))+ elementid + QString::fromLatin1( "W%1H%2").arg(d->scaleddata.w).arg(d->scaleddata.h);
334}
335
336QPixmap KMahjonggTileset::renderElement(short width, short height, const QString & elementid) {
337 //kDebug() << "render element" << elementid << width << height;
338 QImage qiRend(QSize(width, height),QImage::Format_ARGB32_Premultiplied);
339 qiRend.fill(0);
340
341 if (d->svg.isValid()) {
342 QPainter p(&qiRend);
343 d->svg.render(&p, elementid);
344 }
345 return QPixmap::fromImage(qiRend);
346}
347
348QPixmap KMahjonggTileset::selectedTile(int num) {
349 QPixmap pm;
350 QString elemId = d->elementIdTable.at(num+4);//selected offset in our idtable;
351 if (!QPixmapCache::find(pixmapCacheNameFromElementId(elemId), &pm)) {
352 //use tile size
353 pm = renderElement(d->scaleddata.w, d->scaleddata.h, elemId);
354 QPixmapCache::insert(pixmapCacheNameFromElementId(elemId), pm);
355 }
356 return pm;
357}
358
359QPixmap KMahjonggTileset::unselectedTile(int num) {
360 QPixmap pm;
361 QString elemId = d->elementIdTable.at(num);
362 if (!QPixmapCache::find(pixmapCacheNameFromElementId(elemId), &pm)) {
363 //use tile size
364 pm = renderElement(d->scaleddata.w, d->scaleddata.h, elemId);
365 QPixmapCache::insert(pixmapCacheNameFromElementId(elemId), pm);
366 }
367 return pm;
368}
369
370QPixmap KMahjonggTileset::tileface(int num) {
371 QPixmap pm;
372 if ((num + 8) >= d->elementIdTable.count()) {
373 kDebug() << "Client asked for invalid tileface id";
374 return pm;
375 }
376
377 QString elemId = d->elementIdTable.at(num + 8);//tileface offset in our idtable;
378 if (!QPixmapCache::find(pixmapCacheNameFromElementId(elemId), &pm)) {
379 //use face size
380 pm = renderElement(d->scaleddata.fw, d->scaleddata.fh, elemId);
381 QPixmapCache::insert(pixmapCacheNameFromElementId(elemId), pm);
382 }
383 return pm;
384}
385
386