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"