File indexing completed on 2024-10-06 03:45:44
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"