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 © 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