1 | /* |
2 | * Copyright (C) 1995 Paul Olav Tvete <paul@troll.no> |
3 | * Copyright (C) 1997 Mario Weilguni <mweilguni@sime.com> |
4 | * Copyright (C) 2000-2009 Stephan Kulow <coolo@kde.org> |
5 | * Copyright (C) 2009-2010 Parker Coates <coates@kde.org> |
6 | * |
7 | * License of original code: |
8 | * ------------------------------------------------------------------------- |
9 | * Permission to use, copy, modify, and distribute this software and its |
10 | * documentation for any purpose and without fee is hereby granted, |
11 | * provided that the above copyright notice appear in all copies and that |
12 | * both that copyright notice and this permission notice appear in |
13 | * supporting documentation. |
14 | * |
15 | * This file is provided AS IS with no warranties of any kind. The author |
16 | * shall have no liability with respect to the infringement of copyrights, |
17 | * trade secrets or any patents by this file or any part thereof. In no |
18 | * event will the author be liable for any lost revenue or profits or |
19 | * other special, indirect and consequential damages. |
20 | * ------------------------------------------------------------------------- |
21 | * |
22 | * License of modifications/additions made after 2009-01-01: |
23 | * ------------------------------------------------------------------------- |
24 | * This program is free software; you can redistribute it and/or |
25 | * modify it under the terms of the GNU General Public License as |
26 | * published by the Free Software Foundation; either version 2 of |
27 | * the License, or (at your option) any later version. |
28 | * |
29 | * This program is distributed in the hope that it will be useful, |
30 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
31 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
32 | * GNU General Public License for more details. |
33 | * |
34 | * You should have received a copy of the GNU General Public License |
35 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
36 | * ------------------------------------------------------------------------- |
37 | */ |
38 | |
39 | #include "mainwindow.h" |
40 | |
41 | #include "dealer.h" |
42 | #include "dealerinfo.h" |
43 | #include "gameselectionscene.h" |
44 | #include "numbereddealdialog.h" |
45 | #include "renderer.h" |
46 | #include "settings.h" |
47 | #include "soundengine.h" |
48 | #include "statisticsdialog.h" |
49 | #include "version.h" |
50 | #include "view.h" |
51 | |
52 | #include "KAbstractCardDeck" |
53 | #include "KCardTheme" |
54 | #include "KCardThemeWidget" |
55 | |
56 | #include <KgThemeSelector> |
57 | #include <KStandardGameAction> |
58 | #include <KStandardAction> |
59 | |
60 | #include <KAction> |
61 | #include <KActionCollection> |
62 | #include <KConfigDialog> |
63 | #include <KDebug> |
64 | #include <KFileDialog> |
65 | #include <KGlobal> |
66 | #include <KIcon> |
67 | #include <KLocale> |
68 | #include <KMessageBox> |
69 | #include <KRandom> |
70 | #include <KRecentFilesAction> |
71 | #include <KStandardDirs> |
72 | #include <KStatusBar> |
73 | #include <KMenuBar> |
74 | #include <KTemporaryFile> |
75 | #include <KToggleAction> |
76 | #include <KToolInvocation> |
77 | #include <KXMLGUIFactory> |
78 | #include <KIO/NetAccess> |
79 | |
80 | #include <QtCore/QList> |
81 | #include <QtCore/QPointer> |
82 | #include <QtCore/QTimer> |
83 | #include <QtCore/QXmlStreamReader> |
84 | #include <QtGui/QDesktopWidget> |
85 | #include <QtGui/QKeySequence> |
86 | |
87 | |
88 | namespace |
89 | { |
90 | const KUrl dialogUrl( "kfiledialog:///kpat" ); |
91 | const QString saveFileMimeType( "application/vnd.kde.kpatience.savedgame" ); |
92 | const QString legacySaveFileMimeType( "application/vnd.kde.kpatience.savedstate" ); |
93 | } |
94 | |
95 | |
96 | MainWindow::MainWindow() |
97 | : KXmlGuiWindow( 0 ), |
98 | m_view( 0 ), |
99 | m_dealer( 0 ), |
100 | m_selector( 0 ), |
101 | m_cardDeck( 0 ), |
102 | m_soundEngine( 0 ), |
103 | m_dealDialog( 0 ) |
104 | { |
105 | setObjectName( QLatin1String( "MainWindow" ) ); |
106 | // KCrash::setEmergencySaveFunction(::saveGame); |
107 | |
108 | setupActions(); |
109 | |
110 | foreach( const DealerInfo * di, DealerInfoList::self()->games() ) |
111 | { |
112 | m_dealer_map.insert( di->baseId(), di ); |
113 | foreach( int id, di->subtypeIds() ) |
114 | m_dealer_map.insert( id, di ); |
115 | } |
116 | m_dealer_it = m_dealer_map.constEnd(); |
117 | |
118 | m_view = new PatienceView( this ); |
119 | setCentralWidget( m_view ); |
120 | |
121 | QSize defaultSize = qApp->desktop()->availableGeometry().size() * 0.7; |
122 | setupGUI(defaultSize, Create | Save | ToolBar | StatusBar | Keys); |
123 | |
124 | m_solverStatusLabel = new QLabel(QString(), statusBar()); |
125 | m_solverStatusLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); |
126 | statusBar()->addWidget( m_solverStatusLabel, 1); |
127 | |
128 | m_moveCountStatusLabel = new QLabel(QString(), statusBar()); |
129 | m_moveCountStatusLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); |
130 | statusBar()->addWidget( m_moveCountStatusLabel, 0); |
131 | |
132 | m_showMenubarAction->setChecked(!menuBar()->isHidden()); |
133 | } |
134 | |
135 | MainWindow::~MainWindow() |
136 | { |
137 | m_recentFilesAction->saveEntries(KGlobal::config()->group( QString() )); |
138 | |
139 | Settings::self()->writeConfig(); |
140 | |
141 | delete m_dealer; |
142 | delete m_view; |
143 | Renderer::deleteSelf(); |
144 | } |
145 | |
146 | |
147 | void MainWindow::setupActions() |
148 | { |
149 | KAction *a; |
150 | |
151 | // Game Menu |
152 | a = actionCollection()->addAction( QLatin1String( "new_game" )); |
153 | a->setText(i18nc("Start a new game of a different type" ,"New &Game..." )); |
154 | a->setIcon( KIcon( QLatin1String( "document-new" )) ); |
155 | a->setShortcuts( KShortcut( Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_N ) ); |
156 | connect( a, SIGNAL(triggered(bool)), SLOT(slotShowGameSelectionScreen()) ); |
157 | |
158 | a = actionCollection()->addAction( QLatin1String( "new_deal" )); |
159 | a->setText(i18nc("Start a new game of without changing the game type" , "New &Deal" )); |
160 | a->setIcon( KIcon( QLatin1String( "document-new" )) ); |
161 | a->setShortcuts( KShortcut( Qt::ControlModifier | Qt::Key_N ) ); |
162 | connect( a, SIGNAL(triggered(bool)), SLOT(newGame()) ); |
163 | |
164 | a = actionCollection()->addAction( QLatin1String( "new_numbered_deal" )); |
165 | a->setText(i18nc("Start a game by giving its particular number" , "New &Numbered Deal..." )); |
166 | a->setShortcut( KShortcut( Qt::ControlModifier | Qt::Key_D ) ); |
167 | connect( a, SIGNAL(triggered(bool)), SLOT(newNumberedDeal()) ); |
168 | |
169 | a = KStandardGameAction::restart(this, SLOT(restart()), actionCollection()); |
170 | a->setText(i18nc("Replay the current deal from the start" , "Restart Deal" )); |
171 | |
172 | // Note that this action is not shown in the menu or toolbar. It is |
173 | // only provided for advanced users who can use it by shorcut or add it to |
174 | // the toolbar if they wish. |
175 | a = actionCollection()->addAction( QLatin1String( "next_deal" )); |
176 | a->setText(i18nc("Start the game with the number one greater than the current one" , "Next Deal" )); |
177 | a->setIcon( KIcon( QLatin1String( "go-next" )) ); |
178 | a->setShortcut( KShortcut( Qt::ControlModifier | Qt::Key_Plus ) ); |
179 | connect( a, SIGNAL(triggered(bool)), this, SLOT(nextDeal()) ); |
180 | |
181 | // Note that this action is not shown in the menu or toolbar. It is |
182 | // only provided for advanced users who can use it by shorcut or add it to |
183 | // the toolbar if they wish. |
184 | a = actionCollection()->addAction( QLatin1String( "previous_deal" )); |
185 | a->setText(i18nc("Start the game with the number one less than the current one" , "Previous Deal" )); |
186 | a->setIcon( KIcon( QLatin1String( "go-previous" )) ); |
187 | a->setShortcut( KShortcut( Qt::ControlModifier | Qt::Key_Minus ) ); |
188 | connect( a, SIGNAL(triggered(bool)), this, SLOT(previousDeal()) ); |
189 | |
190 | KStandardGameAction::load( this, SLOT(loadGame()), actionCollection() ); |
191 | |
192 | m_recentFilesAction = KStandardGameAction::loadRecent( this, SLOT(loadGame(KUrl)), actionCollection() ); |
193 | m_recentFilesAction->loadEntries(KGlobal::config()->group( QString() )); |
194 | |
195 | m_saveAction = KStandardGameAction::saveAs(this, SLOT(saveGame()), actionCollection()); |
196 | m_saveAction->setShortcut( KShortcut( Qt::ControlModifier | Qt::Key_S ) ); |
197 | |
198 | a = actionCollection()->addAction( QLatin1String( "game_stats" )); |
199 | a->setText(i18n("Statistics" )); |
200 | a->setIcon( KIcon( QLatin1String( "games-highscores" )) ); |
201 | connect( a, SIGNAL(triggered(bool)), SLOT(showStats()) ); |
202 | |
203 | KStandardGameAction::quit(this, SLOT(close()), actionCollection()); |
204 | |
205 | |
206 | // Move Menu |
207 | m_undoAction = KStandardGameAction::undo(this, SLOT(undoMove()), actionCollection()); |
208 | |
209 | m_redoAction = KStandardGameAction::redo(this, SLOT(redoMove()), actionCollection()); |
210 | |
211 | m_demoAction = KStandardGameAction::demo( this, SLOT(toggleDemo()), actionCollection() ); |
212 | |
213 | // KStandardGameAction::hint is a regular action, but we want a toggle |
214 | // action, so we must create a new action and copy all the standard |
215 | // properties over one by one. |
216 | m_hintAction = new KToggleAction( actionCollection() ); |
217 | a = KStandardGameAction::hint( 0, 0, 0 ); |
218 | m_hintAction->setText( a->text() ); |
219 | m_hintAction->setIcon( a->icon() ); |
220 | m_hintAction->setShortcut( a->shortcut() ); |
221 | m_hintAction->setToolTip( a->toolTip() ); |
222 | m_hintAction->setWhatsThis( a->whatsThis() ); |
223 | delete a; |
224 | QString actionName( KStandardGameAction::name( KStandardGameAction::Hint ) ); |
225 | actionCollection()->addAction( actionName, m_hintAction ); |
226 | connect( m_hintAction, SIGNAL(triggered()), this, SLOT(toggleHints()) ); |
227 | |
228 | m_drawAction = actionCollection()->addAction( QLatin1String( "move_draw" )); |
229 | m_drawAction->setText( i18nc("Take one or more cards from the deck, flip them, and place them in play" , "Dra&w" ) ); |
230 | m_drawAction->setIcon( KIcon( QLatin1String( "kpat" )) ); |
231 | m_drawAction->setShortcut( Qt::Key_Tab ); |
232 | |
233 | m_dealAction = actionCollection()->addAction( QLatin1String( "move_deal" )); |
234 | m_dealAction->setText( i18nc("Deal a new row of cards from the deck" , "Dea&l Row" ) ); |
235 | m_dealAction->setIcon( KIcon( QLatin1String( "kpat" )) ); |
236 | m_dealAction->setShortcut( Qt::Key_Return ); |
237 | |
238 | m_redealAction = actionCollection()->addAction( QLatin1String( "move_redeal" )); |
239 | m_redealAction->setText( i18nc("Collect the cards in play, shuffle them and redeal them" , "&Redeal" ) ); |
240 | m_redealAction->setIcon( KIcon( QLatin1String( "roll" )) ); |
241 | m_redealAction->setShortcut( Qt::Key_R ); |
242 | |
243 | m_dropAction = new KToggleAction( actionCollection() ); |
244 | m_dropAction->setText( i18nc("Automatically move cards to the foundation piles" , "Dro&p" ) ); |
245 | m_dropAction->setIcon( KIcon( QLatin1String( "games-endturn" )) ); |
246 | m_dropAction->setShortcut( Qt::Key_P ); |
247 | actionCollection()->addAction( QLatin1String( "move_drop" ), m_dropAction ); |
248 | connect( m_dropAction, SIGNAL(triggered()), this, SLOT(toggleDrop()) ); |
249 | |
250 | |
251 | // Settings Menu |
252 | a = actionCollection()->addAction( QLatin1String( "select_deck" )); |
253 | a->setText(i18n("Change Appearance..." )); |
254 | connect( a, SIGNAL(triggered(bool)), SLOT(configureAppearance()) ); |
255 | a->setShortcuts( KShortcut( Qt::Key_F10 ) ); |
256 | |
257 | m_autoDropEnabledAction = new KToggleAction(i18n("&Enable Autodrop" ), this); |
258 | actionCollection()->addAction( QLatin1String( "enable_autodrop" ), m_autoDropEnabledAction ); |
259 | connect( m_autoDropEnabledAction, SIGNAL(triggered(bool)), SLOT(setAutoDropEnabled(bool)) ); |
260 | m_autoDropEnabledAction->setChecked( Settings::autoDropEnabled() ); |
261 | |
262 | m_solverEnabledAction = new KToggleAction(i18n("E&nable Solver" ), this); |
263 | actionCollection()->addAction( QLatin1String( "enable_solver" ), m_solverEnabledAction ); |
264 | connect( m_solverEnabledAction, SIGNAL(triggered(bool)), SLOT(enableSolver(bool)) ); |
265 | m_solverEnabledAction->setChecked( Settings::solverEnabled() ); |
266 | |
267 | m_playSoundsAction = new KToggleAction( KIcon( QLatin1String( "preferences-desktop-sound" ) ), i18n("Play &Sounds" ), this ); |
268 | actionCollection()->addAction( QLatin1String( "play_sounds" ), m_playSoundsAction ); |
269 | connect( m_playSoundsAction, SIGNAL(triggered(bool)), SLOT(enableSounds(bool)) ); |
270 | m_playSoundsAction->setChecked( Settings::playSounds() ); |
271 | |
272 | m_rememberStateAction = new KToggleAction(i18n("&Remember State on Exit" ), this); |
273 | actionCollection()->addAction( QLatin1String( "remember_state" ), m_rememberStateAction ); |
274 | connect( m_rememberStateAction, SIGNAL(triggered(bool)), SLOT(enableRememberState(bool)) ); |
275 | m_rememberStateAction->setChecked( Settings::rememberStateOnExit() ); |
276 | |
277 | |
278 | // Help Menu |
279 | m_gameHelpAction = actionCollection()->addAction( QLatin1String( "help_game" )); |
280 | m_gameHelpAction->setIcon( KIcon( QLatin1String( "help-browser" )) ); |
281 | connect( m_gameHelpAction, SIGNAL(triggered(bool)), SLOT(helpGame())); |
282 | m_gameHelpAction->setShortcuts( KShortcut( Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_F1 ) ); |
283 | |
284 | |
285 | // Hidden actions |
286 | if (!qgetenv("KDE_DEBUG" ).isEmpty()) // developer shortcut |
287 | { |
288 | a = actionCollection()->addAction( QLatin1String( "themePreview" )); |
289 | a->setText(i18n("Generate a theme preview image" )); |
290 | connect( a, SIGNAL(triggered(bool)), SLOT(generateThemePreview()) ); |
291 | a->setShortcuts( KShortcut( Qt::Key_F7 ) ); |
292 | |
293 | a = actionCollection()->addAction( QLatin1String( "snapshot" )); |
294 | a->setText(i18n("Take Game Preview Snapshots" )); |
295 | connect( a, SIGNAL(triggered(bool)), SLOT(slotSnapshot()) ); |
296 | a->setShortcuts( KShortcut( Qt::Key_F8 ) ); |
297 | |
298 | a = actionCollection()->addAction( QLatin1String( "random_set" )); |
299 | a->setText(i18n("Random Cards" )); |
300 | connect( a, SIGNAL(triggered(bool)), SLOT(slotPickRandom()) ); |
301 | a->setShortcuts( KShortcut( Qt::Key_F9 ) ); |
302 | } |
303 | |
304 | // Keyboard navigation actions |
305 | m_leftAction = actionCollection()->addAction( QLatin1String( "focus_left" )); |
306 | m_leftAction->setText("Move Focus to Previous Pile" ); |
307 | m_leftAction->setShortcuts( KShortcut( Qt::Key_Left ) ); |
308 | |
309 | m_rightAction = actionCollection()->addAction( QLatin1String( "focus_right" )); |
310 | m_rightAction->setText("Move Focus to Next Pile" ); |
311 | m_rightAction->setShortcuts( KShortcut( Qt::Key_Right ) ); |
312 | |
313 | m_upAction = actionCollection()->addAction( QLatin1String( "focus_up" )); |
314 | m_upAction->setText("Move Focus to Card Below" ); |
315 | m_upAction->setShortcuts( KShortcut( Qt::Key_Up ) ); |
316 | |
317 | m_downAction = actionCollection()->addAction( QLatin1String( "focus_down" )); |
318 | m_downAction->setText("Move Focus to Card Above" ); |
319 | m_downAction->setShortcuts( KShortcut( Qt::Key_Down ) ); |
320 | |
321 | m_cancelAction = actionCollection()->addAction( QLatin1String( "focus_cancel" )); |
322 | m_cancelAction->setText("Cancel Focus" ); |
323 | m_cancelAction->setShortcuts( KShortcut( Qt::Key_Escape ) ); |
324 | |
325 | m_pickUpSetDownAction = actionCollection()->addAction( QLatin1String( "focus_activate" )); |
326 | m_pickUpSetDownAction->setText("Pick Up or Set Down Focus" ); |
327 | m_pickUpSetDownAction->setShortcuts( KShortcut( Qt::Key_Space ) ); |
328 | |
329 | // showMenubar isn't a part of KStandardGameAction |
330 | m_showMenubarAction = KStandardAction::showMenubar(this, SLOT(toggleMenubar()), actionCollection()); |
331 | } |
332 | |
333 | void MainWindow::undoMove() { |
334 | if( m_dealer ) |
335 | m_dealer->undo(); |
336 | } |
337 | |
338 | void MainWindow::redoMove() { |
339 | if( m_dealer ) |
340 | m_dealer->redo(); |
341 | } |
342 | |
343 | void MainWindow::helpGame() |
344 | { |
345 | if (m_dealer && m_dealer_map.contains(m_dealer->gameId())) |
346 | { |
347 | const DealerInfo * di = m_dealer_map.value(m_dealer->gameId()); |
348 | QString anchor = QString::fromUtf8( di->untranslatedBaseName() ); |
349 | anchor = anchor.toLower(); |
350 | anchor = anchor.remove('\'').replace('&', "and" ).replace(' ', '-'); |
351 | KToolInvocation::invokeHelp(anchor); |
352 | } |
353 | } |
354 | |
355 | |
356 | void MainWindow::setAutoDropEnabled( bool enabled ) |
357 | { |
358 | Settings::setAutoDropEnabled( enabled ); |
359 | if ( m_dealer ) |
360 | { |
361 | m_dealer->setAutoDropEnabled( enabled ); |
362 | if ( enabled ) |
363 | m_dealer->startDrop(); |
364 | else |
365 | m_dealer->stopDrop(); |
366 | } |
367 | updateGameActionList(); |
368 | } |
369 | |
370 | |
371 | void MainWindow::enableSolver(bool enable) |
372 | { |
373 | Settings::setSolverEnabled( enable ); |
374 | m_solverStatusLabel->setText( QString() ); |
375 | if (m_dealer) |
376 | { |
377 | m_dealer->setSolverEnabled(enable); |
378 | if(enable) |
379 | m_dealer->startSolver(); |
380 | } |
381 | } |
382 | |
383 | |
384 | void MainWindow::enableSounds( bool enable ) |
385 | { |
386 | Settings::setPlaySounds( enable ); |
387 | updateSoundEngine(); |
388 | } |
389 | |
390 | |
391 | void MainWindow::enableRememberState(bool enable) |
392 | { |
393 | Settings::setRememberStateOnExit( enable ); |
394 | } |
395 | |
396 | void MainWindow::newGame() |
397 | { |
398 | if (m_dealer && m_dealer->allowedToStartNewGame()) |
399 | startRandom(); |
400 | } |
401 | |
402 | void MainWindow::restart() |
403 | { |
404 | startNew(-1); |
405 | } |
406 | |
407 | void MainWindow::startRandom() |
408 | { |
409 | startNew(KRandom::random()); |
410 | } |
411 | |
412 | void MainWindow::startNew(int ) |
413 | { |
414 | m_dealer->startNew(gameNumber); |
415 | setGameCaption(); |
416 | } |
417 | |
418 | void MainWindow::slotPickRandom() |
419 | { |
420 | QList<KCardTheme> themes = KCardTheme::findAll(); |
421 | KCardTheme theme = themes.at( KRandom::random() % themes.size() ); |
422 | Settings::setCardTheme( theme.dirName() ); |
423 | |
424 | appearanceChanged(); |
425 | } |
426 | |
427 | void MainWindow::configureAppearance() |
428 | { |
429 | const QString previewFormat = "back;10_spade,jack_diamond,queen_club,king_heart;1_spade" ; |
430 | const QSet<QString> features = QSet<QString>() << "AngloAmerican" << "Backs1" ; |
431 | |
432 | if ( !KConfigDialog::showDialog("KPatAppearanceDialog" ) ) |
433 | { |
434 | KConfigDialog * dialog = new KConfigDialog( this, "KPatAppearanceDialog" , Settings::self() ); |
435 | |
436 | dialog->addPage( new KCardThemeWidget( features, previewFormat ), |
437 | i18n("Card Deck" ), |
438 | "games-config-theme" , |
439 | i18n("Select a card deck" ) |
440 | ); |
441 | |
442 | KgThemeProvider* provider = Renderer::self()->themeProvider(); |
443 | dialog->addPage( new KgThemeSelector(provider), |
444 | i18n("Game Theme" ), |
445 | "games-config-theme" , |
446 | i18n("Select a theme for non-card game elements" ) |
447 | ); |
448 | |
449 | connect( provider, SIGNAL(currentThemeChanged(const KgTheme*)), SLOT(appearanceChanged()) ); |
450 | connect( dialog, SIGNAL(settingsChanged(QString)), this, SLOT(appearanceChanged()) ); |
451 | dialog->show(); |
452 | } |
453 | } |
454 | |
455 | |
456 | void MainWindow::appearanceChanged() |
457 | { |
458 | if ( m_cardDeck && Settings::cardTheme() != m_cardDeck->theme().dirName() ) |
459 | { |
460 | KCardTheme theme( Settings::cardTheme() ); |
461 | if ( theme.isValid() ) |
462 | { |
463 | m_cardDeck->setTheme( KCardTheme( theme ) ); |
464 | if ( m_dealer ) |
465 | m_dealer->relayoutScene(); |
466 | } |
467 | } |
468 | } |
469 | |
470 | |
471 | void MainWindow::setGameCaption() |
472 | { |
473 | QString caption; |
474 | if ( m_dealer ) |
475 | { |
476 | const DealerInfo * di = m_dealer_map.value( m_dealer->gameId() ); |
477 | caption = QString("%1 - %2" ).arg( di->baseName() ).arg(m_dealer->gameNumber()); |
478 | } |
479 | setCaption( caption ); |
480 | } |
481 | |
482 | void MainWindow::slotGameSelected(int id) |
483 | { |
484 | if ( m_dealer_map.contains(id) ) |
485 | { |
486 | setGameType( id ); |
487 | QTimer::singleShot(0 , this, SLOT(startRandom()) ); |
488 | } |
489 | } |
490 | |
491 | void MainWindow::setGameType(int id) |
492 | { |
493 | // Only bother calling creating a new DealerScene if we don't already have |
494 | // the right DealerScene open. |
495 | if ( m_dealer && m_dealer_map.value( id ) == m_dealer_map.value( m_dealer->gameId() ) ) |
496 | { |
497 | m_dealer->recordGameStatistics(); |
498 | m_dealer->mapOldId( id ); |
499 | return; |
500 | } |
501 | |
502 | // If we're replacing an existing DealerScene, record the stats of the |
503 | // game already in progress. |
504 | if ( m_dealer ) |
505 | { |
506 | m_dealer->recordGameStatistics(); |
507 | delete m_dealer; |
508 | m_view->setScene(0); |
509 | m_dealer = 0; |
510 | } |
511 | |
512 | if ( !m_cardDeck ) |
513 | { |
514 | KCardTheme theme = KCardTheme( Settings::cardTheme() ); |
515 | if ( !theme.isValid() ) |
516 | theme = KCardTheme( Settings::defaultCardThemeValue() ); |
517 | |
518 | m_cardDeck = new KCardDeck( theme, this ); |
519 | } |
520 | |
521 | const DealerInfo * di = m_dealer_map.value(id, DealerInfoList::self()->games().first()); |
522 | m_dealer = di->createGame(); |
523 | m_dealer->setDeck( m_cardDeck ); |
524 | m_dealer->initialize(); |
525 | m_dealer->mapOldId( id ); |
526 | m_dealer->setSolverEnabled( m_solverEnabledAction->isChecked() ); |
527 | m_dealer->setAutoDropEnabled( m_autoDropEnabledAction->isChecked() ); |
528 | |
529 | m_view->setScene( m_dealer ); |
530 | |
531 | m_gameHelpAction->setText(i18nc("Is disabled and changes to \"Help &with Current Game\" when" |
532 | " there is no current game." , |
533 | "Help &with %1" , di->baseName().replace('&', "&&" ))); |
534 | |
535 | connect(m_dealer, SIGNAL(solverStateChanged(QString)), SLOT(updateSolverDescription(QString))); |
536 | connect(m_dealer, SIGNAL(updateMoves(int)), SLOT(slotUpdateMoves(int))); |
537 | |
538 | m_solverStatusLabel->setText(QString()); |
539 | m_solverStatusLabel->setVisible(true); |
540 | m_moveCountStatusLabel->setText(QString()); |
541 | m_moveCountStatusLabel->setVisible(true); |
542 | |
543 | updateActions(); |
544 | updateSoundEngine(); |
545 | } |
546 | |
547 | void MainWindow::slotShowGameSelectionScreen() |
548 | { |
549 | if (!m_dealer || m_dealer->allowedToStartNewGame()) |
550 | { |
551 | if (m_dealer) |
552 | { |
553 | m_dealer->recordGameStatistics(); |
554 | delete m_dealer; |
555 | m_view->setScene(0); |
556 | m_dealer = 0; |
557 | } |
558 | |
559 | if (!m_selector) |
560 | { |
561 | m_selector = new GameSelectionScene(this); |
562 | connect( m_selector, SIGNAL(gameSelected(int)), SLOT(slotGameSelected(int)) ); |
563 | } |
564 | m_view->setScene(m_selector); |
565 | |
566 | m_gameHelpAction->setText(i18nc("Shown when there is no game open. Is always disabled." , |
567 | "Help &with Current Game" )); |
568 | |
569 | updateActions(); |
570 | |
571 | setGameCaption(); |
572 | |
573 | m_solverStatusLabel->setVisible(false); |
574 | m_moveCountStatusLabel->setVisible(false); |
575 | } |
576 | } |
577 | |
578 | void MainWindow::updateActions() |
579 | { |
580 | // Enable/disable application actions that aren't appropriate on game |
581 | // selection screen. |
582 | actionCollection()->action( "new_game" )->setEnabled( m_dealer ); |
583 | actionCollection()->action( "new_deal" )->setEnabled( m_dealer ); |
584 | actionCollection()->action( "game_restart" )->setEnabled( m_dealer ); |
585 | actionCollection()->action( "game_save_as" )->setEnabled( m_dealer ); |
586 | m_gameHelpAction->setEnabled( m_dealer ); |
587 | |
588 | // Initially disable game actions. They'll be reenabled through signals |
589 | // if/when appropriate. |
590 | m_undoAction->setEnabled( false ); |
591 | m_redoAction->setEnabled( false ); |
592 | m_hintAction->setEnabled( false ); |
593 | m_demoAction->setEnabled( false ); |
594 | m_dropAction->setEnabled( false ); |
595 | m_drawAction->setEnabled( false ); |
596 | m_dealAction->setEnabled( false ); |
597 | m_redealAction->setEnabled( false ); |
598 | |
599 | // If a dealer exists, connect the game actions to it. |
600 | if ( m_dealer ) |
601 | { |
602 | connect( m_dealer, SIGNAL(undoPossible(bool)), m_undoAction, SLOT(setEnabled(bool)) ); |
603 | connect( m_dealer, SIGNAL(redoPossible(bool)), m_redoAction, SLOT(setEnabled(bool)) ); |
604 | |
605 | connect( m_dealer, SIGNAL(hintActive(bool)), m_hintAction, SLOT(setChecked(bool)) ); |
606 | connect( m_dealer, SIGNAL(gameInProgress(bool)), m_hintAction, SLOT(setEnabled(bool)) ); |
607 | |
608 | connect( m_dealer, SIGNAL(demoActive(bool)), this, SLOT(toggleDemoAction(bool)) ); |
609 | connect( m_dealer, SIGNAL(gameInProgress(bool)), m_demoAction, SLOT(setEnabled(bool)) ); |
610 | |
611 | connect( m_dealer, SIGNAL(dropActive(bool)), m_dropAction, SLOT(setChecked(bool)) ); |
612 | connect( m_dealer, SIGNAL(gameInProgress(bool)), m_dropAction, SLOT(setEnabled(bool)) ); |
613 | |
614 | connect( m_dealer, SIGNAL(gameInProgress(bool)), m_saveAction, SLOT(setEnabled(bool)) ); |
615 | |
616 | connect( m_leftAction, SIGNAL(triggered(bool)), m_dealer, SLOT(keyboardFocusLeft()) ); |
617 | connect( m_rightAction, SIGNAL(triggered(bool)), m_dealer, SLOT(keyboardFocusRight()) ); |
618 | connect( m_upAction, SIGNAL(triggered(bool)), m_dealer, SLOT(keyboardFocusUp()) ); |
619 | connect( m_downAction, SIGNAL(triggered(bool)), m_dealer, SLOT(keyboardFocusDown()) ); |
620 | connect( m_cancelAction, SIGNAL(triggered(bool)), m_dealer, SLOT(keyboardFocusCancel()) ); |
621 | connect( m_pickUpSetDownAction, SIGNAL(triggered(bool)), m_dealer, SLOT(keyboardFocusSelect()) ); |
622 | |
623 | if ( m_dealer->actions() & DealerScene::Draw ) |
624 | { |
625 | connect( m_drawAction, SIGNAL(triggered(bool)), m_dealer, SLOT(drawDealRowOrRedeal()) ); |
626 | connect( m_dealer, SIGNAL(newCardsPossible(bool)), m_drawAction, SLOT(setEnabled(bool)) ); |
627 | } |
628 | else if ( m_dealer->actions() & DealerScene::Deal ) |
629 | { |
630 | connect( m_dealAction, SIGNAL(triggered(bool)), m_dealer, SLOT(drawDealRowOrRedeal()) ); |
631 | connect( m_dealer, SIGNAL(newCardsPossible(bool)), m_dealAction, SLOT(setEnabled(bool)) ); |
632 | } |
633 | else if ( m_dealer->actions() & DealerScene::Redeal ) |
634 | { |
635 | connect( m_redealAction, SIGNAL(triggered(bool)), m_dealer, SLOT(drawDealRowOrRedeal()) ); |
636 | connect( m_dealer, SIGNAL(newCardsPossible(bool)), m_redealAction, SLOT(setEnabled(bool)) ); |
637 | } |
638 | |
639 | guiFactory()->unplugActionList( this, "dealer_options" ); |
640 | guiFactory()->plugActionList( this, "dealer_options" , m_dealer->configActions() ); |
641 | } |
642 | |
643 | updateGameActionList(); |
644 | } |
645 | |
646 | void MainWindow::updateGameActionList() |
647 | { |
648 | guiFactory()->unplugActionList( this, "game_actions" ); |
649 | |
650 | m_dropAction->setEnabled( m_dealer && !m_dealer->autoDropEnabled() ); |
651 | |
652 | if ( m_dealer ) |
653 | { |
654 | QList<QAction*> actionList; |
655 | if ( m_dealer->actions() & DealerScene::Hint ) |
656 | actionList.append( m_hintAction ); |
657 | if ( m_dealer->actions() & DealerScene::Demo ) |
658 | actionList.append( m_demoAction ); |
659 | if ( m_dealer->actions() & DealerScene::Draw ) |
660 | actionList.append( m_drawAction ); |
661 | if ( m_dealer->actions() & DealerScene::Deal ) |
662 | actionList.append( m_dealAction ); |
663 | if ( m_dealer->actions() & DealerScene::Redeal ) |
664 | actionList.append( m_redealAction ); |
665 | if ( !m_dealer->autoDropEnabled() ) |
666 | actionList.append( m_dropAction ); |
667 | guiFactory()->plugActionList( this, "game_actions" , actionList ); |
668 | } |
669 | } |
670 | |
671 | |
672 | void MainWindow::updateSoundEngine() |
673 | { |
674 | if ( m_dealer ) |
675 | { |
676 | if ( Settings::playSounds() ) |
677 | { |
678 | if ( !m_soundEngine ) |
679 | m_soundEngine = new SoundEngine( this ); |
680 | |
681 | connect( m_dealer, SIGNAL(cardsPickedUp()), m_soundEngine, SLOT(cardsPickedUp()) ); |
682 | connect( m_dealer, SIGNAL(cardsPutDown()), m_soundEngine, SLOT(cardsPutDown()) ); |
683 | } |
684 | else if ( m_soundEngine ) |
685 | { |
686 | disconnect( m_dealer, 0, m_soundEngine, 0 ); |
687 | } |
688 | } |
689 | } |
690 | |
691 | |
692 | void MainWindow::toggleDrop() |
693 | { |
694 | if ( m_dealer ) |
695 | { |
696 | if ( !m_dealer->isDropActive() ) |
697 | m_dealer->startDrop(); |
698 | else |
699 | m_dealer->stopDrop(); |
700 | } |
701 | } |
702 | |
703 | |
704 | void MainWindow::toggleHints() |
705 | { |
706 | if ( m_dealer ) |
707 | { |
708 | if ( !m_dealer->isHintActive() ) |
709 | m_dealer->startHint(); |
710 | else |
711 | m_dealer->stop(); |
712 | } |
713 | } |
714 | |
715 | |
716 | void MainWindow::toggleDemo() |
717 | { |
718 | if ( m_dealer ) |
719 | { |
720 | if ( !m_dealer->isDemoActive() ) |
721 | m_dealer->startDemo(); |
722 | else |
723 | m_dealer->stop(); |
724 | } |
725 | } |
726 | |
727 | |
728 | void MainWindow::toggleDemoAction(bool active) |
729 | { |
730 | m_demoAction->setChecked( active ); |
731 | m_demoAction->setIcon( KIcon( QLatin1String( active ? "media-playback-pause" : "media-playback-start" ) ) ); |
732 | } |
733 | |
734 | void MainWindow::toggleMenubar() |
735 | { |
736 | if(m_showMenubarAction->isChecked()) |
737 | menuBar()->show(); |
738 | else if(KMessageBox::warningContinueCancel(this, |
739 | i18n("Are you sure you want to hide the menubar? The current shortcut to show it again is %1." , |
740 | m_showMenubarAction->shortcut().toString(QKeySequence::NativeText)), |
741 | i18n("Hide Menubar" ), |
742 | KStandardGuiItem::cont(), |
743 | KStandardGuiItem::cancel(), |
744 | QLatin1String("MenubarWarning" )) == KMessageBox::Continue) |
745 | menuBar()->hide(); |
746 | else |
747 | m_showMenubarAction->setChecked(true); |
748 | } |
749 | |
750 | void MainWindow::saveNewToolbarConfig() |
751 | { |
752 | KXmlGuiWindow::saveNewToolbarConfig(); |
753 | updateGameActionList(); |
754 | } |
755 | |
756 | void MainWindow::closeEvent(QCloseEvent *e) |
757 | { |
758 | QString stateFileName = KStandardDirs::locateLocal( "appdata" , saved_state_file ); |
759 | QFile stateFile( stateFileName ); |
760 | |
761 | // Remove the existing state file, if any. |
762 | stateFile.remove(); |
763 | |
764 | if ( m_dealer ) |
765 | { |
766 | if ( Settings::rememberStateOnExit() && !m_dealer->isGameWon() ) |
767 | { |
768 | stateFile.open( QFile::WriteOnly | QFile::Truncate ); |
769 | m_dealer->saveFile( &stateFile ); |
770 | } |
771 | else |
772 | { |
773 | // If there's a game in progress and we aren't going to save it |
774 | // then record its statistics, since the DealerScene will be destroyed |
775 | // shortly. |
776 | m_dealer->recordGameStatistics(); |
777 | } |
778 | } |
779 | |
780 | KXmlGuiWindow::closeEvent(e); |
781 | } |
782 | |
783 | |
784 | void MainWindow::newNumberedDeal() |
785 | { |
786 | if ( !m_dealDialog ) |
787 | { |
788 | m_dealDialog = new NumberedDealDialog( this ); |
789 | connect( m_dealDialog, SIGNAL(dealChosen(int,int)), this, SLOT(startNumbered(int,int)) ); |
790 | } |
791 | |
792 | if ( m_dealer ) |
793 | { |
794 | m_dealDialog->setGameType( m_dealer->oldId() ); |
795 | m_dealDialog->setDealNumber( m_dealer->gameNumber() ); |
796 | } |
797 | |
798 | m_dealDialog->show(); |
799 | } |
800 | |
801 | |
802 | void MainWindow::startNumbered( int gameId, int dealNumber ) |
803 | { |
804 | if ( !m_dealer || m_dealer->allowedToStartNewGame() ) |
805 | { |
806 | setGameType( gameId ); |
807 | startNew( dealNumber ); |
808 | } |
809 | } |
810 | |
811 | |
812 | void MainWindow::nextDeal() |
813 | { |
814 | if ( !m_dealer ) |
815 | newNumberedDeal(); |
816 | else if ( m_dealer->allowedToStartNewGame() ) |
817 | startNew( m_dealer->gameNumber() == INT_MAX ? 1 : m_dealer->gameNumber() + 1 ); |
818 | } |
819 | |
820 | |
821 | void MainWindow::previousDeal() |
822 | { |
823 | if ( !m_dealer ) |
824 | newNumberedDeal(); |
825 | else if ( m_dealer->allowedToStartNewGame() ) |
826 | startNew( m_dealer->gameNumber() == 1 ? INT_MAX : m_dealer->gameNumber() - 1 ); |
827 | } |
828 | |
829 | |
830 | bool MainWindow::loadGame( const KUrl & url, bool addToRecentFiles ) |
831 | { |
832 | QString fileName; |
833 | if( !KIO::NetAccess::download( url, fileName, this ) ) |
834 | { |
835 | KMessageBox::error( this, i18n("Downloading file failed." ) ); |
836 | return false; |
837 | } |
838 | QFile file( fileName ); |
839 | |
840 | if ( !file.open( QIODevice::ReadOnly ) ) |
841 | { |
842 | KMessageBox::error( this, i18n("Opening file failed." ) ); |
843 | KIO::NetAccess::removeTempFile( fileName ); |
844 | return false; |
845 | } |
846 | |
847 | QXmlStreamReader xml( &file ); |
848 | if ( !xml.readNextStartElement() ) |
849 | { |
850 | KMessageBox::error( this, i18n("Error reading XML file: " ) + xml.errorString() ); |
851 | KIO::NetAccess::removeTempFile( fileName ); |
852 | return false; |
853 | } |
854 | |
855 | int gameId = -1; |
856 | bool isLegacyFile; |
857 | |
858 | if ( xml.name() == "dealer" ) |
859 | { |
860 | isLegacyFile = true; |
861 | bool ok; |
862 | int id = xml.attributes().value("id" ).toString().toInt( &ok ); |
863 | if ( ok ) |
864 | gameId = id; |
865 | } |
866 | else if ( xml.name() == "kpat-game" ) |
867 | { |
868 | isLegacyFile = false; |
869 | QStringRef gameType = xml.attributes().value("game-type" ); |
870 | foreach ( const DealerInfo * di, DealerInfoList::self()->games() ) |
871 | { |
872 | if ( di->baseIdString() == gameType ) |
873 | { |
874 | gameId = di->baseId(); |
875 | break; |
876 | } |
877 | } |
878 | } |
879 | else |
880 | { |
881 | KMessageBox::error( this, i18n("XML file is not a KPat save." ) ); |
882 | KIO::NetAccess::removeTempFile( fileName ); |
883 | return false; |
884 | } |
885 | |
886 | if ( !m_dealer_map.contains( gameId ) ) |
887 | { |
888 | KMessageBox::error( this, i18n("Unrecognized game id." ) ); |
889 | KIO::NetAccess::removeTempFile( fileName ); |
890 | return false; |
891 | } |
892 | |
893 | // Only bother the user to ask for permission after we've determined the |
894 | // save game file is at least somewhat valid. |
895 | if ( m_dealer && !m_dealer->allowedToStartNewGame() ) |
896 | { |
897 | KIO::NetAccess::removeTempFile( fileName ); |
898 | return false; |
899 | } |
900 | |
901 | setGameType( gameId ); |
902 | |
903 | xml.clear(); |
904 | file.reset(); |
905 | |
906 | bool success = isLegacyFile ? m_dealer->loadLegacyFile( &file ) |
907 | : m_dealer->loadFile( &file ); |
908 | |
909 | file.close(); |
910 | KIO::NetAccess::removeTempFile( fileName ); |
911 | |
912 | if ( !success ) |
913 | { |
914 | KMessageBox::error( this, i18n("Errors encountered while parsing file." ) ); |
915 | slotShowGameSelectionScreen(); |
916 | return false; |
917 | } |
918 | |
919 | setGameCaption(); |
920 | |
921 | if ( addToRecentFiles ) |
922 | m_recentFilesAction->addUrl( url ); |
923 | |
924 | return true; |
925 | } |
926 | |
927 | |
928 | void MainWindow::loadGame() |
929 | { |
930 | KFileDialog dialog( dialogUrl, "" , this, 0 ); |
931 | dialog.setOperationMode( KFileDialog::Opening ); |
932 | dialog.setMimeFilter( QStringList() << saveFileMimeType << legacySaveFileMimeType << "all/allfiles" ); |
933 | dialog.setCaption( i18n("Load" ) ); |
934 | |
935 | if ( dialog.exec() == KFileDialog::Accepted ) |
936 | { |
937 | KUrl url = dialog.selectedUrl(); |
938 | if ( !url.isEmpty() ) |
939 | loadGame( url, true ); |
940 | } |
941 | } |
942 | |
943 | void MainWindow::saveGame() |
944 | { |
945 | if ( !m_dealer ) |
946 | return; |
947 | |
948 | KFileDialog dialog( dialogUrl, "" , this, 0 ); |
949 | dialog.setOperationMode( KFileDialog::Saving ); |
950 | dialog.setMimeFilter( QStringList() << saveFileMimeType << legacySaveFileMimeType, saveFileMimeType ); |
951 | dialog.setConfirmOverwrite( true ); |
952 | dialog.setCaption( i18n("Save" ) ); |
953 | if ( dialog.exec() != KFileDialog::Accepted ) |
954 | return; |
955 | |
956 | KUrl url = dialog.selectedUrl(); |
957 | if ( url.isEmpty() ) |
958 | return; |
959 | |
960 | QFile localFile; |
961 | KTemporaryFile tempFile; |
962 | if ( url.isLocalFile() ) |
963 | { |
964 | localFile.setFileName( url.toLocalFile() ); |
965 | if ( !localFile.open( QFile::WriteOnly ) ) |
966 | { |
967 | KMessageBox::error( this, i18n("Error opening file for writing. Saving failed." ) ); |
968 | return; |
969 | } |
970 | } |
971 | else |
972 | { |
973 | if ( !tempFile.open() ) |
974 | { |
975 | KMessageBox::error( this, i18n("Unable to create temporary file. Saving failed." ) ); |
976 | return; |
977 | } |
978 | } |
979 | QFile & file = url.isLocalFile() ? localFile : tempFile; |
980 | |
981 | if ( dialog.currentFilterMimeType()->is( legacySaveFileMimeType ) ) |
982 | { |
983 | m_dealer->saveLegacyFile( &file ); |
984 | } |
985 | else |
986 | { |
987 | m_dealer->saveFile( &file ); |
988 | } |
989 | |
990 | file.close(); |
991 | |
992 | if ( !url.isLocalFile() && !KIO::NetAccess::upload( file.fileName(), url, this ) ) |
993 | { |
994 | KMessageBox::error( this, i18n("Error uploading file. Saving failed." ) ); |
995 | return; |
996 | } |
997 | |
998 | m_recentFilesAction->addUrl( url ); |
999 | } |
1000 | |
1001 | void MainWindow::showStats() |
1002 | { |
1003 | QPointer<StatisticsDialog> dlg = new StatisticsDialog(this); |
1004 | if (m_dealer) |
1005 | dlg->showGameType(m_dealer->oldId()); |
1006 | dlg->exec(); |
1007 | delete dlg; |
1008 | } |
1009 | |
1010 | void MainWindow::updateSolverDescription( const QString & text ) |
1011 | { |
1012 | m_solverStatusLabel->setText( text ); |
1013 | } |
1014 | |
1015 | void MainWindow::slotUpdateMoves(int moves) |
1016 | { |
1017 | m_moveCountStatusLabel->setText(i18np("1 move" , "%1 moves" , moves)); |
1018 | } |
1019 | |
1020 | void MainWindow::slotSnapshot() |
1021 | { |
1022 | if ( m_dealer_it == m_dealer_map.constEnd() ) |
1023 | { |
1024 | // first call |
1025 | m_dealer_it = m_dealer_map.constBegin(); |
1026 | } |
1027 | |
1028 | setGameType( m_dealer_it.key() ); |
1029 | m_dealer->setAutoDropEnabled( false ); |
1030 | startRandom(); |
1031 | |
1032 | QTimer::singleShot( 1000, this, SLOT(slotSnapshot2()) ); |
1033 | } |
1034 | |
1035 | void MainWindow::slotSnapshot2() |
1036 | { |
1037 | m_dealer->createDump().save( QString( "%1.png" ).arg( m_dealer->gameId() ) ); |
1038 | |
1039 | ++m_dealer_it; |
1040 | if ( m_dealer_it != m_dealer_map.constEnd() ) |
1041 | QTimer::singleShot( 0, this, SLOT(slotSnapshot()) ); |
1042 | } |
1043 | |
1044 | |
1045 | void MainWindow::generateThemePreview() |
1046 | { |
1047 | const QSize previewSize( 240, 160 ); |
1048 | m_view->setMinimumSize( previewSize ); |
1049 | m_view->setMaximumSize( previewSize ); |
1050 | |
1051 | QImage img( m_view->contentsRect().size(), QImage::Format_ARGB32 ); |
1052 | img.fill( Qt::transparent ); |
1053 | QPainter p( &img ); |
1054 | |
1055 | foreach ( KCard * c, m_cardDeck->cards() ) |
1056 | c->completeAnimation(); |
1057 | |
1058 | m_view->render( &p ); |
1059 | |
1060 | slotShowGameSelectionScreen(); |
1061 | |
1062 | QRect leftHalf( 0, 0, m_view->contentsRect().width() / 2, m_view->contentsRect().height() ); |
1063 | m_view->render( &p, leftHalf, leftHalf ); |
1064 | |
1065 | p.end(); |
1066 | |
1067 | img.save( "preview.png" ); |
1068 | |
1069 | m_view->setMinimumSize( 0, 0 ); |
1070 | m_view->setMaximumSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX ); |
1071 | } |
1072 | |
1073 | #include "mainwindow.moc" |
1074 | |