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