1/*
2 * Copyright (c) 2010 Ni Hui <shuizhuyuanluo@126.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#include "mainwindow.h"
20
21#include "bgselector.h"
22#include "customgame.h"
23#include "gameconfig.h"
24#include "gamescene.h"
25#include "gameview.h"
26#include "settings.h"
27
28#include <KAction>
29#include <KActionCollection>
30#include <KApplication>
31#include <KConfigDialog>
32#include <KFileDialog>
33#include <KGameClock>
34#include <KgDifficulty>
35#include <KgThemeSelector>
36#include <KInputDialog>
37#include <KLocale>
38#include <KMessageBox>
39#include <KNotifyConfigWidget>
40#include <KScoreDialog>
41#include <KStandardAction>
42#include <KStandardGameAction>
43#include <KStatusBar>
44#include <KToggleAction>
45
46#include <QPointer>
47
48MainWindow::MainWindow( bool KSameMode, QWidget* parent )
49: KXmlGuiWindow(parent),
50m_KSameMode(KSameMode),
51m_gameClock(NULL),
52m_gameScore(0),
53m_lastRemainCount(0)
54{
55 m_scene = new GameScene;
56 GameView* view = new GameView( m_scene );
57 view->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
58 view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
59 view->setFrameStyle( QFrame::NoFrame );
60 view->setCacheMode( QGraphicsView::CacheBackground );
61 setCentralWidget( view );
62
63 if ( m_KSameMode ) {
64// statusBar()->insertItem( i18n( "Colors: XX" ), 1 );
65// statusBar()->insertItem( i18n( "Board: XXXXXX" ), 2 );
66 statusBar()->insertItem( i18n( "Marked: 0" ), 3 );
67 statusBar()->insertItem( i18n( "Score: 0" ), 4 );
68 connect( m_scene, SIGNAL(remainCountChanged(int)), this, SLOT(changeScore(int)) );
69 connect( m_scene, SIGNAL(markedCountChanged(int)), this, SLOT(changeMarkedCount(int)) );
70 }
71 else {
72 m_gameClock = new KGameClock( this, KGameClock::MinSecOnly );
73 statusBar()->insertItem( i18n( "Pieces: 0" ), 0 );
74 statusBar()->insertItem( i18n( "Time: 00:00" ), 1 );
75 connect( m_scene, SIGNAL(remainCountChanged(int)), this, SLOT(changeRemainCount(int)) );
76 connect( m_gameClock, SIGNAL(timeChanged(QString)), this, SLOT(changeTime(QString)) );
77 }
78
79 connect( m_scene, SIGNAL(gameFinished(int)), this, SLOT(onGameOver(int)) );
80
81 setupActions();
82
83 loadSettings();
84
85 newGame( qrand() );
86}
87
88MainWindow::~MainWindow()
89{
90}
91
92void MainWindow::configureNotifications()
93{
94 KNotifyConfigWidget::configure( this );
95}
96
97void MainWindow::configureSettings()
98{
99 if ( KConfigDialog::showDialog( QLatin1String( "settings" ) ) )
100 return;
101
102 KConfigDialog* dialog = new KConfigDialog( this, QLatin1String( "settings" ), Settings::self() );
103 dialog->addPage( new GameConfig( dialog ), i18n( "General" ), QLatin1String( "games-config-options" ) );
104 dialog->addPage( new KgThemeSelector( m_scene->themeProvider() ), i18n( "Theme" ), QLatin1String( "games-config-theme" ) );
105 dialog->addPage( new BackgroundSelector( dialog ), i18n( "Background" ), QLatin1String( "games-config-background" ) );
106 if ( !m_KSameMode )
107 dialog->addPage( new CustomGameConfig( dialog ), i18n( "Custom Game" ), QLatin1String( "games-config-custom" ) );
108 connect(m_scene->themeProvider(), SIGNAL(currentThemeChanged(const KgTheme*)), SLOT(loadSettings())); //setBackgroundType!
109 connect( dialog, SIGNAL(settingsChanged(QString)), this, SLOT(loadSettings()) );
110 dialog->setHelp( QString(), QLatin1String( "klickety" ) );
111 dialog->show();
112}
113
114void MainWindow::loadSettings()
115{
116 m_scene->setShowBoundLines( Settings::showBoundLines() );
117 m_scene->setEnableAnimation( Settings::enableAnimation() );
118 m_scene->setEnableHighlight( Settings::enableHighlight() );
119 m_scene->setBackgroundType( Settings::bgType() );
120}
121
122void MainWindow::newGame( int gameId )
123{
124 if ( !confirmAbort() )
125 return;
126
127 m_pauseAction->setChecked( false );
128 m_pauseAction->setEnabled( true );
129
130 if ( m_KSameMode ) {
131 m_gameScore = 0;
132 m_lastRemainCount = 15 * 10;
133 m_scene->startNewGame( 15, 10, 3, gameId );
134 return;
135 }
136
137 m_gameClock->restart();
138
139 switch ( Kg::difficultyLevel() ) {
140 case KgDifficultyLevel::VeryEasy:
141 m_scene->startNewGame( 10, 16, 3, gameId );
142 break;
143 case KgDifficultyLevel::Easy:
144 m_scene->startNewGame( 10, 16, 4, gameId );
145 break;
146 case KgDifficultyLevel::Medium:
147 m_scene->startNewGame( 10, 16, 5, gameId );
148 break;
149 case KgDifficultyLevel::Hard:
150 m_scene->startNewGame( 10, 16, 6, gameId );
151 break;
152 case KgDifficultyLevel::Custom:
153 m_scene->startNewGame( Settings::customWidth(),
154 Settings::customHeight(),
155 Settings::customColorCount(), gameId );
156 break;
157 default:
158 break;
159 }
160}
161
162void MainWindow::newNumGame()
163{
164 if ( !confirmAbort() )
165 return;
166
167 bool ok = false;
168 int userGameId = KInputDialog::getInteger( i18n( "Select Board" ),
169 i18n( "Select a board number:" ),
170 qrand(), 1, RAND_MAX, 1,
171 &ok, this );
172 if ( ok )
173 newGame( userGameId );
174}
175
176void MainWindow::pauseGame( bool isPaused )
177{
178 m_scene->setPaused( isPaused );
179 if ( !m_KSameMode ) {
180 if ( isPaused )
181 m_gameClock->pause();
182 else
183 m_gameClock->resume();
184 }
185}
186
187void MainWindow::restartGame()
188{
189 if ( confirmAbort() ) {
190 m_pauseAction->setChecked( false );
191 m_pauseAction->setEnabled( true );
192 if ( m_KSameMode ) {
193 m_gameScore = 0;
194 m_lastRemainCount = 0;
195 }
196 else {
197 m_gameClock->restart();
198 }
199 m_scene->restartGame();
200 }
201}
202
203void MainWindow::loadGame()
204{
205 QString fileName = KFileDialog::getOpenFileName( KUrl(), QLatin1String( "*.klickety" ), this );
206 if ( fileName.isEmpty() || !confirmAbort() )
207 return;
208
209 m_pauseAction->setChecked( false );
210 m_pauseAction->setEnabled( true );
211 KConfig config( fileName, KConfig::SimpleConfig );
212 KConfigGroup group = config.group( "Savegame" );
213 m_scene->loadGame( group );
214}
215
216void MainWindow::saveGame()
217{
218 QString fileName = KFileDialog::getSaveFileName( KUrl(), QLatin1String( "*.klickety" ), this );
219 if ( fileName.isEmpty() )
220 return;
221 KConfig config( fileName, KConfig::SimpleConfig );
222 KConfigGroup group = config.group( "Savegame" );
223 m_scene->saveGame( group );
224}
225
226void MainWindow::changeMarkedCount( int markedCount )
227{
228 int markedScore = ( markedCount < 2 ) ? 0 : ( ( markedCount - 2 ) * ( markedCount - 2 ) );
229 statusBar()->changeItem( i18np( "Marked: %2 (1 Point)", "Marked: %2 (%1 Points)", markedScore, markedCount ), 3 );
230}
231
232void MainWindow::changeScore( int remainCount )
233{
234 if ( m_lastRemainCount == 0 ) {
235 // new game or restart
236 m_lastRemainCount = remainCount;
237 statusBar()->changeItem( i18n( "Score: 0" ), 4 );
238 return;
239 }
240 int removedCount = m_lastRemainCount - remainCount;
241 if ( removedCount > 0 ) {
242 // normal move
243 int score = ( removedCount < 2 ) ? 0 : ( ( removedCount - 2 ) * ( removedCount - 2 ) );
244 m_gameScore += score;
245 }
246 else {
247 // undo action, minus the score
248 int score = ( removedCount > -2 ) ? 0 : ( ( removedCount + 2 ) * ( removedCount + 2 ) );
249 m_gameScore -= score;
250 }
251 statusBar()->changeItem( i18n( "Score: %1", m_gameScore ), 4 );
252 m_lastRemainCount = remainCount;
253}
254
255void MainWindow::changeRemainCount( int remainCount )
256{
257 statusBar()->changeItem( i18n( "Pieces: %1", remainCount ), 0 );
258}
259
260void MainWindow::changeTime( const QString& newTime )
261{
262 statusBar()->changeItem( i18n( "Time: %1", newTime ), 1 );
263}
264
265void MainWindow::showHighscores()
266{
267 if ( m_KSameMode ) {
268 QPointer<KScoreDialog> d = new KScoreDialog( KScoreDialog::Name | KScoreDialog::Score, this );
269 d->initFromDifficulty(Kg::difficulty(), /*setConfigGroup=*/ false);
270 d->setConfigGroup( qMakePair( QByteArray( "KSame" ), i18n( "High Scores" ) ) );
271 d->setHiddenConfigGroups( QList<QByteArray>() << "Very Easy" << "Easy" << "Medium" << "Hard" << "Custom" );
272 d->exec();
273 delete d;
274 return;
275 }
276
277 QPointer<KScoreDialog> d = new KScoreDialog( KScoreDialog::Name, this );
278 d->addField( KScoreDialog::Custom1, i18n( "Remaining pieces" ), QLatin1String( "remains" ) );
279 d->addField( KScoreDialog::Custom2, i18n( "Time" ), QLatin1String( "time" ) );
280 d->initFromDifficulty(Kg::difficulty(), /*setConfigGroup=*/ true);
281 d->setHiddenConfigGroups( QList<QByteArray>() << "KSame" );
282 d->hideField( KScoreDialog::Score );
283 d->exec();
284 delete d;
285}
286
287void MainWindow::onGameOver( int remainCount )
288{
289 m_pauseAction->setEnabled( false );
290
291 if ( m_KSameMode ) {
292 if ( remainCount == 0 ) {
293 // if the board is empty, give a bonus
294 m_gameScore += 1000;
295 }
296
297 QPointer<KScoreDialog> d = new KScoreDialog( KScoreDialog::Name | KScoreDialog::Score, this );
298 d->initFromDifficulty(Kg::difficulty(), /*setConfigGroup=*/ false);
299 d->setConfigGroup( qMakePair( QByteArray( "KSame" ), i18n( "High Scores" ) ) );
300 d->setHiddenConfigGroups( QList<QByteArray>() << "Very Easy" << "Easy" << "Medium" << "Hard" << "Custom" );
301
302 KScoreDialog::FieldInfo scoreInfo;
303 scoreInfo[ KScoreDialog::Score ].setNum( m_gameScore );
304
305 if ( d->addScore( scoreInfo ) )
306 d->exec();
307 delete d;
308 return;
309 }
310
311 m_gameClock->pause();
312
313 QPointer<KScoreDialog> d = new KScoreDialog( KScoreDialog::Name, this );
314 d->addField( KScoreDialog::Custom1, i18n( "Remaining pieces" ), QLatin1String( "remains" ) );
315 d->addField( KScoreDialog::Custom2, i18n( "Time" ), QLatin1String( "time" ) );
316 d->initFromDifficulty(Kg::difficulty(), /*setConfigGroup=*/ true);
317 d->setHiddenConfigGroups( QList<QByteArray>() << "KSame" );
318 d->hideField( KScoreDialog::Score );
319
320 KScoreDialog::FieldInfo scoreInfo;
321 scoreInfo[KScoreDialog::Custom1].setNum( remainCount );
322 scoreInfo[KScoreDialog::Custom2] = m_gameClock->timeString();
323 // remainCount*10000000 is much bigger than a usual time seconds
324 scoreInfo[KScoreDialog::Score].setNum( remainCount*10000000 + m_gameClock->seconds() );
325 if ( d->addScore( scoreInfo, KScoreDialog::LessIsMore ) != 0 )
326 d->exec();
327 delete d;
328}
329
330bool MainWindow::confirmAbort()
331{
332 return m_scene->isGameFinished() || ( KMessageBox::questionYesNo( this, i18n( "Do you want to resign?" ),
333 i18n( "New Game" ),KGuiItem( i18n( "Resign" ) ), KStandardGuiItem::cancel() ) == KMessageBox::Yes );
334}
335
336void MainWindow::setupActions()
337{
338 // game menu
339 KStandardGameAction::gameNew( this, SLOT(newGame()), actionCollection() );
340 if ( !m_KSameMode ) {
341 KStandardGameAction::load( this, SLOT(loadGame()), actionCollection() );
342 KStandardGameAction::save( this, SLOT(saveGame()), actionCollection() );
343 }
344 KStandardGameAction::restart( this, SLOT(restartGame()), actionCollection() );
345 KStandardGameAction::highscores( this, SLOT(showHighscores()), actionCollection() );
346 m_pauseAction = KStandardGameAction::pause( this, SLOT(pauseGame(bool)), actionCollection() );
347 KStandardGameAction::quit( this, SLOT(close()), actionCollection() );
348 KAction* m_newNumGameAction = new KAction( i18n( "New Numbered Game..." ), actionCollection() );
349 actionCollection()->addAction( QLatin1String( "game_new_numeric" ), m_newNumGameAction );
350 connect( m_newNumGameAction, SIGNAL(triggered(bool)), this, SLOT(newNumGame()) );
351
352 // move menu
353 KAction* undoAction = KStandardGameAction::undo( m_scene, SLOT(undoMove()), actionCollection() );
354 undoAction->setEnabled( false );
355 connect( m_scene, SIGNAL(canUndoChanged(bool)), undoAction, SLOT(setEnabled(bool)) );
356 KAction* redoAction = KStandardGameAction::redo( m_scene, SLOT(redoMove()), actionCollection() );
357 redoAction->setEnabled( false );
358 connect( m_scene, SIGNAL(canRedoChanged(bool)), redoAction, SLOT(setEnabled(bool)) );
359
360 KAction* undoAllAction = actionCollection()->addAction( QLatin1String( "move_undo_all" ) );
361 undoAllAction->setIcon( KIcon( QLatin1String( "media-skip-backward" ) ) );
362 undoAllAction->setText( i18n( "Undo All" ) );
363 undoAllAction->setEnabled( false );
364 connect( m_scene, SIGNAL(canUndoChanged(bool)), undoAllAction, SLOT(setEnabled(bool)) );
365 connect( undoAllAction, SIGNAL(triggered(bool)), m_scene, SLOT(undoAllMove()) );
366 KAction* redoAllAction = actionCollection()->addAction( QLatin1String( "move_redo_all" ) );
367 redoAllAction->setIcon( KIcon( QLatin1String( "media-skip-forward" ) ) );
368 redoAllAction->setText( i18n( "Redo All" ) );
369 redoAllAction->setEnabled( false );
370 connect( m_scene, SIGNAL(canRedoChanged(bool)), redoAllAction, SLOT(setEnabled(bool)) );
371 connect( redoAllAction, SIGNAL(triggered(bool)), m_scene, SLOT(redoAllMove()) );
372
373 // settings menu
374 KStandardAction::preferences( this, SLOT(configureSettings()), actionCollection() );
375 KStandardAction::configureNotifications( this, SLOT(configureNotifications()), actionCollection() );
376
377 if ( m_KSameMode ) {
378 setupGUI( QSize( 576, 384 ) );
379 return;
380 }
381
382 Kg::difficulty()->addStandardLevelRange(
383 KgDifficultyLevel::VeryEasy, KgDifficultyLevel::Hard,
384 KgDifficultyLevel::Easy //default
385 );
386 Kg::difficulty()->addLevel(new KgDifficultyLevel(1000,
387 QByteArray( "Custom" ), i18n( "Custom" )
388 ));
389 KgDifficultyGUI::init(this);
390 connect(Kg::difficulty(), SIGNAL(currentLevelChanged(const KgDifficultyLevel*)), SLOT(newGame()));
391
392 setupGUI( QSize( 340, 510 ) );
393}
394