File indexing completed on 2025-04-20 03:48:22
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"