File indexing completed on 2024-05-19 04:06:17
0001 /* 0002 * Copyright (C) 1995 Paul Olav Tvete <paul@troll.no> 0003 * Copyright (C) 1997 Mario Weilguni <mweilguni@sime.com> 0004 * Copyright (C) 2000-2009 Stephan Kulow <coolo@kde.org> 0005 * Copyright (C) 2009-2010 Parker Coates <coates@kde.org> 0006 * 0007 * License of original code: 0008 * ------------------------------------------------------------------------- 0009 * Permission to use, copy, modify, and distribute this software and its 0010 * documentation for any purpose and without fee is hereby granted, 0011 * provided that the above copyright notice appear in all copies and that 0012 * both that copyright notice and this permission notice appear in 0013 * supporting documentation. 0014 * 0015 * This file is provided AS IS with no warranties of any kind. The author 0016 * shall have no liability with respect to the infringement of copyrights, 0017 * trade secrets or any patents by this file or any part thereof. In no 0018 * event will the author be liable for any lost revenue or profits or 0019 * other special, indirect and consequential damages. 0020 * ------------------------------------------------------------------------- 0021 * 0022 * License of modifications/additions made after 2009-01-01: 0023 * ------------------------------------------------------------------------- 0024 * This program is free software; you can redistribute it and/or 0025 * modify it under the terms of the GNU General Public License as 0026 * published by the Free Software Foundation; either version 2 of 0027 * the License, or (at your option) any later version. 0028 * 0029 * This program is distributed in the hope that it will be useful, 0030 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0031 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0032 * GNU General Public License for more details. 0033 * 0034 * You should have received a copy of the GNU General Public License 0035 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0036 * ------------------------------------------------------------------------- 0037 */ 0038 0039 #include "mainwindow.h" 0040 0041 // own 0042 #include "dealer.h" 0043 #include "dealerinfo.h" 0044 #include "gameselectionscene.h" 0045 #include "kpat_debug.h" 0046 #include "numbereddealdialog.h" 0047 #include "renderer.h" 0048 #include "settings.h" 0049 #include "soundengine.h" 0050 #include "statisticsdialog.h" 0051 #include "view.h" 0052 // KCardGame 0053 #include <KCardTheme> 0054 #include <KCardThemeWidget> 0055 // KDEGames 0056 #include <KGameStandardAction> 0057 #include <KGameThemeSelector> 0058 // KF 0059 #include <KActionCollection> 0060 #include <KConfigDialog> 0061 #include <KHelpClient> 0062 #include <KIO/StoredTransferJob> 0063 #include <KLocalizedString> 0064 #include <KMessageBox> 0065 #include <KSharedConfig> 0066 #include <KStandardAction> 0067 #include <KToggleAction> 0068 #include <KXMLGUIFactory> 0069 // Qt 0070 #include <QAction> 0071 #include <QApplication> 0072 #include <QBuffer> 0073 #include <QFileDialog> 0074 #include <QIcon> 0075 #include <QKeySequence> 0076 #include <QList> 0077 #include <QMenuBar> 0078 #include <QPointer> 0079 #include <QRandomGenerator> 0080 #include <QScreen> 0081 #include <QStandardPaths> 0082 #include <QStatusBar> 0083 #include <QTemporaryFile> 0084 #include <QTimer> 0085 #include <QXmlStreamReader> 0086 0087 namespace 0088 { 0089 const QUrl dialogUrl(QStringLiteral("kfiledialog:///kpat")); 0090 const QString saveFileMimeType(QStringLiteral("application/vnd.kde.kpatience.savedgame")); 0091 const QString legacySaveFileMimeType(QStringLiteral("application/vnd.kde.kpatience.savedstate")); 0092 } 0093 0094 MainWindow::MainWindow() 0095 : KXmlGuiWindow(nullptr) 0096 , m_view(nullptr) 0097 , m_dealer(nullptr) 0098 , m_selector(nullptr) 0099 , m_cardDeck(nullptr) 0100 , m_soundEngine(nullptr) 0101 , m_dealDialog(nullptr) 0102 { 0103 setObjectName(QStringLiteral("MainWindow")); 0104 // KCrash::setEmergencySaveFunction(::saveGame); 0105 0106 setupActions(); 0107 0108 const auto games = DealerInfoList::self()->games(); 0109 for (const DealerInfo *di : games) { 0110 m_dealer_map.insert(di->baseId(), di); 0111 const auto subtypeIds = di->subtypeIds(); 0112 for (int id : subtypeIds) 0113 m_dealer_map.insert(id, di); 0114 } 0115 m_dealer_it = m_dealer_map.constEnd(); 0116 0117 m_view = new PatienceView(this); 0118 setCentralWidget(m_view); 0119 0120 const QRect screenSize = screen()->availableGeometry(); 0121 QSize defaultSize = screenSize.size() * 0.7; 0122 setupGUI(defaultSize, Create | Save | ToolBar | StatusBar | Keys); 0123 0124 m_solverStatusLabel = new QLabel(QString(), statusBar()); 0125 m_solverStatusLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); 0126 statusBar()->addWidget(m_solverStatusLabel, 1); 0127 0128 m_moveCountStatusLabel = new QLabel(QString(), statusBar()); 0129 m_moveCountStatusLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); 0130 statusBar()->addWidget(m_moveCountStatusLabel, 0); 0131 0132 m_showMenubarAction->setChecked(!menuBar()->isHidden()); 0133 } 0134 0135 MainWindow::~MainWindow() 0136 { 0137 m_recentFilesAction->saveEntries(KSharedConfig::openConfig()->group(QString())); 0138 0139 Settings::self()->save(); 0140 0141 delete m_dealer; 0142 delete m_view; 0143 Renderer::deleteSelf(); 0144 } 0145 0146 void MainWindow::setupActions() 0147 { 0148 QAction *a; 0149 0150 // Game Menu 0151 a = actionCollection()->addAction(QStringLiteral("new_game")); 0152 a->setText(i18nc("Start a new game of a different type", "New &Game...")); 0153 a->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); 0154 KActionCollection::setDefaultShortcut(a, Qt::CTRL | Qt::SHIFT | Qt::Key_N); 0155 connect(a, &QAction::triggered, this, &MainWindow::slotShowGameSelectionScreen); 0156 0157 a = actionCollection()->addAction(QStringLiteral("new_deal")); 0158 a->setText(i18nc("Start a new game of without changing the game type", "New &Deal")); 0159 a->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); 0160 KActionCollection::setDefaultShortcut(a, Qt::CTRL | Qt::Key_N); 0161 connect(a, &QAction::triggered, this, &MainWindow::newGame); 0162 0163 a = actionCollection()->addAction(QStringLiteral("new_numbered_deal")); 0164 a->setText(i18nc("Start a game by giving its particular number", "New &Numbered Deal...")); 0165 KActionCollection::setDefaultShortcut(a, Qt::CTRL | Qt::Key_D); 0166 connect(a, &QAction::triggered, this, &MainWindow::newNumberedDeal); 0167 0168 a = KGameStandardAction::restart(this, &MainWindow::restart, actionCollection()); 0169 a->setText(i18nc("Replay the current deal from the start", "Restart Deal")); 0170 0171 // Note that this action is not shown in the menu or toolbar. It is 0172 // only provided for advanced users who can use it by shorcut or add it to 0173 // the toolbar if they wish. 0174 a = actionCollection()->addAction(QStringLiteral("next_deal")); 0175 a->setText(i18nc("Start the game with the number one greater than the current one", "Next Deal")); 0176 a->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); 0177 KActionCollection::setDefaultShortcut(a, Qt::CTRL | Qt::Key_Plus); 0178 connect(a, &QAction::triggered, this, &MainWindow::nextDeal); 0179 0180 // Note that this action is not shown in the menu or toolbar. It is 0181 // only provided for advanced users who can use it by shorcut or add it to 0182 // the toolbar if they wish. 0183 a = actionCollection()->addAction(QStringLiteral("previous_deal")); 0184 a->setText(i18nc("Start the game with the number one less than the current one", "Previous Deal")); 0185 a->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); 0186 KActionCollection::setDefaultShortcut(a, Qt::CTRL | Qt::Key_Minus); 0187 connect(a, &QAction::triggered, this, &MainWindow::previousDeal); 0188 0189 KGameStandardAction::load(this, qOverload<>(&MainWindow::loadGame), actionCollection()); 0190 0191 m_recentFilesAction = KGameStandardAction::loadRecent(this, qOverload<const QUrl &>(&MainWindow::loadGame), actionCollection()); 0192 m_recentFilesAction->loadEntries(KSharedConfig::openConfig()->group(QString())); 0193 0194 m_saveAction = KGameStandardAction::saveAs(this, &MainWindow::saveGame, actionCollection()); 0195 KActionCollection::setDefaultShortcut(m_saveAction, Qt::CTRL | Qt::Key_S); 0196 0197 a = actionCollection()->addAction(QStringLiteral("game_stats")); 0198 a->setText(i18n("Statistics")); 0199 a->setIcon(QIcon::fromTheme(QStringLiteral("games-highscores"))); 0200 connect(a, &QAction::triggered, this, &MainWindow::showStats); 0201 0202 KGameStandardAction::quit(this, &MainWindow::close, actionCollection()); 0203 0204 // Move Menu 0205 m_undoAction = KGameStandardAction::undo(this, &MainWindow::undoMove, actionCollection()); 0206 0207 m_redoAction = KGameStandardAction::redo(this, &MainWindow::redoMove, actionCollection()); 0208 0209 m_demoAction = KGameStandardAction::demo(this, &MainWindow::toggleDemo, actionCollection()); 0210 0211 // KGameStandardAction::hint is a regular action, but we want a toggle 0212 // action, so we must create a new action and copy all the standard 0213 // properties over one by one. 0214 m_hintAction = new KToggleAction(actionCollection()); 0215 a = KGameStandardAction::hint(nullptr, nullptr, nullptr); 0216 m_hintAction->setText(a->text()); 0217 m_hintAction->setIcon(a->icon()); 0218 KActionCollection::setDefaultShortcut(m_hintAction, a->shortcut()); 0219 m_hintAction->setToolTip(a->toolTip()); 0220 m_hintAction->setWhatsThis(a->whatsThis()); 0221 delete a; 0222 const QString actionName = KGameStandardAction::name(KGameStandardAction::Hint); 0223 actionCollection()->addAction(actionName, m_hintAction); 0224 connect(m_hintAction, &QAction::triggered, this, &MainWindow::toggleHints); 0225 0226 m_drawAction = actionCollection()->addAction(QStringLiteral("move_draw")); 0227 m_drawAction->setText(i18nc("Take one or more cards from the deck, flip them, and place them in play", "Dra&w")); 0228 m_drawAction->setIcon(QIcon::fromTheme(QStringLiteral("kpat"))); 0229 KActionCollection::setDefaultShortcut(m_drawAction, Qt::Key_Tab); 0230 0231 m_dealAction = actionCollection()->addAction(QStringLiteral("move_deal")); 0232 m_dealAction->setText(i18nc("Deal a new row of cards from the deck", "Dea&l Row")); 0233 m_dealAction->setIcon(QIcon::fromTheme(QStringLiteral("kpat"))); 0234 KActionCollection::setDefaultShortcut(m_dealAction, Qt::Key_Return); 0235 0236 m_redealAction = actionCollection()->addAction(QStringLiteral("move_redeal")); 0237 m_redealAction->setText(i18nc("Collect the cards in play, shuffle them and redeal them", "&Redeal")); 0238 m_redealAction->setIcon(QIcon::fromTheme(QStringLiteral("roll"))); 0239 KActionCollection::setDefaultShortcut(m_redealAction, Qt::Key_R); 0240 0241 m_dropAction = new KToggleAction(actionCollection()); 0242 m_dropAction->setText(i18nc("Automatically move cards to the foundation piles", "Dro&p")); 0243 m_dropAction->setIcon(QIcon::fromTheme(QStringLiteral("games-endturn"))); 0244 KActionCollection::setDefaultShortcut(m_dropAction, Qt::Key_P); 0245 actionCollection()->addAction(QStringLiteral("move_drop"), m_dropAction); 0246 connect(m_dropAction, &QAction::triggered, this, &MainWindow::toggleDrop); 0247 0248 // Settings Menu 0249 m_autoDropEnabledAction = new KToggleAction(i18n("&Enable Autodrop"), this); 0250 actionCollection()->addAction(QStringLiteral("enable_autodrop"), m_autoDropEnabledAction); 0251 connect(m_autoDropEnabledAction, &KToggleAction::triggered, this, &MainWindow::setAutoDropEnabled); 0252 m_autoDropEnabledAction->setChecked(Settings::autoDropEnabled()); 0253 0254 m_solverEnabledAction = new KToggleAction(i18n("E&nable Solver"), this); 0255 actionCollection()->addAction(QStringLiteral("enable_solver"), m_solverEnabledAction); 0256 connect(m_solverEnabledAction, &KToggleAction::triggered, this, &MainWindow::enableSolver); 0257 m_solverEnabledAction->setChecked(Settings::solverEnabled()); 0258 0259 m_playSoundsAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("preferences-desktop-sound")), i18n("Play &Sounds"), this); 0260 actionCollection()->addAction(QStringLiteral("play_sounds"), m_playSoundsAction); 0261 connect(m_playSoundsAction, &KToggleAction::triggered, this, &MainWindow::enableSounds); 0262 m_playSoundsAction->setChecked(Settings::playSounds()); 0263 0264 m_rememberStateAction = new KToggleAction(i18n("&Remember State on Exit"), this); 0265 actionCollection()->addAction(QStringLiteral("remember_state"), m_rememberStateAction); 0266 connect(m_rememberStateAction, &KToggleAction::triggered, this, &MainWindow::enableRememberState); 0267 m_rememberStateAction->setChecked(Settings::rememberStateOnExit()); 0268 0269 // Help Menu 0270 m_gameHelpAction = actionCollection()->addAction(QStringLiteral("help_game")); 0271 m_gameHelpAction->setIcon(QIcon::fromTheme(QStringLiteral("help-browser"))); 0272 connect(m_gameHelpAction, &QAction::triggered, this, &MainWindow::helpGame); 0273 KActionCollection::setDefaultShortcut(m_gameHelpAction, Qt::CTRL | Qt::SHIFT | Qt::Key_F1); 0274 0275 KStandardAction::preferences(this, &MainWindow::configureAppearance, actionCollection()); 0276 0277 // Hidden actions 0278 if (!qEnvironmentVariableIsEmpty("KDE_DEBUG")) // developer shortcut 0279 { 0280 a = actionCollection()->addAction(QStringLiteral("themePreview")); 0281 a->setText(i18n("Generate a theme preview image")); 0282 connect(a, &QAction::triggered, this, &MainWindow::generateThemePreview); 0283 KActionCollection::setDefaultShortcut(a, Qt::Key_F7); 0284 0285 a = actionCollection()->addAction(QStringLiteral("snapshot")); 0286 a->setText(i18n("Take Game Preview Snapshots")); 0287 connect(a, &QAction::triggered, this, &MainWindow::slotSnapshot); 0288 KActionCollection::setDefaultShortcut(a, Qt::Key_F8); 0289 0290 a = actionCollection()->addAction(QStringLiteral("random_set")); 0291 a->setText(i18n("Random Cards")); 0292 connect(a, &QAction::triggered, this, &MainWindow::slotPickRandom); 0293 KActionCollection::setDefaultShortcut(a, Qt::Key_F9); 0294 } 0295 0296 // Keyboard navigation actions 0297 m_leftAction = actionCollection()->addAction(QStringLiteral("focus_left")); 0298 m_leftAction->setText(i18n("Move Focus to Previous Pile")); 0299 KActionCollection::setDefaultShortcut(m_leftAction, Qt::Key_Left); 0300 0301 m_rightAction = actionCollection()->addAction(QStringLiteral("focus_right")); 0302 m_rightAction->setText(i18n("Move Focus to Next Pile")); 0303 KActionCollection::setDefaultShortcut(m_rightAction, Qt::Key_Right); 0304 0305 m_upAction = actionCollection()->addAction(QStringLiteral("focus_up")); 0306 m_upAction->setText(i18n("Move Focus to Card Below")); 0307 KActionCollection::setDefaultShortcut(m_upAction, Qt::Key_Up); 0308 0309 m_downAction = actionCollection()->addAction(QStringLiteral("focus_down")); 0310 m_downAction->setText(i18n("Move Focus to Card Above")); 0311 KActionCollection::setDefaultShortcut(m_downAction, Qt::Key_Down); 0312 0313 m_cancelAction = actionCollection()->addAction(QStringLiteral("focus_cancel")); 0314 m_cancelAction->setText(i18n("Cancel Focus")); 0315 KActionCollection::setDefaultShortcut(m_cancelAction, Qt::Key_Escape); 0316 0317 m_pickUpSetDownAction = actionCollection()->addAction(QStringLiteral("focus_activate")); 0318 m_pickUpSetDownAction->setText(i18n("Pick Up or Set Down Focus")); 0319 KActionCollection::setDefaultShortcut(m_pickUpSetDownAction, Qt::Key_Space); 0320 0321 // showMenubar isn't a part of KGameStandardAction 0322 m_showMenubarAction = KStandardAction::showMenubar(this, &MainWindow::toggleMenubar, actionCollection()); 0323 0324 KStandardAction::fullScreen(this, &MainWindow::toggleFullscreen, this, actionCollection()); 0325 } 0326 0327 void MainWindow::undoMove() 0328 { 0329 if (m_dealer) 0330 m_dealer->undo(); 0331 } 0332 0333 void MainWindow::redoMove() 0334 { 0335 if (m_dealer) 0336 m_dealer->redo(); 0337 } 0338 0339 void MainWindow::helpGame() 0340 { 0341 if (m_dealer && m_dealer_map.contains(m_dealer->gameId())) { 0342 const DealerInfo *di = m_dealer_map.value(m_dealer->gameId()); 0343 QString anchor = di->untranslatedBaseName().toString(); 0344 anchor = anchor.toLower(); 0345 anchor = anchor.remove(QLatin1Char('\'')).replace(QLatin1Char('&'), QLatin1String("and")).replace(QLatin1Char(' '), QLatin1Char('-')); 0346 KHelpClient::invokeHelp(QLatin1String("rules-specific.html#") + anchor); 0347 } 0348 } 0349 0350 void MainWindow::setAutoDropEnabled(bool enabled) 0351 { 0352 Settings::setAutoDropEnabled(enabled); 0353 if (m_dealer) { 0354 m_dealer->setAutoDropEnabled(enabled); 0355 if (enabled) 0356 m_dealer->startDrop(); 0357 else 0358 m_dealer->stopDrop(); 0359 } 0360 updateGameActionList(); 0361 } 0362 0363 void MainWindow::enableSolver(bool enable) 0364 { 0365 Settings::setSolverEnabled(enable); 0366 m_solverStatusLabel->setText(QString()); 0367 if (m_dealer) { 0368 m_dealer->setSolverEnabled(enable); 0369 if (enable) 0370 m_dealer->startSolver(); 0371 } 0372 } 0373 0374 void MainWindow::enableSounds(bool enable) 0375 { 0376 Settings::setPlaySounds(enable); 0377 updateSoundEngine(); 0378 } 0379 0380 void MainWindow::enableRememberState(bool enable) 0381 { 0382 Settings::setRememberStateOnExit(enable); 0383 } 0384 0385 void MainWindow::newGame() 0386 { 0387 if (m_dealer && m_dealer->allowedToStartNewGame()) 0388 startRandom(); 0389 } 0390 0391 void MainWindow::restart() 0392 { 0393 startNew(-1); 0394 } 0395 0396 void MainWindow::startRandom() 0397 { 0398 const int gameNumber = int(QRandomGenerator::global()->bounded(quint32(1), quint32(INT_MAX))); 0399 startNew(gameNumber); 0400 } 0401 0402 void MainWindow::startNew(int gameNumber) 0403 { 0404 m_dealer->startNew(gameNumber); 0405 setGameCaption(); 0406 } 0407 0408 void MainWindow::slotPickRandom() 0409 { 0410 QList<KCardTheme> themes = KCardTheme::findAll(); 0411 KCardTheme theme = themes.at(QRandomGenerator::global()->bounded(themes.size())); 0412 Settings::setCardTheme(theme.dirName()); 0413 0414 appearanceChanged(); 0415 } 0416 0417 void MainWindow::configureAppearance() 0418 { 0419 const QString previewFormat = QStringLiteral("back;10_spade,jack_diamond,queen_club,king_heart;1_spade"); 0420 const QSet<QString> features = QSet<QString>() << QStringLiteral("AngloAmerican") << QStringLiteral("Backs1"); 0421 0422 if (!KConfigDialog::showDialog(QStringLiteral("KPatAppearanceDialog"))) { 0423 KConfigDialog *dialog = new KConfigDialog(this, QStringLiteral("KPatAppearanceDialog"), Settings::self()); 0424 0425 dialog->addPage(new KCardThemeWidget(features, previewFormat, this), 0426 i18n("Card Deck"), 0427 QStringLiteral("games-config-theme"), 0428 i18n("Select a card deck")); 0429 0430 KGameThemeProvider *provider = Renderer::self()->themeProvider(); 0431 auto themeSelector = new KGameThemeSelector(provider, KGameThemeSelector::EnableNewStuffDownload, this); 0432 themeSelector->setNewStuffConfigFileName(QStringLiteral("kpat.knsrc")); 0433 dialog->addPage(themeSelector, i18n("Game Theme"), QStringLiteral("games-config-theme"), i18n("Select a theme for non-card game elements")); 0434 0435 connect(provider, &KGameThemeProvider::currentThemeChanged, this, &MainWindow::appearanceChanged); 0436 connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::appearanceChanged); 0437 dialog->show(); 0438 } 0439 } 0440 0441 void MainWindow::appearanceChanged() 0442 { 0443 if (m_cardDeck && Settings::cardTheme() != m_cardDeck->theme().dirName()) { 0444 KCardTheme theme(Settings::cardTheme()); 0445 if (theme.isValid()) { 0446 m_cardDeck->setTheme(KCardTheme(theme)); 0447 if (m_dealer) 0448 m_dealer->relayoutScene(); 0449 } 0450 } 0451 } 0452 0453 void MainWindow::setGameCaption() 0454 { 0455 QString caption; 0456 if (m_dealer) { 0457 const DealerInfo *di = m_dealer_map.value(m_dealer->gameId()); 0458 caption = QStringLiteral("%1 - %2").arg(di->baseName()).arg(m_dealer->gameNumber()); 0459 } 0460 setCaption(caption); 0461 } 0462 0463 void MainWindow::slotGameSelected(int id) 0464 { 0465 if (m_dealer_map.contains(id)) { 0466 setGameType(id); 0467 QTimer::singleShot(0, this, &MainWindow::startRandom); 0468 } 0469 } 0470 0471 void MainWindow::setGameType(int id) 0472 { 0473 // Only bother calling creating a new DealerScene if we don't already have 0474 // the right DealerScene open. 0475 if (m_dealer && m_dealer_map.value(id) == m_dealer_map.value(m_dealer->gameId())) { 0476 m_dealer->recordGameStatistics(); 0477 m_dealer->mapOldId(id); 0478 return; 0479 } 0480 0481 // If we're replacing an existing DealerScene, record the stats of the 0482 // game already in progress. 0483 if (m_dealer) { 0484 m_dealer->recordGameStatistics(); 0485 delete m_dealer; 0486 m_view->setScene(nullptr); 0487 m_dealer = nullptr; 0488 } 0489 0490 if (!m_cardDeck) { 0491 KCardTheme theme = KCardTheme(Settings::cardTheme()); 0492 if (!theme.isValid()) 0493 theme = KCardTheme(Settings::defaultCardThemeValue()); 0494 0495 m_cardDeck = new KCardDeck(theme, this); 0496 } 0497 0498 const DealerInfo *di = m_dealer_map.value(id, DealerInfoList::self()->games().first()); 0499 m_dealer = di->createGame(); 0500 m_dealer->setDeck(m_cardDeck); 0501 m_dealer->initialize(); 0502 m_dealer->mapOldId(id); 0503 m_dealer->setSolverEnabled(m_solverEnabledAction->isChecked()); 0504 m_dealer->setAutoDropEnabled(m_autoDropEnabledAction->isChecked()); 0505 0506 m_view->setScene(m_dealer); 0507 0508 m_gameHelpAction->setText( 0509 i18nc("Is disabled and changes to \"Help &with Current Game\" when" 0510 " there is no current game.", 0511 "Help &with %1", 0512 di->baseName().replace(QLatin1Char('&'), QLatin1String("&&")))); 0513 0514 connect(m_dealer, &DealerScene::solverStateChanged, this, &MainWindow::updateSolverDescription); 0515 connect(m_dealer, &DealerScene::updateMoves, this, &MainWindow::slotUpdateMoves); 0516 0517 m_solverStatusLabel->setText(QString()); 0518 m_solverStatusLabel->setVisible(true); 0519 m_moveCountStatusLabel->setText(QString()); 0520 m_moveCountStatusLabel->setVisible(true); 0521 0522 updateActions(); 0523 updateSoundEngine(); 0524 } 0525 0526 void MainWindow::slotShowGameSelectionScreen() 0527 { 0528 if (!m_dealer || m_dealer->allowedToStartNewGame()) { 0529 if (m_dealer) { 0530 m_dealer->recordGameStatistics(); 0531 delete m_dealer; 0532 m_view->setScene(nullptr); 0533 m_dealer = nullptr; 0534 } 0535 0536 if (!m_selector) { 0537 m_selector = new GameSelectionScene(this); 0538 connect(m_selector, &GameSelectionScene::gameSelected, this, &MainWindow::slotGameSelected); 0539 } 0540 m_view->setScene(m_selector); 0541 0542 m_gameHelpAction->setText(i18nc("Shown when there is no game open. Is always disabled.", "Help &with Current Game")); 0543 0544 updateActions(); 0545 0546 setGameCaption(); 0547 0548 m_solverStatusLabel->setVisible(false); 0549 m_moveCountStatusLabel->setVisible(false); 0550 } 0551 } 0552 0553 void MainWindow::updateActions() 0554 { 0555 // Enable/disable application actions that aren't appropriate on game 0556 // selection screen. 0557 actionCollection()->action(QStringLiteral("new_game"))->setEnabled(m_dealer); 0558 actionCollection()->action(QStringLiteral("new_deal"))->setEnabled(m_dealer); 0559 actionCollection()->action(QStringLiteral("game_restart"))->setEnabled(m_dealer); 0560 actionCollection()->action(QStringLiteral("game_save_as"))->setEnabled(m_dealer); 0561 m_gameHelpAction->setEnabled(m_dealer); 0562 0563 // Initially disable game actions. They'll be reenabled through signals 0564 // if/when appropriate. 0565 m_undoAction->setEnabled(false); 0566 m_redoAction->setEnabled(false); 0567 m_hintAction->setEnabled(false); 0568 m_demoAction->setEnabled(false); 0569 m_dropAction->setEnabled(false); 0570 m_drawAction->setEnabled(false); 0571 m_dealAction->setEnabled(false); 0572 m_redealAction->setEnabled(false); 0573 0574 // If a dealer exists, connect the game actions to it. 0575 if (m_dealer) { 0576 connect(m_dealer, &DealerScene::undoPossible, m_undoAction, &QAction::setEnabled); 0577 connect(m_dealer, &DealerScene::redoPossible, m_redoAction, &QAction::setEnabled); 0578 0579 connect(m_dealer, &DealerScene::hintActive, m_hintAction, &QAction::setChecked); 0580 connect(m_dealer, &DealerScene::gameInProgress, m_hintAction, &QAction::setEnabled); 0581 0582 connect(m_dealer, &DealerScene::demoActive, this, &MainWindow::toggleDemoAction); 0583 connect(m_dealer, &DealerScene::gameInProgress, m_demoAction, &QAction::setEnabled); 0584 0585 connect(m_dealer, &DealerScene::dropActive, m_dropAction, &QAction::setChecked); 0586 connect(m_dealer, &DealerScene::gameInProgress, m_dropAction, &QAction::setEnabled); 0587 0588 connect(m_dealer, &DealerScene::gameInProgress, m_saveAction, &QAction::setEnabled); 0589 0590 connect(m_leftAction, &QAction::triggered, m_dealer, &DealerScene::keyboardFocusLeft); 0591 connect(m_rightAction, &QAction::triggered, m_dealer, &DealerScene::keyboardFocusRight); 0592 connect(m_upAction, &QAction::triggered, m_dealer, &DealerScene::keyboardFocusUp); 0593 connect(m_downAction, &QAction::triggered, m_dealer, &DealerScene::keyboardFocusDown); 0594 connect(m_cancelAction, &QAction::triggered, m_dealer, &DealerScene::keyboardFocusCancel); 0595 connect(m_pickUpSetDownAction, &QAction::triggered, m_dealer, &DealerScene::keyboardFocusSelect); 0596 0597 connect(m_dealer, &DealerScene::newDeal, this, &MainWindow::newGame); 0598 0599 if (m_dealer->actions() & DealerScene::Draw) { 0600 connect(m_drawAction, &QAction::triggered, m_dealer, &DealerScene::drawDealRowOrRedeal); 0601 connect(m_dealer, &DealerScene::newCardsPossible, m_drawAction, &QAction::setEnabled); 0602 } else if (m_dealer->actions() & DealerScene::Deal) { 0603 connect(m_dealAction, &QAction::triggered, m_dealer, &DealerScene::drawDealRowOrRedeal); 0604 connect(m_dealer, &DealerScene::newCardsPossible, m_dealAction, &QAction::setEnabled); 0605 } else if (m_dealer->actions() & DealerScene::Redeal) { 0606 connect(m_redealAction, &QAction::triggered, m_dealer, &DealerScene::drawDealRowOrRedeal); 0607 connect(m_dealer, &DealerScene::newCardsPossible, m_redealAction, &QAction::setEnabled); 0608 } 0609 0610 guiFactory()->unplugActionList(this, QStringLiteral("dealer_options")); 0611 guiFactory()->plugActionList(this, QStringLiteral("dealer_options"), m_dealer->configActions()); 0612 } 0613 0614 updateGameActionList(); 0615 } 0616 0617 void MainWindow::updateGameActionList() 0618 { 0619 guiFactory()->unplugActionList(this, QStringLiteral("game_actions")); 0620 0621 m_dropAction->setEnabled(m_dealer && !m_dealer->autoDropEnabled()); 0622 0623 if (m_dealer) { 0624 QList<QAction *> actionList; 0625 if (m_dealer->actions() & DealerScene::Hint) 0626 actionList.append(m_hintAction); 0627 if (m_dealer->actions() & DealerScene::Demo) 0628 actionList.append(m_demoAction); 0629 if (m_dealer->actions() & DealerScene::Draw) 0630 actionList.append(m_drawAction); 0631 if (m_dealer->actions() & DealerScene::Deal) 0632 actionList.append(m_dealAction); 0633 if (m_dealer->actions() & DealerScene::Redeal) 0634 actionList.append(m_redealAction); 0635 if (!m_dealer->autoDropEnabled()) 0636 actionList.append(m_dropAction); 0637 guiFactory()->plugActionList(this, QStringLiteral("game_actions"), actionList); 0638 } 0639 } 0640 0641 void MainWindow::updateSoundEngine() 0642 { 0643 if (m_dealer) { 0644 if (Settings::playSounds()) { 0645 if (!m_soundEngine) 0646 m_soundEngine = new SoundEngine(this); 0647 0648 connect(m_dealer, &DealerScene::cardsPickedUp, m_soundEngine, &SoundEngine::cardsPickedUp); 0649 connect(m_dealer, &DealerScene::cardsPutDown, m_soundEngine, &SoundEngine::cardsPutDown); 0650 } else if (m_soundEngine) { 0651 disconnect(m_dealer, nullptr, m_soundEngine, nullptr); 0652 } 0653 } 0654 } 0655 0656 void MainWindow::toggleDrop() 0657 { 0658 if (m_dealer) { 0659 if (!m_dealer->isDropActive()) 0660 m_dealer->startDrop(); 0661 else 0662 m_dealer->stopDrop(); 0663 } 0664 } 0665 0666 void MainWindow::toggleHints() 0667 { 0668 if (m_dealer) { 0669 if (!m_dealer->isHintActive()) 0670 m_dealer->startHint(); 0671 else 0672 m_dealer->stop(); 0673 } 0674 } 0675 0676 void MainWindow::toggleDemo() 0677 { 0678 if (m_dealer) { 0679 if (!m_dealer->isDemoActive()) 0680 m_dealer->startDemo(); 0681 else 0682 m_dealer->stop(); 0683 } 0684 } 0685 0686 void MainWindow::toggleDemoAction(bool active) 0687 { 0688 m_demoAction->setChecked(active); 0689 m_demoAction->setIcon(QIcon::fromTheme(QLatin1String(active ? "media-playback-pause" : "media-playback-start"))); 0690 } 0691 0692 void MainWindow::toggleMenubar() 0693 { 0694 if (m_showMenubarAction->isChecked()) 0695 menuBar()->show(); 0696 else if (KMessageBox::warningContinueCancel(this, 0697 i18n("Are you sure you want to hide the menubar? The current shortcut to show it again is %1.", 0698 m_showMenubarAction->shortcut().toString(QKeySequence::NativeText)), 0699 i18n("Hide Menubar"), 0700 KStandardGuiItem::cont(), 0701 KStandardGuiItem::cancel(), 0702 QStringLiteral("MenubarWarning")) 0703 == KMessageBox::Continue) 0704 menuBar()->hide(); 0705 else 0706 m_showMenubarAction->setChecked(true); 0707 } 0708 0709 void MainWindow::toggleFullscreen(bool fullScreen) 0710 { 0711 KToggleFullScreenAction::setFullScreen(this, fullScreen); 0712 } 0713 0714 void MainWindow::saveNewToolbarConfig() 0715 { 0716 KXmlGuiWindow::saveNewToolbarConfig(); 0717 updateGameActionList(); 0718 } 0719 0720 void MainWindow::closeEvent(QCloseEvent *e) 0721 { 0722 QString stateDirName = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); 0723 QString stateFileName = stateDirName + QLatin1String("/" saved_state_file); 0724 QDir stateFileDir(stateDirName); 0725 if (!stateFileDir.exists()) { 0726 // create the directory if it doesn't exist (bug#350160) 0727 stateFileDir.mkpath(QStringLiteral(".")); 0728 } 0729 QFile stateFile(stateFileName); 0730 0731 // Remove the existing state file, if any. 0732 stateFile.remove(); 0733 0734 if (m_dealer) { 0735 if (Settings::rememberStateOnExit() && !m_dealer->isGameWon()) { 0736 stateFile.open(QFile::WriteOnly | QFile::Truncate); 0737 m_dealer->saveFile(&stateFile); 0738 } else { 0739 // If there's a game in progress and we aren't going to save it 0740 // then record its statistics, since the DealerScene will be destroyed 0741 // shortly. 0742 m_dealer->recordGameStatistics(); 0743 } 0744 } 0745 0746 KXmlGuiWindow::closeEvent(e); 0747 } 0748 0749 void MainWindow::newNumberedDeal() 0750 { 0751 if (!m_dealDialog) { 0752 m_dealDialog = new NumberedDealDialog(this); 0753 connect(m_dealDialog, &NumberedDealDialog::dealChosen, this, &MainWindow::startNumbered); 0754 } 0755 0756 if (m_dealer) { 0757 m_dealDialog->setGameType(m_dealer->oldId()); 0758 m_dealDialog->setDealNumber(m_dealer->gameNumber()); 0759 } 0760 0761 m_dealDialog->show(); 0762 } 0763 0764 void MainWindow::startNumbered(int gameId, int dealNumber) 0765 { 0766 if (!m_dealer || m_dealer->allowedToStartNewGame()) { 0767 setGameType(gameId); 0768 startNew(dealNumber); 0769 } 0770 } 0771 0772 void MainWindow::nextDeal() 0773 { 0774 if (!m_dealer) 0775 newNumberedDeal(); 0776 else if (m_dealer->allowedToStartNewGame()) 0777 startNew(m_dealer->gameNumber() == INT_MAX ? 1 : m_dealer->gameNumber() + 1); 0778 } 0779 0780 void MainWindow::previousDeal() 0781 { 0782 if (!m_dealer) 0783 newNumberedDeal(); 0784 else if (m_dealer->allowedToStartNewGame()) 0785 startNew(m_dealer->gameNumber() == 1 ? INT_MAX : m_dealer->gameNumber() - 1); 0786 } 0787 0788 bool MainWindow::loadGame(const QUrl &url, bool addToRecentFiles) 0789 { 0790 KIO::StoredTransferJob *job = KIO::storedGet(url); 0791 if (!job->exec()) { 0792 KMessageBox::error(this, i18n("Downloading file failed: %1", job->errorString())); 0793 return false; 0794 } 0795 0796 QXmlStreamReader xml(job->data()); 0797 if (!xml.readNextStartElement()) { 0798 KMessageBox::error(this, i18n("Error reading XML file: ") + xml.errorString()); 0799 return false; 0800 } 0801 0802 int gameId = -1; 0803 bool isLegacyFile; 0804 0805 if (xml.name() == QLatin1String("dealer")) { 0806 isLegacyFile = true; 0807 bool ok; 0808 int id = xml.attributes().value(QStringLiteral("id")).toString().toInt(&ok); 0809 if (ok) 0810 gameId = id; 0811 } else if (xml.name() == QLatin1String("kpat-game")) { 0812 isLegacyFile = false; 0813 gameId = DealerInfoList::self()->gameIdForFile(xml); 0814 } else { 0815 KMessageBox::error(this, i18n("XML file is not a KPat save.")); 0816 return false; 0817 } 0818 0819 if (!m_dealer_map.contains(gameId)) { 0820 KMessageBox::error(this, i18n("Unrecognized game id.")); 0821 return false; 0822 } 0823 0824 // Only bother the user to ask for permission after we've determined the 0825 // save game file is at least somewhat valid. 0826 if (m_dealer && !m_dealer->allowedToStartNewGame()) 0827 return false; 0828 0829 setGameType(gameId); 0830 0831 xml.clear(); 0832 0833 QBuffer buffer; 0834 buffer.setData(job->data()); 0835 buffer.open(QBuffer::ReadOnly); 0836 bool success = isLegacyFile ? m_dealer->loadLegacyFile(&buffer) : m_dealer->loadFile(&buffer); 0837 0838 if (!success) { 0839 KMessageBox::error(this, i18n("Errors encountered while parsing file.")); 0840 slotShowGameSelectionScreen(); 0841 return false; 0842 } 0843 0844 setGameCaption(); 0845 0846 if (addToRecentFiles) 0847 m_recentFilesAction->addUrl(url); 0848 0849 return true; 0850 } 0851 0852 void MainWindow::loadGame() 0853 { 0854 QPointer<QFileDialog> dialog = new QFileDialog(this); 0855 dialog->selectUrl(dialogUrl); 0856 dialog->setAcceptMode(QFileDialog::AcceptOpen); 0857 dialog->setMimeTypeFilters(QStringList() << saveFileMimeType << legacySaveFileMimeType << QStringLiteral("application/octet-stream")); 0858 dialog->setWindowTitle(i18n("Load")); 0859 0860 if (dialog->exec() == QFileDialog::Accepted) { 0861 if (dialog) { 0862 QUrl url = dialog->selectedUrls().at(0); 0863 if (!url.isEmpty()) 0864 loadGame(url); 0865 } 0866 } 0867 delete dialog; 0868 } 0869 0870 void MainWindow::loadGame(const QUrl &url) 0871 { 0872 loadGame(url, true); 0873 } 0874 0875 void MainWindow::saveGame() 0876 { 0877 if (!m_dealer) 0878 return; 0879 0880 QPointer<QFileDialog> dialog = new QFileDialog(this); 0881 dialog->selectUrl(dialogUrl); 0882 dialog->setAcceptMode(QFileDialog::AcceptSave); 0883 dialog->setMimeTypeFilters(QStringList() << saveFileMimeType << legacySaveFileMimeType); 0884 dialog->setOption(QFileDialog::DontConfirmOverwrite, false); 0885 dialog->setWindowTitle(i18n("Save")); 0886 if (dialog->exec() != QFileDialog::Accepted) 0887 return; 0888 0889 QUrl url; 0890 if (dialog) { 0891 url = dialog->selectedUrls().at(0); 0892 if (url.isEmpty()) 0893 return; 0894 } 0895 0896 QFile localFile; 0897 QTemporaryFile tempFile; 0898 if (url.isLocalFile()) { 0899 localFile.setFileName(url.toLocalFile()); 0900 if (!localFile.open(QFile::WriteOnly)) { 0901 KMessageBox::error(this, i18n("Error opening file for writing. Saving failed.")); 0902 return; 0903 } 0904 } else { 0905 if (!tempFile.open()) { 0906 KMessageBox::error(this, i18n("Unable to create temporary file. Saving failed.")); 0907 return; 0908 } 0909 } 0910 QFile &file = url.isLocalFile() ? localFile : tempFile; 0911 if (dialog && dialog->selectedNameFilter() == legacySaveFileMimeType) { 0912 m_dealer->saveLegacyFile(&file); 0913 } else { 0914 m_dealer->saveFile(&file); 0915 } 0916 file.close(); 0917 0918 if (!url.isLocalFile()) { 0919 tempFile.open(); 0920 KIO::StoredTransferJob *job = KIO::storedPut(&tempFile, url, -1); 0921 if (!job->exec()) { 0922 KMessageBox::error(this, i18n("Error uploading file. Saving failed: %1", job->errorString())); 0923 return; 0924 } 0925 } 0926 0927 m_recentFilesAction->addUrl(url); 0928 } 0929 0930 void MainWindow::showStats() 0931 { 0932 QPointer<StatisticsDialog> dlg = new StatisticsDialog(this); 0933 if (m_dealer) 0934 dlg->showGameType(m_dealer->oldId()); 0935 dlg->exec(); 0936 delete dlg; 0937 } 0938 0939 void MainWindow::updateSolverDescription(const QString &text) 0940 { 0941 m_solverStatusLabel->setText(text); 0942 } 0943 0944 void MainWindow::slotUpdateMoves(int moves) 0945 { 0946 m_moveCountStatusLabel->setText(i18np("1 move", "%1 moves", moves)); 0947 } 0948 0949 void MainWindow::slotSnapshot() 0950 { 0951 if (m_dealer_it == m_dealer_map.constEnd()) { 0952 // first call 0953 m_dealer_it = m_dealer_map.constBegin(); 0954 } 0955 0956 setGameType(m_dealer_it.key()); 0957 m_dealer->setAutoDropEnabled(false); 0958 startRandom(); 0959 0960 QTimer::singleShot(1000, this, &MainWindow::slotSnapshot2); 0961 } 0962 0963 void MainWindow::slotSnapshot2() 0964 { 0965 m_dealer->createDump().save(QStringLiteral("%1.png").arg(m_dealer->gameId())); 0966 0967 ++m_dealer_it; 0968 if (m_dealer_it != m_dealer_map.constEnd()) 0969 QTimer::singleShot(0, this, &MainWindow::slotSnapshot); 0970 } 0971 0972 void MainWindow::generateThemePreview() 0973 { 0974 const QSize previewSize(240, 160); 0975 m_view->setMinimumSize(previewSize); 0976 m_view->setMaximumSize(previewSize); 0977 0978 QImage img(m_view->contentsRect().size(), QImage::Format_ARGB32); 0979 img.fill(Qt::transparent); 0980 QPainter p(&img); 0981 0982 const auto cards = m_cardDeck->cards(); 0983 for (KCard *c : cards) 0984 c->completeAnimation(); 0985 0986 m_view->render(&p); 0987 0988 slotShowGameSelectionScreen(); 0989 0990 QRect leftHalf(0, 0, m_view->contentsRect().width() / 2, m_view->contentsRect().height()); 0991 m_view->render(&p, leftHalf, leftHalf); 0992 0993 p.end(); 0994 0995 img.save(QStringLiteral("preview.png")); 0996 0997 m_view->setMinimumSize(0, 0); 0998 m_view->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); 0999 } 1000 1001 #include "moc_mainwindow.cpp"