File indexing completed on 2024-04-28 04:04:47

0001 /***************************************************************************
0002  *   Copyright 2005-2007 Francesco Rossi <redsh@email.it>                  *
0003  *   Copyright 2006-2007 Mick Kappenburg <ksudoku@kappendburg.net>         *
0004  *   Copyright 2006-2008 Johannes Bergmeier <johannes.bergmeier@gmx.net>   *
0005  *   Copyright 2012,2015 Ian Wadham <iandw.au@gmail.com>                   *
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or modify  *
0008  *   it under the terms of the GNU General Public License as published by  *
0009  *   the Free Software Foundation; either version 2 of the License, or     *
0010  *   (at your option) any later version.                                   *
0011  *                                                                         *
0012  *   This program is distributed in the hope that it will be useful,       *
0013  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0014  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0015  *   GNU General Public License for more details.                          *
0016  *                                                                         *
0017  *   You should have received a copy of the GNU General Public License     *
0018  *   along with this program; if not, write to the                         *
0019  *   Free Software Foundation, Inc.,                                       *
0020  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
0021  ***************************************************************************/
0022 
0023 #include "ksudoku.h"
0024 #include "ksudoku_logging.h"
0025 #include "globals.h"
0026 
0027 #include <QAction>
0028 #include <QComboBox>
0029 #include <QDir>
0030 #include <QDragEnterEvent>
0031 #include <QDropEvent>
0032 #include <QFileDialog>
0033 #include <QFileInfo>
0034 #include <QMimeData>
0035 #include <QLabel>
0036 #include <QHBoxLayout>
0037 #include <QKeySequence>
0038 #include <QStandardPaths>
0039 #include <QStatusBar>
0040 #include <QUrl>
0041 
0042 
0043 #include <KActionCollection>
0044 #include <KConfigDialog>
0045 #include <KLocalizedString>
0046 #include <KMessageBox>
0047 #include <KSharedConfig>
0048 #include <KStandardAction>
0049 #include <KTar>
0050 
0051 #include <KGameStandardAction>
0052 #include <KGameThemeSelector>
0053 #include <KGameDifficulty>
0054 
0055 #include "ksview.h"
0056 #include "gameactions.h"
0057 #include "renderer.h"
0058 
0059 #include "puzzle.h" // TODO
0060 #include "skgraph.h"
0061 #include "serializer.h"
0062 
0063 #include "puzzleprinter.h"
0064 
0065 #include "gamevariants.h"
0066 #include "welcomescreen.h"
0067 #include "valuelistwidget.h"
0068 
0069 #include "settings.h"
0070 #include "config.h"
0071 
0072 using namespace ksudoku;
0073 
0074 void KSudoku::onCompleted(bool isCorrect, QTime required, bool withHelp) {
0075     if(!isCorrect) {
0076         KMessageBox::information(this, i18n("Sorry, your solution contains mistakes.\n\nEnable \"Show errors\" in the settings to highlight them."));
0077         return;
0078     }
0079 
0080     QString msg;
0081     int secs = QTime(0,0).secsTo(required);
0082     int mins = secs / 60;
0083     secs = secs % 60;
0084 
0085     if(withHelp)
0086         if (mins == 0)
0087             msg = i18np("Congratulations! You made it in 1 second. With some tricks.", "Congratulations! You made it in %1 seconds. With some tricks.", secs);
0088         else if (secs == 0)
0089             msg = i18np("Congratulations! You made it in 1 minute. With some tricks.", "Congratulations! You made it in %1 minutes. With some tricks.", mins);
0090         else
0091             msg = i18nc("The two parameters are strings like '2 minutes' or '1 second'.", "Congratulations! You made it in %1 and %2. With some tricks.", i18np("1 minute", "%1 minutes", mins), i18np("1 second", "%1 seconds", secs));
0092     else
0093         if (mins == 0)
0094             msg = i18np("Congratulations! You made it in 1 second.", "Congratulations! You made it in %1 seconds.", secs);
0095         else if (secs == 0)
0096             msg = i18np("Congratulations! You made it in 1 minute.", "Congratulations! You made it in %1 minutes.", mins);
0097         else
0098             msg = i18nc("The two parameters are strings like '2 minutes' or '1 second'.", "Congratulations! You made it in %1 and %2.", i18np("1 minute", "%1 minutes", mins), i18np("1 second", "%1 seconds", secs));
0099 
0100     onModified(true); // make sure buttons have the correct enabled state
0101 
0102     KMessageBox::information(this, msg);
0103 
0104 }
0105 
0106 // void KSudoku::updateStatusBar()
0107 // {
0108 //  QString m="";
0109 // //   QWidget* current = m_tabs->currentPage();
0110 // //   if(KsView* view = dynamic_cast<KsView*>(current))
0111 // //       m = view->status();
0112 // //   if(currentView())
0113 // //       m = currentView()->status();
0114 //
0115 //  // TODO fix this: add new status bar generation code
0116 //
0117 //  statusBar()->showMessage(m);
0118 // }
0119 
0120 KSudoku::KSudoku()
0121     :
0122     KXmlGuiWindow(),
0123     m_gameVariants(new GameVariantCollection(this, true)),
0124     m_puzzlePrinter(nullptr)
0125 {
0126     setObjectName( QStringLiteral("ksudoku" ));
0127 
0128     m_gameWidget = nullptr;
0129     m_gameUI = nullptr;
0130 
0131     m_gameActions = nullptr;
0132 
0133     // then, setup our actions
0134     setupActions();
0135 
0136     setupGUI(ToolBar | Keys | Save | Create | StatusBar);
0137 
0138     wrapper = new QWidget();
0139     (void) new QHBoxLayout(wrapper);
0140     QMainWindow::setCentralWidget(wrapper);
0141     wrapper->show();
0142 
0143     // Create ValueListWidget
0144     m_valueListWidget = new ValueListWidget(wrapper);
0145     wrapper->layout()->addWidget(m_valueListWidget);
0146     m_valueListWidget->setFixedWidth(60);
0147 
0148     m_welcomeScreen = new WelcomeScreen(wrapper, m_gameVariants);
0149     wrapper->layout()->addWidget(m_welcomeScreen);
0150     connect(m_welcomeScreen, &ksudoku::WelcomeScreen::newGameStarted, this, &KSudoku::startGame);
0151 
0152     setupStatusBar(m_welcomeScreen->difficulty(),
0153                m_welcomeScreen->symmetry());
0154 
0155     showWelcomeScreen();
0156 
0157     updateShapesList();
0158 
0159     connect(Renderer::instance()->themeProvider(), &KGameThemeProvider::currentThemeChanged, this, &KSudoku::updateSettings);
0160 
0161 //  QTimer *timer = new QTimer( this );
0162 //  connect( timer, SIGNAL(timeout()), this, SLOT(updateStatusBar()) );
0163 //  updateStatusBar();
0164 //  timer->start( 1000); //TODO PORT, false ); // 2 seconds single-shot timer
0165 }
0166 
0167 KSudoku::~KSudoku()
0168 {
0169     delete m_puzzlePrinter;
0170     endCurrentGame();
0171 }
0172 
0173 void KSudoku::updateShapesList()
0174 {
0175     // TODO clear the list
0176     GameVariant* variant = nullptr;
0177 
0178     variant = new SudokuGame(i18n("Sudoku Standard (9x9)"), 9, m_gameVariants);
0179     variant->setDescription(i18n("The classic and fashionable game"));
0180     variant->setIcon(QStringLiteral("ksudoku-ksudoku_9x9"));
0181 #ifdef OPENGL_SUPPORT
0182     variant = new RoxdokuGame(i18n("Roxdoku 9 (3x3x3)"), 9, m_gameVariants);
0183     variant->setDescription(i18n("The Rox 3D Sudoku"));
0184     variant->setIcon(QStringLiteral("ksudoku-roxdoku_3x3x3"));
0185 #endif
0186 
0187     const QStringList gamevariantdirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("ksudoku"), QStandardPaths::LocateDirectory);
0188 
0189     QFileInfoList filepaths;
0190     for (const QString& gamevariantdir : gamevariantdirs) {
0191         const auto fileNames = QDir(gamevariantdir).entryInfoList(QStringList() << QStringLiteral("*.desktop"), QDir::Files | QDir::Readable | QDir::NoDotAndDotDot);
0192         filepaths.append(fileNames);
0193     }
0194 
0195     QString variantName;
0196     QString variantDescr;
0197     QString variantDataPath;
0198     QString variantIcon;
0199 
0200     for (const QFileInfo &configFileInfo : std::as_const(filepaths)) {
0201         const QDir variantDir = configFileInfo.dir();
0202         KConfig variantConfig(configFileInfo.filePath(), KConfig::SimpleConfig);
0203         KConfigGroup group = variantConfig.group (QStringLiteral("KSudokuVariant"));
0204 
0205         variantName = group.readEntry("Name", i18n("Missing Variant Name")); // Translated.
0206         variantDescr = group.readEntry("Description", ""); // Translated.
0207         variantIcon = group.readEntry("Icon", "ksudoku-ksudoku_9x9");
0208         const QString variantDataFile = group.readEntry("FileName", "");
0209         if(variantDataFile == QLatin1String("")) continue;
0210 
0211         variantDataPath = variantDir.filePath(variantDataFile);
0212 
0213         variant = new CustomGame(variantName, QUrl::fromLocalFile(variantDataPath), m_gameVariants);
0214         variant->setDescription(variantDescr);
0215         variant->setIcon(variantIcon);
0216     }
0217 
0218     // Put variants first and extra sizes last.
0219     variant = new SudokuGame(i18n("Sudoku 16x16"), 16, m_gameVariants);
0220     variant->setDescription(i18n("Sudoku with 16 symbols"));
0221     variant->setIcon(QStringLiteral("ksudoku-ksudoku_16x16"));
0222     variant = new SudokuGame(i18n("Sudoku 25x25"), 25, m_gameVariants);
0223     variant->setDescription(i18n("Sudoku with 25 symbols"));
0224     variant->setIcon(QStringLiteral("ksudoku-ksudoku_25x25"));
0225 #ifdef OPENGL_SUPPORT
0226     variant = new RoxdokuGame(i18n("Roxdoku 16 (4x4x4)"), 16, m_gameVariants);
0227     variant->setDescription(i18n("The Rox 3D sudoku with 16 symbols"));
0228     variant->setIcon(QStringLiteral("ksudoku-roxdoku_4x4x4"));
0229     variant = new RoxdokuGame(i18n("Roxdoku 25 (5x5x5)"), 25, m_gameVariants);
0230     variant->setDescription(i18n("The Rox 3D sudoku with 25 symbols"));
0231     variant->setIcon(QStringLiteral("ksudoku-roxdoku_5x5x5"));
0232 #endif
0233 }
0234 
0235 void KSudoku::startGame(const Game& game) {
0236     m_welcomeScreen->hide();
0237     endCurrentGame();
0238 
0239 
0240     auto* view = new KsView(game, m_gameActions, this);
0241 
0242     view->setValueListWidget(m_valueListWidget);
0243     view->createView();
0244 
0245     connect(view, &KsView::valueSelected, m_valueListWidget, &ksudoku::ValueListWidget::selectValue);
0246     connect(m_valueListWidget, &ksudoku::ValueListWidget::valueSelected, view, &KsView::selectValue);
0247 //  connect(view, SIGNAL(valueSelected(int)), SLOT(updateStatusBar()));
0248 
0249     QWidget* widget = view->widget();
0250     m_gameUI = view;
0251     Game g = currentGame();
0252     g.setMessageParent(view->widget());
0253 
0254     wrapper->layout()->addWidget(widget);
0255     widget->show();
0256     widget->setFocus();
0257 
0258     connect(game.interface(), &GameIFace::completed, this, &KSudoku::onCompleted);
0259     connect(game.interface(), &GameIFace::modified, this, &KSudoku::onModified);
0260 
0261     adaptActions2View();
0262 
0263     QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0264     policy.setHorizontalStretch(1);
0265     policy.setVerticalStretch(1);
0266     widget->setSizePolicy(policy);
0267 
0268     m_valueListWidget->setMaxValue(view->game().order());
0269     m_valueListWidget->selectValue(1);
0270     m_valueListWidget->show();
0271 
0272     SudokuType t = game.puzzle()->graph()->specificType();
0273     bool playing = game.puzzle()->hasSolution();
0274     if (playing && (t == Mathdoku)) {
0275         KMessageBox::information (this,
0276         i18n("Mathdoku puzzles can have any size from 3x3 up to 9x9. "
0277              "The solution is a grid in which every row and every "
0278              "column contains the available digits (1-3 up to 1-9) "
0279              "exactly once. The grid is covered with irregularly "
0280              "shaped cages.\n"
0281              "\n"
0282              "Cages of size 1 are starting-values or clues, but there "
0283              "are not many of them. Cages of larger size have a target "
0284              "value and an arithmetic operator (+-x/). The digits in "
0285              "the cage must combine together, using the operator, to "
0286              "reach the target value, e.g. '12x' means that the digits "
0287              "must multiply together to make 12. A digit can occur "
0288              "more than once in a cage, provided it occurs in "
0289              "different rows and columns.\n"
0290              "\n"
0291              "In general, larger Mathdokus are more difficult and so "
0292              "are larger cages. You can select the puzzle size in "
0293              "KSudoku's Settings dialog and the maximum cage-size by "
0294              "using KSudoku's Difficulty button."),
0295         i18n("Playing Mathdoku"), QStringLiteral("PlayingMathdoku"));
0296     }
0297     else if (playing && (t == KillerSudoku)) {
0298         KMessageBox::information (this,
0299         i18n("Killer Sudoku puzzles can have sizes 4x4 or 9x9, with "
0300              "either four 2x2 blocks or nine 3x3 blocks. The solution "
0301              "must follow Classic Sudoku rules. The difference is that "
0302              "there are few starting-values or clues (if any). Instead "
0303              "the grid is covered with irregularly shaped cages.\n"
0304              "\n"
0305              "Cages of size 1 are starting-values or clues. Cages of "
0306              "larger size have a target value and the digits in them "
0307              "must add up to that value. In Killer Sudoku, a cage "
0308              "cannot contain any digit more than once.\n"
0309              "\n"
0310              "In general, larger cages are more difficult. You can "
0311              "select the maximum cage-size by using KSudoku's "
0312              "Difficulty button."),
0313         i18n("Playing Killer Sudoku"), QStringLiteral("PlayingKillerSudoku"));
0314     }
0315     else if ((t == Mathdoku) || (t == KillerSudoku)) {
0316         KMessageBox::information (this,
0317         i18n("Mathdoku and Killer Sudoku puzzles have to be keyed in "
0318              "by working on one cage at a time. To start a cage, left "
0319              "click on any unused cell or enter a number in the cell "
0320              "that is under the cursor or enter + - / or x there. A "
0321              "small cage-label will appear in that cell. To extend the "
0322              "cage in any direction, left-click on a neigbouring cell "
0323              "or move the cursor there and type a Space.\n"
0324              "\n"
0325              "The number you type is the cage's value and it can have "
0326              "one or more digits, including zero. A cell of size 1 has "
0327              "to have a 1-digit number, as in a normal Sudoku puzzle. "
0328              "It becomes a starting-value or clue for the player.\n"
0329              "\n"
0330              "The + - / or x is the operator (Add, Subtract, Divide or "
0331              "Multiply). You must have one in cages of size 2 or more. "
0332              "In Killer Sudoku, the operator is provided automatically "
0333              "because it is always + or none.\n"
0334              "\n"
0335              "You can enter digits, operators and cells in any order. "
0336              "To complete the cage and start another cage, always "
0337              "press Return. If you make a mistake, the only thing to "
0338              "do is delete a whole cage and re-enter it. Use right "
0339              "click in the current cage or any earlier cage, if you "
0340              "wish to delete it. Alternatively, use the cursor and the "
0341              "Delete or Backspace key.\n"
0342              "\n"
0343              "When the grid is filled with cages, hit the Check "
0344              "button, to solve the puzzle and make sure there is only "
0345              "one solution. If the check fails, you have probably made "
0346              "an error somewhere in one of the cages."),
0347         i18n("Data-entry for Puzzles with Cages"),
0348         QStringLiteral("CageDataEntry"));
0349     }
0350 }
0351 
0352 void KSudoku::endCurrentGame() {
0353     m_valueListWidget->hide();
0354 
0355     delete m_gameUI;
0356     m_gameUI = nullptr;
0357 
0358     adaptActions2View();
0359 
0360 }
0361 
0362 void KSudoku::loadGame(const QUrl& Url) {
0363     QString errorMsg;
0364     const Game game = ksudoku::Serializer::load(Url, this, errorMsg);
0365     if(!game.isValid()) {
0366         KMessageBox::information(this, errorMsg);
0367         return;
0368     }
0369 
0370     startGame(game);
0371 }
0372 
0373 void KSudoku::showWelcomeScreen() {
0374     endCurrentGame();
0375 
0376     m_welcomeScreen->show();
0377 }
0378 
0379 void KSudoku::giveHint()
0380 {
0381     Game game = currentGame();
0382     if(!game.isValid()) return;
0383     game.giveHint();
0384 }
0385 
0386 void KSudoku::autoSolve()
0387 {
0388     Game game = currentGame();
0389     if(!game.isValid()) return;
0390     game.autoSolve();
0391 }
0392 
0393 // Check the game setup, copy the puzzle, init and solve the copy and show the
0394 // result (i.e. implement the "Check" action). If the user agrees, start play.
0395 
0396 void KSudoku::dubPuzzle()
0397 {
0398     Game game = currentGame();
0399 
0400     if(!game.isValid()) return;
0401 
0402     if(!game.simpleCheck()) {
0403         KMessageBox::information(this, i18n("The puzzle you entered contains some errors."));
0404         return;
0405     }
0406 
0407     // Create a new Puzzle object, with same Graph and solution flag = true.
0408     ksudoku::Puzzle* puzzle = game.puzzle()->dubPuzzle();
0409 
0410     // Copy the given values of the puzzle, then run it through the solver.
0411     // The solution, if valid, is saved in puzzle->m_solution2.
0412     int state = puzzle->init(game.allValues());
0413 
0414     if(state <= 0) {
0415         KMessageBox::information (this,
0416             i18n("Sorry, no solutions have been found. Please check "
0417              "that you have entered in the puzzle completely and "
0418              "correctly."), i18n("Check Puzzle"));
0419         delete puzzle;
0420         return;
0421     } else if(state == 1) {
0422         KMessageBox::information (this,
0423             i18n("The Puzzle you entered has a unique solution and "
0424              "is ready to be played."), i18n("Check Puzzle"));
0425     } else {
0426         KMessageBox::information (this,
0427             i18n("The Puzzle you entered has multiple solutions. "
0428              "Please check that you have entered in the puzzle "
0429              "completely and correctly."), i18n("Check Puzzle"));
0430     }
0431 
0432     if(KMessageBox::questionTwoActions(this, i18n("Do you wish to play the puzzle now?"), i18n("Play Puzzle"), KGuiItem(i18n("Play")), KStandardGuiItem::cancel() ) == KMessageBox::PrimaryAction)
0433     {
0434         startGame(ksudoku::Game(puzzle));
0435     }
0436     else
0437     {
0438         delete puzzle;
0439     }
0440 
0441     return;
0442 }
0443 
0444 void KSudoku::genMultiple()
0445 {
0446     //KMessageBox::information(this, i18n("Sorry, this feature is under development."));
0447 }
0448 
0449 void KSudoku::setupActions()
0450 {
0451     m_gameActions = new ksudoku::GameActions(actionCollection(), this);
0452     m_gameActions->init();
0453 
0454     QKeySequence shortcut;
0455 
0456     setAcceptDrops(true);
0457 
0458     KGameStandardAction::gameNew(this, &KSudoku::gameNew, actionCollection());
0459     KGameStandardAction::restart(this, &KSudoku::gameRestart, actionCollection());
0460     KGameStandardAction::load(this, &KSudoku::gameOpen, actionCollection());
0461     m_gameSave = KGameStandardAction::save(this, &KSudoku::gameSave, actionCollection());
0462     m_gameSaveAs = KGameStandardAction::saveAs(this, &KSudoku::gameSaveAs, actionCollection());
0463     KGameStandardAction::print(this, &KSudoku::gamePrint, actionCollection());
0464     KGameStandardAction::quit(this, &KSudoku::close, actionCollection());
0465     // TODO Export is disabled due to missing port to KDE4.
0466 //  createAction("game_export", SLOT(gameExport()), i18n("Export"));
0467 
0468     KStandardAction::preferences(this, &KSudoku::optionsPreferences, actionCollection());
0469     // Settings: enable messages that the user marked "Do not show again".
0470     auto* enableMessagesAct = new QAction(i18n("Enable all messages"),this);
0471     actionCollection()->addAction(QStringLiteral("enable_messages"), enableMessagesAct);
0472     connect(enableMessagesAct, &QAction::triggered, this, &KSudoku::enableMessages);
0473 
0474     //History
0475     KGameStandardAction::undo(this, &KSudoku::undo, actionCollection());
0476     KGameStandardAction::redo(this, &KSudoku::redo, actionCollection());
0477 
0478     QAction * a = KGameStandardAction::hint(this, &KSudoku::giveHint, actionCollection());
0479     // The default value (H) conflicts with the keys assigned
0480     // to add letter/numbers to the board.
0481     KActionCollection::setDefaultShortcut(a, QKeySequence(Qt::Key_F2));
0482 
0483     KGameStandardAction::solve(this, &KSudoku::autoSolve, actionCollection());
0484 
0485     a = new QAction(this);
0486     actionCollection()->addAction( QStringLiteral( "move_dub_puzzle" ), a);
0487     a->setText(i18n("Check"));
0488     a->setIcon(QIcon::fromTheme( QStringLiteral( "games-endturn" )));
0489     connect(a, &QAction::triggered, this, &KSudoku::dubPuzzle);
0490     addAction(a);
0491 }
0492 
0493 void KSudoku::setupStatusBar (int difficulty, int symmetry)
0494 {
0495     // Use the standard combo box for difficulty, from KDE Games library.
0496     KGameDifficulty::global()->addStandardLevelRange(KGameDifficultyLevel::VeryEasy, KGameDifficultyLevel::Hard);
0497     // and add our custom ones
0498     enum CustomHardness {
0499         DiabolicalHardness = KGameDifficultyLevel::Hard + 1,
0500         UnlimitedHardness
0501     };
0502     KGameDifficulty::global()->addLevel(new KGameDifficultyLevel(DiabolicalHardness, "Diabolical",
0503         i18nc("A level of difficulty in Sudoku puzzles", "Diabolical")));
0504     KGameDifficulty::global()->addLevel(new KGameDifficultyLevel(UnlimitedHardness, "Unlimited",
0505         i18nc("A level of difficulty in Sudoku puzzles", "Unlimited")));
0506 
0507     // Set default value of difficulty
0508     const KGameDifficultyLevel * level = KGameDifficulty::global()->levels().value(difficulty);
0509     if (level) {
0510         KGameDifficulty::global()->select(level);
0511     }
0512 
0513     connect(KGameDifficulty::global(), &KGameDifficulty::currentLevelChanged,
0514         this, &KSudoku::handleCurrentDifficultyLevelChanged);
0515 
0516     statusBar()->addPermanentWidget (new QLabel (i18nc("@option drop down box", "Difficulty:")));
0517     KGameDifficultyGUI::init(this);
0518 
0519     // Set up a combo box for symmetry of puzzle layout.
0520     statusBar()->addPermanentWidget (new QLabel (i18nc("@option drop down box", "Symmetry:")));
0521     auto * symmetryBox = new QComboBox (this);
0522     QObject::connect(symmetryBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &KSudoku::symmetryChanged);
0523     symmetryBox->setToolTip(i18nc(
0524         "Symmetry of layout of clues when puzzle starts", "Symmetry"));
0525     symmetryBox->setWhatsThis(i18n(
0526         "The symmetry of layout of the clues when the puzzle starts"));
0527     statusBar()->addPermanentWidget (symmetryBox);
0528     symmetryBox->addItem(i18nc("Symmetry of layout of clues", "Diagonal"));
0529     symmetryBox->addItem(i18nc("Symmetry of layout of clues", "Central"));
0530     symmetryBox->addItem(i18nc("Symmetry of layout of clues", "Left-Right"));
0531     symmetryBox->addItem(i18nc("Symmetry of layout of clues", "Spiral"));
0532     symmetryBox->addItem(i18nc("Symmetry of layout of clues", "Four-Way"));
0533     symmetryBox->addItem(i18nc("Symmetry of layout of clues", "Random Choice"));
0534     symmetryBox->addItem(i18n("No Symmetry"));
0535     symmetryBox->setCurrentIndex (symmetry);
0536 }
0537 
0538 void KSudoku::adaptActions2View() {
0539     Game game = currentGame();
0540 
0541     m_gameSave->setEnabled(game.isValid());
0542     m_gameSaveAs->setEnabled(game.isValid());
0543     action(QStringLiteral("game_new"))->setEnabled(game.isValid());
0544     action(QStringLiteral("game_restart"))->setEnabled(game.isValid());
0545     action(QStringLiteral("game_print"))->setEnabled(game.isValid());
0546     if(game.isValid()) {
0547         bool isEnterPuzzleMode = !game.puzzle()->hasSolution();
0548         action(QStringLiteral("move_hint"))->setVisible(!isEnterPuzzleMode);
0549         action(QStringLiteral("move_solve"))->setVisible(!isEnterPuzzleMode);
0550         action(QStringLiteral("move_dub_puzzle"))->setVisible(isEnterPuzzleMode);
0551 
0552         action(QStringLiteral("move_undo"))->setEnabled(game.canUndo());
0553         action(QStringLiteral("move_redo"))->setEnabled(game.canRedo());
0554 
0555         action(QStringLiteral("move_hint"))      ->setEnabled(   game.puzzle()->hasSolution());
0556         action(QStringLiteral("move_solve"))     ->setEnabled(   game.puzzle()->hasSolution());
0557         action(QStringLiteral("move_dub_puzzle"))->setEnabled( ! game.puzzle()->hasSolution());
0558     } else {
0559         action(QStringLiteral("move_undo"))->setEnabled(false);
0560         action(QStringLiteral("move_redo"))->setEnabled(false);
0561 
0562         action(QStringLiteral("move_hint"))->setVisible(false);
0563         action(QStringLiteral("move_solve"))->setVisible(false);
0564         action(QStringLiteral("move_dub_puzzle"))->setVisible(false);
0565     }
0566 }
0567 
0568 void KSudoku::onModified(bool /*isModified*/) {
0569     Game game = currentGame();
0570     if(game.isValid()) {
0571         action(QStringLiteral("move_undo"))->setEnabled(game.canUndo());
0572         action(QStringLiteral("move_redo"))->setEnabled(game.canRedo());
0573         action(QStringLiteral("move_hint"))->setEnabled(!game.allValuesSetAndUsable());
0574         action(QStringLiteral("move_solve"))->setEnabled(!game.wasFinished());
0575     }
0576 }
0577 
0578 void KSudoku::undo() {
0579     Game game = currentGame();
0580     if(!game.isValid()) return;
0581 
0582     game.interface()->undo();
0583 
0584     if(!game.canUndo()) {
0585         action(QStringLiteral("move_undo"))->setEnabled(false);
0586     }
0587 }
0588 
0589 void KSudoku::redo() {
0590     Game game = currentGame();
0591     if(!game.isValid()) return;
0592 
0593     game.interface()->redo();
0594 
0595     if(!game.canRedo()) {
0596         action(QStringLiteral("move_redo"))->setEnabled(false);
0597     }
0598 }
0599 
0600 void KSudoku::push()
0601 {
0602     // TODO replace this with history
0603 //  if(type == 0) {if(m_view) m_view->push();return;}
0604 //  if(glwin) glwin->push();
0605 }
0606 
0607 void KSudoku::pop()
0608 {
0609     // TODO replace this with history
0610 //  if(type == 0) {if(m_view)  m_view->pop(); return;}
0611 //  if(glwin) glwin->pop();
0612 }
0613 
0614 void KSudoku::dragEnterEvent(QDragEnterEvent * event)
0615 {
0616     // accept uri drops only
0617     if(event->mimeData()->hasUrls())
0618         event->accept();
0619 }
0620 
0621 void KSudoku::dropEvent(QDropEvent *event)
0622 {
0623     const QMimeData * data = event->mimeData();
0624     if(data->hasUrls())
0625     {
0626         QList<QUrl> Urls = data->urls();
0627 
0628         if ( !Urls.isEmpty() )
0629         {
0630             // okay, we have a URI.. process it
0631             const QUrl &Url = Urls.first();
0632 
0633             QString errorMsg;
0634             const Game game = ksudoku::Serializer::load(Url, this, errorMsg);
0635             if(game.isValid())
0636                 startGame(game);
0637             else
0638                 KMessageBox::error(this, errorMsg, i18n("Could not load game."));
0639         }
0640     }
0641 }
0642 
0643 void KSudoku::gameNew()
0644 {
0645     // this slot is called whenever the Game->New menu is selected,
0646     // the New shortcut is pressed (usually CTRL+N) or the New toolbar
0647     // button is clicked
0648 
0649     if(!currentView()) return;
0650 
0651     // only show question when the current game hasn't been finished until now
0652     if(!m_gameUI->game().wasFinished()) {
0653         if(KMessageBox::questionTwoActions(this,
0654                                   i18n("Do you really want to end this game in order to start a new one?"),
0655                                   i18nc("window title", "New Game"),
0656                                   KGuiItem(i18nc("button label", "New Game")),
0657                                   KStandardGuiItem::cancel() ) != KMessageBox::PrimaryAction)
0658             return;
0659     }
0660 
0661     showWelcomeScreen();
0662 }
0663 
0664 void KSudoku::gameRestart()
0665 {
0666     if (!currentView()) return;
0667 
0668     auto game = currentGame();
0669 
0670     // only show question when the current game hasn't been finished until now
0671     if (!game.wasFinished()) {
0672         if (KMessageBox::questionTwoActions(this,
0673                                 i18n("Do you really want to restart this game?"),
0674                                 i18nc("window title", "Restart Game"),
0675                                 KGuiItem(i18nc("button label", "Restart Game")),
0676                                 KStandardGuiItem::cancel() ) != KMessageBox::PrimaryAction) {
0677             return;
0678         }
0679     }
0680 
0681     game.restart();
0682 }
0683 
0684 void KSudoku::gameOpen()
0685 {
0686     // this slot is called whenever the Game->Open menu is selected,
0687     // the Open shortcut is pressed (usually CTRL+O) or the Open toolbar
0688     // button is clicked
0689     // standard filedialog
0690     const QUrl Url = QFileDialog::getOpenFileUrl(this, i18n("Open Location"), QUrl::fromLocalFile(QDir::homePath()), QString());
0691 
0692     if (!Url.isEmpty() && Url.isValid())
0693     {
0694         QString errorMsg;
0695         Game game = ksudoku::Serializer::load(Url, this, errorMsg);
0696         if(!game.isValid()) {
0697             KMessageBox::error(this, errorMsg, i18n("Could not load game."));
0698             return;
0699         }
0700 
0701         game.setUrl(Url);
0702 //      (new KSudoku(game))->show();
0703         startGame(game);
0704 //      delete game;
0705     }
0706 }
0707 
0708 void KSudoku::gameSave()
0709 {
0710     // this slot is called whenever the Game->Save menu is selected,
0711     // the Save shortcut is pressed (usually CTRL+S) or the Save toolbar
0712     // button is clicked
0713 
0714     // save the current file
0715 
0716     Game game = currentGame();
0717     if(!game.isValid()) return;
0718 
0719     if(game.getUrl().isEmpty()) game.setUrl(QFileDialog::getSaveFileUrl());
0720     if (!game.getUrl().isEmpty() && game.getUrl().isValid())
0721     {
0722         QString errorMsg;
0723         if (!ksudoku::Serializer::store(game, game.getUrl(), this, errorMsg))
0724             KMessageBox::error(this, errorMsg, i18n("Error Writing File"));
0725     }
0726 }
0727 
0728 void KSudoku::gameSaveAs()
0729 {
0730     // this slot is called whenever the Game->Save As menu is selected,
0731     Game game = currentGame();
0732     if(!game.isValid()) return;
0733 
0734     game.setUrl(QFileDialog::getSaveFileUrl());
0735     if (!game.getUrl().isEmpty() && game.getUrl().isValid())
0736         gameSave();
0737 }
0738 
0739 
0740 void KSudoku::gamePrint()
0741 {
0742     // This slot is called whenever the Game->Print action is selected.
0743     Game game = currentGame();
0744     if (! game.isValid()) {
0745         KMessageBox::information (this,
0746             i18n("There seems to be no puzzle to print."));
0747         return;
0748     }
0749     if (! m_puzzlePrinter) {
0750     m_puzzlePrinter = new PuzzlePrinter (this);
0751     }
0752     m_puzzlePrinter->print (game);
0753 }
0754 
0755 bool KSudoku::queryClose()
0756 {
0757     if (m_puzzlePrinter) {
0758     m_puzzlePrinter->endPrint();
0759     }
0760     return true;
0761 }
0762 
0763 void KSudoku::gameExport()
0764 {
0765     //TODO PORT
0766     /*
0767     Game game = currentGame();
0768     if(!game.isValid()) return;
0769 
0770     ksudoku::ExportDlg e(*game.puzzle(), *game.symbols() );
0771 
0772     e.exec();
0773     */
0774 }
0775 
0776 void KSudoku::optionsPreferences()
0777 {
0778     if ( KConfigDialog::showDialog(QStringLiteral("settings")) ) return;
0779 
0780     auto *dialog = new KConfigDialog(this, QStringLiteral("settings"), Settings::self());
0781 
0782     auto* gameConfig = new GameConfig();
0783     dialog->addPage(gameConfig, i18nc("Game Section in Config", "Game"), QStringLiteral("games-config-options"));
0784     dialog->addPage(new KGameThemeSelector(Renderer::instance()->themeProvider()), i18n("Theme"), QStringLiteral("games-config-theme"));
0785 
0786     //QT5 dialog->setHelp(QString(),"ksudoku");
0787     connect(dialog, &KConfigDialog::settingsChanged, this, &KSudoku::updateSettings);
0788     dialog->show();
0789 }
0790 
0791 void KSudoku::updateSettings() {
0792     KsView* view = currentView();
0793     if(view) {
0794         int order = view->game().order();
0795         m_valueListWidget->setMaxValue(order);
0796 
0797         view->settingsChanged();
0798     }
0799 
0800     Q_EMIT settingsChanged();
0801 }
0802 
0803 void KSudoku::handleCurrentDifficultyLevelChanged(const KGameDifficultyLevel *level)
0804 {
0805     const int difficulty = KGameDifficulty::global()->levels().indexOf(level);
0806 
0807     if (difficulty == -1) {
0808     return;
0809     }
0810 
0811     qCDebug(KSudokuLog) << "Set new difficulty =" << difficulty;
0812     m_welcomeScreen->setDifficulty(difficulty);
0813 
0814     if (difficulty == Unlimited) {
0815     KMessageBox::information (this,
0816         i18n("Warning: The Unlimited difficulty level has no limit on "
0817              "how many guesses or branch points are required to solve "
0818              "the puzzle and there is no lower limit on how soon "
0819              "guessing becomes necessary.\n\n"
0820              "Please also note that the generation of this type of puzzle "
0821              "might take much longer than other ones. During this time "
0822              "KSudoku will not respond."),
0823         i18n("Warning"), QStringLiteral("WarningReUnlimited"));
0824     }
0825 }
0826 
0827 void KSudoku::symmetryChanged (int symmetry)
0828 {
0829     qCDebug(KSudokuLog) << "Set symmetry =" << symmetry;
0830     m_welcomeScreen->setSymmetry(symmetry);
0831 }
0832 
0833 // void KSudoku::changeStatusbar(const QString& text)
0834 // {
0835 //     // display the text on the statusbar
0836 //     statusBar()->showMessage(text);
0837 // }
0838 
0839 void KSudoku::changeCaption(const QString& text)
0840 {
0841     // display the text on the caption
0842     setCaption(text);
0843 }
0844 
0845 Game KSudoku::currentGame() const {
0846     ksudoku::KsView* view = currentView();
0847 
0848     if(view)
0849         return view->game();
0850     else
0851         return Game();
0852 }
0853 
0854 ksudoku::KsView* KSudoku::currentView() const{
0855     return m_gameUI;
0856 }
0857 
0858 void KSudoku::enableMessages()
0859 {
0860     // Enable all messages that the user has marked "Do not show again".
0861     int result = KMessageBox::questionTwoActions(this,
0862                     i18n("This will enable all the dialogs that you had disabled by marking "
0863                          "the 'Do not show this message again' option.\n\n"
0864                          "Do you want to continue?"),
0865                     QString(),
0866                     KGuiItem(i18nc("@action:button", "Enable")),
0867                     KStandardGuiItem::cancel());
0868     if (result == KMessageBox::PrimaryAction) {
0869         KMessageBox::enableAllMessages();
0870         KSharedConfig::openConfig()->sync();    // Save the changes to disk.
0871     }
0872 }
0873 
0874 #if 0
0875 KSudokuNewStuff::KSudokuNewStuff( KSudoku* v ) :
0876         KNewStuff( "ksudoku", (QWidget*) v )
0877 {
0878     parent = v;
0879 }
0880 
0881 bool KSudokuNewStuff::install( const QString &fileName )
0882 {
0883     KTar archive( fileName );
0884     if ( !archive.open( QIODevice::ReadOnly ) )
0885         return false;
0886 
0887     const KArchiveDirectory *archiveDir = archive.directory();
0888     const QString destDir = QStandardPaths::writableLocation( QStandardPaths::GenericDataLocation ) + QStringLiteral("/ksudoku/");
0889     QDir().mkpath(destDir);
0890 
0891     archiveDir->copyTo(destDir);
0892     archive.close();
0893     //find custom shapes
0894     parent->updateShapesList();
0895     return true;
0896 }
0897 
0898 bool KSudokuNewStuff::createUploadFile( const QString &/*fileName*/ )
0899 {
0900     return true;
0901 }
0902 #endif
0903 
0904 #include "moc_ksudoku.cpp"