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