1 /*
2     SPDX-FileCopyrightText: 2007-2009 Fela Winkelmolen <fela.kde@gmail.com>
3     SPDX-FileCopyrightText: 2010 Brian Croom <brian.s.croom@gmail.com>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "mainwindow.h"
9 
10 // own
11 #include "gameengine.h"
12 #include "canvaswidget.h"
13 #include "ui_generalsettings.h"
14 #include "settings.h"
15 #include "globals.h"
16 // KDEGames
17 #include <KScoreDialog>
18 #include <KgThemeSelector>
19 #include <KStandardGameAction>
20 // KF
21 #include <KStandardAction>
22 #include <KToggleFullScreenAction>
23 #include <KActionCollection>
24 #include <KMessageBox>
25 #include <KConfigDialog>
26 #include <KConfig>
27 // Qt
28 #include <QPointer>
29 #include <QMenuBar>
30 #include <QIcon>
31 #include <QAction>
32 #include <QKeySequence>
33 
34 class GeneralSettings : public QWidget
35 {
36     Q_OBJECT
37 public:
GeneralSettings(QWidget * parent)38     explicit GeneralSettings(QWidget *parent)
39         : QWidget(parent)
40     {
41         ui.setupUi(this);
42     }
~GeneralSettings()43     ~GeneralSettings() override {}
44 private:
45     Ui::GeneralSettings ui;
46 };
47 
MainWindow(QWidget * parent)48 MainWindow::MainWindow(QWidget *parent)
49     : KXmlGuiWindow(parent),
50       canvasWidget(new CanvasWidget(this))
51 {
52     gameEngine = new GameEngine(this);
53 
54     m_cheatsEnabled = !qEnvironmentVariableIsEmpty("KDE_DEBUG");
55 
56     connect(canvasWidget, &CanvasWidget::focusLost, this, &MainWindow::pauseGame);
57 
58     connect(canvasWidget, &CanvasWidget::levelComplete, gameEngine, &GameEngine::loadNextLevel);
59     connect(canvasWidget, &CanvasWidget::gameEnded, this, &MainWindow::handleEndedGame);
60     connect(canvasWidget, &CanvasWidget::mousePressed, this, &MainWindow::handleMousePressed);
61 
62     connect(gameEngine, &GameEngine::loadingNewGame, canvasWidget, &CanvasWidget::newGame);
63     connect(gameEngine, &GameEngine::newLine, canvasWidget, &CanvasWidget::showLine);
64     connect(gameEngine, &GameEngine::newGift, canvasWidget, &CanvasWidget::putGift);
65     connect(gameEngine, &GameEngine::ready, canvasWidget, &CanvasWidget::startGame);
66 
67     setCentralWidget(canvasWidget);
68 
69     setupActions();
70     setFocusProxy(canvasWidget);
71 
72     QSize defaultSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
73     setupGUI(defaultSize,
74              KXmlGuiWindow::Keys | KXmlGuiWindow::Save | KXmlGuiWindow::Create);
75 
76     if (isFullScreen()) {
77         menuBar()->hide();
78     } else {
79         menuBar()->show();
80     }
81 
82     // show here (instead of in main) else the mouse can't be grabbed
83     show();
84     gameEngine->start(QStringLiteral("default"));
85 }
86 
~MainWindow()87 MainWindow::~MainWindow()
88 {
89     delete gameEngine;
90     delete canvasWidget;
91 }
92 
setupActions()93 void MainWindow::setupActions()
94 {
95     KStandardGameAction::gameNew(this, &MainWindow::startNewGame, actionCollection());
96 
97     KStandardGameAction::quit(this, &MainWindow::close, actionCollection());
98 
99     KStandardGameAction::highscores(this, &MainWindow::showHighscores, actionCollection());
100 
101     KStandardAction::preferences(this, &MainWindow::configureSettings, actionCollection());
102 
103     QAction *fullScreenAction = KStandardAction::fullScreen(this,
104                                 &MainWindow::viewFullScreen, this, actionCollection());
105     // set the default primary shortcut as alternate shortcut
106     // and make F the default
107     QKeySequence fullScreenShortcut(i18nc("Key (shortcut) to toggle full screen", "F"));
108     fullScreenAction->setShortcut(fullScreenShortcut);
109     QAction *fireAction = new QAction(this);
110     fireAction->setText(i18n("Fire the ball"));
111     actionCollection()->setDefaultShortcut(fireAction, Qt::Key_Space);
112     fireAction->setIcon(QIcon::fromTheme(QStringLiteral("kbreakout")));
113     connect(fireAction, &QAction::triggered, this, &MainWindow::fire);
114     connect(fireAction, &QAction::changed, canvasWidget, &CanvasWidget::updateFireShortcut);
115     actionCollection()->addAction(QStringLiteral("fire"), fireAction);
116 
117     if (m_cheatsEnabled) {
118         QAction *cheatSkipLevelAction = new QAction(this);
119         cheatSkipLevelAction->setText(i18n("Skip level"));
120         actionCollection()->setDefaultShortcut(cheatSkipLevelAction, Qt::Key_S);
121         cheatSkipLevelAction->setIcon(QIcon::fromTheme(QStringLiteral("kbreakout")));
122         connect(cheatSkipLevelAction, &QAction::triggered, this, &MainWindow::cheatSkipLevel);
123         actionCollection()->addAction(QStringLiteral("cheatSkipLevel"), cheatSkipLevelAction);
124 
125         QAction *cheatAddLifeAction = new QAction(this);
126         cheatAddLifeAction->setText(i18n("Add life"));
127         actionCollection()->setDefaultShortcut(cheatAddLifeAction, Qt::Key_L);
128         cheatAddLifeAction->setIcon(QIcon::fromTheme(QStringLiteral("kbreakout")));
129         connect(cheatAddLifeAction, &QAction::triggered, this, &MainWindow::cheatAddLife);
130         actionCollection()->addAction(QStringLiteral("cheatAddLife"), cheatAddLifeAction);
131     }
132 
133     pauseAction = KStandardGameAction::pause(this, &MainWindow::setGamePaused, actionCollection());
134     // set custom keys
135     QList<QKeySequence> keys;
136     keys.append(i18nc("Key (shortcut) to pause the game", "P"));
137     keys.append(Qt::Key_Escape);
138     // the following won't work (no more than 2 shortcuts allowed..)
139     // TODO: make the pause key work
140     //keys.append(Qt::Key_Pause);
141     pauseAction->setShortcuts(keys);
142 }
143 
configureSettings()144 void MainWindow::configureSettings()
145 {
146     if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
147         return;
148     }
149     // else it doesn't exist, thus create the dialog
150 
151     KConfigDialog *dialog = new KConfigDialog(this, QStringLiteral("settings"),
152             Settings::self());
153     dialog->setModal(true);
154 
155     dialog->addPage(new KgThemeSelector(canvasWidget->getProvider()),
156                     i18n("Theme"), QStringLiteral("games-config-theme"));
157 
158     // TODO: when will the page be destroyed?
159     dialog->addPage(new GeneralSettings(dialog),
160                     i18nc("General settings", "General"),
161                     QStringLiteral("games-config-options"));
162 
163     dialog->show();
164 }
165 
showHighscores()166 void MainWindow::showHighscores()
167 {
168     KScoreDialog ksdialog(KScoreDialog::Name | KScoreDialog::Level, this);
169     ksdialog.addField(KScoreDialog::Custom1, i18n("   Time (hh:mm)"), QStringLiteral("moves"));
170 
171     ksdialog.exec();
172 }
173 
startNewGame()174 void MainWindow::startNewGame()
175 {
176     int ret = KMessageBox::warningYesNo(
177                   this,
178                   i18n("Starting a new game will end the current one!"),
179                   i18n("New Game"),
180                   KGuiItem(i18n("Start a New Game")),
181                   KStandardGuiItem::cancel());
182 
183     if (ret == KMessageBox::Yes) {
184         pauseAction->setChecked(false);
185         gameEngine->start(QStringLiteral("default"));
186     }
187 }
188 
pauseGame()189 void MainWindow::pauseGame()
190 {
191     if (!pauseAction->isChecked()) {
192         pauseAction->activate(QAction::Trigger);
193     }
194 }
195 
setGamePaused(bool paused)196 void MainWindow::setGamePaused(bool paused)
197 {
198     canvasWidget->setGamePaused(paused);
199 }
200 
handleEndedGame(int score,int level,int time)201 void MainWindow::handleEndedGame(int score, int level, int time)
202 {
203 
204     QTime t = QTime(0, 0).addSecs(time);
205     // TODO: check int overflow and fix 24 hours "overflow"
206     QString timeString = t.toString(QStringLiteral("HH:mm"));
207 
208     const int ALL_LEVELS = -1;
209 
210     KScoreDialog::FieldInfo scoreInfo;
211     scoreInfo[KScoreDialog::Score].setNum(score);
212     scoreInfo[KScoreDialog::Custom1] = timeString;
213     if (level == ALL_LEVELS) {
214         scoreInfo[KScoreDialog::Level] = i18n("Game won!");
215     } else {
216         scoreInfo[KScoreDialog::Level].setNum(level);
217     }
218 
219     QPointer<KScoreDialog> ksdialog =
220         new KScoreDialog(KScoreDialog::Name | KScoreDialog::Level, this);
221     ksdialog->addField(KScoreDialog::Custom1, i18n("Time (hh:mm)"), QStringLiteral("moves"));
222     ksdialog->addScore(scoreInfo);
223     ksdialog->exec();
224 
225     if (ksdialog) {
226         gameEngine->start(QStringLiteral("default"));
227         delete ksdialog;
228     }
229 }
230 
fire()231 void MainWindow::fire()
232 {
233     if (pauseAction->isChecked()) {
234         pauseAction->activate(QAction::Trigger);
235     } else {
236         canvasWidget->fire();
237     }
238 }
239 
cheatSkipLevel()240 void MainWindow::cheatSkipLevel()
241 {
242     if (pauseAction->isChecked()) {
243         pauseAction->activate(QAction::Trigger);
244     } else {
245         canvasWidget->cheatSkipLevel();
246     }
247 }
248 
cheatAddLife()249 void MainWindow::cheatAddLife()
250 {
251     if (pauseAction->isChecked()) {
252         pauseAction->activate(QAction::Trigger);
253     } else {
254         canvasWidget->cheatAddLife();
255     }
256 }
257 
viewFullScreen(bool fullScreen)258 void MainWindow::viewFullScreen(bool fullScreen)
259 {
260     KToggleFullScreenAction::setFullScreen(this, fullScreen);
261     if (fullScreen) {
262         menuBar()->hide();
263     } else {
264         menuBar()->show();
265     }
266 }
267 
handleMousePressed()268 void MainWindow::handleMousePressed()
269 {
270     if (pauseAction->isChecked()) {
271         pauseAction->activate(QAction::Trigger);
272         return;
273     }
274 
275     if (Settings::fireOnClick()) {
276         canvasWidget->fire();
277         return;
278     }
279 
280     // not fire on click
281 
282     // check if dontAskFireOnClick is set
283     // we want to override it's default effect
284     // if it's set to _any_ value we want to keep the
285     // settings of fireOnClick at false
286     bool dontAsk = false; // true if dontAskFireOnClick was set
287 #if 0 //QT5
288     KConfig config(componentData(), "kbreakoutrc");
289     if (config.hasGroup("Notification Messages")) {
290         KConfigGroup group(&config, "Notification Messages");
291         if (group.hasKey("dontAskFireOnClick")) {
292             dontAsk = true;
293         }
294     }
295 #endif
296     if (dontAsk == false) {
297         // ask the user if he wants to fire on mouse click
298         int res = KMessageBox::questionYesNo(
299                       this,
300                       i18n("Do you want to fire the ball on mouse click?\n"
301                            "Answering Yes will make the game steal the\n"
302                            "mouse cursor, pause the game to get\n"
303                            "the cursor back."),
304                       i18n("Fire on click?"),
305                       KStandardGuiItem::yes(),
306                       KStandardGuiItem::no(),
307                       QStringLiteral("dontAskFireOnClick") // doesntAskAgainName
308                   );
309 
310         if (res == KMessageBox::Yes) {
311             Settings::setFireOnClick(true);
312             Settings::self()->save();
313         }
314     }
315 }
316 
317 #include "mainwindow.moc"
318