File indexing completed on 2024-04-14 03:59:52

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 <QPointer>
0022 #include <QScreen>
0023 #include <QShortcut>
0024 #include <QStatusBar>
0025 #include <QWindowStateChangeEvent>
0026 #include <QRandomGenerator>
0027 
0028 // KF
0029 #include <KActionCollection>
0030 #include <KConfigDialog>
0031 #include <KLocalizedString>
0032 #include <KMessageBox>
0033 #include <KStandardAction>
0034 
0035 // KDEGames
0036 #include <KGameClock>
0037 #include <KGameHighScoreDialog>
0038 #include <KGameStandardAction>
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     KGameStandardAction::gameNew(this, &KMahjongg::startNewGame, actionCollection());
0135     KGameStandardAction::load(this, &KMahjongg::loadGame, actionCollection());
0136     KGameStandardAction::save(this, &KMahjongg::saveGame, actionCollection());
0137     KGameStandardAction::quit(this, &KMahjongg::close, actionCollection());
0138     KGameStandardAction::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 = KGameStandardAction::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     KActionCollection::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     KActionCollection::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 = KGameStandardAction::demo(this, &KMahjongg::demoMode, actionCollection());
0169 
0170     KGameStandardAction::highscores(this, &KMahjongg::showHighscores, actionCollection());
0171     m_pauseAction = KGameStandardAction::pause(this, &KMahjongg::pause, actionCollection());
0172 
0173     // move
0174     m_undoAction = KGameStandardAction::undo(this, &KMahjongg::undo, actionCollection());
0175     m_redoAction = KGameStandardAction::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     KGameHighScoreDialog ksdialog(KGameHighScoreDialog::Name | KGameHighScoreDialog::Time, this);
0382     const QString layoutName = m_boardLayout->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(KGameStandardAction::name(KGameStandardAction::New))->icon();
0401     const QIcon restartIcon = actionCollection()->action(KGameStandardAction::name(KGameStandardAction::Restart))->icon();
0402     int answer = KMessageBox::questionTwoActionsCancel(
0403         this,
0404         i18n("Game Over: You have no moves left."),
0405         i18n("Game Over"),
0406         KGuiItem(i18n("New Game"), newGameIcon),
0407         KGuiItem(i18n("Restart"), restartIcon));
0408     if (answer == KMessageBox::PrimaryAction) {
0409         startNewGame();
0410     } else if (answer == KMessageBox::SecondaryAction) {
0411         restartGame();
0412     }
0413 }
0414 
0415 void KMahjongg::startNewGame()
0416 {
0417     startNewGameWithNumber(-1);
0418 }
0419 
0420 void KMahjongg::startNewGameWithNumber(int item)
0421 {
0422     if (!testForGameChangeSave(QStringLiteral("kmahjongg_start_new_game_ask_for_saving"))) {
0423         return;
0424     }
0425 
0426     loadSettings(); // In case loadGame() has changed the settings.
0427 
0428     // Only load new layout in random mode if we are not given a game number.
0429     // Use same layout if restarting game or starting a numbered game.
0430     if (Prefs::randomLayout() && item == -1) {
0431         QStringList availableLayouts;
0432         const QStringList layoutDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("layouts"), QStandardPaths::LocateDirectory);
0433         for (const QString & dir : layoutDirs) {
0434             const QStringList fileNames = QDir(dir).entryList({QStringLiteral("*.desktop")});
0435             availableLayouts.reserve(availableLayouts.size() + fileNames.size());
0436             for (const QString & file : fileNames) {
0437                 availableLayouts.append(dir + QLatin1Char('/') + file);
0438             }
0439         }
0440         const QString layout = availableLayouts.at(QRandomGenerator::global()->bounded(availableLayouts.size()));
0441 
0442         if (m_boardLayout->path() != layout) {
0443             // Try to load the random layout.
0444             if (!m_boardLayout->load(layout)) {
0445                 // Or load the default.
0446                 m_boardLayout->loadDefault();
0447             }
0448 
0449             delete m_gameData;
0450             m_gameData = new GameData(m_boardLayout->board());
0451             m_gameView->setGameData(m_gameData);
0452         }
0453     }
0454 
0455     m_gameView->createNewGame(item);
0456 
0457     m_gameTimer->restart();
0458 
0459     if (m_gameView->gameGenerated()) {
0460         updateState(GameState::Gameplay);
0461         setCaption(m_boardLayout->name());
0462     } else {
0463         updateState(GameState::Finished);
0464         m_gameTimer->pause();
0465         showItemNumber(0, 0, 0);
0466     }
0467 
0468     // At the beginning, the game has not been changed.
0469     m_gameChanged = false;
0470 }
0471 
0472 void KMahjongg::demoOrMoveListAnimationOver(bool demoGameLost)
0473 {
0474     if (demoGameLost) {
0475         KMessageBox::information(this, i18n("Your computer has lost the game."));
0476     }
0477 
0478     startNewGame();
0479 }
0480 
0481 void KMahjongg::changeEvent(QEvent * event)
0482 {
0483     if (event->type() == QEvent::WindowStateChange) {
0484         const QWindowStateChangeEvent * stateEvent = static_cast<QWindowStateChangeEvent *>(event);
0485         const Qt::WindowStates oldMinimizedState = stateEvent->oldState() & Qt::WindowMinimized;
0486 
0487         // N.B. KMahjongg::pause() is not used here, because it is irrelevant to
0488         // hide the tiles and change the Pause button's state when minimizing.
0489         if (isMinimized() && oldMinimizedState != Qt::WindowMinimized && m_gameState == GameState::Gameplay) {
0490             // If playing a game and not paused, stop the clock during minimise.
0491             m_gameTimer->pause();
0492         } else if (!isMinimized() && oldMinimizedState == Qt::WindowMinimized && m_gameState == GameState::Gameplay) {
0493             // If playing a game, start the clock when restoring the window.
0494             m_gameTimer->resume();
0495         }
0496     }
0497 }
0498 
0499 void KMahjongg::closeEvent(QCloseEvent * event)
0500 {
0501     saveSettings();
0502     event->accept();
0503 }
0504 
0505 void KMahjongg::gameOver(unsigned short numRemoved, unsigned short cheats)
0506 {
0507     m_gameTimer->pause();
0508     updateState(GameState::Finished);
0509 
0510     // get the time in milli secs
0511     // subtract from 20 minutes to get bonus. if longer than 20 then ignore
0512     int time = (60 * 20) - m_gameTimer->seconds();
0513     if (time < 0) {
0514         time = 0;
0515     }
0516     // conv back to  secs (max bonus = 60*20 = 1200
0517 
0518     // points per removed tile bonus (for deragon max = 144*10 = 1440
0519     int score = (numRemoved * 20);
0520     // time bonus one point per second under one hour
0521     score += time;
0522     // points per cheat penalty (max penalty = 1440 for dragon)
0523     score -= (cheats * 20);
0524     if (score < 0) {
0525         score = 0;
0526     }
0527 
0528     const QString infoGameWon = i18n("You have won with a final time of %1 and a score of %2!", m_gameTimer->timeString(), score);
0529     KMessageBox::information(this, infoGameWon);
0530 
0531     //TODO: add gameNum as a Custom KGameHighScoreDialog field?
0532     //int elapsed = gameTimer->seconds();
0533     //long gameNum = m_pGameView->getGameNumber();
0534     //theHighScores->checkHighScore(score, elapsed, gameNum, m_pGameView->getBoardName());
0535     QPointer<KGameHighScoreDialog> ksdialog = new KGameHighScoreDialog(KGameHighScoreDialog::Name | KGameHighScoreDialog::Time, this);
0536     const QString layoutName = m_boardLayout->name();
0537     ksdialog->setConfigGroup(qMakePair(QByteArray(layoutName.toUtf8()), layoutName));
0538     KGameHighScoreDialog::FieldInfo scoreInfo;
0539     scoreInfo[KGameHighScoreDialog::Score].setNum(score);
0540     scoreInfo[KGameHighScoreDialog::Time] = m_gameTimer->timeString();
0541     if (ksdialog->addScore(scoreInfo, KGameHighScoreDialog::AskName)) {
0542         ksdialog->exec();
0543     }
0544 
0545     m_gameView->startMoveListAnimation();
0546 }
0547 
0548 void KMahjongg::showStatusText(const QString & msg, long board)
0549 {
0550     m_statusLabel->setText(msg);
0551     const QString str = i18n("Game number: %1", board);
0552     m_gameNumLabel->setText(str);
0553 }
0554 
0555 void KMahjongg::showItemNumber(int maximum, int current, int left)
0556 {
0557     // This method will be called, if the number of tiles change. If there is a lower number of tiles than the
0558     // maximum, some tiles have been removed. If the game is not in demo mode, mark the game as changed.
0559     m_gameChanged = current < maximum && !m_demoAction->isChecked();
0560 
0561     const QString szBuffer = i18n("Removed: %1/%2  Combinations left: %3", maximum - current, maximum, left);
0562     m_tilesLeftLabel->setText(szBuffer);
0563 
0564     updateUndoAndRedoStates();
0565 }
0566 
0567 void KMahjongg::updateState(GameState state)
0568 {
0569     m_gameState = state;
0570     // KXMLGUIClient::stateChanged() sets action-states def. by kmahjonggui.rc.
0571     switch (state) {
0572         case GameState::Demo:
0573             stateChanged(QStringLiteral("demo_state"));
0574             break;
0575         case GameState::Paused:
0576             stateChanged(QStringLiteral("paused_state"));
0577             break;
0578         case GameState::Finished:
0579             stateChanged(QStringLiteral("finished_state"));
0580             break;
0581         case GameState::Stuck:
0582             stateChanged(QStringLiteral("stuck_state"));
0583             break;
0584         default:
0585             stateChanged(QStringLiteral("gameplay_state"));
0586             updateUndoAndRedoStates();
0587             break;
0588     }
0589 
0590     m_demoAction->setChecked(state == GameState::Demo);
0591     m_pauseAction->setChecked(state == GameState::Paused);
0592 }
0593 
0594 void KMahjongg::updateUndoAndRedoStates()
0595 {
0596     m_undoAction->setEnabled(m_gameView->checkUndoAllowed());
0597     m_redoAction->setEnabled(m_gameView->checkRedoAllowed());
0598 }
0599 
0600 void KMahjongg::restartGame()
0601 {
0602     if (!testForGameChangeSave(QStringLiteral("kmahjongg_restart_game_ask_for_saving"))) {
0603         return;
0604     }
0605 
0606     if (m_gameView->gameGenerated()) {
0607         m_gameView->createNewGame(m_gameView->getGameNumber());
0608         m_gameTimer->restart();
0609         updateState(GameState::Gameplay);
0610     }
0611 
0612     // The game has not been changed at the beginning.
0613     m_gameChanged = false;
0614 }
0615 
0616 void KMahjongg::loadGame()
0617 {
0618     if (!testForGameChangeSave(QStringLiteral("kmahjongg_load_game_ask_for_saving"))) {
0619         return;
0620     }
0621 
0622     const QString filename = QFileDialog::getOpenFileName(this, i18nc("@title:window", "Load Game"), QString(), i18n("KMahjongg Game (*.kmgame)"));
0623 
0624     if (filename.isEmpty()) {
0625         return;
0626     }
0627 
0628     QFile infile(filename);
0629 
0630     if (!infile.open(QFile::ReadOnly)) {
0631         KMessageBox::error(this, i18n("Could not read from file. Aborting."));
0632         return;
0633     }
0634 
0635     QDataStream in(&infile);
0636 
0637     // verify that it is a kmahjongg game file
0638     QString magic;
0639     in >> magic;
0640 
0641     if (QString::compare(magic, gameMagic(), Qt::CaseSensitive) != 0) {
0642         KMessageBox::error(this, i18n("File is not a KMahjongg game."));
0643         infile.close();
0644 
0645         return;
0646     }
0647 
0648     // verify data version of saved data
0649     qint32 version;
0650     in >> version;
0651 
0652     if (version == gameDataVersion) {
0653         in.setVersion(QDataStream::Qt_4_0);
0654     } else {
0655         KMessageBox::error(this, i18n("File format not recognized."));
0656         infile.close();
0657         return;
0658     }
0659 
0660     QString tileSetName;
0661     in >> tileSetName;
0662     m_gameView->setTilesetPath(tileSetName);
0663 
0664     QString backgroundName;
0665     in >> backgroundName;
0666     m_gameView->setBackgroundPath(backgroundName);
0667 
0668     QString boardLayoutName;
0669     in >> boardLayoutName;
0670     m_boardLayout->load(boardLayoutName);
0671 
0672     //GameTime
0673     uint seconds;
0674     in >> seconds;
0675     m_gameTimer->setTime(seconds);
0676 
0677     delete m_gameData;
0678     m_gameData = new GameData(m_boardLayout->board());
0679     m_gameData->loadFromStream(in);
0680     m_gameView->setGameData(m_gameData);
0681 
0682     // Get GameNumber (used not to be saved, so might evaluate to zero).
0683     qint64 gameNum = 0;
0684     in >> gameNum;
0685     if (gameNum > 0) {
0686         m_gameView->setGameNumber(gameNum);
0687     }
0688 
0689     infile.close();
0690     updateState(GameState::Gameplay);
0691 }
0692 
0693 void KMahjongg::saveGame()
0694 {
0695     m_gameTimer->pause();
0696 
0697     const QString filename = QFileDialog::getSaveFileName(this, i18nc("@title:window", "Save Game"), QString(), i18n("KMahjongg Game (*.kmgame)"));
0698 
0699     if (filename.isEmpty()) {
0700         m_gameTimer->resume();
0701         return;
0702     }
0703 
0704     QFile outfile(filename);
0705 
0706     if (!outfile.open(QFile::WriteOnly)) {
0707         KMessageBox::error(this, i18n("Could not open file for saving."));
0708         m_gameTimer->resume();
0709         return;
0710     }
0711 
0712     QDataStream out(&outfile);
0713 
0714     // Write a header with a "magic number" and a version
0715     out << gameMagic();
0716     out << static_cast<qint32>(gameDataVersion);
0717     out.setVersion(QDataStream::Qt_4_0);
0718 
0719     out << m_gameView->getTilesetPath();
0720     out << m_gameView->getBackgroundPath();
0721     out << m_boardLayout->path();
0722 
0723     // GameTime
0724     out << m_gameTimer->seconds();
0725 
0726     // GameData
0727     m_gameData->saveToStream(out);
0728 
0729     // GameNumber
0730     // write game number after game data to obtain backwards compatibility
0731     out << static_cast<qint64>(m_gameView->getGameNumber());
0732 
0733     outfile.close();
0734     m_gameTimer->resume();
0735 
0736     // If the game has been saved, there has been no changed done to ask for saving.
0737     m_gameChanged = false;
0738 }
0739 
0740 bool KMahjongg::askSaveGame(const QString &dontAskAgainName)
0741 {
0742     const KMessageBox::ButtonCode ret = KMessageBox::questionTwoActionsCancel(this,
0743             i18n("Do you want to save your game?"), i18n("Save game?"), KStandardGuiItem::save(), 
0744             KStandardGuiItem::dontSave(), KStandardGuiItem::cancel(), dontAskAgainName);
0745 
0746     switch (ret) {
0747     case KMessageBox::PrimaryAction:
0748         saveGame();
0749         break;
0750     case KMessageBox::SecondaryAction:
0751         break;
0752     case KMessageBox::ButtonCode::Cancel:
0753         return false;
0754     default:
0755         return false;
0756     }
0757 
0758     return true;
0759 }
0760 
0761 bool KMahjongg::testForGameChangeSave(const QString &dontAskAgainName)
0762 {
0763     // Ask to save the game when it has been changed. The question is also only relevant, if the user is in gameplay
0764     // or the game has been paused.
0765     if (m_gameChanged && (m_gameState == GameState::Gameplay || m_gameState == GameState::Paused)) {
0766         if (!askSaveGame(dontAskAgainName)) {
0767             // The user wants to cancel the action.
0768             return false;
0769         }
0770     }
0771 
0772     return true;
0773 }
0774 
0775 #include "moc_kmahjongg.cpp"