1 /***********************************************************************
2 *
3 * Copyright (C) 2007, 2008, 2009, 2012, 2014, 2015, 2019 Graeme Gott <graeme@gottcode.org>
4 *
5 * This program 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 3 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, see <http://www.gnu.org/licenses/>.
17 *
18 ***********************************************************************/
19
20 #include "theme.h"
21
22 #include <QApplication>
23 #include <QDir>
24 #include <QFileInfo>
25 #include <QIcon>
26 #include <QMainWindow>
27 #include <QPainter>
28 #include <QSet>
29 #include <QStandardPaths>
30 #include <QSvgRenderer>
31
32 namespace {
33 // ============================================================================
34
35 struct CornerType
36 {
37 unsigned char renderer;
38 unsigned char transform;
39 };
40 CornerType corners[15] = {
41 {4, 1},
42 {4, 2},
43 {3, 0},
44 {4, 3},
45 {2, 1},
46 {3, 1},
47 {1, 3},
48 {4, 0},
49 {3, 3},
50 {2, 0},
51 {1, 2},
52 {3, 2},
53 {1, 1},
54 {1, 0},
55 {0, 0}
56 };
57
58 // ============================================================================
59 }
60
61 // ============================================================================
62
Theme()63 Theme::Theme() :
64 m_unit(32),
65 m_ratio(1)
66 {
67 m_renderer = new QSvgRenderer;
68
69 // Load theme locations
70 #if defined(Q_OS_MAC)
71 m_locations = QStandardPaths::standardLocations(QStandardPaths::DataLocation);
72 #elif defined(Q_OS_UNIX)
73 m_locations = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
74 for (int i = 0; i < m_locations.size(); ++i) {
75 m_locations[i] += "/games/cutemaze";
76 }
77 m_locations.prepend(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
78 #elif defined(Q_OS_WIN)
79 m_locations = QStandardPaths::standardLocations(QStandardPaths::DataLocation);
80 m_locations.append(QCoreApplication::applicationDirPath() + "/Themes");
81 #endif
82 m_locations.append(":/games/cutemaze");
83 }
84
85 // ============================================================================
86
~Theme()87 Theme::~Theme()
88 {
89 delete m_renderer;
90 }
91
92 // ============================================================================
93
available() const94 QStringList Theme::available() const
95 {
96 QSet<QString> files;
97 QDir dir("", "*.svg");
98 for (const QString& path : m_locations) {
99 if (dir.cd(path)) {
100 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
101 const QStringList entries = dir.entryList();
102 files += QSet<QString>(entries.begin(), entries.end());
103 #else
104 files += dir.entryList(QDir::Files).toSet();
105 #endif
106 }
107 }
108
109 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
110 QStringList list(files.begin(), files.end());
111 #else
112 QStringList list = QStringList::fromSet(files);
113 #endif
114 int count = list.count();
115 for (int i = 0; i < count; ++i) {
116 QString& theme = list[i];
117 theme.remove(theme.length() - 4, 4);
118 }
119 list.sort();
120
121 return list;
122 }
123
124 // ============================================================================
125
load(const QString & name)126 void Theme::load(const QString& name)
127 {
128 QString theme = name;
129
130 QFileInfo info;
131 for (const QString& location : m_locations) {
132 info.setFile(location + '/' + name + ".svg");
133 if (info.exists()) {
134 theme = info.canonicalFilePath();
135 break;
136 }
137 }
138
139 m_renderer->load(theme);
140 scale(m_unit);
141 }
142
143 // ============================================================================
144
scale(int unit)145 void Theme::scale(int unit)
146 {
147 m_unit = unit;
148
149 QRect bounds(0, 0, unit * 2, unit * 2);
150 cache("flag", m_pixmap[Flag], bounds);
151 cache("start", m_pixmap[Start], bounds);
152 cache("target", m_pixmap[Target], bounds);
153 for (int i = 0; i < 4; ++i) {
154 cache("hint", m_pixmap_rotated[Hint][i], bounds, i * 90);
155 cache("marker", m_pixmap_rotated[Marker][i], bounds, i * 90);
156 cache("player", m_pixmap_rotated[Player][i], bounds, i * 90);
157 }
158
159 bounds.setSize(QSize(unit, unit));
160 for (int i = 0; i < 15; ++i) {
161 const CornerType& corner = corners[i];
162 cache(QString("corner%1").arg(corner.renderer), m_pixmap_corner[i], bounds, corner.transform * 90);
163 }
164
165 bounds.setSize(QSize(unit * 2, unit));
166 cache("wall", m_pixmap_wall[0], bounds);
167 cache("wall", m_pixmap_wall[1], bounds, 90);
168
169 bounds.setSize(QSize(unit * 3, unit * 3));
170 cache("background", m_pixmap[Background], bounds);
171 }
172
173 // ============================================================================
174
setDevicePixelRatio(int ratio)175 void Theme::setDevicePixelRatio(int ratio)
176 {
177 m_ratio = ratio;
178 scale(m_unit);
179 }
180
181 // ============================================================================
182
draw(QPainter & painter,int column,int row,enum Element element) const183 void Theme::draw(QPainter& painter, int column, int row, enum Element element) const
184 {
185 Q_ASSERT(element != TotalElements);
186 painter.drawPixmap(column * 3 * m_unit, row * 3 * m_unit, m_pixmap[element]);
187 }
188
189 // ============================================================================
190
draw(QPainter & painter,int column,int row,enum RotatedElement element,int angle) const191 void Theme::draw(QPainter& painter, int column, int row, enum RotatedElement element, int angle) const
192 {
193 Q_ASSERT(element != TotalRotatedElements);
194 Q_ASSERT(angle == 90 || angle == 180 || angle == 270 || angle == 360);
195 angle /= 90;
196 if (angle == 4) {
197 angle = 0;
198 }
199 painter.drawPixmap(column * 3 * m_unit, row * 3 * m_unit, m_pixmap_rotated[element][angle]);
200 }
201
202 // ============================================================================
203
drawBackground(QPainter & painter) const204 void Theme::drawBackground(QPainter& painter) const
205 {
206 painter.fillRect(0, 0, painter.device()->width(), painter.device()->height(), m_pixmap[Background]);
207 }
208
209 // ============================================================================
210
drawCorner(QPainter & painter,int column,int row,unsigned char walls) const211 void Theme::drawCorner(QPainter& painter, int column, int row, unsigned char walls) const
212 {
213 Q_ASSERT(walls > 0);
214 Q_ASSERT(walls < 16);
215 painter.drawPixmap((column * 3 * m_unit) - m_unit, (row * 3 * m_unit) - m_unit, m_pixmap_corner[walls - 1]);
216 }
217
218 // ============================================================================
219
drawWall(QPainter & painter,int column,int row,bool vertical) const220 void Theme::drawWall(QPainter& painter, int column, int row, bool vertical) const
221 {
222 if (vertical) {
223 painter.drawPixmap((column * 3 * m_unit) - m_unit, row * 3 * m_unit, m_pixmap_wall[1]);
224 } else {
225 painter.drawPixmap(column * 3 * m_unit, (row * 3 * m_unit) - m_unit, m_pixmap_wall[0]);
226 }
227 }
228
229 // ============================================================================
230
cache(const QString & element,QPixmap & pixmap,const QRect & bounds,int angle) const231 void Theme::cache(const QString& element, QPixmap& pixmap, const QRect& bounds, int angle) const
232 {
233 pixmap = QPixmap(bounds.size() * m_ratio);
234 pixmap.setDevicePixelRatio(m_ratio);
235 pixmap.fill(QColor(255, 255, 255, 0));
236 QPainter painter(&pixmap);
237 m_renderer->render(&painter, element, bounds);
238
239 // Handle rotated images
240 if (angle) {
241 Q_ASSERT(angle == 90 || angle == 180 || angle == 270);
242 painter.end();
243 pixmap = pixmap.transformed(QTransform().rotate(angle));
244 pixmap.setDevicePixelRatio(m_ratio);
245 }
246 }
247
248 // ============================================================================
249