File indexing completed on 2024-09-15 03:46:16
0001 /* 0002 This file is part of the KDE games lskat program 0003 SPDX-FileCopyrightText: 2006 Martin Heni <kde@heni-online.de> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "mainwindow.h" 0009 0010 // Include files for Qt 0011 #include <QAction> 0012 #include <QDir> 0013 #include <QKeySequence> 0014 #include <QPointer> 0015 #include <QRandomGenerator> 0016 #include <QStatusBar> 0017 #include <QStandardPaths> 0018 0019 // KF includes 0020 #include <KActionCollection> 0021 #include <KConfigGroup> 0022 #include <KMessageBox> 0023 #include <KSharedConfig> 0024 #include <KGameStandardAction> 0025 #include <KLocalizedString> 0026 #include <KSelectAction> 0027 // Application specific includes 0028 #include "lskat_debug.h" 0029 #include "lskatglobal.h" 0030 #include "gameview.h" 0031 #include "abstractengine.h" 0032 #include "engine_two.h" 0033 #include "display_two.h" 0034 #include "config_two.h" 0035 #include "display_intro.h" 0036 #include "deck.h" 0037 #include "player.h" 0038 #include "mouseinput.h" 0039 #include "aiinput.h" 0040 #include "namedialogwidget.h" 0041 #include "fromlibkdegames/kcarddialog.h" 0042 #include "fromlibkdegames/carddeckinfo.h" 0043 0044 // Configuration file 0045 #include <config-src.h> 0046 0047 // Forward declarations 0048 const int ADVANCE_PERIOD = 20; 0049 0050 // Shortcut to access the actions 0051 #define ACTION(x) (actionCollection()->action(x)) 0052 0053 using namespace InputDevice; 0054 0055 // Construct the main application window 0056 Mainwindow::Mainwindow(QWidget *parent) 0057 : KXmlGuiWindow(parent) 0058 { 0059 // Reset stuff 0060 mDeck = nullptr; 0061 mEngine = nullptr; 0062 mDisplay = nullptr; 0063 mView = nullptr; 0064 mLSkatConfig = nullptr; 0065 mCanvas = nullptr; 0066 mTheme = nullptr; 0067 0068 // Read theme files 0069 QStringList themeList; 0070 const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("themes"), QStandardPaths::LocateDirectory); 0071 for (const QString& dir : dirs) { 0072 const QStringList fileNames = QDir(dir).entryList({QStringLiteral("*.desktop")}); 0073 themeList.reserve(themeList.size() + fileNames.size()); 0074 for (const QString& file : fileNames) { 0075 themeList.append(dir + QLatin1Char('/') + file); 0076 } 0077 } 0078 if (themeList.isEmpty()) 0079 { 0080 KMessageBox::error(this, i18n("Installation error: No theme list found.")); 0081 QTimer::singleShot(0, this, &QWidget::close); 0082 return; 0083 } 0084 0085 // Read theme files 0086 for (const QString &themePath : std::as_const(themeList)) { 0087 KConfig themeInfo(themePath, KConfig::SimpleConfig); 0088 KConfigGroup themeGroup(&themeInfo, QStringLiteral("Theme")); 0089 QString name = themeGroup.readEntry("Name", QString()); 0090 QString file = themeGroup.readEntry("File", QString()); 0091 bool isDefault = themeGroup.readEntry("Default", false); 0092 mThemeFiles[name] = file; 0093 if (mThemeDefault.isNull()) mThemeDefault = name; 0094 if (isDefault) mThemeDefault = name; 0095 0096 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Found theme: " << themePath << " Name(i18n)=" << name << " File=" << file << " default=" << isDefault; 0097 } 0098 mThemeIndexNo = themeIdxFromName(mThemeDefault); 0099 0100 // Create menus etc 0101 initGUI(); 0102 0103 // The LSkat config 0104 mLSkatConfig = new ConfigTwo(this); 0105 connect(mLSkatConfig, &ConfigTwo::signalInputType, this, &Mainwindow::setInputType); 0106 mLSkatConfig->reset(); 0107 0108 // Read game properties and set default values (after config) 0109 readProperties(); 0110 0111 // TODO: Bugfix: Needs to be here if initGUI is before readProperties 0112 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Setting current theme item to " << mThemeIndexNo; 0113 ((KSelectAction *)ACTION(QStringLiteral("theme")))->setCurrentItem(mThemeIndexNo); 0114 0115 // Get the card deck 0116 const quint32 seed = QRandomGenerator::global()->generate(); 0117 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Random seed" << seed; 0118 mDeck = new Deck(seed, this); 0119 0120 // Theme manager 0121 QString themeFile = themefileFromIdx(mThemeIndexNo); 0122 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Load theme" << themeFile << " no=" << mThemeIndexNo; 0123 mTheme = new ThemeManager(mCardTheme, themeFile, this, this->width()); 0124 if (mTheme->checkTheme() != 0) 0125 { 0126 KMessageBox::error(this, i18n("Installation error: Theme file error.")); 0127 QTimer::singleShot(0, this, &QWidget::close); 0128 return; 0129 } 0130 0131 // Overall view 0132 mCanvas = new QGraphicsScene(this); 0133 mView = new GameView(QSize(880, 675), ADVANCE_PERIOD, mCanvas, mTheme, this); 0134 0135 // Create intro 0136 mGameMode = Intro; 0137 mDisplay = new DisplayIntro(mDeck, mCanvas, mTheme, ADVANCE_PERIOD, mView); 0138 setCentralWidget(mView); 0139 connect(mView, &GameView::signalLeftMousePress, this, &Mainwindow::menuNewLSkatGame); 0140 0141 // Create GUI 0142 setupGUI(); 0143 0144 statusBar()->showMessage(i18n("Welcome to Skat! Please start a new game.")); 0145 0146 // Skip intro? 0147 if (global_skip_intro) 0148 { 0149 menuNewLSkatGame(); 0150 } 0151 // Start game automatically in demo mode 0152 else if (global_demo_mode) 0153 { 0154 // Start intro 0155 mDisplay->start(); 0156 QTimer::singleShot(12500, this, &Mainwindow::menuNewLSkatGame); 0157 } 0158 else 0159 { 0160 // Start intro 0161 mDisplay->start(); 0162 } 0163 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Mainwindow setup constructor done"; 0164 } 0165 0166 // Destructor 0167 Mainwindow::~Mainwindow() 0168 { 0169 saveProperties(); 0170 delete mEngine; 0171 delete mDisplay; 0172 delete mLSkatConfig; 0173 delete mDeck; 0174 delete mView; 0175 delete mCanvas; 0176 delete mTheme; 0177 } 0178 0179 // Called by KMainWindow when the last window of the application is 0180 void Mainwindow::closeEvent(QCloseEvent *event) 0181 { 0182 if (mEngine) 0183 { 0184 mEngine->stopGame(); 0185 } 0186 saveProperties(); 0187 KXmlGuiWindow::closeEvent(event); 0188 } 0189 0190 // Retrieve a theme file name from the menu index number 0191 QString Mainwindow::themefileFromIdx(int idx) 0192 { 0193 QStringList list(mThemeFiles.keys()); 0194 list.sort(); 0195 QString themeFile = mThemeFiles[list.at(idx)]; 0196 return themeFile; 0197 } 0198 0199 // Retrieve a theme idx from a theme name 0200 int Mainwindow::themeIdxFromName(const QString &name) 0201 { 0202 QStringList list(mThemeFiles.keys()); 0203 list.sort(); 0204 for (int i = 0; i < list.size(); ++i) 0205 { 0206 if (list[i] == name) return i; 0207 } 0208 qCCritical(LSKAT_LOG) << "Theme index lookup failed for " << name; 0209 return 0; 0210 } 0211 0212 // Save properties 0213 void Mainwindow::saveProperties() 0214 { 0215 KConfig *config = KSharedConfig::openConfig().data(); 0216 0217 // Program data 0218 KConfigGroup cfg = config->group(QStringLiteral("ProgramData")); 0219 cfg.writeEntry("startplayer", mStartPlayer); 0220 cfg.writeEntry("ThemeIndexNo", mThemeIndexNo); 0221 0222 // LSkat data 0223 mLSkatConfig->save(config); 0224 config->sync(); 0225 } 0226 0227 // Load properties 0228 void Mainwindow::readProperties() 0229 { 0230 KConfig *config = KSharedConfig::openConfig().data(); 0231 0232 // Program data 0233 KConfigGroup cfg = config->group(QStringLiteral("ProgramData")); 0234 0235 // Theme number 0236 mThemeIndexNo = cfg.readEntry("ThemeIndexNo", themeIdxFromName(mThemeDefault)); 0237 if (mThemeIndexNo >= mThemeFiles.size()) mThemeIndexNo = 0; 0238 0239 // Read card path 0240 mCardTheme = CardDeckInfo::deckName(cfg); 0241 0242 int no = cfg.readEntry("startplayer", 0); 0243 setStartPlayer(no); 0244 mLSkatConfig->load(config); 0245 } 0246 0247 // Create a input with the given type 0248 AbstractInput *Mainwindow::createInput( 0249 InputDeviceType inputType, 0250 AbstractDisplay *display, 0251 AbstractEngine *engine) 0252 { 0253 AbstractInput *input = nullptr; 0254 0255 // Always use AI input in demo mode 0256 if (global_demo_mode) 0257 { 0258 inputType = TypeAiInput; 0259 } 0260 0261 // Create the player input 0262 if (inputType == TypeMouseInput) 0263 { 0264 MouseInput *mouseInput = new MouseInput(this); 0265 connect(mView, &GameView::signalLeftMousePress, 0266 mouseInput, &MouseInput::mousePress); 0267 connect(mouseInput, &MouseInput::signalConvertMousePress, 0268 display, &AbstractDisplay::convertMousePress); 0269 connect(mouseInput, &MouseInput::signalPlayerInput, 0270 engine, &AbstractEngine::playerInput); 0271 input = mouseInput; 0272 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Create MOUSE INPUT"; 0273 } 0274 else if (inputType == TypeAiInput) 0275 { 0276 AiInput *aiInput = new AiInput((EngineTwo *)engine, this); 0277 connect(aiInput, &AiInput::signalPlayerInput, 0278 engine, &AbstractEngine::playerInput); 0279 input = aiInput; 0280 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Create AI INPUT"; 0281 } 0282 else 0283 { 0284 qCCritical(LSKAT_LOG) << "Unsupported input device type" << inputType; 0285 } 0286 0287 return input; 0288 } 0289 0290 // Start a new game 0291 void Mainwindow::startGame() 0292 { 0293 // Enable game action 0294 const QString endName(KGameStandardAction::name(KGameStandardAction::End)); 0295 ACTION(endName)->setEnabled(true); 0296 0297 // Deal cards to player - Shuffle card deck and reset pile 0298 mDeck->shuffle(); 0299 0300 // Draw Trump 0301 Suite trump = mDeck->randomTrump(); 0302 0303 // Loop all players in the game 0304 QHashIterator<int,Player *> it = mLSkatConfig->playerIterator(); 0305 while (it.hasNext()) 0306 { 0307 it.next(); 0308 Player *player = it.value(); 0309 player->setDeck(mDeck); 0310 // Deal cards 0311 player->deal(16); 0312 // Store trump 0313 player->setTrump(trump); 0314 } 0315 0316 // Start display 0317 mDisplay->start(); 0318 0319 // Start the game engine 0320 mEngine->startGame(trump, mStartPlayer); 0321 0322 // Start player for next game 0323 setStartPlayer(1-mStartPlayer); 0324 0325 // statusBar()->clearMessage(); 0326 } 0327 0328 // Here a game over is signalled 0329 void Mainwindow::gameOver(int /*winner*/) 0330 { 0331 const QString endName(KGameStandardAction::name(KGameStandardAction::End)); 0332 ACTION(endName)->setEnabled(false); 0333 statusBar()->showMessage(i18n("Game Over. Please start a new game.")); 0334 0335 // Automatically restart game in demo mode 0336 if (global_demo_mode) 0337 { 0338 QTimer::singleShot(10000, this, &Mainwindow::menuNewLSkatGame); 0339 } 0340 } 0341 0342 // Show next player 0343 void Mainwindow::nextPlayer(Player *player) 0344 { 0345 int no = player->id() + 1; 0346 QString name = player->name(); 0347 statusBar()->showMessage(i18nc("Player name and number", "Next move for %1 (player %2)", name, no)); 0348 } 0349 0350 // Setup the GUI 0351 void Mainwindow::initGUI() 0352 { 0353 QAction *action; 0354 0355 // Start a new game 0356 action = KGameStandardAction::gameNew(this, &Mainwindow::menuNewLSkatGame, actionCollection()); 0357 if (global_demo_mode) action->setEnabled(false); 0358 0359 // Clear all time statistics 0360 action = KGameStandardAction::clearStatistics(this, &Mainwindow::menuClearStatistics, actionCollection()); 0361 action->setWhatsThis(i18n("Clears the all time statistics which is kept in all sessions.")); 0362 if (global_demo_mode) action->setEnabled(false); 0363 0364 // End a game 0365 action = KGameStandardAction::end(this, &Mainwindow::menuEndGame, actionCollection()); 0366 action->setWhatsThis(i18n("Ends a currently played game. No winner will be declared.")); 0367 action->setEnabled(false); 0368 0369 // Quit the program 0370 action = KGameStandardAction::quit(this, &QWidget::close, actionCollection()); 0371 action->setWhatsThis(i18n("Quits the program.")); 0372 0373 // Determine start player 0374 KSelectAction *startPlayerAct = new KSelectAction(i18n("Starting Player"), this); 0375 actionCollection()->addAction(QStringLiteral("startplayer"), startPlayerAct); 0376 connect(startPlayerAct, &KSelectAction::indexTriggered, this, &Mainwindow::menuStartplayer); 0377 startPlayerAct->setToolTip(i18n("Changing starting player...")); 0378 startPlayerAct->setWhatsThis(i18n("Chooses which player begins the next game.")); 0379 startPlayerAct->setItems({ 0380 i18n("Player &1"), 0381 i18n("Player &2"), 0382 }); 0383 if (global_demo_mode) startPlayerAct->setEnabled(false); 0384 0385 // Determine who plays player 1 0386 KSelectAction *player1Act = new KSelectAction(i18n("Player &1 Played By"), this); 0387 actionCollection()->addAction(QStringLiteral("player1"), player1Act); 0388 connect(player1Act, &KSelectAction::indexTriggered, this, &Mainwindow::menuPlayer1By); 0389 player1Act->setToolTip(i18n("Changing who plays player 1...")); 0390 player1Act->setWhatsThis(i18n("Changing who plays player 1.")); 0391 const QStringList inputDevices { 0392 i18n("&Mouse"), 0393 i18n("&Computer"), 0394 }; 0395 player1Act->setItems(inputDevices); 0396 if (global_demo_mode) player1Act->setEnabled(false); 0397 0398 // Determine who plays player 2 0399 KSelectAction *player2Act = new KSelectAction(i18n("Player &2 Played By"), this); 0400 actionCollection()->addAction(QStringLiteral("player2"), player2Act); 0401 connect(player2Act, &KSelectAction::indexTriggered, this, &Mainwindow::menuPlayer2By); 0402 player2Act->setToolTip(i18n("Changing who plays player 2...")); 0403 player2Act->setWhatsThis(i18n("Changing who plays player 2.")); 0404 player2Act->setItems(inputDevices); 0405 if (global_demo_mode) player2Act->setEnabled(false); 0406 0407 // Add all theme files to the menu 0408 QStringList themes(mThemeFiles.keys()); 0409 themes.sort(); 0410 0411 KSelectAction *themeAct = new KSelectAction(i18n("&Theme"), this); 0412 actionCollection()->addAction(QStringLiteral("theme"), themeAct); 0413 themeAct->setItems(themes); 0414 connect(themeAct, &KSelectAction::indexTriggered, this, &Mainwindow::changeTheme); 0415 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Setting current theme item to " << mThemeIndexNo; 0416 themeAct->setCurrentItem(mThemeIndexNo); 0417 themeAct->setToolTip(i18n("Changing theme...")); 0418 themeAct->setWhatsThis(i18n("Changing theme.")); 0419 0420 // Choose card deck 0421 QAction *action1 = actionCollection()->addAction(QStringLiteral("select_carddeck")); 0422 action1->setText(i18n("Select &Card Deck...")); 0423 connect(action1, &QAction::triggered, this, &Mainwindow::menuCardDeck); 0424 action1->setToolTip(i18n("Configure card decks...")); 0425 action1->setWhatsThis(i18n("Choose how the cards should look.")); 0426 0427 // Change player names 0428 action = actionCollection()->addAction(QStringLiteral("change_names")); 0429 action->setText(i18n("&Change Player Names...")); 0430 connect(action, &QAction::triggered, this, &Mainwindow::menuPlayerNames); 0431 if (global_demo_mode) action->setEnabled(false); 0432 } 0433 0434 // Choose start player 0435 void Mainwindow::menuStartplayer() 0436 { 0437 int i = ((KSelectAction *)ACTION(QStringLiteral("startplayer")))->currentItem(); 0438 setStartPlayer(i); 0439 } 0440 0441 // Change the theme of the game 0442 void Mainwindow::changeTheme(int idx) 0443 { 0444 mThemeIndexNo = idx; 0445 QString themeFile = themefileFromIdx(idx); 0446 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Select theme " << themeFile; 0447 mTheme->updateTheme(themeFile); 0448 } 0449 0450 // Select input for player 1 0451 void Mainwindow::menuPlayer1By() 0452 { 0453 int i = ((KSelectAction *)ACTION(QStringLiteral("player1")))->currentItem(); 0454 mLSkatConfig->setInputType(0, (InputDeviceType)i); 0455 } 0456 0457 // Select input for player 2 0458 void Mainwindow::menuPlayer2By() 0459 { 0460 int i = ((KSelectAction *)ACTION(QStringLiteral("player2")))->currentItem(); 0461 mLSkatConfig->setInputType(1, (InputDeviceType)i); 0462 } 0463 0464 // Choose a card deck 0465 void Mainwindow::menuCardDeck() 0466 { 0467 QString front = mCardTheme; 0468 0469 KConfigGroup grp = KSharedConfig::openConfig()->group(QStringLiteral("ProgramData")); 0470 KCardWidget *cardwidget = new KCardWidget(); 0471 QPointer<KCardDialog> dlg; 0472 0473 cardwidget->readSettings(grp); 0474 dlg = new KCardDialog(cardwidget); 0475 if (dlg->exec() == QDialog::Accepted) 0476 { 0477 // Always store the settings, other things than the deck may have changed 0478 cardwidget->saveSettings(grp); 0479 grp.sync(); 0480 if (global_debug > 0) qCDebug(LSKAT_LOG) << "NEW CARDDECK: " << front; 0481 bool change = false; // Avoid unnecessary changes 0482 if (!cardwidget->deckName().isEmpty() && cardwidget->deckName() != mCardTheme) 0483 { 0484 mCardTheme = cardwidget->deckName(); 0485 change = true; 0486 } 0487 if (change) 0488 { 0489 mTheme->updateCardTheme(mCardTheme); 0490 mView->update(); // Be on the safe side and update 0491 } 0492 } 0493 delete dlg; 0494 } 0495 0496 // Clear all time statistics 0497 void Mainwindow::menuClearStatistics() 0498 { 0499 QString message; 0500 message = i18n("Do you really want to clear the all time " 0501 "statistical data?"); 0502 0503 if (KMessageBox::PrimaryAction == KMessageBox::questionTwoActions(this, 0504 message, 0505 QString(), 0506 KStandardGuiItem::clear(), 0507 KStandardGuiItem::cancel())) 0508 { 0509 QHashIterator<int,Player *> it = mLSkatConfig->playerIterator(); 0510 while (it.hasNext()) 0511 { 0512 it.next(); 0513 Player *player = it.value(); 0514 player->clear(); 0515 } 0516 } 0517 } 0518 0519 // Abort a game 0520 void Mainwindow::menuEndGame() 0521 { 0522 if (mEngine) 0523 { 0524 mEngine->stopGame(); 0525 } 0526 } 0527 0528 // Start a new game 0529 void Mainwindow::menuNewLSkatGame() 0530 { 0531 disconnect(mView, &GameView::signalLeftMousePress, this, &Mainwindow::menuNewLSkatGame); 0532 0533 Player *p1 = mLSkatConfig->player(0); 0534 Player *p2 = mLSkatConfig->player(1); 0535 0536 // Stop running games 0537 if (mEngine) 0538 { 0539 mEngine->stopGame(); 0540 } 0541 0542 // Get rid of old stuff? 0543 if (true || (mGameMode != LSkat)) // Yes! Fixes bugs 330308 and 228067. 0544 // Jenkins objected to the indentation if the above was simply commented out. 0545 { 0546 // Set new game mode 0547 mGameMode = LSkat; 0548 0549 // Start deleting 0550 delete mDisplay; 0551 delete mEngine; 0552 0553 auto *display = new DisplayTwo(mDeck, mCanvas, mTheme, ADVANCE_PERIOD, mView); 0554 mDisplay = display; 0555 mEngine = new EngineTwo(this, mDeck, (DisplayTwo *)mDisplay); 0556 connect(mEngine, &AbstractEngine::signalGameOver, this, &Mainwindow::gameOver); 0557 connect(mEngine, &AbstractEngine::signalNextPlayer, this, &Mainwindow::nextPlayer); 0558 0559 // Connect player score widget updates 0560 connect(p1, &Player::signalUpdate, display, &DisplayTwo::updatePlayer); 0561 connect(p2, &Player::signalUpdate, display, &DisplayTwo::updatePlayer); 0562 0563 mEngine->addPlayer(0, p1); 0564 mEngine->addPlayer(1, p2); 0565 }// end if 0566 0567 // Create inputs and store in player 0568 AbstractInput *input1 = createInput(mLSkatConfig->inputType(0), mDisplay, mEngine); 0569 p1->setInput(input1); 0570 AbstractInput *input2 = createInput(mLSkatConfig->inputType(1), mDisplay, mEngine); 0571 p2->setInput(input2); 0572 0573 statusBar()->showMessage(i18n("Dealing cards...")); 0574 0575 // Start game 0576 startGame(); 0577 } 0578 0579 // Change the player names in a dialog 0580 void Mainwindow::menuPlayerNames() 0581 { 0582 QPointer<NameDialogWidget> dlg = new NameDialogWidget(this); 0583 for (int i = 0; i < 2; i++) 0584 { 0585 Player *p = mLSkatConfig->player(i); 0586 dlg->setName(i, p->name()); 0587 } 0588 0589 if (dlg->exec() == QDialog::Accepted) 0590 { 0591 for (int i = 0; i < 2; i++) 0592 { 0593 Player *p = mLSkatConfig->player(i); 0594 p->setName(dlg->name(i)); 0595 } 0596 } 0597 0598 delete dlg; 0599 } 0600 0601 // Set the start player. 0602 void Mainwindow::setStartPlayer(int no) 0603 { 0604 mStartPlayer = no; 0605 ((KSelectAction *)ACTION(QStringLiteral("startplayer")))->setCurrentItem(mStartPlayer); 0606 } 0607 0608 // Set the input type for a given player number. 0609 void Mainwindow::setInputType(int no, InputDeviceType type) 0610 { 0611 Player *p = nullptr; 0612 // Player 1 0613 if (no == 0) 0614 { 0615 ((KSelectAction *)ACTION(QStringLiteral("player1")))->setCurrentItem((int)type); 0616 p = mLSkatConfig->player(0); 0617 } 0618 else if (no == 1) 0619 { 0620 ((KSelectAction *)ACTION(QStringLiteral("player2")))->setCurrentItem((int)type); 0621 p = mLSkatConfig->player(1); 0622 } 0623 0624 // Exchange player input at runtime 0625 if (mEngine && p && mDisplay && mEngine->isGameRunning()) 0626 { 0627 AbstractInput *input = createInput(type, mDisplay, mEngine); 0628 p->setInput(input); 0629 } 0630 } 0631 0632 #include "moc_mainwindow.cpp"