File indexing completed on 2024-05-19 04:04:48

0001 /*
0002     SPDX-FileCopyrightText: 2008 Sascha Peilicke <sasch.pe@gmx.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "mainwindow.h"
0008 #include "game/game.h"
0009 #include "game/score.h"
0010 #include "gui/config/generalconfig.h"
0011 #include "gui/graphicsview/gamescene.h"
0012 #include "gui/graphicsview/gameview.h"
0013 #include "gui/graphicsview/themerenderer.h"
0014 #include "gui/widgets/errorwidget.h"
0015 #include "gui/widgets/gamewidget.h"
0016 #include "gui/widgets/setupwidget.h"
0017 #include "preferences.h"
0018 
0019 #include <KGameThemeSelector>
0020 #include <KGameStandardAction>
0021 
0022 #include <QAction>
0023 #include <KActionCollection>
0024 #include <KConfigDialog>
0025 #include <KToggleAction>
0026 #include <QIcon>
0027 
0028 #include <QDockWidget>
0029 #include <QFileDialog>
0030 #include <QTimer>
0031 #include <QUndoView>
0032 
0033 namespace Kigo {
0034 
0035 MainWindow::MainWindow(const QString &fileName, QWidget *parent)
0036     : KXmlGuiWindow(parent), m_game(new Game(this))
0037     , m_gameScene(new GameScene(m_game)), m_gameView(new GameView(m_gameScene))
0038 {
0039     setCentralWidget(m_gameView);
0040 
0041     setupActions();
0042     setupDockWindows();
0043     setupGUI(QSize(800, 700), KXmlGuiWindow::ToolBar | KXmlGuiWindow::Keys |
0044                               KXmlGuiWindow::Save | KXmlGuiWindow::Create);
0045 
0046     connect(m_game, &Game::waiting, this, &MainWindow::showBusy);
0047     connect(m_game, &Game::consecutivePassMovesPlayed, this, &MainWindow::showFinishGameAction);
0048     connect(m_game, &Game::resigned, this, &MainWindow::finishGame);
0049     connect(m_game, &Game::passMovePlayed, this, &MainWindow::passMovePlayed);
0050 
0051     if (isBackendWorking()) {
0052         if (!loadGame(fileName)) {
0053             newGame();
0054         }
0055     } else {
0056         backendError();
0057     }
0058 }
0059 
0060 void MainWindow::newGame()
0061 {
0062     m_game->start(Preferences::engineCommand());
0063 
0064     m_gameScene->showTerritory(false);
0065     m_gameView->setInteractive(false);
0066 
0067     m_newGameAction->setEnabled(true);
0068     m_loadGameAction->setEnabled(true);
0069     m_saveAction->setEnabled(false);
0070     m_startGameAction->setEnabled(true);
0071     m_finishGameAction->setEnabled(false);
0072 
0073     m_undoMoveAction->setEnabled(false);
0074     m_redoMoveAction->setEnabled(false);
0075     m_passMoveAction->setEnabled(false);
0076     m_hintAction->setEnabled(false);
0077 
0078     disconnect(m_game, &Game::canRedoChanged, m_redoMoveAction, &QAction::setEnabled);
0079     disconnect(m_game, &Game::canUndoChanged, m_undoMoveAction, &QAction::setEnabled);
0080     disconnect(m_game, &Game::currentPlayerChanged, this, &MainWindow::playerChanged);
0081 
0082     m_gameDock->setVisible(false);
0083     m_gameDock->toggleViewAction()->setEnabled(false);
0084     m_movesDock->setVisible(false);
0085     m_movesDock->toggleViewAction()->setEnabled(false);
0086     m_setupDock->setVisible(true);
0087     m_errorDock->setVisible(false);
0088 
0089     m_setupWidget->newGame();
0090     QTimer::singleShot(0, this, [this] { m_gameScene->showMessage(i18n("Set up a new game...")); } );
0091 }
0092 
0093 void MainWindow::loadGame()
0094 {
0095     const QString folderName = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("games"), QStandardPaths::LocateDirectory);
0096     const QString fileName = QFileDialog::getOpenFileName(this, QString(), folderName, i18n("Kigo Game Files (*.sgf)"));
0097     if (!fileName.isEmpty()) {
0098         loadGame(fileName);
0099     }
0100 }
0101 
0102 bool MainWindow::loadGame(const QString &fileName)
0103 {
0104     if (!fileName.isEmpty() && QFile(fileName).exists()) {
0105         m_game->start(Preferences::engineCommand());
0106 
0107         m_gameScene->showTerritory(false);
0108         m_gameView->setInteractive(false);
0109 
0110         m_newGameAction->setEnabled(true);
0111         m_loadGameAction->setEnabled(true);
0112         m_saveAction->setEnabled(false);
0113         m_startGameAction->setEnabled(true);
0114         m_finishGameAction->setEnabled(false);
0115 
0116         m_undoMoveAction->setEnabled(false);
0117         m_redoMoveAction->setEnabled(false);
0118         m_passMoveAction->setEnabled(false);
0119         m_hintAction->setEnabled(false);
0120 
0121         disconnect(m_game, &Game::canRedoChanged, m_redoMoveAction, &QAction::setEnabled);
0122         disconnect(m_game, &Game::canUndoChanged, m_undoMoveAction, &QAction::setEnabled);
0123         disconnect(m_game, &Game::currentPlayerChanged, this, &MainWindow::playerChanged);
0124 
0125         m_gameDock->setVisible(false);
0126         m_gameDock->toggleViewAction()->setEnabled(false);
0127         m_movesDock->setVisible(false);
0128         m_movesDock->toggleViewAction()->setEnabled(false);
0129         m_setupDock->setVisible(true);
0130         m_errorDock->setVisible(false);
0131 
0132         m_setupWidget->loadedGame(fileName);
0133         m_gameScene->showMessage(i18n("Set up a loaded game..."));
0134         return true;
0135     } else {
0136         m_gameScene->showMessage(i18n("Unable to load game..."));
0137         //Note: New game implied here
0138         return false;
0139     }
0140 }
0141 
0142 void MainWindow::backendError()
0143 {
0144     m_gameView->setInteractive(false);
0145 
0146     m_newGameAction->setEnabled(false);
0147     m_loadGameAction->setEnabled(false);
0148     m_saveAction->setEnabled(false);
0149     m_startGameAction->setEnabled(false);
0150     m_finishGameAction->setEnabled(false);
0151 
0152     m_undoMoveAction->setEnabled(false);
0153     m_redoMoveAction->setEnabled(false);
0154     m_passMoveAction->setEnabled(false);
0155     m_hintAction->setEnabled(false);
0156 
0157     disconnect(m_game, &Game::canRedoChanged, m_redoMoveAction, &QAction::setEnabled);
0158     disconnect(m_game, &Game::canUndoChanged, m_undoMoveAction, &QAction::setEnabled);
0159     disconnect(m_game, &Game::currentPlayerChanged, this, &MainWindow::playerChanged);
0160     m_gameDock->setVisible(false);
0161     m_gameDock->toggleViewAction()->setEnabled(false);
0162     m_movesDock->setVisible(false);
0163     m_movesDock->toggleViewAction()->setEnabled(false);
0164     m_setupDock->setVisible(false);
0165     m_errorDock->setVisible(true);
0166 }
0167 
0168 void MainWindow::saveGame()
0169 {
0170     const QString fileName = QFileDialog::getSaveFileName(this, QString(), QStandardPaths::writableLocation(QStandardPaths::HomeLocation), i18n("Kigo Game Files (*.sgf)"));
0171 
0172     if (!fileName.isEmpty()) {
0173         if (m_game->save(fileName)) {
0174             m_gameScene->showMessage(i18n("Game saved..."));
0175         } else {
0176             m_gameScene->showMessage(i18n("Unable to save game."));
0177         }
0178     }
0179 }
0180 
0181 void MainWindow::startGame()
0182 {
0183     m_saveAction->setEnabled(true);
0184     m_startGameAction->setEnabled(false);
0185     m_finishGameAction->setEnabled(false);
0186 
0187     m_setupWidget->commit();
0188 
0189     //Decide on players how to display the UI
0190     if (m_game->whitePlayer().isHuman() || m_game->blackPlayer().isHuman()) {
0191         connect(m_game, &Game::canRedoChanged, m_redoMoveAction, &QAction::setEnabled);
0192         connect(m_game, &Game::canUndoChanged, m_undoMoveAction, &QAction::setEnabled);
0193 
0194         m_passMoveAction->setEnabled(true);
0195         m_hintAction->setEnabled(true);
0196 
0197         m_gameView->setInteractive(true);
0198         m_undoView->setEnabled(true);
0199 
0200         m_gameScene->showPlacementMarker(true);
0201     } else {
0202         m_passMoveAction->setEnabled(false);
0203         m_hintAction->setEnabled(false);
0204 
0205         m_gameView->setInteractive(false);
0206         m_undoView->setEnabled(false);
0207 
0208         m_gameScene->showPlacementMarker(false);
0209     }
0210 
0211     connect(m_game, &Game::currentPlayerChanged, this, &MainWindow::playerChanged);
0212     // Trigger the slot once to make a move if the starting player
0213     // (black) is a computer player.
0214     playerChanged();
0215 
0216     m_setupDock->setVisible(false);
0217     m_errorDock->setVisible(false);
0218     m_gameDock->setVisible(true);
0219     m_gameDock->toggleViewAction()->setEnabled(true);
0220     m_movesDock->setVisible(true);
0221     m_movesDock->toggleViewAction()->setEnabled(true);
0222 
0223     m_gameScene->showMessage(i18n("Game started..."));
0224 
0225     //TODO: Fix undo view clicking somehow
0226     m_undoView->setEnabled(false);
0227 }
0228 
0229 void MainWindow::finishGame()
0230 {
0231     m_gameView->setInteractive(false);
0232     m_gameScene->showHint(false);
0233     m_gameScene->showTerritory(true);
0234 
0235     m_undoMoveAction->setEnabled(false);
0236     m_redoMoveAction->setEnabled(false);
0237     m_passMoveAction->setEnabled(false);
0238     m_hintAction->setEnabled(false);
0239     m_startGameAction->setEnabled(false);
0240     m_finishGameAction->setEnabled(false);
0241 
0242     m_undoView->setEnabled(false);
0243 
0244     //qCDebug(KIGO_LOG) << "Fetching final score from engine ...";
0245     Score score = m_game->estimatedScore();
0246     QString name;
0247     if (score.color() == QLatin1Char('W')) {
0248         name = i18n("%1 (White)", m_game->whitePlayer().name());
0249     } else {
0250         name = i18n("%1 (Black)", m_game->blackPlayer().name());
0251     }
0252     // Show a score message that stays visible until the next
0253     // popup message arrives
0254     if (score.upperBound() == score.lowerBound()) {
0255         m_gameScene->showMessage(i18n("%1 won with a score of %2.",
0256                              name, score.score()), 0);
0257     } else {
0258         m_gameScene->showMessage(i18n("%1 won with a score of %2 (bounds: %3 and %4).",
0259                              name, score.score(), score.upperBound(), score.lowerBound()), 0);
0260     }
0261 }
0262 
0263 void MainWindow::undo()
0264 {
0265     if (m_game->undoMove()) {
0266         m_gameScene->showMessage(i18n("Undone move"));
0267         m_gameScene->showHint(false);
0268     }
0269 }
0270 
0271 void MainWindow::redo()
0272 {
0273     if (m_game->redoMove()) {
0274         m_gameScene->showMessage(i18n("Redone move"));
0275         m_gameScene->showHint(false);
0276     }
0277 }
0278 
0279 void MainWindow::pass()
0280 {
0281     if (m_game->playMove(m_game->currentPlayer())) { // E.g. Pass move
0282         m_gameScene->showHint(false);
0283     }
0284 }
0285 
0286 void MainWindow::hint()
0287 {
0288     m_gameScene->showHint(true);
0289 }
0290 
0291 void MainWindow::showPreferences()
0292 {
0293     if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
0294         return;
0295     }
0296 
0297     KConfigDialog *dialog = new KConfigDialog(this, QStringLiteral("settings"), Preferences::self());
0298     dialog->addPage(new GeneralConfig(), i18n("General"), QStringLiteral("preferences-other"));
0299     dialog->addPage(new KGameThemeSelector(ThemeRenderer::self()->themeProvider(), KGameThemeSelector::EnableNewStuffDownload),
0300                     i18n("Themes"), QStringLiteral("games-config-theme"));
0301     if (QPushButton *restore = dialog->button(QDialogButtonBox::RestoreDefaults)) {
0302         restore->hide();
0303     }
0304     connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::applyPreferences);
0305     dialog->show();
0306 }
0307 
0308 void MainWindow::applyPreferences()
0309 {
0310     //qCDebug(KIGO_LOG) << "Update settings based on changed configuration...";
0311     m_gameScene->showLabels(Preferences::showBoardLabels());
0312 
0313     if (!isBackendWorking()) {
0314         backendError();
0315     } else if (m_game->engineCommand() != Preferences::engineCommand()) {
0316         // Restart the Go game if the game command was changed by the user.
0317         m_gameScene->showMessage(i18n("Backend was changed, restart necessary..."));
0318         newGame();
0319     }
0320 }
0321 
0322 void MainWindow::showBusy(bool busy)
0323 {
0324     //Decide on players how to display the UI
0325     if (m_game->whitePlayer().isHuman() || m_game->blackPlayer().isHuman()) {
0326         if (busy) {
0327             m_undoMoveAction->setDisabled(true);
0328             m_redoMoveAction->setDisabled(true);
0329         } else {
0330             // Only re-enable undo/redo if it actually makes sense
0331             if (m_game->canUndo()) {
0332                 m_undoMoveAction->setDisabled(false);
0333             }
0334             if (m_game->canRedo()) {
0335                 m_redoMoveAction->setDisabled(false);
0336             }
0337         }
0338         m_passMoveAction->setDisabled(busy);
0339         m_hintAction->setDisabled(busy);
0340         m_undoView->setEnabled(false);
0341     }
0342 
0343     m_gameView->setInteractive(!busy);
0344 }
0345 
0346 void MainWindow::showFinishGameAction()
0347 {
0348     m_finishGameAction->setEnabled(true);
0349 }
0350 
0351 void MainWindow::playerChanged()
0352 {
0353     if (!m_game->currentPlayer().isHuman() && !m_game->isFinished()) {
0354         QTimer::singleShot(200, this, &MainWindow::generateMove);
0355     }
0356 }
0357 
0358 void MainWindow::generateMove()
0359 {
0360     m_game->generateMove(m_game->currentPlayer());
0361 }
0362 
0363 void MainWindow::passMovePlayed(const Player &player)
0364 {
0365     if (player.isComputer()) {
0366         if (player.isWhite()) {
0367             m_gameScene->showMessage(i18n("White passed"));
0368         } else {
0369             m_gameScene->showMessage(i18n("Black passed"));
0370         }
0371     }
0372 }
0373 
0374 void MainWindow::setupActions()
0375 {
0376     // Game menu
0377     m_newGameAction = KGameStandardAction::gameNew(this, &MainWindow::newGame, actionCollection());
0378     m_loadGameAction = KGameStandardAction::load(
0379         this, qOverload<>(&MainWindow::loadGame), actionCollection());
0380     m_getMoreGamesAction = new KNSWidgets::Action(i18nc("@action", "Get More Games..." ), QStringLiteral("kigo-games.knsrc"), actionCollection());
0381     KActionCollection::setDefaultShortcut(m_getMoreGamesAction, Qt::CTRL | Qt::Key_G);
0382     m_getMoreGamesAction->setToolTip(i18nc("@action", "Get More Games..."));
0383     actionCollection()->addAction( QStringLiteral( "get_more_games" ), m_getMoreGamesAction);
0384     m_saveAction = KGameStandardAction::save(this, &MainWindow::saveGame, actionCollection());
0385     KGameStandardAction::quit(this, &QWidget::close, actionCollection());
0386 
0387     m_startGameAction = new QAction(QIcon::fromTheme( QStringLiteral( "media-playback-start") ), i18nc("@action", "Start Game" ), this);
0388     KActionCollection::setDefaultShortcut(m_startGameAction, Qt::Key_S);
0389     m_startGameAction->setToolTip(i18nc("@action", "Start Game"));
0390     connect(m_startGameAction, &QAction::triggered, this, &MainWindow::startGame);
0391     actionCollection()->addAction( QStringLiteral( "game_start" ), m_startGameAction);
0392 
0393     m_finishGameAction = new QAction(QIcon::fromTheme( QStringLiteral( "media-playback-stop") ), i18nc("@action", "Finish Game" ), this);
0394     KActionCollection::setDefaultShortcut(m_finishGameAction, Qt::Key_F);
0395     m_finishGameAction->setToolTip(i18nc("@action", "Finish Game"));
0396     connect(m_finishGameAction, &QAction::triggered, this, &MainWindow::finishGame);
0397     actionCollection()->addAction( QStringLiteral( "game_finish" ), m_finishGameAction);
0398 
0399     // Move menu
0400     m_undoMoveAction = KGameStandardAction::undo(this, &MainWindow::undo, actionCollection());
0401     m_redoMoveAction = KGameStandardAction::redo(this, &MainWindow::redo, actionCollection());
0402     m_passMoveAction = KGameStandardAction::endTurn(this, &MainWindow::pass, actionCollection());
0403     m_passMoveAction->setText(i18nc("@action:inmenu Move", "Pass Move"));
0404     KActionCollection::setDefaultShortcut(m_passMoveAction, Qt::Key_P);
0405     m_hintAction = KGameStandardAction::hint(this, &MainWindow::hint, actionCollection());
0406 
0407     // View menu
0408     m_moveNumbersAction = new KToggleAction(QIcon::fromTheme( QStringLiteral( "pin") ), i18nc("@action:inmenu View", "Show Move &Numbers" ), this);
0409     KActionCollection::setDefaultShortcut(m_moveNumbersAction, Qt::Key_N);
0410     m_moveNumbersAction->setChecked(Preferences::showMoveNumbers());
0411     connect(m_moveNumbersAction, &KToggleAction::toggled, m_gameScene, &GameScene::showMoveNumbers);
0412     actionCollection()->addAction( QStringLiteral( "move_numbers" ), m_moveNumbersAction);
0413 
0414     // Settings menu
0415     KStandardAction::preferences(this, &MainWindow::showPreferences, actionCollection());
0416 }
0417 
0418 void MainWindow::setupDockWindows()
0419 {
0420     // Setup dock
0421     m_setupDock = new QDockWidget(i18nc("@title:window", "Game Setup"), this);
0422     m_setupDock->setObjectName( QStringLiteral("setupDock" ));
0423     m_setupDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
0424     m_setupWidget = new SetupWidget(m_game, this);
0425     m_setupDock->setWidget(m_setupWidget);
0426     connect(m_setupWidget, &SetupWidget::startClicked, this, &MainWindow::startGame);
0427     addDockWidget(Qt::RightDockWidgetArea, m_setupDock);
0428 
0429     // Game dock
0430     m_gameDock = new QDockWidget(i18nc("@title:window", "Information"), this);
0431     m_gameDock->setObjectName( QStringLiteral("gameDock" ));
0432     m_gameDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
0433     auto gameWidget = new GameWidget(m_game, this);
0434     connect(gameWidget, &GameWidget::finishClicked, this, &MainWindow::finishGame);
0435     m_gameDock->setWidget(gameWidget);
0436     m_gameDock->toggleViewAction()->setText(i18nc("@title:window", "Information"));
0437     KActionCollection::setDefaultShortcut(m_gameDock->toggleViewAction(), Qt::Key_I);
0438     actionCollection()->addAction( QStringLiteral( "show_game_panel" ), m_gameDock->toggleViewAction());
0439     addDockWidget(Qt::RightDockWidgetArea, m_gameDock);
0440 
0441     // Move history dock
0442     m_movesDock = new QDockWidget(i18nc("@title:window", "Moves"), this);
0443     m_movesDock->setObjectName( QStringLiteral("movesDock" ));
0444     m_undoView = new QUndoView(m_game->undoStack());
0445     m_undoView->setEmptyLabel(i18n("No move"));
0446     m_undoView->setAlternatingRowColors(true);
0447     m_undoView->setFocusPolicy(Qt::NoFocus);
0448     m_undoView->setEditTriggers(QAbstractItemView::NoEditTriggers);
0449     m_undoView->setSelectionMode(QAbstractItemView::NoSelection);
0450     m_movesDock->setWidget(m_undoView);
0451     m_movesDock->toggleViewAction()->setText(i18nc("@title:window", "Moves"));
0452     KActionCollection::setDefaultShortcut(m_movesDock->toggleViewAction(), Qt::Key_M);
0453     actionCollection()->addAction( QStringLiteral( "show_moves_panel" ), m_movesDock->toggleViewAction());
0454     addDockWidget(Qt::RightDockWidgetArea, m_movesDock);
0455 
0456     m_errorDock = new QDockWidget(i18nc("@title:window", "Error"), this);
0457     m_errorDock->setObjectName( QStringLiteral("errorDock" ));
0458     m_errorDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
0459     auto errorWidget = new ErrorWidget(this);
0460     connect(errorWidget, &ErrorWidget::configureClicked, this, &MainWindow::showPreferences);
0461     m_errorDock->setWidget(errorWidget);
0462     addDockWidget(Qt::BottomDockWidgetArea, m_errorDock);
0463 }
0464 
0465 bool MainWindow::isBackendWorking()
0466 {
0467     Game game;
0468     return game.start(Preferences::engineCommand());
0469 }
0470 
0471 } // End of namespace Kigo
0472 
0473 #include "moc_mainwindow.cpp"