File indexing completed on 2023-10-03 07:18:01

0001 /*
0002     SPDX-FileCopyrightText: 1997 Mathias Mueller <in5y158@public.uni-hamburg.de>
0003     SPDX-FileCopyrightText: 2006-2007 Mauricio Piacentini <mauricio@tabuleiro.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 // own
0009 #include "kmahjongg.h"
0010 
0011 // STL
0012 #include <limits.h>
0013 
0014 // Qt
0015 #include <QAction>
0016 #include <QFileDialog>
0017 #include <QIcon>
0018 #include <QInputDialog>
0019 #include <QLabel>
0020 #include <QPixmapCache>
0021 #include <QScreen>
0022 #include <QShortcut>
0023 #include <QStatusBar>
0024 #include <QWindowStateChangeEvent>
0025 #include <QRandomGenerator>
0026 
0027 // KF
0028 #include <kwidgetsaddons_version.h>
0029 #include <KActionCollection>
0030 #include <KConfigDialog>
0031 #include <KLocalizedString>
0032 #include <KMessageBox>
0033 #include <KStandardAction>
0034 
0035 // KDEGames
0036 #include <KGameClock>
0037 #include <KScoreDialog>
0038 #include <KStandardGameAction>
0039 
0040 // LibKMahjongg
0041 #include <KMahjonggConfigDialog>
0042 
0043 // KMahjongg
0044 #include "editor.h"
0045 #include "gamedata.h"
0046 #include "gamescene.h"
0047 #include "gameview.h"
0048 #include "kmahjongg_debug.h"
0049 #include "kmahjongglayout.h"
0050 #include "kmahjongglayoutselector.h"
0051 #include "prefs.h"
0052 #include "ui_settings.h"
0053 
0054 namespace {
0055 inline QString gameMagic() { return QStringLiteral("kmahjongg-gamedata"); }
0056 constexpr int gameDataVersion = 1;
0057 }
0058 
0059 /**
0060  * @author Mauricio Piacentini  <mauricio@tabuleiro.com>
0061  */
0062 class Settings : public QWidget, public Ui::Settings
0063 {
0064 public:
0065     explicit Settings(QWidget * parent)
0066         : QWidget(parent)
0067     {
0068         setupUi(this);
0069     }
0070 };
0071 
0072 KMahjongg::KMahjongg(QWidget * parent)
0073     : KXmlGuiWindow(parent)
0074     , m_gameState(GameState::Gameplay)
0075     , m_gameView(nullptr)
0076     , m_gameData(nullptr)
0077     , m_boardLayout(new KMahjonggLayout())
0078 {
0079     //Use up to 3MB for global application pixmap cache
0080     QPixmapCache::setCacheLimit(3 * 1024);
0081 
0082     // minimum area required to display the field
0083     setMinimumSize(320, 320);
0084 
0085     // init board widget
0086     m_gameScene = new GameScene();
0087 
0088     loadLayout();
0089 
0090     // init game data
0091     m_gameData = new GameData(m_boardLayout->board());
0092 
0093     // init view and add to window
0094     m_gameView = new GameView(m_gameScene, m_gameData, this);
0095     setCentralWidget(m_gameView);
0096 
0097     m_boardEditor = new Editor();
0098     m_boardEditor->setModal(false);
0099 
0100     setupStatusBar();
0101     setupKAction();
0102 
0103     m_gameTimer = new KGameClock(this);
0104 
0105     connect(m_gameTimer, &KGameClock::timeChanged, this, &KMahjongg::displayTime);
0106     connect(m_gameView, &GameView::statusTextChanged, this, &KMahjongg::showStatusText);
0107     connect(m_gameView, &GameView::itemNumberChanged, this, &KMahjongg::showItemNumber);
0108     connect(m_gameView, &GameView::gameOver, this, &KMahjongg::gameOver);
0109     connect(m_gameView, &GameView::demoOrMoveListAnimationOver, this, &KMahjongg::demoOrMoveListAnimationOver);
0110     connect(m_gameView, &GameView::noMovesAvailable, this, &KMahjongg::noMovesAvailable);
0111     connect(m_gameScene, &GameScene::rotateCW, m_gameView, &GameView::angleSwitchCW);
0112     connect(m_gameScene, &GameScene::rotateCCW, m_gameView, &GameView::angleSwitchCCW);
0113 
0114     m_bLastRandomSetting = Prefs::randomLayout();
0115 
0116     loadSettings();
0117 
0118     m_boardEditor->setTilesetFromSettings();
0119 
0120     startNewGame();
0121 }
0122 
0123 KMahjongg::~KMahjongg()
0124 {
0125     delete m_gameView;
0126     delete m_gameScene;
0127     delete m_boardLayout;
0128     delete m_boardEditor;
0129     delete m_gameData;
0130 }
0131 
0132 void KMahjongg::setupKAction()
0133 {
0134     KStandardGameAction::gameNew(this, &KMahjongg::startNewGame, actionCollection());
0135     KStandardGameAction::load(this, &KMahjongg::loadGame, actionCollection());
0136     KStandardGameAction::save(this, &KMahjongg::saveGame, actionCollection());
0137     KStandardGameAction::quit(this, &KMahjongg::close, actionCollection());
0138     KStandardGameAction::restart(this, &KMahjongg::restartGame, actionCollection());
0139 
0140     QAction * newNumGame = actionCollection()->addAction(QStringLiteral("game_new_numeric"));
0141     newNumGame->setText(i18n("New Numbered Game..."));
0142     connect(newNumGame, &QAction::triggered, this, &KMahjongg::startNewNumeric);
0143 
0144     QAction * action = KStandardGameAction::hint(m_gameView, &GameView::helpMove, this);
0145     actionCollection()->addAction(action->objectName(), action);
0146 
0147     QAction * shuffle = actionCollection()->addAction(QStringLiteral("move_shuffle"));
0148     shuffle->setText(i18n("Shu&ffle"));
0149     shuffle->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
0150     connect(shuffle, &QAction::triggered, m_gameView, &GameView::shuffle);
0151 
0152     QAction * angleccw = actionCollection()->addAction(QStringLiteral("view_angleccw"));
0153     angleccw->setText(i18n("Rotate View Counterclockwise"));
0154     angleccw->setIcon(QIcon::fromTheme(QStringLiteral("object-rotate-left")));
0155     actionCollection()->setDefaultShortcut(angleccw, Qt::Key_F);
0156     connect(angleccw, &QAction::triggered, m_gameView, &GameView::angleSwitchCCW);
0157 
0158     QAction * anglecw = actionCollection()->addAction(QStringLiteral("view_anglecw"));
0159     anglecw->setText(i18n("Rotate View Clockwise"));
0160     anglecw->setIcon(QIcon::fromTheme(QStringLiteral("object-rotate-right")));
0161     actionCollection()->setDefaultShortcut(anglecw, Qt::Key_G);
0162     connect(anglecw, &QAction::triggered, m_gameView, &GameView::angleSwitchCW);
0163 
0164     m_fullscreenAction = KStandardAction::fullScreen(
0165         this, &KMahjongg::toggleFullscreen, this, actionCollection()
0166     );
0167 
0168     m_demoAction = KStandardGameAction::demo(this, &KMahjongg::demoMode, actionCollection());
0169 
0170     KStandardGameAction::highscores(this, &KMahjongg::showHighscores, actionCollection());
0171     m_pauseAction = KStandardGameAction::pause(this, &KMahjongg::pause, actionCollection());
0172 
0173     // move
0174     m_undoAction = KStandardGameAction::undo(this, &KMahjongg::undo, actionCollection());
0175     m_redoAction = KStandardGameAction::redo(this, &KMahjongg::redo, actionCollection());
0176 
0177     // edit
0178     QAction * boardEdit = actionCollection()->addAction(QStringLiteral("game_board_editor"));
0179     boardEdit->setText(i18n("&Board Editor"));
0180     connect(boardEdit, &QAction::triggered, this, &KMahjongg::slotBoardEditor);
0181 
0182     // settings
0183     KStandardAction::preferences(this, &KMahjongg::showSettings, actionCollection());
0184     setupGUI(qApp->primaryScreen()->geometry().size() * 0.7);
0185 }
0186 
0187 void KMahjongg::toggleFullscreen(bool fullscreen)
0188 {
0189     if (fullscreen) {
0190         setWindowState(Qt::WindowState::WindowFullScreen);
0191     } else {
0192         setWindowState(Qt::WindowState::WindowNoState);
0193     }
0194 }
0195 
0196 void KMahjongg::setupStatusBar()
0197 {
0198     m_gameTimerLabel = new QLabel(i18n("Time: 0:00:00"), statusBar());
0199     statusBar()->addWidget(m_gameTimerLabel);
0200 
0201     QFrame * timerDivider = new QFrame(statusBar());
0202     timerDivider->setFrameStyle(QFrame::VLine);
0203     statusBar()->addWidget(timerDivider);
0204 
0205     m_tilesLeftLabel = new QLabel(i18n("Removed: 0000/0000"), statusBar());
0206     statusBar()->addWidget(m_tilesLeftLabel, 1);
0207 
0208     QFrame * tileDivider = new QFrame(statusBar());
0209     tileDivider->setFrameStyle(QFrame::VLine);
0210     statusBar()->addWidget(tileDivider);
0211 
0212     m_gameNumLabel = new QLabel(i18n("Game: 000000000000000000000"), statusBar());
0213     statusBar()->addWidget(m_gameNumLabel);
0214 
0215     QFrame * gameNumDivider = new QFrame(statusBar());
0216     gameNumDivider->setFrameStyle(QFrame::VLine);
0217     statusBar()->addWidget(gameNumDivider);
0218 
0219     m_statusLabel = new QLabel(QStringLiteral("Kmahjongg"), statusBar());
0220     statusBar()->addWidget(m_statusLabel);
0221 }
0222 
0223 void KMahjongg::displayTime(const QString & timestring)
0224 {
0225     m_gameTimerLabel->setText(i18n("Time: ") + timestring);
0226 }
0227 
0228 void KMahjongg::startNewNumeric()
0229 {
0230     bool ok;
0231     int s = QInputDialog::getInt(this, i18nc("@title:window", "New Game"), i18n("Enter game number:"), 0, 0, INT_MAX, 1, &ok);
0232 
0233     if (ok) {
0234         startNewGameWithNumber(s);
0235     }
0236 }
0237 
0238 void KMahjongg::undo()
0239 {
0240     // If the game got stuck (no more matching tiles), the game timer is paused.
0241     // So resume timer if the player decides to undo moves from that state.
0242     if (m_gameState == GameState::Stuck) {
0243         m_gameTimer->resume();
0244     }
0245     m_gameView->undo();
0246     updateState(GameState::Gameplay);
0247     updateUndoAndRedoStates();
0248 }
0249 
0250 void KMahjongg::redo()
0251 {
0252     m_gameView->redo();
0253     updateUndoAndRedoStates();
0254 }
0255 
0256 void KMahjongg::showSettings()
0257 {
0258     if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
0259         return;
0260     }
0261 
0262     //Use the classes exposed by LibKmahjongg for our configuration dialog
0263     KMahjonggConfigDialog * dialog = new KMahjonggConfigDialog(this, QStringLiteral("settings"), Prefs::self());
0264 
0265     //The Settings class is ours
0266     dialog->addPage(new Settings(dialog), i18n("General"), QStringLiteral("games-config-options"));
0267     dialog->addPage(new KMahjonggLayoutSelector(dialog, Prefs::self()), i18n("Board Layout"), QStringLiteral("games-config-board"));
0268     dialog->addTilesetPage();
0269     dialog->addBackgroundPage();
0270 
0271     connect(dialog, &KMahjonggConfigDialog::settingsChanged, this, &KMahjongg::loadSettings);
0272     connect(dialog, &KMahjonggConfigDialog::settingsChanged, m_boardEditor, &Editor::setTilesetFromSettings);
0273 
0274     dialog->show();
0275 }
0276 
0277 void KMahjongg::loadLayout()
0278 {
0279     if (!m_boardLayout->load(Prefs::layout())) {
0280         qCDebug(KMAHJONGG_LOG) << "Error loading the layout. Try to load the default layout.";
0281 
0282         m_boardLayout->loadDefault();
0283         Prefs::setLayout(m_boardLayout->path());
0284     }
0285 }
0286 
0287 void KMahjongg::saveSettings()
0288 {
0289     Prefs::setLayout(m_boardLayout->path());
0290     Prefs::setTileSet(m_gameView->getTilesetPath());
0291     Prefs::setBackground(m_gameView->getBackgroundPath());
0292     Prefs::setAngle(m_gameView->getAngle());
0293     Prefs::self()->save();
0294 }
0295 
0296 void KMahjongg::loadSettings()
0297 {
0298     // Set the blink-matching-tiles option.
0299     m_gameView->setMatch(Prefs::showMatchingTiles());
0300 
0301     // Load the tileset.
0302     if (!m_gameView->setTilesetPath(Prefs::tileSet())) {
0303         qCDebug(KMAHJONGG_LOG) << "An error occurred when loading the tileset " << Prefs::tileSet() << " KMahjongg will continue with the default tileset.";
0304     }
0305 
0306     // Load the background
0307     if (!m_gameView->setBackgroundPath(Prefs::background())) {
0308         qCDebug(KMAHJONGG_LOG) << "An error occurred when loading the background " << Prefs::background() << " KMahjongg will continue with the default background.";
0309     }
0310 
0311     // Set whether removed tiles should be shown.
0312     m_gameView->showRemovedTiles(Prefs::removedTiles());
0313 
0314     // Maybe load a new layout and start a new game if the layout or random mode has changed.
0315     if (m_boardLayout->path() != Prefs::layout() || m_bLastRandomSetting != Prefs::randomLayout()) {
0316         // The boardlayout path will likely not be the same as the preference setting if
0317         // random layouts are set. If they are and were last time we don't want to load
0318         // a new layout or start a new game when the user may have just changed the
0319         // tileset, background or other settings.
0320         // Also, if no saved layout setting, avoid endless recursion via startNewGame.
0321         if ((!m_bLastRandomSetting || !Prefs::randomLayout()) && !Prefs::layout().isEmpty()) {
0322             // The user has changed the layout, or the random setting.
0323 
0324             // If random layouts are set a new layout will be loaded when we call
0325             // startNewGame, so no need to do so here.
0326             if (!Prefs::randomLayout()) {
0327                 loadLayout();
0328 
0329                 delete m_gameData;
0330                 m_gameData = new GameData(m_boardLayout->board());
0331                 m_gameView->setGameData(m_gameData);
0332             }
0333 
0334             // Track the last random setting.
0335             m_bLastRandomSetting = Prefs::randomLayout();
0336 
0337             startNewGame();
0338         }
0339     }
0340 
0341     saveSettings();
0342 }
0343 
0344 void KMahjongg::demoMode()
0345 {
0346     // Test if a game is already running and whether the user wants to save the game.
0347     if (m_gameChanged && m_demoAction->isChecked()) {
0348         if (!askSaveGame(QStringLiteral("kmahjongg_start_demo_ask_for_saving"))) {
0349             // The user canceled the action, so don't go further with demo mode.
0350             m_demoAction->setChecked(false);
0351             return;
0352         }
0353     }
0354 
0355     if (m_demoAction->isChecked()) {
0356         loadSettings(); // In case loadGame() has changed the settings.
0357         updateState(GameState::Demo);
0358         m_gameTimer->setTime(0);
0359         m_gameTimer->pause();
0360         m_gameView->startDemo();
0361     } else {
0362         startNewGame();
0363     }
0364 }
0365 
0366 void KMahjongg::pause()
0367 {
0368     if (m_pauseAction->isChecked()) {
0369         m_gameTimer->pause();
0370         updateState(GameState::Paused);
0371         m_gameView->pause(true);
0372     } else {
0373         m_gameTimer->resume();
0374         updateState(GameState::Gameplay);
0375         m_gameView->pause(false);
0376     }
0377 }
0378 
0379 void KMahjongg::showHighscores()
0380 {
0381     KScoreDialog ksdialog(KScoreDialog::Name | KScoreDialog::Time, this);
0382     const QString layoutName = m_boardLayout->authorProperty(QStringLiteral("Name"));
0383     ksdialog.setConfigGroup(qMakePair(QByteArray(layoutName.toUtf8()), layoutName));
0384     ksdialog.exec();
0385 }
0386 
0387 void KMahjongg::slotBoardEditor()
0388 {
0389     m_boardEditor->setVisible(true);
0390 
0391     // Set the default size.
0392     m_boardEditor->setGeometry(Prefs::editorGeometry());
0393 }
0394 
0395 void KMahjongg::noMovesAvailable()
0396 {
0397     m_gameTimer->pause();
0398     updateState(GameState::Stuck);
0399 
0400     const QIcon newGameIcon = actionCollection()->action(QString::fromLatin1(KStandardGameAction::name(KStandardGameAction::New)))->icon();
0401     const QIcon restartIcon = actionCollection()->action(QString::fromLatin1(KStandardGameAction::name(KStandardGameAction::Restart)))->icon();
0402 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
0403     int answer = KMessageBox::questionTwoActionsCancel(
0404 #else
0405     int answer = KMessageBox::questionYesNoCancel(
0406 #endif
0407         this,
0408         i18n("Game Over: You have no moves left."),
0409         i18n("Game Over"),
0410         KGuiItem(i18n("New Game"), newGameIcon),
0411         KGuiItem(i18n("Restart"), restartIcon));
0412 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
0413     if (answer == KMessageBox::PrimaryAction) {
0414 #else
0415     if (answer == KMessageBox::Yes) {
0416 #endif
0417         startNewGame();
0418 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
0419     } else if (answer == KMessageBox::SecondaryAction) {
0420 #else
0421     } else if (answer == KMessageBox::No) {
0422 #endif
0423         restartGame();
0424     }
0425 }
0426 
0427 void KMahjongg::startNewGame()
0428 {
0429     startNewGameWithNumber(-1);
0430 }
0431 
0432 void KMahjongg::startNewGameWithNumber(int item)
0433 {
0434     if (!testForGameChangeSave(QStringLiteral("kmahjongg_start_new_game_ask_for_saving"))) {
0435         return;
0436     }
0437 
0438     loadSettings(); // In case loadGame() has changed the settings.
0439 
0440     // Only load new layout in random mode if we are not given a game number.
0441     // Use same layout if restarting game or starting a numbered game.
0442     if (Prefs::randomLayout() && item == -1) {
0443         QStringList availableLayouts;
0444         const QStringList layoutDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("layouts/"), QStandardPaths::LocateDirectory);
0445         for (const QString & dir : layoutDirs) {
0446             const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.desktop"));
0447             for (const QString & file : fileNames) {
0448                 availableLayouts.append(dir + QLatin1Char('/') + file);
0449             }
0450         }
0451         const QString layout = availableLayouts.at(QRandomGenerator::global()->bounded(availableLayouts.size()));
0452 
0453         if (m_boardLayout->path() != layout) {
0454             // Try to load the random layout.
0455             if (!m_boardLayout->load(layout)) {
0456                 // Or load the default.
0457                 m_boardLayout->loadDefault();
0458             }
0459 
0460             delete m_gameData;
0461             m_gameData = new GameData(m_boardLayout->board());
0462             m_gameView->setGameData(m_gameData);
0463         }
0464     }
0465 
0466     m_gameView->createNewGame(item);
0467 
0468     m_gameTimer->restart();
0469 
0470     if (m_gameView->gameGenerated()) {
0471         updateState(GameState::Gameplay);
0472         setCaption(m_boardLayout->layoutName());
0473     } else {
0474         updateState(GameState::Finished);
0475         m_gameTimer->pause();
0476         showItemNumber(0, 0, 0);
0477     }
0478 
0479     // At the beginning, the game has not been changed.
0480     m_gameChanged = false;
0481 }
0482 
0483 void KMahjongg::demoOrMoveListAnimationOver(bool demoGameLost)
0484 {
0485     if (demoGameLost) {
0486         KMessageBox::information(this, i18n("Your computer has lost the game."));
0487     }
0488 
0489     startNewGame();
0490 }
0491 
0492 void KMahjongg::changeEvent(QEvent * event)
0493 {
0494     if (event->type() == QEvent::WindowStateChange) {
0495         const QWindowStateChangeEvent * stateEvent = static_cast<QWindowStateChangeEvent *>(event);
0496         const Qt::WindowStates oldMinimizedState = stateEvent->oldState() & Qt::WindowMinimized;
0497 
0498         // N.B. KMahjongg::pause() is not used here, because it is irrelevant to
0499         // hide the tiles and change the Pause button's state when minimizing.
0500         if (isMinimized() && oldMinimizedState != Qt::WindowMinimized && m_gameState == GameState::Gameplay) {
0501             // If playing a game and not paused, stop the clock during minimise.
0502             m_gameTimer->pause();
0503         } else if (!isMinimized() && oldMinimizedState == Qt::WindowMinimized && m_gameState == GameState::Gameplay) {
0504             // If playing a game, start the clock when restoring the window.
0505             m_gameTimer->resume();
0506         }
0507     }
0508 }
0509 
0510 void KMahjongg::closeEvent(QCloseEvent * event)
0511 {
0512     saveSettings();
0513     event->accept();
0514 }
0515 
0516 void KMahjongg::gameOver(unsigned short numRemoved, unsigned short cheats)
0517 {
0518     m_gameTimer->pause();
0519     updateState(GameState::Finished);
0520 
0521     // get the time in milli secs
0522     // subtract from 20 minutes to get bonus. if longer than 20 then ignore
0523     int time = (60 * 20) - m_gameTimer->seconds();
0524     if (time < 0) {
0525         time = 0;
0526     }
0527     // conv back to  secs (max bonus = 60*20 = 1200
0528 
0529     // points per removed tile bonus (for deragon max = 144*10 = 1440
0530     int score = (numRemoved * 20);
0531     // time bonus one point per second under one hour
0532     score += time;
0533     // points per cheat penalty (max penalty = 1440 for dragon)
0534     score -= (cheats * 20);
0535     if (score < 0) {
0536         score = 0;
0537     }
0538 
0539     const QString infoGameWon = i18n("You have won with a final time of %1 and a score of %2!", m_gameTimer->timeString(), score);
0540     KMessageBox::information(this, infoGameWon);
0541 
0542     //TODO: add gameNum as a Custom KScoreDialog field?
0543     //int elapsed = gameTimer->seconds();
0544     //long gameNum = m_pGameView->getGameNumber();
0545     //theHighScores->checkHighScore(score, elapsed, gameNum, m_pGameView->getBoardName());
0546     QPointer<KScoreDialog> ksdialog = new KScoreDialog(KScoreDialog::Name | KScoreDialog::Time, this);
0547     const QString layoutName = m_boardLayout->authorProperty(QStringLiteral("Name"));
0548     ksdialog->setConfigGroup(qMakePair(QByteArray(layoutName.toUtf8()), layoutName));
0549     KScoreDialog::FieldInfo scoreInfo;
0550     scoreInfo[KScoreDialog::Score].setNum(score);
0551     scoreInfo[KScoreDialog::Time] = m_gameTimer->timeString();
0552     if (ksdialog->addScore(scoreInfo, KScoreDialog::AskName)) {
0553         ksdialog->exec();
0554     }
0555 
0556     m_gameView->startMoveListAnimation();
0557 }
0558 
0559 void KMahjongg::showStatusText(const QString & msg, long board)
0560 {
0561     m_statusLabel->setText(msg);
0562     const QString str = i18n("Game number: %1", board);
0563     m_gameNumLabel->setText(str);
0564 }
0565 
0566 void KMahjongg::showItemNumber(int maximum, int current, int left)
0567 {
0568     // This method will be called, if the number of tiles change. If there is a lower number of tiles than the
0569     // maximum, some tiles have been removed. If the game is not in demo mode, mark the game as changed.
0570     m_gameChanged = current < maximum && !m_demoAction->isChecked();
0571 
0572     const QString szBuffer = i18n("Removed: %1/%2  Combinations left: %3", maximum - current, maximum, left);
0573     m_tilesLeftLabel->setText(szBuffer);
0574 
0575     updateUndoAndRedoStates();
0576 }
0577 
0578 void KMahjongg::updateState(GameState state)
0579 {
0580     m_gameState = state;
0581     // KXMLGUIClient::stateChanged() sets action-states def. by kmahjonggui.rc.
0582     switch (state) {
0583         case GameState::Demo:
0584             stateChanged(QStringLiteral("demo_state"));
0585             break;
0586         case GameState::Paused:
0587             stateChanged(QStringLiteral("paused_state"));
0588             break;
0589         case GameState::Finished:
0590             stateChanged(QStringLiteral("finished_state"));
0591             break;
0592         case GameState::Stuck:
0593             stateChanged(QStringLiteral("stuck_state"));
0594             break;
0595         default:
0596             stateChanged(QStringLiteral("gameplay_state"));
0597             updateUndoAndRedoStates();
0598             break;
0599     }
0600 
0601     m_demoAction->setChecked(state == GameState::Demo);
0602     m_pauseAction->setChecked(state == GameState::Paused);
0603 }
0604 
0605 void KMahjongg::updateUndoAndRedoStates()
0606 {
0607     m_undoAction->setEnabled(m_gameView->checkUndoAllowed());
0608     m_redoAction->setEnabled(m_gameView->checkRedoAllowed());
0609 }
0610 
0611 void KMahjongg::restartGame()
0612 {
0613     if (!testForGameChangeSave(QStringLiteral("kmahjongg_restart_game_ask_for_saving"))) {
0614         return;
0615     }
0616 
0617     if (m_gameView->gameGenerated()) {
0618         m_gameView->createNewGame(m_gameView->getGameNumber());
0619         m_gameTimer->restart();
0620         updateState(GameState::Gameplay);
0621     }
0622 
0623     // The game has not been changed at the beginning.
0624     m_gameChanged = false;
0625 }
0626 
0627 void KMahjongg::loadGame()
0628 {
0629     if (!testForGameChangeSave(QStringLiteral("kmahjongg_load_game_ask_for_saving"))) {
0630         return;
0631     }
0632 
0633     const QString filename = QFileDialog::getOpenFileName(this, i18nc("@title:window", "Load Game"), QString(), i18n("KMahjongg Game (*.kmgame)"));
0634 
0635     if (filename.isEmpty()) {
0636         return;
0637     }
0638 
0639     QFile infile(filename);
0640 
0641     if (!infile.open(QFile::ReadOnly)) {
0642         KMessageBox::error(this, i18n("Could not read from file. Aborting."));
0643         return;
0644     }
0645 
0646     QDataStream in(&infile);
0647 
0648     // verify that it is a kmahjongg game file
0649     QString magic;
0650     in >> magic;
0651 
0652     if (QString::compare(magic, gameMagic(), Qt::CaseSensitive) != 0) {
0653         KMessageBox::error(this, i18n("File is not a KMahjongg game."));
0654         infile.close();
0655 
0656         return;
0657     }
0658 
0659     // verify data version of saved data
0660     qint32 version;
0661     in >> version;
0662 
0663     if (version == gameDataVersion) {
0664         in.setVersion(QDataStream::Qt_4_0);
0665     } else {
0666         KMessageBox::error(this, i18n("File format not recognized."));
0667         infile.close();
0668         return;
0669     }
0670 
0671     QString tileSetName;
0672     in >> tileSetName;
0673     m_gameView->setTilesetPath(tileSetName);
0674 
0675     QString backgroundName;
0676     in >> backgroundName;
0677     m_gameView->setBackgroundPath(backgroundName);
0678 
0679     QString boardLayoutName;
0680     in >> boardLayoutName;
0681     m_boardLayout->load(boardLayoutName);
0682 
0683     //GameTime
0684     uint seconds;
0685     in >> seconds;
0686     m_gameTimer->setTime(seconds);
0687 
0688     delete m_gameData;
0689     m_gameData = new GameData(m_boardLayout->board());
0690     m_gameData->loadFromStream(in);
0691     m_gameView->setGameData(m_gameData);
0692 
0693     // Get GameNumber (used not to be saved, so might evaluate to zero).
0694     qint64 gameNum = 0;
0695     in >> gameNum;
0696     if (gameNum > 0) {
0697         m_gameView->setGameNumber(gameNum);
0698     }
0699 
0700     infile.close();
0701     updateState(GameState::Gameplay);
0702 }
0703 
0704 void KMahjongg::saveGame()
0705 {
0706     m_gameTimer->pause();
0707 
0708     const QString filename = QFileDialog::getSaveFileName(this, i18nc("@title:window", "Save Game"), QString(), i18n("KMahjongg Game (*.kmgame)"));
0709 
0710     if (filename.isEmpty()) {
0711         m_gameTimer->resume();
0712         return;
0713     }
0714 
0715     QFile outfile(filename);
0716 
0717     if (!outfile.open(QFile::WriteOnly)) {
0718         KMessageBox::error(this, i18n("Could not open file for saving."));
0719         m_gameTimer->resume();
0720         return;
0721     }
0722 
0723     QDataStream out(&outfile);
0724 
0725     // Write a header with a "magic number" and a version
0726     out << gameMagic();
0727     out << static_cast<qint32>(gameDataVersion);
0728     out.setVersion(QDataStream::Qt_4_0);
0729 
0730     out << m_gameView->getTilesetPath();
0731     out << m_gameView->getBackgroundPath();
0732     out << m_boardLayout->path();
0733 
0734     // GameTime
0735     out << m_gameTimer->seconds();
0736 
0737     // GameData
0738     m_gameData->saveToStream(out);
0739 
0740     // GameNumber
0741     // write game number after game data to obtain backwards compatibility
0742     out << static_cast<qint64>(m_gameView->getGameNumber());
0743 
0744     outfile.close();
0745     m_gameTimer->resume();
0746 
0747     // If the game has been saved, there has been no changed done to ask for saving.
0748     m_gameChanged = false;
0749 }
0750 
0751 bool KMahjongg::askSaveGame(const QString &dontAskAgainName)
0752 {
0753 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
0754     const KMessageBox::ButtonCode ret = KMessageBox::questionTwoActionsCancel(this,
0755 #else
0756     const KMessageBox::ButtonCode ret = KMessageBox::questionYesNoCancel(this, 
0757 #endif
0758             i18n("Do you want to save your game?"), i18n("Save game?"), KStandardGuiItem::save(), 
0759             KStandardGuiItem::dontSave(), KStandardGuiItem::cancel(), dontAskAgainName);
0760 
0761     switch (ret) {
0762 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
0763     case KMessageBox::PrimaryAction:
0764 #else
0765     case KMessageBox::ButtonCode::Yes:
0766 #endif
0767         saveGame();
0768         break;
0769 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
0770     case KMessageBox::SecondaryAction:
0771 #else
0772     case KMessageBox::ButtonCode::No:
0773 #endif
0774         break;
0775     case KMessageBox::ButtonCode::Cancel:
0776         return false;
0777     default:
0778         return false;
0779     }
0780 
0781     return true;
0782 }
0783 
0784 bool KMahjongg::testForGameChangeSave(const QString &dontAskAgainName)
0785 {
0786     // Ask to save the game when it has been changed. The question is also only relevant, if the user is in gameplay
0787     // or the game has been paused.
0788     if (m_gameChanged && (m_gameState == GameState::Gameplay || m_gameState == GameState::Paused)) {
0789         if (!askSaveGame(dontAskAgainName)) {
0790             // The user wants to cancel the action.
0791             return false;
0792         }
0793     }
0794 
0795     return true;
0796 }
0797 
0798 #include "moc_kmahjongg.cpp"