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
88namespace
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
96MainWindow::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
135MainWindow::~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
147void 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
333void MainWindow::undoMove() {
334 if( m_dealer )
335 m_dealer->undo();
336}
337
338void MainWindow::redoMove() {
339 if( m_dealer )
340 m_dealer->redo();
341}
342
343void 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
356void 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
371void 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
384void MainWindow::enableSounds( bool enable )
385{
386 Settings::setPlaySounds( enable );
387 updateSoundEngine();
388}
389
390
391void MainWindow::enableRememberState(bool enable)
392{
393 Settings::setRememberStateOnExit( enable );
394}
395
396void MainWindow::newGame()
397{
398 if (m_dealer && m_dealer->allowedToStartNewGame())
399 startRandom();
400}
401
402void MainWindow::restart()
403{
404 startNew(-1);
405}
406
407void MainWindow::startRandom()
408{
409 startNew(KRandom::random());
410}
411
412void MainWindow::startNew(int gameNumber)
413{
414 m_dealer->startNew(gameNumber);
415 setGameCaption();
416}
417
418void 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
427void 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
456void 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
471void 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
482void 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
491void 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
547void 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
578void 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
646void 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
672void 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
692void 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
704void 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
716void 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
728void 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
734void 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
750void MainWindow::saveNewToolbarConfig()
751{
752 KXmlGuiWindow::saveNewToolbarConfig();
753 updateGameActionList();
754}
755
756void 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
784void 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
802void 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
812void 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
821void 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
830bool 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
928void 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
943void 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
1001void 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
1010void MainWindow::updateSolverDescription( const QString & text )
1011{
1012 m_solverStatusLabel->setText( text );
1013}
1014
1015void MainWindow::slotUpdateMoves(int moves)
1016{
1017 m_moveCountStatusLabel->setText(i18np("1 move", "%1 moves", moves));
1018}
1019
1020void 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
1035void 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
1045void 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