1 /***********************************************************************
2  *
3  * Copyright (C) 2009, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 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 "window.h"
21 
22 #include "board.h"
23 #include "clock.h"
24 #include "definitions.h"
25 #include "locale_dialog.h"
26 #include "new_game_dialog.h"
27 #include "pattern.h"
28 #include "score_board.h"
29 #include "view.h"
30 
31 #include <QApplication>
32 #include <QGridLayout>
33 #include <QHBoxLayout>
34 #include <QInputDialog>
35 #include <QLabel>
36 #include <QMenu>
37 #include <QMenuBar>
38 #include <QMessageBox>
39 #include <QSettings>
40 
41 #include <algorithm>
42 #include <cmath>
43 
Window()44 Window::Window() {
45 	m_board = new Board(this);
46 	connect(m_board, &Board::finished, this, &Window::gameFinished);
47 	connect(m_board, &Board::started, this, &Window::gameStarted);
48 	connect(m_board, &Board::pauseChanged, this, &Window::gamePauseChanged);
49 
50 	QWidget* contents = new QWidget(this);
51 	setCentralWidget(contents);
52 
53 	View* view = new View(m_board, contents);
54 
55 	m_scores = new ScoreBoard(this);
56 
57 	m_definitions = new Definitions(m_board->words(), this);
58 	connect(m_board, &Board::wordAdded, m_definitions, &Definitions::addWord);
59 	connect(m_board, &Board::wordSolved, m_definitions, &Definitions::solveWord);
60 	connect(m_board, &Board::wordSelected, m_definitions, &Definitions::selectWord);
61 	connect(m_board, &Board::loading, m_definitions, &Definitions::clear);
62 
63 	// Create success message
64 	m_success = new QLabel(contents);
65 	m_success->setAttribute(Qt::WA_TransparentForMouseEvents);
66 
67 	QFont f = font();
68 	f.setPointSize(24);
69 	QFontMetrics metrics(f);
70 	int width = metrics.boundingRect(tr("Success")).width();
71 	int height = metrics.height();
72 	int ratio = devicePixelRatio();
73 	QPixmap pixmap(QSize(width + height, height * 2) * ratio);
74 	pixmap.setDevicePixelRatio(ratio);
75 	pixmap.fill(QColor(0, 0, 0, 0));
76 	{
77 		QPainter painter(&pixmap);
78 
79 		painter.setPen(Qt::NoPen);
80 		painter.setBrush(QColor(0, 0, 0, 200));
81 		painter.setRenderHint(QPainter::Antialiasing, true);
82 		painter.drawRoundedRect(0, 0, width + height, height * 2, 10, 10);
83 
84 		painter.setFont(f);
85 		painter.setPen(Qt::white);
86 		painter.setRenderHint(QPainter::TextAntialiasing, true);
87 		painter.drawText(height / 2, height / 2 + metrics.ascent(), tr("Success"));
88 	}
89 	m_success->setPixmap(pixmap);
90 	m_success->hide();
91 	connect(m_board, &Board::loading, m_success, &QLabel::hide);
92 
93 	// Create overlay background
94 	QLabel* overlay = new QLabel(this);
95 
96 	f = font();
97 	f.setPixelSize(20);
98 	metrics = QFontMetrics(f);
99 	width = std::max(metrics.boundingRect(tr("Loading")).width(), metrics.boundingRect(tr("Paused")).width());
100 	for (int i = 0; i < 10; ++i) {
101 		QString test(6, QChar(i + 48));
102 		test.insert(4, QLatin1Char(':'));
103 		test.insert(2, QLatin1Char(':'));
104 		width = std::max(width, metrics.boundingRect(test).width());
105 	}
106 	pixmap = QPixmap(QSize(width + 82, 32) * ratio);
107 	pixmap.setDevicePixelRatio(ratio);
108 	pixmap.fill(Qt::transparent);
109 	{
110 		QPainter painter(&pixmap);
111 
112 		painter.setPen(Qt::NoPen);
113 		painter.setBrush(QColor(0, 0, 0, 200));
114 		painter.setRenderHint(QPainter::Antialiasing, true);
115 		painter.drawRoundedRect(0, -32, width + 82, 64, 5, 5);
116 	}
117 	overlay->setPixmap(pixmap);
118 
119 	// Create overlay buttons
120 	m_definitions_button = new QLabel(overlay);
121 	m_definitions_button->setPixmap(QIcon(":/definitions.png").pixmap(24,24));
122 	m_definitions_button->setCursor(Qt::PointingHandCursor);
123 	m_definitions_button->setToolTip(tr("Definitions"));
124 	m_definitions_button->installEventFilter(this);
125 
126 	m_hint_button = new QLabel(overlay);
127 	m_hint_button->setPixmap(QIcon(":/hint.png").pixmap(24,24));
128 	m_hint_button->setCursor(Qt::PointingHandCursor);
129 	m_hint_button->setToolTip(tr("Hint"));
130 	m_hint_button->setDisabled(true);
131 	m_hint_button->installEventFilter(this);
132 	connect(m_board, &Board::hintAvailable, m_hint_button, &QLabel::setEnabled);
133 
134 	// Create clock
135 	m_clock = new Clock(overlay);
136 	m_clock->setDisabled(true);
137 	connect(m_clock, &Clock::togglePaused, m_board, &Board::togglePaused);
138 	connect(m_board, &Board::loading, m_clock, &Clock::setLoading);
139 
140 	QHBoxLayout* overlay_layout = new QHBoxLayout(overlay);
141 	overlay_layout->setContentsMargins(0, 0, 0, 0);
142 	overlay_layout->setSpacing(0);
143 	overlay_layout->addSpacing(10);
144 	overlay_layout->addWidget(m_definitions_button);
145 	overlay_layout->addStretch();
146 	overlay_layout->addWidget(m_clock, 0, Qt::AlignCenter);
147 	overlay_layout->addStretch();
148 	overlay_layout->addWidget(m_hint_button);
149 	overlay_layout->addSpacing(10);
150 
151 	// Lay out board
152 	QGridLayout* layout = new QGridLayout(contents);
153 	layout->setContentsMargins(0, 0, 0, 0);
154 	layout->setSpacing(0);
155 	layout->addWidget(view, 0, 0);
156 	layout->addWidget(m_success, 0, 0, Qt::AlignCenter);
157 	layout->addWidget(overlay, 0, 0, Qt::AlignHCenter | Qt::AlignTop);
158 
159 	// Create menus
160 	QMenu* menu = menuBar()->addMenu(tr("&Game"));
161 	menu->addAction(tr("&New"), this, SLOT(newGame()), QKeySequence::New);
162 	menu->addAction(tr("&Choose..."), this, SLOT(chooseGame()));
163 	menu->addSeparator();
164 	m_pause_action = menu->addAction(tr("&Pause"), m_board, SLOT(togglePaused()), tr("P"));
165 	m_pause_action->setDisabled(true);
166 	QAction* action = menu->addAction(tr("&Hint"), m_board, SLOT(showHint()), tr("H"));
167 	action->setDisabled(true);
168 	connect(m_board, &Board::hintAvailable, action, &QAction::setEnabled);
169 	menu->addAction(tr("D&efinitions"), m_definitions, SLOT(selectWord()), tr("D"));
170 	menu->addSeparator();
171 	menu->addAction(tr("&Details"), this, SLOT(showDetails()));
172 	menu->addAction(tr("&Scores"), m_scores, SLOT(exec()));
173 	menu->addSeparator();
174 	action = menu->addAction(tr("&Quit"), qApp, SLOT(quit()), QKeySequence::Quit);
175 	action->setMenuRole(QAction::QuitRole);
176 
177 	menu = menuBar()->addMenu(tr("&Settings"));
178 	menu->addAction(tr("Application &Language..."), this, SLOT(setLocale()));
179 
180 	menu = menuBar()->addMenu(tr("&Help"));
181 	action = menu->addAction(tr("&About"), this, SLOT(about()));
182 	action->setMenuRole(QAction::AboutRole);
183 	action = menu->addAction(tr("About &Qt"), qApp, SLOT(aboutQt()));
184 	action->setMenuRole(QAction::AboutQtRole);
185 
186 	// Restore window geometry
187 	QSettings settings;
188 	resize(800, 600);
189 	restoreGeometry(settings.value("Geometry").toByteArray());
190 
191 	// Continue previous or start new game
192 	show();
193 	if (settings.contains("Current/Words") && (settings.value("Current/Version" ).toInt() == 4)) {
194 		m_board->openGame();
195 	} else {
196 		settings.remove("Current");
197 		newGame();
198 	}
199 }
200 
201 //-----------------------------------------------------------------------------
202 
~Window()203 Window::~Window() {
204 	m_board->saveGame();
205 }
206 
207 //-----------------------------------------------------------------------------
208 
newGame()209 void Window::newGame() {
210 	NewGameDialog dialog(m_board, this);
211 	dialog.exec();
212 }
213 
214 //-----------------------------------------------------------------------------
215 
chooseGame()216 void Window::chooseGame() {
217 	bool ok = false;
218 	QString number = QInputDialog::getText(this, tr("Choose Game"), tr("Game Number:"), QLineEdit::Normal, QString(), &ok);
219 	if (ok && !number.isEmpty()) {
220 		number = number.trimmed();
221 		if (!m_board->openGame(number)) {
222 			QMessageBox::warning(this, tr("Sorry"), tr("Unable to start requested game."));
223 		}
224 	}
225 }
226 
227 //-----------------------------------------------------------------------------
228 
closeEvent(QCloseEvent * event)229 void Window::closeEvent(QCloseEvent* event) {
230 	m_board->saveGame();
231 	QSettings().setValue("Geometry", saveGeometry());
232 	QMainWindow::closeEvent(event);
233 }
234 
235 //-----------------------------------------------------------------------------
236 
eventFilter(QObject * object,QEvent * event)237 bool Window::eventFilter(QObject* object, QEvent* event) {
238 	if (event->type() == QEvent::MouseButtonPress) {
239 		if (object == m_definitions_button) {
240 			m_definitions->selectWord();
241 			return true;
242 		} else if (object == m_hint_button) {
243 			m_board->showHint();
244 			return true;
245 		}
246 	}
247 	return QMainWindow::eventFilter(object, event);
248 }
249 
250 //-----------------------------------------------------------------------------
251 
event(QEvent * event)252 bool Window::event(QEvent* event) {
253 	if (event->type() == QEvent::WindowBlocked || event->type() == QEvent::WindowDeactivate) {
254 		m_board->setPaused(true);
255 	}
256 	return QMainWindow::event(event);
257 }
258 
259 //-----------------------------------------------------------------------------
260 
about()261 void Window::about() {
262 	QMessageBox::about(this, tr("About"), QString("<p><center><big><b>%1 %2</b></big><br/>%3<br/><small>%4<br/>%5</small></center></p><p><center>%6</center></p>")
263 		.arg(tr("Connectagram"), QCoreApplication::applicationVersion(),
264 			tr("A word unscrambling game"),
265 			tr("Copyright &copy; 2009-%1 by Graeme Gott").arg("2020"),
266 			tr("Released under the <a href=\"http://www.gnu.org/licenses/gpl.html\">GPL 3</a> license"),
267 			tr("Definitions are from <a href=\"http://wiktionary.org/\">Wiktionary</a>"))
268 	);
269 }
270 
271 //-----------------------------------------------------------------------------
272 
showDetails()273 void Window::showDetails() {
274 	Pattern* pattern = m_board->pattern();
275 	if (!pattern) {
276 		return;
277 	}
278 	QString patternid = QSettings().value("Current/Pattern", 0).toString();
279 	static const QStringList sizes = QStringList() << NewGameDialog::tr("Low")
280 		<< NewGameDialog::tr("Medium")
281 		<< NewGameDialog::tr("High")
282 		<< NewGameDialog::tr("Very High");
283 	QString number = "4"
284 		+ m_board->words()->language()
285 		+ patternid
286 		+ QString::number(pattern->wordCount())
287 		+ QString("%1").arg(int(pattern->wordLength() - 4), 2, 16, QLatin1Char('0'))
288 		+ QString::number(pattern->seed(), 16);
289 	QMessageBox dialog(QMessageBox::Information,
290 		tr("Details"),
291 		QString("<p><b>%1</b> %2<br><b>%3</b> %4<br><b>%5</b> %6<br><b>%7</b> %8<br><b>%9</b> %10</p>")
292 			.arg(tr("Pattern:")).arg(pattern->name())
293 			.arg(NewGameDialog::tr("Language:")).arg(LocaleDialog::languageName(m_board->words()->language()))
294 			.arg(NewGameDialog::tr("Amount of Words:")).arg(sizes.value(pattern->wordCount()))
295 			.arg(NewGameDialog::tr("Word Length:")).arg(NewGameDialog::tr("%n letter(s)", "", pattern->wordLength() + 1))
296 			.arg(tr("Game Number:")).arg(number),
297 		QMessageBox::NoButton,
298 		this);
299 	dialog.setIconPixmap(QIcon(QString(":/patterns/%1.png").arg(patternid)).pixmap(96,96));
300 	dialog.exec();
301 }
302 
303 //-----------------------------------------------------------------------------
304 
setLocale()305 void Window::setLocale() {
306 	LocaleDialog dialog(this);
307 	dialog.exec();
308 }
309 
310 //-----------------------------------------------------------------------------
311 
gameStarted()312 void Window::gameStarted() {
313 	m_clock->setEnabled(true);
314 	m_clock->start();
315 	m_pause_action->setEnabled(true);
316 	m_success->hide();
317 }
318 
319 //-----------------------------------------------------------------------------
320 
gameFinished()321 void Window::gameFinished() {
322 	QSettings settings;
323 	int count = settings.value("Current/Count").toInt();
324 	int length = settings.value("Current/Length").toInt();
325 	int msecs = settings.value("Current/Time").toInt();
326 
327 	m_clock->stop();
328 	m_clock->setDisabled(true);
329 	m_pause_action->setDisabled(true);
330 	m_success->show();
331 
332 	m_scores->addScore(std::lround(msecs / 1000.0), count, length);
333 }
334 
335 //-----------------------------------------------------------------------------
336 
gamePauseChanged()337 void Window::gamePauseChanged() {
338 	bool paused = m_board->isPaused();
339 	m_pause_action->setText(paused ? tr("&Resume") : tr("&Pause"));
340 	m_clock->setPaused(paused);
341 }
342