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 | |
36 | class 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 | |
51 | class 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 | |
70 | KMahjonggTileset::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 | |
86 | KMahjonggTileset::~KMahjonggTileset() { |
87 | delete d; |
88 | } |
89 | |
90 | void 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 | |
101 | QSize 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 | |
125 | bool 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 | |
137 | QString KMahjonggTileset::authorProperty(const QString &key) const |
138 | { |
139 | return d->authorproperties[key]; |
140 | } |
141 | |
142 | short KMahjonggTileset::width() const |
143 | { |
144 | return d->scaleddata.w; |
145 | } |
146 | |
147 | short KMahjonggTileset::height() const |
148 | { |
149 | return d->scaleddata.h; |
150 | } |
151 | |
152 | short KMahjonggTileset::levelOffsetX() const |
153 | { |
154 | return d->scaleddata.lvloffx; |
155 | } |
156 | |
157 | short KMahjonggTileset::levelOffsetY() const |
158 | { |
159 | return d->scaleddata.lvloffy; |
160 | } |
161 | |
162 | short KMahjonggTileset::qWidth() const |
163 | { |
164 | return (short) (d->scaleddata.fw / 2.0); |
165 | } |
166 | |
167 | short KMahjonggTileset::qHeight() const |
168 | { |
169 | return (short) (d->scaleddata.fh / 2.0); |
170 | } |
171 | |
172 | QString KMahjonggTileset::path() const |
173 | { |
174 | return d->filename; |
175 | } |
176 | |
177 | #define kTilesetVersionFormat 1 |
178 | |
179 | // --------------------------------------------------------- |
180 | bool 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 | // --------------------------------------------------------- |
254 | bool 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 | // --------------------------------------------------------- |
277 | bool 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 | |
298 | void 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 | |
332 | QString 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 | |
336 | QPixmap 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 | |
348 | QPixmap 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 | |
359 | QPixmap 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 | |
370 | QPixmap 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 | |