File indexing completed on 2023-10-03 10:39:42

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