1 /***********************************************************************
2  *
3  * Copyright (C) 2007, 2008, 2009, 2012, 2014, 2015 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 "settings.h"
21 
22 #include "theme.h"
23 
24 #include <QCheckBox>
25 #include <QComboBox>
26 #include <QDialogButtonBox>
27 #include <QDir>
28 #include <QFileDialog>
29 #include <QFormLayout>
30 #include <QHBoxLayout>
31 #include <QKeyEvent>
32 #include <QLabel>
33 #include <QMessageBox>
34 #include <QPainter>
35 #include <QPushButton>
36 #include <QSettings>
37 #include <QStandardPaths>
38 #include <QTabWidget>
39 #include <QVBoxLayout>
40 
41 namespace {
42 // ============================================================================
43 
homeDataPath()44 QString homeDataPath()
45 {
46 	return QStandardPaths::writableLocation(QStandardPaths::DataLocation);
47 }
48 
49 // ============================================================================
50 
51 // ControlButton class
52 // ============================================================================
53 
54 class ControlButton : public QPushButton
55 {
56 public:
57 	ControlButton(const QString& type, Qt::Key default_key_value, QWidget* parent = 0);
58 
59 	unsigned int key;
60 	unsigned int default_key;
61 
62 protected:
63 	virtual void hideEvent(QHideEvent* event);
64 	virtual void keyPressEvent(QKeyEvent* event);
65 	virtual void mousePressEvent(QMouseEvent* event);
66 };
67 
68 QList<ControlButton*> controls;
69 ControlButton* active_control = 0;
70 
71 // ============================================================================
72 
ControlButton(const QString & type,Qt::Key default_key_value,QWidget * parent)73 ControlButton::ControlButton(const QString& type, Qt::Key default_key_value, QWidget* parent)
74 :	QPushButton(parent),
75 	key(default_key_value),
76 	default_key(default_key_value)
77 {
78 	setObjectName("control_" + type);
79 	setText(QKeySequence(key).toString());
80 	setCheckable(true);
81 	setAutoDefault(false);
82 	setFocusPolicy(Qt::StrongFocus);
83 	installEventFilter(this);
84 }
85 
86 // ============================================================================
87 
hideEvent(QHideEvent * event)88 void ControlButton::hideEvent(QHideEvent* event)
89 {
90 	if (this == active_control) {
91 		active_control = 0;
92 		setChecked(false);
93 	}
94 	QPushButton::hideEvent(event);
95 }
96 
97 // ============================================================================
98 
keyPressEvent(QKeyEvent * event)99 void ControlButton::keyPressEvent(QKeyEvent* event)
100 {
101 	if ( (this == active_control) && (event->key() != Qt::Key_Escape) && (event->count() == 1) ) {
102 		key = event->key();
103 		QString control;
104 		switch (key) {
105 		case Qt::Key_Shift:
106 			control = tr("Shift");
107 			break;
108 #ifndef Q_OS_MAC
109 		case Qt::Key_Control:
110 			control = tr("Ctrl");
111 			break;
112 		case Qt::Key_Meta:
113 			control = tr("Meta");
114 			break;
115 		case Qt::Key_Alt:
116 			control = tr("Alt");
117 			break;
118 		case Qt::Key_Super_L:
119 		case Qt::Key_Super_R:
120 #ifndef Q_OS_WIN
121 			control = tr("Super");
122 #else
123 			control = tr("Windows");
124 #endif
125 			break;
126 #else
127 		case Qt::Key_Control:
128 			control = tr("Command");
129 			break;
130 		case Qt::Key_Meta:
131 			control = tr("Control");
132 			break;
133 		case Qt::Key_Alt:
134 			control = tr("Option");
135 			break;
136 #endif
137 		default:
138 			control = QKeySequence(key).toString(QKeySequence::NativeText);
139 			break;
140 		};
141 		for (ControlButton* button : controls) {
142 			if (button->text() == control) {
143 				button->setText("");
144 				button->key = 0;
145 			}
146 		}
147 		setText(control);
148 		setChecked(false);
149 		active_control = 0;
150 	} else {
151 		QPushButton::keyPressEvent(event);
152 	}
153 }
154 
155 // ============================================================================
156 
mousePressEvent(QMouseEvent * event)157 void ControlButton::mousePressEvent(QMouseEvent* event)
158 {
159 	if (this != active_control) {
160 		if (active_control) {
161 			active_control->setChecked(false);
162 		}
163 		active_control = this;
164 	} else {
165 		active_control = 0;
166 	}
167 	QPushButton::mousePressEvent(event);
168 }
169 
170 // ============================================================================
171 }
172 
173 // Settings class
174 // ============================================================================
175 
Settings(QWidget * parent)176 Settings::Settings(QWidget* parent)
177 :	QDialog(parent)
178 {
179 	setWindowTitle(tr("Settings"));
180 	m_theme = new Theme;
181 	m_theme->setDevicePixelRatio(devicePixelRatio());
182 
183 	QTabWidget* tabs = new QTabWidget(this);
184 	QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
185 	connect(buttons, &QDialogButtonBox::accepted, this, &Settings::accept);
186 	connect(buttons, &QDialogButtonBox::rejected, this, &Settings::reject);
187 
188 	QVBoxLayout* layout = new QVBoxLayout(this);
189 	layout->setContentsMargins(10, 10, 10, 10);
190 	layout->setSpacing(18);
191 	layout->addWidget(tabs);
192 	layout->addWidget(buttons);
193 
194 
195 	// Create Gameplay tab
196 	QWidget* gameplay_tab = new QWidget;
197 	tabs->addTab(gameplay_tab, tr("Game"));
198 
199 	m_gameplay_path = new QCheckBox(tr("Show where you've been"), gameplay_tab);
200 	m_gameplay_steps = new QCheckBox(tr("Show number of steps taken"), gameplay_tab);
201 	m_gameplay_time = new QCheckBox(tr("Show elapsed time"), gameplay_tab);
202 	m_gameplay_smooth = new QCheckBox(tr("Smooth movement"), gameplay_tab);
203 
204 	QFormLayout* gameplay_layout = new QFormLayout(gameplay_tab);
205 	gameplay_layout->addRow(m_gameplay_path);
206 	gameplay_layout->addRow(m_gameplay_steps);
207 	gameplay_layout->addRow(m_gameplay_time);
208 	gameplay_layout->addRow(m_gameplay_smooth);
209 
210 
211 	// Create Controls tab
212 	QWidget* controls_tab = new QWidget;
213 	tabs->addTab(controls_tab, tr("Controls"));
214 
215 	controls.append(new ControlButton("Up", Qt::Key_Up, this));
216 	controls.append(new ControlButton("Down", Qt::Key_Down, this));
217 	controls.append(new ControlButton("Left", Qt::Key_Left, this));
218 	controls.append(new ControlButton("Right", Qt::Key_Right, this));
219 	controls.append(new ControlButton("Flag", Qt::Key_Space, this));
220 	controls.append(new ControlButton("Hint", Qt::Key_H, this));
221 
222 	QFormLayout * controls_layout = new QFormLayout(controls_tab);
223 	controls_layout->addRow(tr("Move Up:"), controls[0]);
224 	controls_layout->addRow(tr("Move Down:"), controls[1]);
225 	controls_layout->addRow(tr("Move Left:"), controls[2]);
226 	controls_layout->addRow(tr("Move Right:"), controls[3]);
227 	controls_layout->addRow(tr("Toggle Flag:"), controls[4]);
228 	controls_layout->addRow(tr("Show Hint:"), controls[5]);
229 
230 
231 	// Create Themes tab
232 	QWidget* themes_tab = new QWidget;
233 	tabs->addTab(themes_tab, tr("Themes"));
234 
235 	m_themes_preview = new QLabel(themes_tab);
236 
237 	m_themes_selector = new QComboBox(themes_tab);
238 	connect(m_themes_selector, &QComboBox::currentTextChanged, this, &Settings::themeSelected);
239 
240 	QHBoxLayout* themes_selector_layout = new QHBoxLayout;
241 	themes_selector_layout->setContentsMargins(0, 0, 0, 0);
242 	themes_selector_layout->addWidget(m_themes_selector, 1);
243 
244 	QPushButton* add_button = new QPushButton(tr("Add"), themes_tab);
245 	connect(add_button, &QPushButton::clicked, this, &Settings::addTheme);
246 	m_themes_remove_button = new QPushButton(tr("Remove"), themes_tab);
247 	connect(m_themes_remove_button, &QPushButton::clicked, this, &Settings::removeTheme);
248 
249 	themes_selector_layout->addWidget(add_button, 0);
250 	themes_selector_layout->addWidget(m_themes_remove_button, 0);
251 
252 	QVBoxLayout* themes_layout = new QVBoxLayout(themes_tab);
253 	themes_layout->addWidget(m_themes_preview, 1, Qt::AlignCenter);
254 	themes_layout->addLayout(themes_selector_layout);
255 
256 
257 	// Load current settings
258 	loadSettings();
259 }
260 
261 // ============================================================================
262 
~Settings()263 Settings::~Settings()
264 {
265 	controls.clear();
266 	delete m_theme;
267 }
268 
269 // ============================================================================
270 
accept()271 void Settings::accept()
272 {
273 	QSettings settings;
274 
275 	// Write gameplay settings to disk
276 	settings.setValue("Show Path", m_gameplay_path->isChecked());
277 	settings.setValue("Show Steps", m_gameplay_steps->isChecked());
278 	settings.setValue("Show Time", m_gameplay_time->isChecked());
279 	settings.setValue("Smooth Movement", m_gameplay_smooth->isChecked());
280 
281 	// Write control button settings to disk
282 	for (ControlButton* button : controls) {
283 		settings.setValue("Controls/" + button->objectName().mid(8), button->key);
284 	}
285 
286 	// Write theme to disk
287 	settings.setValue("Theme", m_themes_selector->currentText());
288 
289 	emit settingsChanged();
290 
291 	QDialog::accept();
292 }
293 
294 // ============================================================================
295 
themeSelected(const QString & theme)296 void Settings::themeSelected(const QString& theme)
297 {
298 	if (!theme.isEmpty()) {
299 		m_theme->load(theme);
300 		generatePreview();
301 		m_themes_remove_button->setEnabled(QFileInfo(homeDataPath() + '/' + theme + ".svg").exists());
302 	}
303 }
304 
305 // ============================================================================
306 
addTheme()307 void Settings::addTheme()
308 {
309 	// Select theme file
310 	QString path = QFileDialog::getOpenFileName(this, tr("Select Theme File"), QDir::homePath(), QString("*.svg"));
311 	if (path.isEmpty()) {
312 		return;
313 	}
314 
315 	// Create data folder if necessary
316 	QString dirpath = homeDataPath();
317 	if (!QFileInfo(dirpath).exists()) {
318 		QDir dir = QDir::home();
319 		if (!dir.mkpath(dirpath)) {
320 			QMessageBox::warning(this, tr("Sorry"), tr("Unable to create data folder."));
321 			return;
322 		}
323 	}
324 
325 	// Copy theme file
326 	QFileInfo file(path);
327 	if (!QFile::copy(path, dirpath + '/' + file.fileName())) {
328 		QMessageBox::warning(this, tr("Sorry"), tr("Unable to copy theme file."));
329 		return;
330 	}
331 
332 	// Add theme to list
333 	QStringList themes = m_theme->available();
334 	int theme = themes.indexOf(m_themes_selector->currentText());
335 	if (theme == -1) {
336 		theme = themes.indexOf("Mouse");
337 	}
338 	m_themes_selector->clear();
339 	m_themes_selector->addItems(themes);
340 	m_themes_selector->setCurrentIndex(theme);
341 }
342 
343 // ============================================================================
344 
removeTheme()345 void Settings::removeTheme()
346 {
347 	// Find theme
348 	if (m_themes_selector->currentIndex() == -1) {
349 		return;
350 	}
351 	QString dirpath = homeDataPath();
352 	QString file = m_themes_selector->currentText() + ".svg";
353 	if (!QFileInfo(dirpath + '/' + file).exists()) {
354 		return;
355 	}
356 
357 	if (QMessageBox::question(this, tr("Question"), tr("Remove the selected theme?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) {
358 		// Delete theme file
359 		QDir dir(dirpath);
360 		if (dir.remove(file) == false) {
361 			QMessageBox::warning(this, tr("Sorry"), tr("Unable to remove the selected theme."));
362 			return;
363 		}
364 
365 		// Delete theme from list
366 		if (!m_theme->available().contains(m_themes_selector->currentText())) {
367 			m_themes_selector->removeItem(m_themes_selector->currentIndex());
368 		}
369 
370 		// Force change to next theme in list
371 		themeSelected(m_themes_selector->currentText());
372 		QSettings().setValue("Theme", m_themes_selector->currentText());
373 		emit settingsChanged();
374 	}
375 }
376 
377 // ============================================================================
378 
loadSettings()379 void Settings::loadSettings()
380 {
381 	QSettings settings;
382 
383 	// Read gameplay settings from disk
384 	m_gameplay_path->setChecked(settings.value("Show Path", true).toBool());
385 	m_gameplay_steps->setChecked(settings.value("Show Steps", true).toBool());
386 	m_gameplay_time->setChecked(settings.value("Show Time", true).toBool());
387 	m_gameplay_smooth->setChecked(settings.value("Smooth Movement", true).toBool());
388 
389 	// Read control button settings from disk
390 	for (ControlButton* button : controls) {
391 		button->key = settings.value("Controls/" + button->objectName().mid(8), button->default_key).toUInt();
392 		button->setText(QKeySequence(button->key).toString());
393 	}
394 
395 	// Read theme from disk
396 	QStringList themes = m_theme->available();
397 	int theme = themes.indexOf(settings.value("Theme", "Mouse").toString());
398 	if (theme == -1) {
399 		theme = themes.indexOf("Mouse");
400 	}
401 	m_themes_selector->clear();
402 	m_themes_selector->addItems(themes);
403 	m_themes_selector->setCurrentIndex(theme);
404 }
405 
406 // ============================================================================
407 
generatePreview()408 void Settings::generatePreview()
409 {
410 	int ratio = devicePixelRatio();
411 	QPixmap pixmap(QSize(192, 192) * ratio);
412 	pixmap.setDevicePixelRatio(ratio);
413 	pixmap.fill(Qt::white);
414 	{
415 		QPainter painter(&pixmap);
416 		painter.translate(-80, -80);
417 		for (int c = 0; c < 4; ++c) {
418 			for (int r = 0; r < 4; ++r) {
419 				m_theme->draw(painter, c, r, Theme::Background);
420 			}
421 		}
422 
423 		m_theme->drawWall(painter, 1, 1);
424 		m_theme->drawWall(painter, 2, 1);
425 		m_theme->drawWall(painter, 2, 2);
426 		m_theme->drawWall(painter, 1, 3);
427 		m_theme->drawWall(painter, 2, 1, true);
428 		m_theme->drawWall(painter, 1, 2, true);
429 		m_theme->drawWall(painter, 3, 2, true);
430 
431 		m_theme->drawCorner(painter, 1, 1, 10);
432 		m_theme->drawCorner(painter, 2, 1, 14);
433 		m_theme->drawCorner(painter, 3, 1, 10);
434 		m_theme->drawCorner(painter, 1, 2, 4);
435 		m_theme->drawCorner(painter, 2, 2, 3);
436 		m_theme->drawCorner(painter, 3, 2, 12);
437 		m_theme->drawCorner(painter, 1, 3, 3);
438 		m_theme->drawCorner(painter, 2, 3, 8);
439 		m_theme->drawCorner(painter, 3, 3, 5);
440 
441 		m_theme->draw(painter, 1, 1, Theme::Start);
442 		m_theme->draw(painter, 1, 1, Theme::Marker, 180);
443 		m_theme->draw(painter, 1, 2, Theme::Marker, 90);
444 		m_theme->draw(painter, 1, 2, Theme::Flag);
445 		m_theme->draw(painter, 2, 1, Theme::Target);
446 		m_theme->draw(painter, 2, 2, Theme::Player, 360);
447 	}
448 
449 	m_themes_preview->setPixmap(pixmap);
450 }
451 
452 // ============================================================================
453