1 /*
2 This file is part of the KDE games lskat program
3 SPDX-FileCopyrightText: 2006 Martin Heni <kde@heni-online.de>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
8 #include "mainwindow.h"
9
10 // Include files for Qt
11 #include <QAction>
12 #include <QDir>
13 #include <QKeySequence>
14 #include <QPointer>
15 #include <QRandomGenerator>
16 #include <QStatusBar>
17 #include <QStandardPaths>
18
19 // KF includes
20 #include <KActionCollection>
21 #include <KConfigGroup>
22 #include <KMessageBox>
23 #include <KSharedConfig>
24 #include <KStandardGameAction>
25 #include <KLocalizedString>
26 #include <KSelectAction>
27 // Application specific includes
28 #include "lskat_debug.h"
29 #include "lskatglobal.h"
30 #include "gameview.h"
31 #include "abstractengine.h"
32 #include "engine_two.h"
33 #include "display_two.h"
34 #include "config_two.h"
35 #include "display_intro.h"
36 #include "deck.h"
37 #include "player.h"
38 #include "mouseinput.h"
39 #include "aiinput.h"
40 #include "namedialogwidget.h"
41 #include "fromlibkdegames/kcarddialog.h"
42 #include "fromlibkdegames/carddeckinfo.h"
43
44 // Configuration file
45 #include <config-src.h>
46
47 // Forward declarations
48 const int ADVANCE_PERIOD = 20;
49
50 // Shortcut to access the actions
51 #define ACTION(x) (actionCollection()->action(x))
52
53 using namespace InputDevice;
54
55 // Construct the main application window
Mainwindow(QWidget * parent)56 Mainwindow::Mainwindow(QWidget *parent)
57 : KXmlGuiWindow(parent)
58 {
59 // Reset stuff
60 mDeck = nullptr;
61 mEngine = nullptr;
62 mDisplay = nullptr;
63 mView = nullptr;
64 mLSkatConfig = nullptr;
65 mCanvas = nullptr;
66 mTheme = nullptr;
67
68 // Read theme files
69 QStringList themeList;
70 const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("grafix"), QStandardPaths::LocateDirectory);
71 for (const QString& dir : dirs) {
72 const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.desktop"));
73 for (const QString& file : fileNames) {
74 themeList.append(dir + '/' + file);
75 }
76 }
77 if (themeList.isEmpty())
78 {
79 KMessageBox::error(this, i18n("Installation error: No theme list found."));
80 QTimer::singleShot(0, this, &QWidget::close);
81 return;
82 }
83
84 // Read theme files
85 for (int i = 0; i < themeList.size(); i++)
86 {
87 KConfig themeInfo(themeList.at(i), KConfig::SimpleConfig);
88 KConfigGroup themeGroup(&themeInfo, "Theme");
89 QString name = themeGroup.readEntry("Name", QString());
90 QString file = themeGroup.readEntry("File", QString());
91 bool isDefault = themeGroup.readEntry("Default", false);
92 mThemeFiles[name] = file;
93 if (mThemeDefault.isNull()) mThemeDefault = name;
94 if (isDefault) mThemeDefault = name;
95
96 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Found theme: " << themeList.at(i) << " Name(i18n)=" << name << " File=" << file << " default=" << isDefault;
97 }
98 mThemeIndexNo = themeIdxFromName(mThemeDefault);
99
100 // Create menus etc
101 initGUI();
102
103 // The LSkat config
104 mLSkatConfig = new ConfigTwo(this);
105 connect(mLSkatConfig, &ConfigTwo::signalInputType, this, &Mainwindow::setInputType);
106 mLSkatConfig->reset();
107
108 // Read game properties and set default values (after config)
109 readProperties();
110
111 // TODO: Bugfix: Needs to be here if initGUI is before readProperties
112 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Setting current theme item to " << mThemeIndexNo;
113 ((KSelectAction *)ACTION(QLatin1String("theme")))->setCurrentItem(mThemeIndexNo);
114
115 // Get the card deck
116 const quint32 seed = QRandomGenerator::global()->generate();
117 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Random seed" << seed;
118 mDeck = new Deck(seed, this);
119
120 // Theme manager
121 QString themeFile = themefileFromIdx(mThemeIndexNo);
122 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Load theme" << themeFile << " no=" << mThemeIndexNo;
123 mTheme = new ThemeManager(mCardTheme, themeFile, this, this->width());
124 if (mTheme->checkTheme() != 0)
125 {
126 KMessageBox::error(this, i18n("Installation error: Theme file error."));
127 QTimer::singleShot(0, this, &QWidget::close);
128 return;
129 }
130
131 // Overall view
132 mCanvas = new QGraphicsScene(this);
133 mView = new GameView(QSize(880, 675), ADVANCE_PERIOD, mCanvas, mTheme, this);
134
135 // Create intro
136 mGameMode = Intro;
137 mDisplay = new DisplayIntro(mDeck, mCanvas, mTheme, ADVANCE_PERIOD, mView);
138 setCentralWidget(mView);
139 connect(mView, &GameView::signalLeftMousePress, this, &Mainwindow::menuNewLSkatGame);
140
141 // Create GUI
142 setupGUI();
143
144 statusBar()->showMessage(i18n("Welcome to Skat! Please start a new game."));
145
146 // Skip intro?
147 if (global_skip_intro)
148 {
149 menuNewLSkatGame();
150 }
151 // Start game automatically in demo mode
152 else if (global_demo_mode)
153 {
154 // Start intro
155 mDisplay->start();
156 QTimer::singleShot(12500, this, &Mainwindow::menuNewLSkatGame);
157 }
158 else
159 {
160 // Start intro
161 mDisplay->start();
162 }
163 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Mainwindow setup constructor done";
164 }
165
166 // Destructor
~Mainwindow()167 Mainwindow::~Mainwindow()
168 {
169 saveProperties();
170 delete mEngine;
171 delete mDisplay;
172 delete mLSkatConfig;
173 delete mDeck;
174 delete mView;
175 delete mCanvas;
176 delete mTheme;
177 }
178
179 // Called by KMainWindow when the last window of the application is
closeEvent(QCloseEvent * event)180 void Mainwindow::closeEvent(QCloseEvent *event)
181 {
182 if (mEngine)
183 {
184 mEngine->stopGame();
185 }
186 saveProperties();
187 KXmlGuiWindow::closeEvent(event);
188 }
189
190 // Retrieve a theme file name from the menu index number
themefileFromIdx(int idx)191 QString Mainwindow::themefileFromIdx(int idx)
192 {
193 QStringList list(mThemeFiles.keys());
194 list.sort();
195 QString themeFile = mThemeFiles[list.at(idx)];
196 return themeFile;
197 }
198
199 // Retrieve a theme idx from a theme name
themeIdxFromName(const QString & name)200 int Mainwindow::themeIdxFromName(const QString &name)
201 {
202 QStringList list(mThemeFiles.keys());
203 list.sort();
204 for (int i = 0; i < list.size(); ++i)
205 {
206 if (list[i] == name) return i;
207 }
208 qCCritical(LSKAT_LOG) << "Theme index lookup failed for " << name;
209 return 0;
210 }
211
212 // Save properties
saveProperties()213 void Mainwindow::saveProperties()
214 {
215 KConfig *config = KSharedConfig::openConfig().data();
216
217 // Program data
218 KConfigGroup cfg = config->group("ProgramData");
219 cfg.writeEntry("startplayer", mStartPlayer);
220 cfg.writeEntry("ThemeIndexNo", mThemeIndexNo);
221
222 // LSkat data
223 mLSkatConfig->save(config);
224 config->sync();
225 }
226
227 // Load properties
readProperties()228 void Mainwindow::readProperties()
229 {
230 KConfig *config = KSharedConfig::openConfig().data();
231
232 // Program data
233 KConfigGroup cfg = config->group("ProgramData");
234
235 // Theme number
236 mThemeIndexNo = cfg.readEntry("ThemeIndexNo", themeIdxFromName(mThemeDefault));
237 if (mThemeIndexNo >= mThemeFiles.size()) mThemeIndexNo = 0;
238
239 // Read card path
240 mCardTheme = CardDeckInfo::deckName(cfg);
241
242 int no = cfg.readEntry("startplayer", 0);
243 setStartPlayer(no);
244 mLSkatConfig->load(config);
245 }
246
247 // Create a input with the given type
createInput(InputDeviceType inputType,AbstractDisplay * display,AbstractEngine * engine)248 AbstractInput *Mainwindow::createInput(
249 InputDeviceType inputType,
250 AbstractDisplay *display,
251 AbstractEngine *engine)
252 {
253 AbstractInput *input = nullptr;
254
255 // Always use AI input in demo mode
256 if (global_demo_mode)
257 {
258 inputType = TypeAiInput;
259 }
260
261 // Create the player input
262 if (inputType == TypeMouseInput)
263 {
264 MouseInput *mouseInput = new MouseInput(this);
265 connect(mView, &GameView::signalLeftMousePress,
266 mouseInput, &MouseInput::mousePress);
267 connect(mouseInput, &MouseInput::signalConvertMousePress,
268 display, &AbstractDisplay::convertMousePress);
269 connect(mouseInput, &MouseInput::signalPlayerInput,
270 engine, &AbstractEngine::playerInput);
271 input = mouseInput;
272 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Create MOUSE INPUT";
273 }
274 else if (inputType == TypeAiInput)
275 {
276 AiInput *aiInput = new AiInput((EngineTwo *)engine, this);
277 connect(aiInput, &AiInput::signalPlayerInput,
278 engine, &AbstractEngine::playerInput);
279 input = aiInput;
280 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Create AI INPUT";
281 }
282 else
283 {
284 qCCritical(LSKAT_LOG) << "Unsupported input device type" << inputType;
285 }
286
287 return input;
288 }
289
290 // Start a new game
startGame()291 void Mainwindow::startGame()
292 {
293 // Enable game action
294 QLatin1String endName(KStandardGameAction::name(KStandardGameAction::End));
295 ACTION(endName)->setEnabled(true);
296
297 // Deal cards to player - Shuffle card deck and reset pile
298 mDeck->shuffle();
299
300 // Draw Trump
301 Suite trump = mDeck->randomTrump();
302
303 // Loop all players in the game
304 QHashIterator<int,Player *> it = mLSkatConfig->playerIterator();
305 while (it.hasNext())
306 {
307 it.next();
308 Player *player = it.value();
309 player->setDeck(mDeck);
310 // Deal cards
311 player->deal(16);
312 // Store trump
313 player->setTrump(trump);
314 }
315
316 // Start display
317 mDisplay->start();
318
319 // Start the game engine
320 mEngine->startGame(trump, mStartPlayer);
321
322 // Start player for next game
323 setStartPlayer(1-mStartPlayer);
324
325 // statusBar()->clearMessage();
326 }
327
328 // Here a game over is signalled
gameOver(int)329 void Mainwindow::gameOver(int /*winner*/)
330 {
331 QLatin1String endName(KStandardGameAction::name(KStandardGameAction::End));
332 ACTION(endName)->setEnabled(false);
333 statusBar()->showMessage(i18n("Game Over. Please start a new game."));
334
335 // Automatically restart game in demo mode
336 if (global_demo_mode)
337 {
338 QTimer::singleShot(10000, this, &Mainwindow::menuNewLSkatGame);
339 }
340 }
341
342 // Show next player
nextPlayer(Player * player)343 void Mainwindow::nextPlayer(Player *player)
344 {
345 int no = player->id() + 1;
346 QString name = player->name();
347 statusBar()->showMessage(i18nc("Player name and number", "Next move for %1 (player %2)", name, no));
348 }
349
350 // Setup the GUI
initGUI()351 void Mainwindow::initGUI()
352 {
353 QAction *action;
354
355 // Start a new game
356 action = KStandardGameAction::gameNew(this, &Mainwindow::menuNewLSkatGame, actionCollection());
357 if (global_demo_mode) action->setEnabled(false);
358
359 // Clear all time statistics
360 action = KStandardGameAction::clearStatistics(this, &Mainwindow::menuClearStatistics, actionCollection());
361 action->setWhatsThis(i18n("Clears the all time statistics which is kept in all sessions."));
362 if (global_demo_mode) action->setEnabled(false);
363
364 // End a game
365 action = KStandardGameAction::end(this, &Mainwindow::menuEndGame, actionCollection());
366 action->setWhatsThis(i18n("Ends a currently played game. No winner will be declared."));
367 action->setEnabled(false);
368
369 // Quit the program
370 action = KStandardGameAction::quit(this, &QWidget::close, actionCollection());
371 action->setWhatsThis(i18n("Quits the program."));
372
373 // Determine start player
374 KSelectAction *startPlayerAct = new KSelectAction(i18n("Starting Player"), this);
375 actionCollection()->addAction(QStringLiteral("startplayer"), startPlayerAct);
376 connect(startPlayerAct, &KSelectAction::indexTriggered, this, &Mainwindow::menuStartplayer);
377 startPlayerAct->setToolTip(i18n("Changing starting player..."));
378 startPlayerAct->setWhatsThis(i18n("Chooses which player begins the next game."));
379 QStringList list;
380 list.clear();
381 list.append(i18n("Player &1"));
382 list.append(i18n("Player &2"));
383 startPlayerAct->setItems(list);
384 if (global_demo_mode) startPlayerAct->setEnabled(false);
385
386 // Determine who plays player 1
387 KSelectAction *player1Act = new KSelectAction(i18n("Player &1 Played By"), this);
388 actionCollection()->addAction(QStringLiteral("player1"), player1Act);
389 connect(player1Act, &KSelectAction::indexTriggered, this, &Mainwindow::menuPlayer1By);
390 player1Act->setToolTip(i18n("Changing who plays player 1..."));
391 player1Act->setWhatsThis(i18n("Changing who plays player 1."));
392 list.clear();
393 list.append(i18n("&Mouse"));
394 list.append(i18n("&Computer"));
395 player1Act->setItems(list);
396 if (global_demo_mode) player1Act->setEnabled(false);
397
398 // Determine who plays player 2
399 KSelectAction *player2Act = new KSelectAction(i18n("Player &2 Played By"), this);
400 actionCollection()->addAction(QStringLiteral("player2"), player2Act);
401 connect(player2Act, &KSelectAction::indexTriggered, this, &Mainwindow::menuPlayer2By);
402 player2Act->setToolTip(i18n("Changing who plays player 2..."));
403 player2Act->setWhatsThis(i18n("Changing who plays player 2."));
404 player2Act->setItems(list);
405 if (global_demo_mode) player2Act->setEnabled(false);
406
407 // Add all theme files to the menu
408 QStringList themes(mThemeFiles.keys());
409 themes.sort();
410
411 KSelectAction *themeAct = new KSelectAction(i18n("&Theme"), this);
412 actionCollection()->addAction(QStringLiteral("theme"), themeAct);
413 themeAct->setItems(themes);
414 connect(themeAct, &KSelectAction::indexTriggered, this, &Mainwindow::changeTheme);
415 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Setting current theme item to " << mThemeIndexNo;
416 themeAct->setCurrentItem(mThemeIndexNo);
417 themeAct->setToolTip(i18n("Changing theme..."));
418 themeAct->setWhatsThis(i18n("Changing theme."));
419
420 // Choose card deck
421 QAction *action1 = actionCollection()->addAction(QStringLiteral("select_carddeck"));
422 action1->setText(i18n("Select &Card Deck..."));
423 actionCollection()->setDefaultShortcut(action1, QKeySequence(Qt::Key_F10));
424 connect(action1, &QAction::triggered, this, &Mainwindow::menuCardDeck);
425 action1->setToolTip(i18n("Configure card decks..."));
426 action1->setWhatsThis(i18n("Choose how the cards should look."));
427
428 // Change player names
429 action = actionCollection()->addAction(QStringLiteral("change_names"));
430 action->setText(i18n("&Change Player Names..."));
431 connect(action, &QAction::triggered, this, &Mainwindow::menuPlayerNames);
432 if (global_demo_mode) action->setEnabled(false);
433 }
434
435 // Choose start player
menuStartplayer()436 void Mainwindow::menuStartplayer()
437 {
438 int i = ((KSelectAction *)ACTION(QLatin1String("startplayer")))->currentItem();
439 setStartPlayer(i);
440 }
441
442 // Change the theme of the game
changeTheme(int idx)443 void Mainwindow::changeTheme(int idx)
444 {
445 mThemeIndexNo = idx;
446 QString themeFile = themefileFromIdx(idx);
447 if (global_debug > 0) qCDebug(LSKAT_LOG) << "Select theme " << themeFile;
448 mTheme->updateTheme(themeFile);
449 }
450
451 // Select input for player 1
menuPlayer1By()452 void Mainwindow::menuPlayer1By()
453 {
454 int i = ((KSelectAction *)ACTION(QLatin1String("player1")))->currentItem();
455 mLSkatConfig->setInputType(0, (InputDeviceType)i);
456 }
457
458 // Select input for player 2
menuPlayer2By()459 void Mainwindow::menuPlayer2By()
460 {
461 int i = ((KSelectAction *)ACTION(QLatin1String("player2")))->currentItem();
462 mLSkatConfig->setInputType(1, (InputDeviceType)i);
463 }
464
465 // Choose a card deck
menuCardDeck()466 void Mainwindow::menuCardDeck()
467 {
468 QString front = mCardTheme;
469
470 KConfigGroup grp = KSharedConfig::openConfig()->group("ProgramData");
471 KCardWidget *cardwidget = new KCardWidget();
472 QPointer<KCardDialog> dlg;
473
474 cardwidget->readSettings(grp);
475 dlg = new KCardDialog(cardwidget);
476 if (dlg->exec() == QDialog::Accepted)
477 {
478 // Always store the settings, other things than the deck may have changed
479 cardwidget->saveSettings(grp);
480 grp.sync();
481 if (global_debug > 0) qCDebug(LSKAT_LOG) << "NEW CARDDECK: " << front;
482 bool change = false; // Avoid unnecessary changes
483 if (!cardwidget->deckName().isEmpty() && cardwidget->deckName() != mCardTheme)
484 {
485 mCardTheme = cardwidget->deckName();
486 change = true;
487 }
488 if (change)
489 {
490 mTheme->updateCardTheme(mCardTheme);
491 mView->update(); // Be on the safe side and update
492 }
493 }
494 delete dlg;
495 }
496
497 // Clear all time statistics
menuClearStatistics()498 void Mainwindow::menuClearStatistics()
499 {
500 QString message;
501 message = i18n("Do you really want to clear the all time "
502 "statistical data?");
503
504 if (KMessageBox::Yes == KMessageBox::questionYesNo(this,
505 message,
506 QString(),
507 KStandardGuiItem::clear()))
508 {
509 QHashIterator<int,Player *> it = mLSkatConfig->playerIterator();
510 while (it.hasNext())
511 {
512 it.next();
513 Player *player = it.value();
514 player->clear();
515 }
516 }
517 }
518
519 // Abort a game
menuEndGame()520 void Mainwindow::menuEndGame()
521 {
522 if (mEngine)
523 {
524 mEngine->stopGame();
525 }
526 }
527
528 // Start a new game
menuNewLSkatGame()529 void Mainwindow::menuNewLSkatGame()
530 {
531 disconnect(mView, &GameView::signalLeftMousePress, this, &Mainwindow::menuNewLSkatGame);
532
533 Player *p1 = mLSkatConfig->player(0);
534 Player *p2 = mLSkatConfig->player(1);
535
536 // Stop running games
537 if (mEngine)
538 {
539 mEngine->stopGame();
540 }
541
542 // Get rid of old stuff?
543 if (true || (mGameMode != LSkat)) // Yes! Fixes bugs 330308 and 228067.
544 // Jenkins objected to the indentation if the above was simply commented out.
545 {
546 // Set new game mode
547 mGameMode = LSkat;
548
549 // Start deleting
550 delete mDisplay;
551 delete mEngine;
552
553 auto *display = new DisplayTwo(mDeck, mCanvas, mTheme, ADVANCE_PERIOD, mView);
554 mDisplay = display;
555 mEngine = new EngineTwo(this, mDeck, (DisplayTwo *)mDisplay);
556 connect(mEngine, &AbstractEngine::signalGameOver, this, &Mainwindow::gameOver);
557 connect(mEngine, &AbstractEngine::signalNextPlayer, this, &Mainwindow::nextPlayer);
558
559 // Connect player score widget updates
560 connect(p1, &Player::signalUpdate, display, &DisplayTwo::updatePlayer);
561 connect(p2, &Player::signalUpdate, display, &DisplayTwo::updatePlayer);
562
563 mEngine->addPlayer(0, p1);
564 mEngine->addPlayer(1, p2);
565 }// end if
566
567 // Create inputs and store in player
568 AbstractInput *input1 = createInput(mLSkatConfig->inputType(0), mDisplay, mEngine);
569 p1->setInput(input1);
570 AbstractInput *input2 = createInput(mLSkatConfig->inputType(1), mDisplay, mEngine);
571 p2->setInput(input2);
572
573 statusBar()->showMessage(i18n("Dealing cards..."));
574
575 // Start game
576 startGame();
577 }
578
579 // Change the player names in a dialog
menuPlayerNames()580 void Mainwindow::menuPlayerNames()
581 {
582 QPointer<NameDialogWidget> dlg = new NameDialogWidget(this);
583 for (int i = 0; i < 2; i++)
584 {
585 Player *p = mLSkatConfig->player(i);
586 dlg->setName(i, p->name());
587 }
588
589 if (dlg->exec() == QDialog::Accepted)
590 {
591 for (int i = 0; i < 2; i++)
592 {
593 Player *p = mLSkatConfig->player(i);
594 p->setName(dlg->name(i));
595 }
596 }
597
598 delete dlg;
599 }
600
601 // Set the start player.
setStartPlayer(int no)602 void Mainwindow::setStartPlayer(int no)
603 {
604 mStartPlayer = no;
605 ((KSelectAction *)ACTION(QLatin1String("startplayer")))->setCurrentItem(mStartPlayer);
606 }
607
608 // Set the input type for a given player number.
setInputType(int no,InputDeviceType type)609 void Mainwindow::setInputType(int no, InputDeviceType type)
610 {
611 Player *p = nullptr;
612 // Player 1
613 if (no == 0)
614 {
615 ((KSelectAction *)ACTION(QLatin1String("player1")))->setCurrentItem((int)type);
616 p = mLSkatConfig->player(0);
617 }
618 else if (no == 1)
619 {
620 ((KSelectAction *)ACTION(QLatin1String("player2")))->setCurrentItem((int)type);
621 p = mLSkatConfig->player(1);
622 }
623
624 // Exchange player input at runtime
625 if (mEngine && p && mDisplay && mEngine->isGameRunning())
626 {
627 AbstractInput *input = createInput(type, mDisplay, mEngine);
628 p->setInput(input);
629 }
630 }
631