1 /*
2 SPDX-FileCopyrightText: 2009-2021 Graeme Gott <graeme@gottcode.org>
3
4 SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7 #include "window.h"
8
9 #include "board.h"
10 #include "clock.h"
11 #include "gzip.h"
12 #include "language_dialog.h"
13 #include "locale_dialog.h"
14 #include "new_game_dialog.h"
15 #include "scores_dialog.h"
16
17 #include <QAction>
18 #include <QActionGroup>
19 #include <QApplication>
20 #include <QDialog>
21 #include <QDialogButtonBox>
22 #include <QDragEnterEvent>
23 #include <QDropEvent>
24 #include <QFile>
25 #include <QFileDialog>
26 #include <QLabel>
27 #include <QMenu>
28 #include <QMenuBar>
29 #include <QMessageBox>
30 #include <QMimeData>
31 #include <QSettings>
32 #include <QStackedWidget>
33 #include <QStandardPaths>
34 #include <QStyle>
35 #include <QTemporaryFile>
36 #include <QTextEdit>
37 #include <QTextStream>
38 #include <QVBoxLayout>
39
40 //-----------------------------------------------------------------------------
41
42 /**
43 * @brief The Window::State class controls which central widget is shown in the main window.
44 */
45 class Window::State
46 {
47 public:
48 /**
49 * Constructs a state instance.
50 * @param window the window to control
51 */
State(Window * window)52 explicit State(Window* window)
53 : m_window(window)
54 {
55 }
56
57 /**
58 * Destroys a state instance.
59 */
~State()60 virtual ~State() { }
61
62 /**
63 * Handle the state being entered.
64 */
enter()65 virtual void enter() { }
66
67 /**
68 * Handle request to switch to the new game state.
69 */
newGame()70 virtual void newGame()
71 {
72 setState("NewGame");
73 }
74
75 /**
76 * Handle request to switch to the open game state.
77 */
openGame()78 virtual void openGame()
79 {
80 setState("OpenGame");
81 }
82
83 /**
84 * Handle request to switch to the optimizing state.
85 */
optimizingStarted()86 virtual void optimizingStarted() { }
87
88 /**
89 * Handle request to switch back from the optimizing state.
90 */
optimizingFinished()91 virtual void optimizingFinished() { }
92
93 /**
94 * Handle request to switch to the play state.
95 */
play()96 virtual void play() { }
97
98 /**
99 * Handle request to switch to the automatically paused state.
100 */
autoPause()101 virtual void autoPause() { }
102
103 /**
104 * Handle request to switch back from the automatically paused state.
105 */
autoResume()106 virtual void autoResume() { }
107
108 /**
109 * Handle request to switch to the paused state.
110 */
pause()111 virtual void pause() { }
112
113 /**
114 * Handle request to switch back from the paused state.
115 */
resume()116 virtual void resume() { }
117
118 /**
119 * Handle request to switch to the finished state.
120 */
finish()121 virtual void finish()
122 {
123 setState("Finish");
124 }
125
126 protected:
127 /**
128 * Pause or resume the game.
129 * @param paused whether to pause or resume
130 */
setPaused(bool paused)131 void setPaused(bool paused)
132 {
133 m_window->m_board->setPaused(paused);
134 m_window->m_pause_action->setChecked(paused);
135 }
136
137 /**
138 * Set which widget is shown in the main window.
139 * @param index
140 */
setContentsIndex(int index)141 void setContentsIndex(int index)
142 {
143 m_window->m_contents->setCurrentIndex(index);
144 }
145
146 /**
147 * Sets the current state of the main window.
148 * @param state which state to switch to
149 */
setState(const QString & state)150 void setState(const QString& state)
151 {
152 m_window->m_previous_state = m_window->m_state;
153 m_window->m_state = m_window->m_states.value(state);
154 m_window->m_state->enter();
155 }
156
157 /**
158 * Restores the previous state of the main window.
159 */
setPreviousState()160 void setPreviousState()
161 {
162 m_window->m_state = m_window->m_previous_state;
163 m_window->m_state->enter();
164 m_window->m_previous_state = nullptr;
165 }
166
167 private:
168 Window* m_window; /**< window to control */
169 };
170
171 //-----------------------------------------------------------------------------
172
173 /**
174 * The Window::NewGameState class is a temporary state that shows a load screen while generating a
175 * board before switching to the Play state. It will also show the automatic pause screen and the
176 * optimizing screen if necessary.
177 */
178 class Window::NewGameState : public Window::State
179 {
180 public:
NewGameState(Window * window)181 explicit NewGameState(Window* window)
182 : State(window)
183 {
184 }
185
enter()186 void enter() override
187 {
188 m_next_state = "Play";
189 setPaused(true);
190 setContentsIndex(4);
191 }
192
optimizingStarted()193 void optimizingStarted() override
194 {
195 setState("Optimizing");
196 }
197
play()198 void play() override
199 {
200 setState(m_next_state);
201 }
202
autoPause()203 void autoPause() override
204 {
205 m_next_state = "AutoPause";
206 }
207
autoResume()208 void autoResume() override
209 {
210 m_next_state = "Play";
211 }
212
213 private:
214 QString m_next_state; /**< state to enter once the game has been started */
215 };
216
217 //-----------------------------------------------------------------------------
218
219 /**
220 * The Window::OpenGameState class is a temporary state that shows a load screen while opening a
221 * board before switching to the Play state. It will also show the automatic pause screen and the
222 * optimizing screen if necessary.
223 */
224 class Window::OpenGameState : public Window::State
225 {
226 public:
OpenGameState(Window * window)227 explicit OpenGameState(Window* window)
228 : State(window)
229 {
230 }
231
enter()232 void enter() override
233 {
234 m_next_state = "Play";
235 setPaused(true);
236 setContentsIndex(2);
237 }
238
optimizingStarted()239 void optimizingStarted() override
240 {
241 setState("Optimizing");
242 }
243
play()244 void play() override
245 {
246 setState(m_next_state);
247 }
248
autoPause()249 void autoPause() override
250 {
251 m_next_state = "AutoPause";
252 }
253
autoResume()254 void autoResume() override
255 {
256 m_next_state = "Play";
257 }
258
259 private:
260 QString m_next_state; /**< state to enter once the game has been loaded */
261 };
262
263 //-----------------------------------------------------------------------------
264
265 /**
266 * The Window::OptimizingState class shows the optimizing screen while the word list is being
267 * optimized and then switches back to the previous state.
268 */
269 class Window::OptimizingState : public Window::State
270 {
271 public:
OptimizingState(Window * window)272 explicit OptimizingState(Window* window)
273 : State(window)
274 {
275 }
276
enter()277 void enter() override
278 {
279 setContentsIndex(5);
280 }
281
optimizingFinished()282 void optimizingFinished() override
283 {
284 setPreviousState();
285 }
286 };
287
288 //-----------------------------------------------------------------------------
289
290 /**
291 * The Window::PlayState class shows the play area. It will switch to the automatic pause or regular
292 * pause screen if needed.
293 */
294 class Window::PlayState : public Window::State
295 {
296 public:
PlayState(Window * window)297 explicit PlayState(Window* window)
298 : State(window)
299 {
300 }
301
enter()302 void enter() override
303 {
304 setPaused(false);
305 setContentsIndex(0);
306 }
307
autoPause()308 void autoPause() override
309 {
310 setState("AutoPause");
311 }
312
pause()313 void pause() override
314 {
315 setState("Pause");
316 }
317 };
318
319 //-----------------------------------------------------------------------------
320
321 /**
322 * The Window::AutoPauseState class will show the pause screen until it its reference count of times
323 * called is reduced to 0, at which point it will show the previous state. If any state is requested,
324 * it will switch to that instead.
325 *
326 * This state is used to temporarily pause the game while a menu is shown.
327 */
328 class Window::AutoPauseState : public Window::State
329 {
330 public:
AutoPauseState(Window * window)331 explicit AutoPauseState(Window* window)
332 : State(window)
333 , m_count(0)
334 {
335 }
336
enter()337 void enter() override
338 {
339 setPaused(true);
340 setContentsIndex(1);
341 m_count++;
342 }
343
autoPause()344 void autoPause() override
345 {
346 m_count++;
347 }
348
autoResume()349 void autoResume() override
350 {
351 m_count--;
352 if (m_count < 1) {
353 m_count = 0;
354 setState("Play");
355 }
356 }
357
newGame()358 void newGame() override
359 {
360 m_count = 0;
361 setState("NewGame");
362 }
363
openGame()364 void openGame() override
365 {
366 m_count = 0;
367 setState("OpenGame");
368 }
369
pause()370 void pause() override
371 {
372 m_count = 0;
373 setState("Pause");
374 }
375
resume()376 void resume() override
377 {
378 m_count = 0;
379 setState("Play");
380 }
381
finish()382 void finish() override
383 {
384 m_count = 0;
385 setState("Finish");
386 }
387
388 private:
389 int m_count; /**< reference count of how many times automatic pause has been entered */
390 };
391
392 //-----------------------------------------------------------------------------
393
394 /**
395 * The Window::PauseState class shows the pause screen. It will switch to the play state when
396 * hidden. The base class handles switching to the new or open states when requested.
397 */
398 class Window::PauseState : public Window::State
399 {
400 public:
PauseState(Window * window)401 explicit PauseState(Window* window)
402 : State(window)
403 {
404 }
405
enter()406 void enter() override
407 {
408 setPaused(true);
409 setContentsIndex(1);
410 }
411
resume()412 void resume() override
413 {
414 setState("Play");
415 }
416 };
417
418 //-----------------------------------------------------------------------------
419
420 /**
421 * The Window::FinishState class shows the main play area and does not switch to any other state
422 * like pause or automatic pause, because the game is already over. The base class handles switching
423 * to the new or open states when requested.
424 */
425 class Window::FinishState : public Window::State
426 {
427 public:
FinishState(Window * window)428 explicit FinishState(Window* window)
429 : State(window)
430 {
431 }
432
enter()433 void enter() override
434 {
435 setPaused(false);
436 setContentsIndex(0);
437 }
438 };
439
440 //-----------------------------------------------------------------------------
441
Window(const QString & file)442 Window::Window(const QString& file)
443 : m_pause_action(nullptr)
444 , m_previous_state(nullptr)
445 {
446 setAcceptDrops(true);
447
448 // Create states
449 m_states.insert("NewGame", new NewGameState(this));
450 m_states.insert("OpenGame", new OpenGameState(this));
451 m_states.insert("Optimizing", new OptimizingState(this));
452 m_states.insert("Play", new PlayState(this));
453 m_states.insert("AutoPause", new AutoPauseState(this));
454 m_states.insert("Pause", new PauseState(this));
455 m_states.insert("Finish", new FinishState(this));
456 m_state = m_states.value("NewGame");
457
458 // Create widgets
459 m_contents = new QStackedWidget(this);
460 setCentralWidget(m_contents);
461
462 m_board = new Board(this);
463 m_contents->addWidget(m_board);
464 connect(m_board, &Board::started, this, &Window::gameStarted);
465 connect(m_board, &Board::finished, this, &Window::gameFinished);
466 connect(m_board, &Board::optimizingStarted, this, &Window::optimizingStarted);
467 connect(m_board, &Board::optimizingFinished, this, &Window::optimizingFinished);
468
469 // Create pause screen
470 m_pause_screen = new QLabel(tr("<p><b><big>Paused</big></b><br>Click to resume playing.</p>"), this);
471 m_pause_screen->setAlignment(Qt::AlignCenter);
472 m_pause_screen->installEventFilter(this);
473 m_contents->addWidget(m_pause_screen);
474
475 // Create open game screen
476 QLabel* open_game_screen = new QLabel(tr("<p><b><big>Please wait</big></b><br>Loading game...</p>"), this);
477 open_game_screen->setAlignment(Qt::AlignCenter);
478 m_contents->addWidget(open_game_screen);
479
480 // Create start screen
481 QLabel* start_screen = new QLabel(tr("Click to start a new game."), this);
482 start_screen->setAlignment(Qt::AlignCenter);
483 start_screen->installEventFilter(this);
484 m_contents->addWidget(start_screen);
485
486 // Create new game screen
487 QLabel* new_game_screen = new QLabel(tr("<p><b><big>Please wait</big></b><br>Generating a new board...</p>"), this);
488 new_game_screen->setAlignment(Qt::AlignCenter);
489 m_contents->addWidget(new_game_screen);
490
491 // Create optimizing screen
492 QLabel* optimizing_screen = new QLabel(tr("<p><b><big>Please wait</big></b><br>Optimizing word list...</p>"), this);
493 optimizing_screen->setAlignment(Qt::AlignCenter);
494 m_contents->addWidget(optimizing_screen);
495
496 // Create game menu
497 QMenu* menu = menuBar()->addMenu(tr("&Game"));
498 menu->addAction(tr("New &Game..."), this, SLOT(newGame()), tr("Ctrl+Shift+N"));
499 menu->addAction(tr("&New Roll"), this, SLOT(newRoll()), QKeySequence::New);
500 menu->addAction(tr("&Choose..."), this, SLOT(chooseGame()), QKeySequence::Open);
501 menu->addAction(tr("&Share..."), this, SLOT(shareGame()), QKeySequence::Save);
502 menu->addSeparator();
503 QAction* end_action = menu->addAction(tr("&End"), this, SLOT(endGame()));
504 end_action->setEnabled(false);
505 connect(m_board, &Board::pauseAvailable, end_action, &QAction::setEnabled);
506 m_pause_action = menu->addAction(tr("&Pause"));
507 m_pause_action->setCheckable(true);
508 m_pause_action->setShortcut(tr("Ctrl+P"));
509 m_pause_action->setEnabled(false);
510 connect(m_pause_action, &QAction::triggered, this, &Window::setPaused);
511 connect(m_board, &Board::pauseAvailable, m_pause_action, &QAction::setEnabled);
512 menu->addSeparator();
513 m_details_action = menu->addAction(tr("&Details"), this, SLOT(showDetails()));
514 m_details_action->setEnabled(false);
515 menu->addAction(tr("&High Scores"), this, SLOT(showScores()), tr("Ctrl+H"));
516 menu->addSeparator();
517 QAction* action = menu->addAction(tr("&Quit"), this, SLOT(close()), tr("Ctrl+Q"));
518 action->setMenuRole(QAction::QuitRole);
519 monitorVisibility(menu);
520
521 // Create settings menu
522 menu = menuBar()->addMenu(tr("&Settings"));
523 QMenu* submenu = menu->addMenu(tr("Show &Maximum Score"));
524 QAction* score_actions[3];
525 score_actions[0] = submenu->addAction(tr("&Never"));
526 score_actions[1] = submenu->addAction(tr("&End Of Game"));
527 score_actions[2] = submenu->addAction(tr("&Always"));
528 QActionGroup* group = new QActionGroup(this);
529 for (int i = 0; i < 3; ++i) {
530 score_actions[i]->setData(i);
531 score_actions[i]->setCheckable(true);
532 group->addAction(score_actions[i]);
533 }
534 connect(group, &QActionGroup::triggered, m_board, &Board::setShowMaximumScore);
535 QAction* missed_action = menu->addAction(tr("Show Missed &Words"));
536 missed_action->setCheckable(true);
537 connect(missed_action, &QAction::toggled, m_board, &Board::setShowMissedWords);
538 QAction* counts_action = menu->addAction(tr("Show Word &Counts"));
539 counts_action->setCheckable(true);
540 counts_action->setChecked(true);
541 connect(counts_action, &QAction::toggled, m_board, &Board::setShowWordCounts);
542 menu->addAction(tr("&Board Language..."), this, SLOT(showLanguage()));
543 menu->addSeparator();
544 menu->addAction(tr("Application &Language..."), this, SLOT(showLocale()));
545 monitorVisibility(menu);
546
547 // Create help menu
548 menu = menuBar()->addMenu(tr("&Help"));
549 menu->addAction(tr("&Controls"), this, SLOT(showControls()));
550 menu->addSeparator();
551 action = menu->addAction(tr("&About"), this, SLOT(about()));
552 action->setMenuRole(QAction::AboutRole);
553 action = menu->addAction(tr("About &Qt"), qApp, SLOT(aboutQt()));
554 action->setMenuRole(QAction::AboutQtRole);
555 monitorVisibility(menu);
556
557 // Load settings
558 QSettings settings;
559 QAction* score_action = score_actions[qBound(0, settings.value("ShowMaximumScore", 1).toInt(), 2)];
560 score_action->setChecked(true);
561 m_board->setShowMaximumScore(score_action);
562 missed_action->setChecked(settings.value("ShowMissed", true).toBool());
563 counts_action->setChecked(settings.value("ShowWordCounts", true).toBool());
564 restoreGeometry(settings.value("Geometry").toByteArray());
565
566 // Start game
567 QString current = file;
568 if (settings.contains("Current/Version")) {
569 if (current.isEmpty() ||
570 QMessageBox::question(this, tr("Question"), tr("End the current game?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) {
571 current = ":saved:";
572 }
573 }
574
575 m_state->finish();
576 m_contents->setCurrentIndex(3);
577 if (current.isEmpty()) {
578 newGame();
579 } else {
580 startGame(current);
581 }
582 }
583
584 //-----------------------------------------------------------------------------
585
eventFilter(QObject * watched,QEvent * event)586 bool Window::eventFilter(QObject* watched, QEvent* event)
587 {
588 if (watched == m_pause_screen) {
589 if (event->type() == QEvent::MouseButtonPress) {
590 m_state->resume();
591 return true;
592 } else {
593 return false;
594 }
595 } else if (watched == m_contents->widget(3)) {
596 if (event->type() == QEvent::MouseButtonPress) {
597 newGame();
598 return true;
599 } else {
600 return false;
601 }
602 } else {
603 return QMainWindow::eventFilter(watched, event);
604 }
605 }
606
607 //-----------------------------------------------------------------------------
608
closeEvent(QCloseEvent * event)609 void Window::closeEvent(QCloseEvent* event)
610 {
611 QSettings().setValue("Geometry", saveGeometry());
612 QMainWindow::closeEvent(event);
613 }
614
615 //-----------------------------------------------------------------------------
616
dragEnterEvent(QDragEnterEvent * event)617 void Window::dragEnterEvent(QDragEnterEvent* event)
618 {
619 if (!(event->possibleActions() & Qt::CopyAction)) {
620 return;
621 }
622
623 // Handle dragged file
624 if (event->mimeData()->hasUrls()) {
625 const QList<QUrl> urls = event->mimeData()->urls();
626 if (urls.size() != 1) {
627 return;
628 }
629 const QUrl url = urls.first();
630 if (!url.isLocalFile()) {
631 return;
632 }
633 const QString filename = url.toLocalFile();
634 if (!filename.endsWith(".tanglet")) {
635 return;
636 }
637 // Handle dragged game data
638 } else if (!event->mimeData()->hasFormat("application/x-tanglet")) {
639 return;
640 }
641
642 event->setDropAction(Qt::CopyAction);
643 event->acceptProposedAction();
644 }
645
646 //-----------------------------------------------------------------------------
647
dropEvent(QDropEvent * event)648 void Window::dropEvent(QDropEvent* event)
649 {
650 if (!(event->possibleActions() & Qt::CopyAction)) {
651 return;
652 }
653
654 // Handle dropped file
655 if (event->mimeData()->hasUrls()) {
656 const QList<QUrl> urls = event->mimeData()->urls();
657 if (urls.size() != 1) {
658 return;
659 }
660 const QUrl url = urls.first();
661 if (!url.isLocalFile()) {
662 return;
663 }
664 const QString filename = url.toLocalFile();
665 if (!filename.endsWith(".tanglet")) {
666 return;
667 }
668 if (endGame()) {
669 startGame(filename);
670 }
671 // Handle dropped game data
672 } else if (event->mimeData()->hasFormat("application/x-tanglet")) {
673 QTemporaryFile file;
674 if (file.open()) {
675 file.write(event->mimeData()->data("application/x-tanglet"));
676 }
677 if (endGame()) {
678 startGame(file.fileName());
679 }
680 }
681
682 event->setDropAction(Qt::CopyAction);
683 event->acceptProposedAction();
684 }
685
686 //-----------------------------------------------------------------------------
687
event(QEvent * event)688 bool Window::event(QEvent* event)
689 {
690 if (m_pause_action && m_pause_action->isEnabled()) {
691 switch (event->type()) {
692 case QEvent::WindowDeactivate:
693 if (!QApplication::activeWindow() && !QApplication::activePopupWidget() && !QApplication::activeModalWidget()) {
694 m_state->pause();
695 }
696 break;
697 case QEvent::WindowBlocked:
698 m_state->autoPause();
699 break;
700 case QEvent::WindowUnblocked:
701 m_state->autoResume();
702 break;
703 default:
704 break;
705 }
706 }
707 return QMainWindow::event(event);
708 }
709
710 //-----------------------------------------------------------------------------
711
about()712 void Window::about()
713 {
714 QMessageBox::about(this, tr("About"),
715 QString("<center><p><big><b>%1</b></big><br/>%2<br/><small>%3<br/>%4</small></p><p>%5</p><p>%6</p></center>")
716 .arg(tr("Tanglet %1").arg(QCoreApplication::applicationVersion()),
717 tr("A single player variant of <a href=\"http://en.wikipedia.org/wiki/Boggle\">Boggle</a>"),
718 tr("Copyright © 2009-%1 Graeme Gott").arg("2021"),
719 tr("Released under the <a href=\"http://www.gnu.org/licenses/gpl.html\">GPL 3</a> license"),
720 tr("English word list is based on <a href=\"http://wordlist.sourceforge.net/\">SCOWL</a> by Kevin Atkinson"),
721 tr("Hebrew word list is based on <a href=\"http://hspell.ivrix.org.il/\">Hspell</a> by Nadav Har'El and Dan Kenigsberg"))
722 );
723 }
724
725 //-----------------------------------------------------------------------------
726
newRoll()727 void Window::newRoll()
728 {
729 if (endGame()) {
730 startGame();
731 }
732 }
733
734 //-----------------------------------------------------------------------------
735
newGame()736 void Window::newGame()
737 {
738 if (endGame()) {
739 NewGameDialog dialog(this);
740 if (dialog.exec() == QDialog::Accepted) {
741 startGame();
742 }
743 }
744 }
745
746
747 //-----------------------------------------------------------------------------
748
chooseGame()749 void Window::chooseGame()
750 {
751 if (!endGame()) {
752 return;
753 }
754
755 QString filename = QFileDialog::getOpenFileName(window(),
756 tr("Import Game"),
757 QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
758 tr("Tanglet Games (*.tanglet)"));
759 if (filename.isEmpty()) {
760 return;
761 }
762
763 startGame(filename);
764 }
765
766 //-----------------------------------------------------------------------------
767
shareGame()768 void Window::shareGame()
769 {
770 QString filename = QFileDialog::getSaveFileName(window(),
771 tr("Export Game"),
772 QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation),
773 tr("Tanglet Games (*.tanglet)"));
774
775 if (filename.isEmpty()) {
776 return;
777 }
778
779 if (!filename.endsWith(".tanglet")) {
780 filename += ".tanglet";
781 }
782
783 // Share game
784 {
785 QSettings settings;
786 settings.beginGroup("Current");
787
788 QSettings game(filename, QSettings::IniFormat);
789 game.beginGroup("Game");
790
791 game.setValue("Version", settings.value("Version"));
792 game.setValue("Size", settings.value("Size"));
793 game.setValue("Density", settings.value("Density"));
794 game.setValue("Minimum", settings.value("Minimum"));
795 game.setValue("TimerMode", settings.value("TimerMode"));
796 game.setValue("Letters", settings.value("Letters"));
797 game.setValue("Locale", settings.value("Locale"));
798 game.setValue("Dictionary", settings.value("Dictionary"));
799
800 const QString dice = settings.value("Dice").toString();
801 if (dice.startsWith("tanglet:")) {
802 game.setValue("Dice", dice);
803 } else {
804 QFile file(dice);
805 if (file.open(QFile::ReadOnly)) {
806 game.setValue("Dice", file.readAll().toBase64());
807 }
808 }
809
810 const QString words = settings.value("Words").toString();
811 if (words.startsWith("tanglet:")) {
812 game.setValue("Words", words);
813 } else {
814 QFile file(words);
815 if (file.open(QFile::ReadOnly)) {
816 game.setValue("Words", file.readAll().toBase64());
817 }
818 }
819 }
820 gzip(filename);
821 }
822
823 //-----------------------------------------------------------------------------
824
endGame()825 bool Window::endGame()
826 {
827 if (!m_board->isFinished()) {
828 if (QMessageBox::question(this, tr("Question"), tr("End the current game?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) {
829 m_board->abort();
830 } else {
831 return false;
832 }
833 }
834 return true;
835 }
836
837 //-----------------------------------------------------------------------------
838
autoPause()839 void Window::autoPause()
840 {
841 m_state->autoPause();
842 }
843
844 //-----------------------------------------------------------------------------
845
autoResume()846 void Window::autoResume()
847 {
848 m_state->autoResume();
849 }
850
851 //-----------------------------------------------------------------------------
852
setPaused(bool paused)853 void Window::setPaused(bool paused)
854 {
855 if (paused) {
856 m_state->pause();
857 } else {
858 m_state->resume();
859 }
860 }
861
862 //-----------------------------------------------------------------------------
863
showDetails()864 void Window::showDetails()
865 {
866 QSettings settings;
867 int size = settings.value("Current/Size").toInt();
868 int density = settings.value("Current/Density").toInt();
869 int minimum = settings.value("Current/Minimum").toInt();
870 int timer = settings.value("Current/TimerMode").toInt();
871 QMessageBox::information(this, tr("Details"),
872 QString("<p><b>%1</b> %2<br>"
873 "<b>%3</b> %4<br>"
874 "<b>%5</b> %6<br>"
875 "<b>%7</b> %8<br>"
876 "<b>%9</b> %10</p>")
877 .arg(tr("Board Size:"), (size == 4) ? tr("Normal") : tr("Large"))
878 .arg(tr("Word Density:"), NewGameDialog::densityString(density))
879 .arg(tr("Word Length:"), tr("%1 or more letters").arg(minimum))
880 .arg(tr("Game Type:"), Clock::timerToString(timer))
881 .arg(tr("Description:"), Clock::timerDescription(timer)));
882 }
883
884 //-----------------------------------------------------------------------------
885
showScores()886 void Window::showScores()
887 {
888 ScoresDialog scores(this);
889 scores.exec();
890 }
891
892 //-----------------------------------------------------------------------------
893
showLanguage()894 void Window::showLanguage()
895 {
896 LanguageDialog dialog(this);
897 if (dialog.exec() == QDialog::Accepted) {
898 newGame();
899 }
900 }
901
902 //-----------------------------------------------------------------------------
903
showLocale()904 void Window::showLocale()
905 {
906 LocaleDialog dialog(this);
907 dialog.exec();
908 }
909
910 //-----------------------------------------------------------------------------
911
showControls()912 void Window::showControls()
913 {
914 QMessageBox::information(this, tr("Controls"), tr(
915 "<p><b><big>Mouse Play:</big></b><br>"
916 "<b>Select a word:</b> Click on the letters of a word.<br>"
917 "<b>Make a guess:</b> Click on the last selected letter.<br>"
918 "<b>Erase letters:</b> Click on an earlier selected letter.<br>"
919 "<b>Clear the word:</b> Click twice on the first selected letter.</p>"
920 "<p><b><big>Keyboard Play:</big></b><br>"
921 "<b>Select a word:</b> Type the letters of a word.<br>"
922 "<b>Make a guess:</b> Press Enter.<br>"
923 "<b>Clear the word:</b> Press Ctrl+Backspace.</p>"
924 ));
925 }
926
927 //-----------------------------------------------------------------------------
928
optimizingStarted()929 void Window::optimizingStarted()
930 {
931 m_state->optimizingStarted();
932 }
933
934 //-----------------------------------------------------------------------------
935
optimizingFinished()936 void Window::optimizingFinished()
937 {
938 m_state->optimizingFinished();
939 }
940
941 //-----------------------------------------------------------------------------
942
gameStarted()943 void Window::gameStarted()
944 {
945 m_state->play();
946 m_details_action->setEnabled(true);
947 }
948
949 //-----------------------------------------------------------------------------
950
gameFinished(int score,int max_score)951 void Window::gameFinished(int score, int max_score)
952 {
953 m_state->finish();
954 ScoresDialog scores(this);
955 if (scores.addScore(score, max_score)) {
956 scores.exec();
957 }
958
959 QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
960 dir.remove("current");
961 dir.remove("current-dice");
962 dir.remove("current-words");
963 }
964
965 //-----------------------------------------------------------------------------
966
monitorVisibility(QMenu * menu)967 void Window::monitorVisibility(QMenu* menu)
968 {
969 #ifndef Q_OS_MAC
970 connect(menu, &QMenu::aboutToShow, this, &Window::autoPause);
971 connect(menu, &QMenu::aboutToHide, this, &Window::autoResume);
972 #endif
973 }
974
975 //-----------------------------------------------------------------------------
976
startGame(const QString & filename)977 void Window::startGame(const QString& filename)
978 {
979 QSettings settings;
980
981 if (filename.isEmpty()) {
982 // Start a new game
983 settings.remove("Current");
984 settings.sync();
985 m_state->newGame();
986 settings.beginGroup("Board");
987 m_board->generate(settings);
988 } else if (filename == ":saved:") {
989 // Continue previous game
990 m_state->openGame();
991 settings.beginGroup("Current");
992 if (!m_board->generate(settings)) {
993 QMessageBox::warning(this, tr("Error"), tr("Unable to start requested game."));
994 m_state->finish();
995 m_contents->setCurrentIndex(3);
996 }
997 } else {
998 // Uncompress requested game
999 QString current = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
1000 QDir::home().mkpath(current);
1001 current += "/current";
1002
1003 QByteArray data = gunzip(filename);
1004 QFile file(current);
1005 if (!file.open(QFile::WriteOnly)) {
1006 QMessageBox::warning(this, tr("Error"), tr("Unable to start requested game."));
1007 return;
1008 }
1009 file.write(data);
1010 file.close();
1011
1012 QSettings game(current, QSettings::IniFormat);
1013 game.beginGroup("Game");
1014
1015 // Extract dice
1016 data = game.value("Dice").toByteArray();
1017 if (data.startsWith("tanglet:")) {
1018 game.setValue("Dice", data);
1019 } else {
1020 const QString dice = QString("%1-dice").arg(current);
1021 game.setValue("Dice", dice);
1022 file.setFileName(dice);
1023 if (!file.open(QFile::WriteOnly)) {
1024 QMessageBox::warning(this, tr("Error"), tr("Unable to start requested game."));
1025 return;
1026 }
1027 file.write(QByteArray::fromBase64(data));
1028 file.close();
1029 }
1030
1031 // Extract words
1032 data = game.value("Words").toByteArray();
1033 if (data.startsWith("tanglet:")) {
1034 game.setValue("Words", data);
1035 } else {
1036 const QString words = QString("%1-words").arg(current);
1037 game.setValue("Words", words);
1038 file.setFileName(words);
1039 if (!file.open(QFile::WriteOnly)) {
1040 QMessageBox::warning(this, tr("Error"), tr("Unable to start requested game."));
1041 return;
1042 }
1043 file.write(QByteArray::fromBase64(data));
1044 file.close();
1045 }
1046 data.clear();
1047
1048 // Start requested game
1049 settings.remove("Current");
1050 settings.sync();
1051 m_state->openGame();
1052 if (!m_board->generate(game)) {
1053 QMessageBox::warning(this, tr("Error"), tr("Unable to start requested game."));
1054 m_state->finish();
1055 m_contents->setCurrentIndex(3);
1056 }
1057 }
1058 }
1059
1060 //-----------------------------------------------------------------------------
1061