File indexing completed on 2024-04-28 04:05:20

0001 /*
0002     SPDX-FileCopyrightText: 2015 Jakob Gruber <jakob.gruber@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "mainwindow.h"
0008 
0009 #include <KActionCollection>
0010 #include <KLocalizedString>
0011 #include <KMessageBox>
0012 #include <KGameStandardAction>
0013 #include <KToggleFullScreenAction>
0014 #include <QCoreApplication>
0015 #include <QGraphicsSimpleTextItem>
0016 #include <QPointer>
0017 #include <QPushButton>
0018 #include <QStatusBar>
0019 
0020 #include "src/constants.h"
0021 #include "src/logic/levelloader.h"
0022 #include "src/logic/picmi.h"
0023 #include "src/logic/settings.h"
0024 #include "selectboardwindow.h"
0025 #include "settingswindow.h"
0026 #include "scene.h"
0027 
0028 MainWindow::MainWindow(QWidget *parent) :
0029     KXmlGuiWindow(parent),
0030     m_key_pos(QStringLiteral("window/position")), m_in_progress(false), m_mode(Random)
0031 {
0032     QCoreApplication::setApplicationName(QStringLiteral("picmi"));
0033     QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org"));
0034 
0035     m_timer.setInterval(500);
0036 
0037     setCentralWidget(&m_view);
0038 
0039     setupActions();
0040     restoreWindowState();
0041 
0042     startRandomGame();
0043 }
0044 
0045 void MainWindow::setupActions() {
0046     KGameStandardAction::gameNew(this, &MainWindow::startRandomGame, actionCollection());
0047     KGameStandardAction::load(this, &MainWindow::loadBoard, actionCollection());
0048     KGameStandardAction::restart(this, &MainWindow::restartGame, actionCollection());
0049     KGameStandardAction::highscores(this, &MainWindow::highscores, actionCollection());
0050     KGameStandardAction::quit(this, &MainWindow::close, actionCollection());
0051     KStandardAction::preferences(this, &MainWindow::settings, actionCollection());
0052     KStandardAction::fullScreen(this, &MainWindow::toggleFullscreen, this, actionCollection());
0053     m_action_pause = KGameStandardAction::pause(this, &MainWindow::togglePaused, actionCollection());
0054     m_action_undo = KGameStandardAction::undo(this, &MainWindow::undo, actionCollection());
0055     m_action_hint = KGameStandardAction::hint(this, &MainWindow::hint, actionCollection());
0056     m_action_solve = KGameStandardAction::solve(this, &MainWindow::solve, actionCollection());
0057 
0058     /* Prevent the default hint shortcut from overwriting our HJKL vim-like control mapping. */
0059     KActionCollection::setDefaultShortcut(m_action_hint, QKeySequence(Qt::CTRL | Qt::Key_I));
0060 
0061     m_action_save_state = actionCollection()->addAction(QStringLiteral("save-position"));
0062     m_action_save_state->setText(i18n("Save Position"));
0063     m_action_save_state->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0064     KActionCollection::setDefaultShortcut(m_action_save_state, QKeySequence(Qt::CTRL | Qt::Key_S));
0065     connect(m_action_save_state, &QAction::triggered, this, &MainWindow::saveState);
0066 
0067     m_action_load_state = actionCollection()->addAction(QStringLiteral("load-position"));
0068     m_action_load_state->setText(i18n("Load Position"));
0069     m_action_load_state->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
0070     KActionCollection::setDefaultShortcut(m_action_load_state, QKeySequence(Qt::CTRL | Qt::Key_L));
0071     connect(m_action_load_state, &QAction::triggered, this, &MainWindow::loadState);
0072 
0073     m_status_time = new QLabel;
0074     m_status_time->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
0075     m_status_position = new QLabel;
0076     m_status_position->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0077 
0078     m_new_game = new QPushButton;
0079     m_new_game->setText(i18n("New Game"));
0080     m_new_game->setVisible(false);
0081     connect(m_new_game, &QAbstractButton::clicked, this, &MainWindow::startRandomGame);
0082 
0083     m_load_game = new QPushButton;
0084     m_load_game->setText(i18n("Load New Game"));
0085     m_load_game->setVisible(false);
0086     connect(m_load_game, &QAbstractButton::clicked, this, &MainWindow::loadBoard);
0087 
0088     this->statusBar()->addWidget(m_new_game, 0);
0089     this->statusBar()->addWidget(m_load_game, 0);
0090     this->statusBar()->addWidget(m_status_position, 1);
0091     this->statusBar()->addWidget(m_status_time, 1);
0092 
0093     KGameDifficulty::global()->addStandardLevel(KGameDifficultyLevel::Easy);
0094     KGameDifficulty::global()->addStandardLevel(KGameDifficultyLevel::Medium, true);
0095     KGameDifficulty::global()->addStandardLevel(KGameDifficultyLevel::Hard);
0096 
0097     KGameDifficultyLevel *configurable = new KGameDifficultyLevel(90, QByteArray("Custom"), i18nc("custom difficulty", "Custom"));
0098     KGameDifficulty::global()->addLevel(configurable);
0099 
0100     KGameDifficultyGUI::init(this);
0101     connect(KGameDifficulty::global(), &KGameDifficulty::currentLevelChanged, this,
0102             &MainWindow::levelChanged);
0103 
0104     /* Disable the toolbar configuration menu entry.
0105      * The default size is used at first start up. */
0106     setupGUI(QSize(700, 560), Keys | StatusBar | Save | Create);
0107 }
0108 
0109 void MainWindow::loadBoard() {
0110     QPointer<SelectBoardWindow> w(new SelectBoardWindow(this));
0111     if (w->exec() == QDialog::Accepted) {
0112         startPresetGame(w->selectedBoard());
0113     }
0114     delete w;
0115 }
0116 
0117 void MainWindow::levelChanged(const KGameDifficultyLevel* level) {
0118     Settings::instance()->setLevel(level->standardLevel());
0119     Settings::instance()->sync();
0120     startRandomGame();
0121 }
0122 
0123 void MainWindow::toggleFullscreen(bool full_screen) {
0124     KToggleFullScreenAction::setFullScreen(this, full_screen);
0125 }
0126 
0127 void MainWindow::undoStackSizeChanged(int size)
0128 {
0129     m_action_undo->setEnabled(size != 0);
0130 }
0131 
0132 void MainWindow::saveStackSizeChanged(int size)
0133 {
0134     m_action_load_state->setEnabled(size != 0);
0135 }
0136 
0137 void MainWindow::closeEvent(QCloseEvent *event) {
0138     saveWindowState();
0139     KXmlGuiWindow::closeEvent(event);
0140 }
0141 
0142 void MainWindow::saveWindowState() {
0143     QSharedPointer<QSettings> settings = Settings::instance()->qSettings();
0144     settings->setValue(m_key_pos, pos());
0145     settings->sync();
0146 }
0147 
0148 void MainWindow::restoreWindowState() {
0149     QSharedPointer<QSettings> settings = Settings::instance()->qSettings();
0150     QPoint p = settings->value(m_key_pos, pos()).toPoint();
0151 
0152     move(p);
0153 }
0154 
0155 void MainWindow::undo() {
0156     QPoint p = m_game->undo();
0157     m_scene->refresh(p);
0158 }
0159 
0160 void MainWindow::hint()
0161 {
0162     QPoint p = m_game->hint();
0163 
0164     if (m_in_progress) {
0165         m_scene->refresh(p);
0166         m_scene->hover(p.x(), p.y());
0167     }
0168 }
0169 
0170 void MainWindow::solve()
0171 {
0172     m_game->solve();
0173 }
0174 
0175 void MainWindow::saveState() {
0176     m_game->saveState();
0177     updatePositions();
0178 }
0179 
0180 void MainWindow::loadState() {
0181     m_game->loadState();
0182     m_scene->refresh();
0183 }
0184 
0185 void MainWindow::startRandomGame() {
0186     m_game = QSharedPointer<Picmi>(new Picmi());
0187     m_mode = Random;
0188 
0189     startGame();
0190 }
0191 
0192 void MainWindow::restartGame()
0193 {
0194     m_game = QSharedPointer<Picmi>(new Picmi(m_game->getBoardMap()));
0195 
0196     startGame();
0197 }
0198 
0199 void MainWindow::startPresetGame(QSharedPointer<Level> board) {
0200     QSharedPointer<BoardMap> p(new BoardMap(board->width(), board->height(), board->map()));
0201     m_game = QSharedPointer<Picmi>(new Picmi(p));
0202     m_mode = Preset;
0203     m_current_level = board;
0204 
0205     startGame();
0206 }
0207 
0208 void MainWindow::startGame() {
0209 
0210     if (m_scene) {
0211         disconnect(&m_timer, &QTimer::timeout, this, &MainWindow::updatePlayedTime);
0212     }
0213 
0214     m_action_undo->setEnabled(false);
0215     m_action_hint->setEnabled(true);
0216     m_action_solve->setEnabled(true);
0217     m_action_save_state->setEnabled(true);
0218     m_action_load_state->setEnabled(false);
0219     m_action_pause->setEnabled(true);
0220     m_action_pause->setChecked(false);
0221     m_status_position->setVisible(true);
0222     m_load_game->setVisible(false);
0223     m_new_game->setVisible(false);
0224     KGameDifficulty::global()->setGameRunning(true);
0225 
0226     m_timer.start();
0227     m_scene = m_view.createScene(m_game);
0228     updatePlayedTime();
0229     updatePositions();
0230 
0231     m_view.setEnabled(true);
0232     m_view.setFocus();
0233     m_view.setPaused(false);
0234 
0235     connect(&m_timer, &QTimer::timeout, this, &MainWindow::updatePlayedTime);
0236     connect(m_game.data(), &Picmi::stateChanged, this, &MainWindow::updatePositions);
0237     connect(m_game.data(), &Picmi::gameCompleted, this, &MainWindow::gameCompleted);
0238     connect(m_game.data(), &Picmi::gameWon, this, &MainWindow::gameWon);
0239     connect(m_game.data(), &Picmi::undoStackSizeChanged, this, &MainWindow::undoStackSizeChanged);
0240     connect(m_game.data(), &Picmi::saveStackSizeChanged, this, &MainWindow::saveStackSizeChanged);
0241 
0242     m_in_progress = true;
0243 }
0244 
0245 void MainWindow::updatePlayedTime() {
0246     m_status_time->setText(i18n("Elapsed time: %1",
0247                                 Time(m_game->elapsedSecs()).toString()));
0248 }
0249 
0250 void MainWindow::updatePositions() {
0251     m_status_position->setText(i18n("Actions since last saved position: %1",
0252                                     m_game->currentStateAge()));
0253 }
0254 
0255 QSharedPointer<KGameHighScoreDialog> MainWindow::createScoreDialog() {
0256     QSharedPointer<KGameHighScoreDialog> p(new KGameHighScoreDialog(KGameHighScoreDialog::Name | KGameHighScoreDialog::Date | KGameHighScoreDialog::Time));
0257 
0258     p->initFromDifficulty(KGameDifficulty::global());
0259     p->hideField(KGameHighScoreDialog::Score);
0260 
0261     return p;
0262 }
0263 
0264 void MainWindow::gameWon() {
0265     KGameHighScoreDialog::FieldInfo score = m_game->endGame();
0266     m_status_position->setVisible(false);
0267     if (m_mode == Random) {
0268         bool notified = false;
0269         m_new_game->setVisible(true);
0270         if (KGameDifficulty::globalLevel() != KGameDifficultyLevel::Custom) {
0271             QSharedPointer<KGameHighScoreDialog> scoreDialog = createScoreDialog();
0272             if (scoreDialog->addScore(score, KGameHighScoreDialog::LessIsMore | KGameHighScoreDialog::AskName) != 0) {
0273                 scoreDialog->exec();
0274                 notified = true;
0275             }
0276         }
0277 
0278         /* Ensure that the user gets some kind of feedback about solving the board. */
0279         if (!notified) {
0280             KMessageBox::information(this, i18n("Congratulations, you have solved this board!"),
0281                                      i18n("Board Solved!"));
0282         }
0283     } else if (m_mode == Preset) {
0284         m_load_game->setVisible(true);
0285         m_current_level->setSolved(m_game->elapsedSecs());
0286         KMessageBox::information(this, i18n("Congratulations, you have solved board '%1'!",
0287                                             m_current_level->name()),
0288                                  i18n("Board Solved!"));
0289     }
0290 
0291     m_view.setFocus();
0292 }
0293 
0294 void MainWindow::gameCompleted() {
0295     m_view.setEnabled(false);
0296     m_action_pause->setEnabled(false);
0297     m_action_solve->setEnabled(false);
0298     m_action_hint->setEnabled(false);
0299     m_action_undo->setEnabled(false);
0300     m_action_save_state->setEnabled(false);
0301     m_action_load_state->setEnabled(false);
0302     KGameDifficulty::global()->setGameRunning(false);
0303     m_timer.stop();
0304     updatePlayedTime();
0305     m_in_progress = false;
0306 }
0307 
0308 void MainWindow::highscores() {
0309     pauseGame();
0310 
0311     QSharedPointer<KGameHighScoreDialog> scoreDialog = createScoreDialog();
0312     scoreDialog->exec();
0313 
0314     m_view.setFocus();
0315 }
0316 
0317 void MainWindow::togglePaused(bool paused) {
0318     m_view.setPaused(paused);
0319     m_action_undo->setEnabled(!paused);
0320     m_action_hint->setEnabled(!paused);
0321     m_action_solve->setEnabled(!paused);
0322     m_action_save_state->setEnabled(!paused);
0323     m_action_load_state->setEnabled(!paused);
0324 
0325     if (paused) {
0326         m_timer.stop();
0327     } else {
0328         m_timer.start();
0329     }
0330 }
0331 
0332 void MainWindow::pauseGame() {
0333     if (m_action_pause->isChecked() || !m_in_progress) {
0334         return;
0335     }
0336 
0337     m_action_pause->setChecked(true);
0338     togglePaused(true);
0339 }
0340 
0341 void MainWindow::settings() {
0342     pauseGame();
0343 
0344     QPointer<SettingsWindow> w(new SettingsWindow(this));
0345     w->exec();
0346     delete w;
0347 }
0348 
0349 #include "moc_mainwindow.cpp"