File indexing completed on 2024-05-05 08:05:43
0001 /* 0002 This file is part of Killbots. 0003 0004 SPDX-FileCopyrightText: 2006-2009 Parker Coates <coates@kde.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "mainwindow.h" 0010 0011 #include "coordinator.h" 0012 #include "engine.h" 0013 #include "optionspage.h" 0014 #include "renderer.h" 0015 #include "rulesetselector.h" 0016 #include "scene.h" 0017 #include "settings.h" 0018 #include "view.h" 0019 0020 #include <KGameHighScoreDialog> 0021 #include <KGameStandardAction> 0022 #include <KGameThemeProvider> 0023 #include <KGameThemeSelector> 0024 0025 #include <KActionCollection> 0026 #include <KConfigDialog> 0027 #include <KLocalizedString> 0028 #include <KMessageBox> 0029 #include <KShortcutsDialog> 0030 #include <KStandardAction> 0031 0032 #include <QAction> 0033 #include <QDir> 0034 #include <QIcon> 0035 #include <QTimer> 0036 0037 #include "killbots_debug.h" 0038 0039 Killbots::MainWindow::MainWindow(QWidget *parent) 0040 : KXmlGuiWindow(parent), 0041 m_scoreDialog(nullptr), 0042 m_rulesetChanged(false) 0043 { 0044 setAcceptDrops(false); 0045 0046 m_coordinator = new Coordinator(this); 0047 m_coordinator->setAnimationSpeed(Settings::animationSpeed()); 0048 0049 m_engine = new Engine(m_coordinator, this); 0050 m_coordinator->setEngine(m_engine); 0051 connect(m_engine, &Engine::gameOver, this, &MainWindow::onGameOver); 0052 0053 m_scene = new Scene(this); 0054 m_coordinator->setScene(m_scene); 0055 connect(m_scene, &Scene::clicked, m_coordinator, &Coordinator::requestAction); 0056 connect(Renderer::self()->themeProvider(), &KGameThemeProvider::currentThemeChanged, m_scene, &Scene::doLayout); 0057 0058 m_view = new View(m_scene, this); 0059 m_view->setMinimumSize(400, 280); 0060 m_view->setWhatsThis(i18n("This is the main game area used to interact with Killbots. It shows the current state of the game grid and allows one to control the hero using the mouse. It also displays certain statistics about the game in progress.")); 0061 setCentralWidget(m_view); 0062 connect(m_view, &View::sizeChanged, m_scene, &Scene::doLayout); 0063 0064 setupActions(); 0065 connect(m_engine, &Engine::teleportAllowed, actionCollection()->action(QStringLiteral("teleport")), &QAction::setEnabled); 0066 connect(m_engine, &Engine::teleportAllowed, actionCollection()->action(QStringLiteral("teleport_sip")), &QAction::setEnabled); 0067 connect(m_engine, &Engine::teleportSafelyAllowed, actionCollection()->action(QStringLiteral("teleport_safely")), &QAction::setEnabled); 0068 connect(m_engine, &Engine::vaporizerAllowed, actionCollection()->action(QStringLiteral("vaporizer")), &QAction::setEnabled); 0069 connect(m_engine, &Engine::waitOutRoundAllowed, actionCollection()->action(QStringLiteral("wait_out_round")), &QAction::setEnabled); 0070 0071 setupGUI(Save | Create | ToolBar | Keys); 0072 0073 // Delaying the start of the first game by 50ms to avoid resize events after 0074 // the game has been started. Delaying by 0ms doesn't seem to be enough. 0075 QTimer::singleShot(50, m_coordinator, &Coordinator::requestNewGame); 0076 } 0077 0078 Killbots::MainWindow::~MainWindow() 0079 { 0080 Killbots::Renderer::cleanup(); 0081 } 0082 0083 void Killbots::MainWindow::configurePreferences() 0084 { 0085 // An instance of the dialog could be already created and could be cached, 0086 // in which case we want to display the cached dialog instead of creating 0087 // another one 0088 if (!KConfigDialog::showDialog(QStringLiteral("configurePreferencesDialog"))) { 0089 // Creating a dialog because KConfigDialog didn't find an instance of it 0090 KConfigDialog *configDialog = new KConfigDialog(this, QStringLiteral("configurePreferencesDialog"), Settings::self()); 0091 0092 // Creating setting pages and adding them to the dialog 0093 configDialog->addPage(new OptionsPage(this), 0094 i18nc("@title", "General"), 0095 QStringLiteral("configure"), 0096 i18nc("@info", "Configure general settings") 0097 ); 0098 configDialog->addPage(new RulesetSelector(this), 0099 i18nc("@title", "Game Type"), 0100 QStringLiteral("games-config-custom"), 0101 i18nc("@info", "Select a game type") 0102 ); 0103 configDialog->addPage(new KGameThemeSelector(Renderer::self()->themeProvider()), 0104 i18nc("@title", "Appearance"), 0105 QStringLiteral("games-config-theme"), 0106 i18nc("@info", "Select a graphical theme") 0107 ); 0108 0109 configDialog->setMaximumSize(800, 600); 0110 0111 // Update the sprite style if it has changed 0112 connect(configDialog, &KConfigDialog::settingsChanged, this, &MainWindow::onSettingsChanged); 0113 0114 configDialog->show(); 0115 } 0116 } 0117 0118 void Killbots::MainWindow::onSettingsChanged() 0119 { 0120 m_coordinator->setAnimationSpeed(Settings::animationSpeed()); 0121 0122 if (m_engine->ruleset()->fileName() != Settings::ruleset()) { 0123 qCDebug(KILLBOTS_LOG) << "Detected a changed in ruleset. From" << m_engine->ruleset()->fileName() << "to" << Settings::ruleset(); 0124 0125 // We don't act on the changed ruleset here because the config dialog 0126 // is still visible. We want the game in progress to be visible when 0127 // we display the message box. We also don't want a new game to start 0128 // while hidden behind the config dialog. So we wait until the config 0129 // dialog is closed. See onConfigDialogClosed below. 0130 m_rulesetChanged = true; 0131 } 0132 onConfigDialogClosed(); 0133 } 0134 0135 void Killbots::MainWindow::onConfigDialogClosed() 0136 { 0137 if (m_rulesetChanged) { 0138 if (!m_engine->gameHasStarted() 0139 || KMessageBox::questionTwoActions(this, 0140 i18n("A new game type has been selected, but there is already a game in progress."), 0141 i18nc("@title:window", "Game Type Changed"), 0142 KGuiItem(i18nc("@action:button", "Continue Current Game")), 0143 KGuiItem(i18nc("@action:button", "Start a New Game")) 0144 ) == KMessageBox::SecondaryAction 0145 ) { 0146 m_coordinator->requestNewGame(); 0147 } 0148 0149 m_rulesetChanged = false; 0150 } 0151 } 0152 0153 void Killbots::MainWindow::createScoreDialog() 0154 { 0155 m_scoreDialog = new KGameHighScoreDialog(KGameHighScoreDialog::Name, this); 0156 m_scoreDialog->addField(KGameHighScoreDialog::Level, i18nc("@title:column round of the game", "Round"), QStringLiteral("round")); 0157 m_scoreDialog->setModal(false); 0158 QStringList fileList; 0159 const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("killbots/ruleset"), QStandardPaths::LocateDirectory); 0160 for (const QString &dir : dirs) { 0161 const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.desktop")); 0162 for (const QString &file : fileNames) { 0163 fileList.append(dir + QLatin1Char('/') + file); 0164 } 0165 } 0166 for (const QString &fileName : std::as_const(fileList)) { 0167 const Ruleset *ruleset = Ruleset::load(fileName); 0168 if (ruleset) { 0169 m_scoreDialog->addLocalizedConfigGroupName(qMakePair(ruleset->scoreGroupKey(), ruleset->name())); 0170 } 0171 delete ruleset; 0172 } 0173 } 0174 0175 void Killbots::MainWindow::onGameOver(int score, int round) 0176 { 0177 if (score && m_engine->ruleset()) { 0178 if (!m_scoreDialog) { 0179 createScoreDialog(); 0180 } 0181 0182 m_scoreDialog->setConfigGroup(qMakePair(m_engine->ruleset()->scoreGroupKey(), m_engine->ruleset()->name())); 0183 0184 KGameHighScoreDialog::FieldInfo scoreEntry; 0185 scoreEntry[ KGameHighScoreDialog::Score ].setNum(score); 0186 scoreEntry[ KGameHighScoreDialog::Level ].setNum(round); 0187 0188 if (m_scoreDialog->addScore(scoreEntry)) { 0189 QTimer::singleShot(1000, this, &MainWindow::showHighscores); 0190 } 0191 } 0192 } 0193 0194 void Killbots::MainWindow::showHighscores() 0195 { 0196 if (!m_scoreDialog) { 0197 createScoreDialog(); 0198 } 0199 0200 // TODO: Find out why this line breaks adding high scores. 0201 //m_scoreDialog->setConfigGroup(qMakePair(m_engine->ruleset()->scoreGroupKey(), m_engine->ruleset()->name())); 0202 m_scoreDialog->exec(); 0203 } 0204 0205 QAction *Killbots::MainWindow::createMappedAction(int mapping, 0206 const QString &internalName, 0207 const QString &displayName, 0208 const QString &translatedShortcut, 0209 const int alternateShortcut, 0210 const QString &helpText, 0211 const QString &icon 0212 ) 0213 { 0214 QAction *action = new QAction(displayName, actionCollection()); 0215 action->setObjectName(internalName); 0216 KActionCollection::setDefaultShortcuts(action, {QKeySequence(translatedShortcut), QKeySequence(alternateShortcut), QKeySequence(alternateShortcut | Qt::KeypadModifier)}); 0217 if (!helpText.isEmpty()) { 0218 action->setWhatsThis(helpText); 0219 action->setToolTip(helpText); 0220 } 0221 if (!icon.isEmpty()) { 0222 action->setIcon(QIcon::fromTheme(icon)); 0223 } 0224 0225 connect(action, &QAction::triggered, this, [this, mapping] { m_coordinator->requestAction(mapping); }); 0226 actionCollection()->addAction(internalName, action); 0227 0228 return action; 0229 } 0230 0231 void Killbots::MainWindow::setupActions() 0232 { 0233 KGameStandardAction::gameNew(m_coordinator, &Coordinator::requestNewGame, actionCollection()); 0234 KGameStandardAction::highscores(this, &MainWindow::showHighscores, actionCollection()); 0235 KGameStandardAction::quit(qApp, &QCoreApplication::quit, actionCollection()); 0236 KStandardAction::preferences(this, &MainWindow::configurePreferences, actionCollection()); 0237 0238 createMappedAction(TeleportSafely, 0239 QStringLiteral("teleport_safely"), 0240 i18nc("@action", "Teleport Safely"), 0241 i18nc("Shortcut for teleport safely. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "T"), 0242 Qt::Key_Plus, 0243 i18nc("@info:tooltip", "Teleport to a safe location"), 0244 QStringLiteral("games-solve") 0245 ); 0246 createMappedAction(Teleport, 0247 QStringLiteral("teleport"), 0248 i18nc("@action", "Teleport"), 0249 i18nc("Shortcut for teleport. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "R"), 0250 Qt::Key_Minus, 0251 i18nc("@info:tooltip", "Teleport to a random location"), 0252 QStringLiteral("roll") 0253 ); 0254 createMappedAction(TeleportSafelyIfPossible, 0255 QStringLiteral("teleport_sip"), 0256 i18nc("@action", "Teleport (Safely if Possible)"), 0257 i18nc("Shortcut for teleport safely if possible. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "Space"), 0258 Qt::Key_0, 0259 i18nc("@info:tooltip", "Teleport safely if that action is enabled, otherwise teleport randomly") 0260 ); 0261 createMappedAction(Vaporizer, 0262 QStringLiteral("vaporizer"), 0263 i18nc("@action", "Vaporizer"), 0264 i18nc("Shortcut for vaporizer. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "F"), 0265 Qt::Key_Period, 0266 i18nc("@info:tooltip", "Destroy all enemies in neighboring cells"), 0267 QStringLiteral("edit-bomb") 0268 ); 0269 createMappedAction(WaitOutRound, 0270 QStringLiteral("wait_out_round"), 0271 i18nc("@action", "Wait Out Round"), 0272 i18nc("Shortcut for wait out round. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "V"), 0273 Qt::Key_Asterisk, 0274 i18nc("@info:tooltip", "Risk remaining in place until the end of the round for bonuses"), 0275 QStringLiteral("process-stop") 0276 ); 0277 createMappedAction(UpLeft, 0278 QStringLiteral("move_up_left"), 0279 i18nc("@action", "Move Up and Left"), 0280 i18nc("Shortcut for move up and left. https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "Q"), 0281 Qt::Key_7 0282 ); 0283 createMappedAction(Up, 0284 QStringLiteral("move_up"), 0285 i18nc("@action", "Move Up"), 0286 i18nc("Shortcut for move up. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "W"), 0287 Qt::Key_8 0288 ); 0289 createMappedAction(UpRight, 0290 QStringLiteral("move_up_right"), 0291 i18nc("@action", "Move Up and Right"), 0292 i18nc("Shortcut for move up and right. https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "E"), 0293 Qt::Key_9 0294 ); 0295 createMappedAction(Left, 0296 QStringLiteral("move_left"), 0297 i18nc("@action", "Move Left"), 0298 i18nc("Shortcut for move left. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "A"), 0299 Qt::Key_4 0300 ); 0301 createMappedAction(Hold, 0302 QStringLiteral("stand_still"), 0303 i18nc("@action", "Stand Still"), 0304 i18nc("Shortcut for stand still. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "S"), 0305 Qt::Key_5 0306 ); 0307 createMappedAction(Right, 0308 QStringLiteral("move_right"), 0309 i18nc("@action", "Move Right"), 0310 i18nc("Shortcut for move right. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "D"), 0311 Qt::Key_6 0312 ); 0313 createMappedAction(DownLeft, 0314 QStringLiteral("move_down_left"), 0315 i18nc("@action", "Move Down and Left"), 0316 i18nc("Shortcut for move down and left. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "Z"), 0317 Qt::Key_1 0318 ); 0319 createMappedAction(Down, 0320 QStringLiteral("move_down"), 0321 i18nc("@action", "Move Down"), 0322 i18nc("Shortcut for move down. See https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "X"), 0323 Qt::Key_2 0324 ); 0325 createMappedAction(DownRight, 0326 QStringLiteral("move_down_right"), 0327 i18nc("@action", "Move Down and Right"), 0328 i18nc("Shortcut for move down and right. https://quickgit.kde.org/?p=killbots.git&a=blob&f=README.translators&o=plain", "C"), 0329 Qt::Key_3 0330 ); 0331 } 0332 0333 #include "moc_mainwindow.cpp"