File indexing completed on 2024-05-12 08:00:35

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"