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