2 Copyright (c) 2006-2008 Paolo Capriotti <p.capriotti@gmail.com>
3 (c) 2006-2007 Maurizio Monge <maurizio.monge@kdemail.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
11 #include "mainwindow.h"
12 #include <boost/scoped_ptr.hpp>
14 #include <QKeySequence>
15 #include <QStackedWidget>
16 #include <QDockWidget>
17 #include <QCloseEvent>
18 #include <QTextStream>
22 #include <KActionCollection>
24 #include <KFileDialog>
27 #include <kio/netaccess.h>
28 #include <KMessageBox>
30 #include <KStandardAction>
31 #include <KTemporaryFile>
33 #include <core/behaviour.h>
34 #include <core/color.h>
35 #include <core/state.h>
38 #include "actioncollection.h"
39 #include "chesstable.h"
45 #include "controllers/editgame.h"
46 #include "controllers/editposition.h"
47 #include "engineinfo.h"
48 #include "movelist_table.h"
49 #include "icsconnection.h"
51 #include "mastersettings.h"
54 #include "pgnparser.h"
55 #include "pref_highlight.h"
56 #include "pref_preferences.h"
57 #include "tabwidget.h"
60 using namespace boost
;
62 MainWindow::~MainWindow() {
67 MainWindow::MainWindow(const QString
& variant
)
69 , m_ui(actionCollection()) {
70 setObjectName("tagua_main");
71 m_main
= new TabWidget(this);
72 m_main
->setTabBarHidden(true);
73 setCentralWidget(m_main
);
75 m_movelist_stack
= new QStackedWidget
;
77 connect(m_main
, SIGNAL(currentChanged(int)),
78 this, SLOT(changeTab(int)));
79 connect(m_main
, SIGNAL(closeTab()),
80 this, SLOT(closeTab()));
82 movelist_dock
= new QDockWidget(this);
83 movelist_dock
->setWidget(m_movelist_stack
);
84 movelist_dock
->setWindowTitle(i18n("Move list"));
85 movelist_dock
->setObjectName("move_list");
86 addDockWidget(Qt::LeftDockWidgetArea
, movelist_dock
, Qt::Vertical
);
87 movelist_dock
->show();
89 Variant
* v
= Variants::self().create(variant
);
91 kFatal() << "Cannot instantiate variant \"" << variant
<< "\", aborting";
93 ChessTable
* board
= new ChessTable(v
);
97 console_dock
= new QDockWidget(this);
98 console
= new Console(0, i18n("FICS Connection"));
99 console_dock
->setWidget(console
);
100 console_dock
->setFocusProxy(console
);
101 console_dock
->setWindowTitle(i18n("Console"));
102 console_dock
->setObjectName("console");
103 addDockWidget(Qt::BottomDockWidgetArea
, console_dock
, Qt::Horizontal
);
104 console_dock
->setWindowFlags(console_dock
->windowFlags() & ~Qt::WindowStaysOnTopHint
);
105 console_dock
->show();
107 settings().onChange(this, "settingsChanged");
109 connect(board
, SIGNAL(error(ErrorCode
)), this, SLOT(displayErrorMessage(ErrorCode
)));
115 // start in edit game mode
116 newGame(v
->name(), StatePtr(), true);
117 updateVariantActions();
120 ChessTable
* MainWindow::table() {
121 return qobject_cast
<ChessTable
*>(m_main
->currentWidget());
124 KAction
* MainWindow::installRegularAction(const QString
& name
, const KIcon
& icon
, const QString
& text
, QObject
* obj
, const char* slot
) {
125 KAction
* temp
= new KAction(icon
, text
, this);
126 actionCollection()->addAction(name
, temp
);
127 connect(temp
, SIGNAL(triggered(bool)), obj
, slot
);
131 void MainWindow::setupEngineMenu() {
132 QMenu
* engine_menu
= 0;
133 SettingArray engine_settings
= settings().group("engines").array("engine");
134 foreach (Settings s
, engine_settings
) {
136 // this way the menu is created only if there is at least one engine
137 engine_menu
= menuBar()->addMenu(i18n("E&ngines"));
141 EngineDetails engine_details
;
142 engine_details
.load(s
);
144 EngineInfo
* engine
= new EngineInfo(engine_details
, ui());
146 m_engines
.push_back(engine
);
148 QMenu
* menu
= engine_menu
->addMenu(engine_details
.name
);
151 KAction
* play_white
= new KAction(i18n("Play as &white"), this);
152 play_white
->setCheckable(true);
153 connect(play_white
, SIGNAL(triggered()), engine
, SLOT(playAsWhite()));
154 menu
->addAction(play_white
);
157 KAction
* play_black
= new KAction(i18n("Play as &black"), this);
158 play_black
->setCheckable(true);
159 connect(play_black
, SIGNAL(triggered()), engine
, SLOT(playAsBlack()));
160 menu
->addAction(play_black
);
162 #if 0 // disable analysis for the moment
164 KAction
* analyze
= new KAction(i18n("&Analyze"), this);
165 analyze
->setCheckable(true);
166 connect(analyze
, SIGNAL(triggered()), engine
, SLOT(analyze()));
167 menu
->addAction(analyze
);
173 void MainWindow::setupActions() {
176 KStandardAction::openNew(this, SLOT(newGame()), actionCollection());
177 KStandardAction::open(this, SLOT(loadGame()), actionCollection());
178 KStandardAction::save(this, SLOT(saveGame()), actionCollection());
179 KStandardAction::saveAs(this, SLOT(saveGameAs()), actionCollection());
180 KStandardAction::quit(this, SLOT(quit()), actionCollection());
181 KStandardAction::preferences(this, SLOT(preferences()), actionCollection());
183 installRegularAction("back", KIcon("go-previous"), i18n("&Back"), &ui(), SLOT(back()));
184 installRegularAction("forward", KIcon("go-next"), i18n("&Forward"), &ui(), SLOT(forward()));
185 installRegularAction("begin", KIcon("go-first"), i18n("Be&gin"), &ui(), SLOT(gotoFirst()));
186 installRegularAction("end", KIcon("go-last"), i18n("&End"), &ui(), SLOT(gotoLast()));
187 installRegularAction("connect", KIcon("network-connect"), i18n("&Connect"), this, SLOT(icsConnect()));
188 installRegularAction("disconnect", KIcon("network-disconnect"),
189 i18n("&Disconnect"), this, SLOT(icsDisconnect()));
191 KStandardAction::undo(&ui(), SLOT(undo()), actionCollection());
192 KStandardAction::redo(&ui(), SLOT(redo()), actionCollection());
194 // installRegularAction("pgnCopy", KIcon("edit-copy"), i18n("Copy PGN"), this, SLOT(pgnCopy()));
195 // installRegularAction("pgnPaste", KIcon("edit-paste"), i18n("Paste PGN"), this, SLOT(pgnPaste()));
196 installRegularAction("editPosition", KIcon("edit"), i18n("&Edit position"), this, SLOT(editPosition()));
197 installRegularAction("clearBoard", KIcon("edit-delete"), i18n("&Clear board"), &ui(), SLOT(clearBoard()));
198 installRegularAction("setStartingPosition", KIcon("contents"), i18n("&Set starting position"),
199 &ui(), SLOT(setStartingPosition()));
200 // installRegularAction("copyPosition", KIcon(), i18n("&Copy position"), &ui(), SLOT(copyPosition()));
201 // installRegularAction("pastePosition", KIcon(), i18n("&Paste position"), &ui(), SLOT(pastePosition()));
202 tmp
= installRegularAction("flip", KIcon("object-rotate-left"), i18n("&Flip view"), this, SLOT(flipView()));
203 tmp
->setShortcut(Qt::CTRL
+ Qt::Key_F
);
204 installRegularAction("toggleConsole", KIcon("utilities-terminal"), i18n("Toggle &console"), this, SLOT(toggleConsole()));
205 installRegularAction("toggleMoveList", KIcon("view-list-tree"), i18n("Toggle &move list"), this, SLOT(toggleMoveList()));
208 void MainWindow::updateVariantActions(bool unplug
) {
209 ActionCollection
* variant_actions
= m_ui
.variantActions();
211 unplugActionList("variantActions");
212 if (variant_actions
) {
213 plugActionList("variantActions", variant_actions
->actions());
216 kWarning() << "No variant actions";
220 void MainWindow::readSettings() { }
221 void MainWindow::writeSettings() { }
223 void MainWindow::closeEvent(QCloseEvent
* e
) {
230 void MainWindow::keyPressEvent(QKeyEvent
* event
) {
231 if (event
->key() == Qt::Key_F5
) {
232 ui().createCtrlAction();
236 void MainWindow::keyReleaseEvent(QKeyEvent
* event
) {
237 if (event
->key() == Qt::Key_F5
) {
238 ui().destroyCtrlAction();
242 void MainWindow::changeTab(int index
) {
243 m_ui
.setCurrentTab(m_main
->currentWidget());
244 m_movelist_stack
->setCurrentIndex(index
);
245 updateVariantActions();
248 void MainWindow::closeTab() {
249 if (m_main
->count() > 1) {
250 int old_index
= m_main
->currentIndex();
251 ChessTable
* old_board
= table();
253 int new_index
= old_index
- 1;
255 new_index
= old_index
+ 1;
256 m_main
->setCurrentIndex(new_index
);
258 m_main
->removeTab(old_index
);
259 m_movelist_stack
->removeWidget(m_movelist_stack
->widget(old_index
));
260 m_ui
.removeController(old_board
);
262 if (m_main
->count() <= 1) {
263 m_main
->setTabBarHidden(true);
266 #if 0 // this doesn't work... why?
267 ChessTable
* old_board
= table();
268 m_ui
.removeController(old_board
);
269 m_movelist_stack
->removeWidget(
270 m_movelist_stack
->widget(m_main
->currentIndex()));
271 m_main
->removeTab(m_main
->currentIndex());
275 if (m_main
->count() <= 1) {
276 m_main
->hideTabBar();
279 // update ui controller (just in case...)
280 m_ui
.setCurrentTab(m_main
->currentWidget());
285 void MainWindow::createTab(ChessTable
* board
, const shared_ptr
<Controller
>& controller
,
286 const QString
& caption
, int index
) {
288 index
= m_main
->addTab(board
, caption
);
290 m_main
->insertTab(index
, board
, caption
);
292 m_ui
.addController(board
, controller
);
293 // m_ui.setCurrentTab(board);
294 m_movelist_stack
->addWidget(board
->moveListTable());
296 m_main
->setCurrentIndex(index
);
297 m_movelist_stack
->setCurrentIndex(index
);
299 if (m_main
->count() > 1) m_main
->setTabBarHidden(false);
303 void MainWindow::cleanupGame(const QString
& what
, const QString
& result
) {
310 void MainWindow::cleanupGame() {
315 bool MainWindow::newGame(const QString
& variantName
,
316 const StatePtr
& startingPos
,
318 Variant
* variant
= Variants::self().create(variantName
);
320 kWarning() << "no variant" << variantName
<< "found";
321 variant
= Variants::self().create("chess");
325 ChessTable
* board
= newTab
? new ChessTable(variant
) : table();
326 QString text
= QString("Editing %1 game").arg(variant
->name().toLower());
328 shared_ptr
<Controller
> controller(
329 new EditGameController(board
, startingPos
));
331 createTab(board
, controller
, text
);
334 unplugActionList("variantActions");
335 ui().setController(controller
);
336 table()->setPlayers(Player(), Player());
337 m_main
->setTabText(m_main
->currentIndex(), text
);
338 updateVariantActions(false);
343 kError() << "Could not find the chess variant";
350 void MainWindow::editPosition() {
353 shared_ptr
<Controller
> controller(new EditPositionController(table(), ChessVariant::info()));
354 m_main
->setTabText(m_main
->currentIndex(), "Editing chess position");
355 ui().setController(controller
);
359 void MainWindow::setupGame(const GameInfo
* gameInfo
, const PositionInfo
& style12
) {
360 QString variantCode
= gameInfo
->variant();
361 Variant
* variant
= Variants::self().create(variantCode
);
362 shared_ptr
<EditGameController
> controller(
363 new EditGameController(table()));
364 Q_ASSERT(style12
.relation
== PositionInfo::NotMyMove
||
365 style12
.relation
== PositionInfo::MyMove
);
368 const IColor
* turn
= style12
.position
->turn();
369 const IBehaviour
* behaviour
= style12
.position
->behaviour();
370 if (!behaviour
) return;
371 const IColor
* side
= style12
.relation
== PositionInfo::MyMove
?
372 behaviour
->opponent(turn
) : turn
;
374 if (controller
->addICSPlayer(side
, style12
.gameNumber
, m_connection
)) {
375 ui().setController(controller
);
376 table()->setPlayers(gameInfo
->white(), gameInfo
->black());
377 m_main
->setTabText(m_main
->currentIndex(),
378 QString("FICS Game - %1 vs %2").arg(style12
.whitePlayer
)
379 .arg(style12
.blackPlayer
));
383 void MainWindow::setupExaminedGame(const GameInfo
* gameInfo
, const PositionInfo
& style12
) {
385 Variants::self().create(gameInfo
->variant()));
386 shared_ptr
<EditGameController
> controller(
387 new EditGameController(table()));
388 if (controller
->setExaminationMode(style12
.gameNumber
, m_connection
)) {
389 table()->setPlayers(Player(style12
.whitePlayer
, -1),
390 Player(style12
.blackPlayer
, -1));
391 ui().setController(controller
);
392 m_main
->setTabText(m_main
->currentIndex(),
393 QString("Examining - %1 vs %2").arg(style12
.whitePlayer
)
394 .arg(style12
.blackPlayer
));
399 void MainWindow::setupObservedGame(const GameInfo
* g
, const PositionInfo
& style12
) {
400 Variant
* variant
= Variants::self().create(g
->variant());
401 std::auto_ptr
<ChessTable
> board(new ChessTable(variant
));
403 shared_ptr
<EditGameController
> controller(
404 new EditGameController(board
.get()));
405 if (controller
->setObserveMode(style12
.gameNumber
, m_connection
)) {
406 board
->setPlayers(Player(style12
.whitePlayer
, -1),
407 Player(style12
.blackPlayer
, -1));
408 // ui().setController(controller);
409 //kDebug() << "adding tab";
410 createTab(board
.get(), controller
,
411 QString("Observing - %1 vs %2").arg(style12
.whitePlayer
)
412 .arg(style12
.blackPlayer
));
417 void MainWindow::setupPGN(const QString
& s
) {
420 std::map
<QString
, QString
>::const_iterator var
= pgn
.m_tags
.find("Variant");
423 if (var
== pgn
.m_tags
.end())
426 variant
= var
->second
;
428 newGame(variant
, StatePtr(), false);
432 bool MainWindow::openFile(const QString
& filename
) {
433 QFileInfo
info(filename
);
436 KMessageBox::sorry(this, i18n("You have specified a folder"), i18n("Error"));
440 if(!info
.exists() || !info
.isFile()) {
441 KMessageBox::sorry(this, i18n("The specified file does not exist"), i18n("Error"));
445 QFile
file(filename
);
447 if(!file
.open(QIODevice::ReadOnly
)) {
448 KMessageBox::sorry(this, i18n("You do not have read permission to this file."), i18n("Error"));
452 QTextStream
stream(&file
);
454 codec
= QTextCodec::codecForLocale();
455 stream
.setCodec(codec
);
457 setupPGN(stream
.readAll());
458 //ui().pgnPaste(stream.readAll());
462 void MainWindow::loadGame() {
463 KUrl url
= KFileDialog::getOpenUrl(KUrl(), "*.pgn", this, i18n("Open PGN file"));
469 if (KIO::NetAccess::download(url
, tmp_file
, this)) {
471 KIO::NetAccess::removeTempFile(tmp_file
);
474 KMessageBox::error(this, KIO::NetAccess::lastErrorString());
477 void MainWindow::saveGame() {
478 if (ui().url().isEmpty())
481 ui().setUrl(saveGame(ui().url()));
484 void MainWindow::saveGameAs() {
485 ui().setUrl(saveGame(KFileDialog::getSaveUrl(KUrl(), "*.pgn", this, i18n("Save PGN file"))));
488 bool MainWindow::checkOverwrite(const KUrl
& url
) {
489 if (!url
.isLocalFile())
492 QFileInfo
info(url
.path());
496 return KMessageBox::Cancel
!= KMessageBox::warningContinueCancel(this,
497 i18n("A file named \"%1\" already exists. "
498 "Are you sure you want to overwrite it?" , info
.fileName()),
499 i18n("Overwrite File?"),
500 KGuiItem(i18n("&Overwrite")));
503 KUrl
MainWindow::saveGame(const KUrl
& url
) {
504 if (!checkOverwrite(url
))
510 if (!url
.isLocalFile()) {
511 // save in a temporary file
512 KTemporaryFile tmp_file
;
515 if (!KIO::NetAccess::upload(tmp_file
.fileName(), url
, this))
519 QFile
file(url
.path());
520 if (!file
.open(QIODevice::WriteOnly
))
528 void MainWindow::saveFile(QFile
& file
) {
529 QTextStream
stream(&file
);
531 codec
= QTextCodec::codecForLocale();
532 stream
.setCodec(codec
);
533 stream
<< ui().currentPGN() << "\n";
536 void MainWindow::createConnection(const QString
& username
, const QString
& password
,
537 const QString
& host
, quint16 port
,
538 const QString
& timeseal
, const QString
& timeseal_cmd
) {
539 m_connection
= shared_ptr
<ICSConnection
>(new ICSConnection
);
541 connect(m_connection
.get(), SIGNAL(established()), this, SLOT(onEstablishConnection()));
542 connect(m_connection
.get(), SIGNAL(hostLookup()), this, SLOT(onHostLookup()));
543 connect(m_connection
.get(), SIGNAL(hostFound()), this, SLOT(onHostFound()));
545 connect(m_connection
.get(), SIGNAL(receivedLine(QString
, int)), console
, SLOT(displayText(QString
, int)));
546 connect(m_connection
.get(), SIGNAL(receivedText(QString
, int)), console
, SLOT(displayText(QString
, int)));
548 connect(console
, SIGNAL(receivedInput(const QString
&)), m_connection
.get(), SLOT(sendText(const QString
&)));
549 connect(console
, SIGNAL(notify()), this, SLOT(flash()));
551 connect(m_connection
.get(), SIGNAL(loginPrompt()), this, SLOT(sendLogin()));
552 connect(m_connection
.get(), SIGNAL(registeredNickname()), this, SLOT(sendBlankPassword()));
553 connect(m_connection
.get(), SIGNAL(prompt()), this, SLOT(prompt()));
556 connect(m_connection
.get(), SIGNAL(creatingExaminedGame(const GameInfo
*, const PositionInfo
&)),
557 this, SLOT(setupExaminedGame(const GameInfo
*, const PositionInfo
&)));
558 connect(m_connection
.get(), SIGNAL(endingExaminedGame(int)), this, SLOT(cleanupGame()));
560 connect(m_connection
.get(), SIGNAL(creatingObservedGame(const GameInfo
*, const PositionInfo
&)),
561 this, SLOT(setupObservedGame(const GameInfo
*, const PositionInfo
&)));
562 connect(m_connection
.get(), SIGNAL(endingObservedGame(int)), this, SLOT(cleanupGame()));
565 connect(m_connection
.get(), SIGNAL(creatingGame(const GameInfo
*, const PositionInfo
&)),
566 this, SLOT(setupGame(const GameInfo
*, const PositionInfo
&)));
567 connect(m_connection
.get(), SIGNAL(endingGame(const QString
&, const QString
&)),
568 this, SLOT(cleanupGame(const QString
&, const QString
&)));
569 connect(m_connection
.get(), SIGNAL(notification()), this, SLOT(flash()));
571 m_connection
->establish(host
.toAscii().constData(), port
, timeseal
, timeseal_cmd
);
574 // send username / password combination
575 if (!username
.isEmpty()) {
576 m_connection
->sendText(username
);
577 m_connection
->sendText(password
);
580 quickConnectDialog
.reset();
583 void MainWindow::icsConnect() {
584 quickConnectDialog
= shared_ptr
<QConnect
>(new QConnect(this));
585 connect(quickConnectDialog
.get(),
586 SIGNAL(acceptConnection(const QString
&,
593 SLOT(createConnection(const QString
&,
599 quickConnectDialog
->show();
602 void MainWindow::destroyConnection() {
603 m_connection
.reset();
606 void MainWindow::icsDisconnect() {
612 void MainWindow::testConnect() {
613 Settings s_ics
= settings().group("ics");
614 if (s_ics
["username"]) {
615 QString username
= s_ics
["username"].value
<QString
>();
616 QString password
= (s_ics
["password"] | "");
617 QString host
= (s_ics
["icsHost"] | "freechess.org");
618 quint16 port
= (s_ics
["icsPort"] | 5000);
619 createConnection(username
, password
, host
, port
, QString(), QString() );
625 void MainWindow::onEstablishConnection() {
626 // kDebug() << "connection established";
629 void MainWindow::onConnectionError(int ) {
630 // kDebug() << "connection error (" << code << ")";
633 void MainWindow::onHostLookup() {
634 // kDebug() << "hostLookup..." << std::flush;
637 void MainWindow::onHostFound() {
638 // kDebug() << "found";
642 void MainWindow::sendLogin() {
643 // kDebug() << "sending username";
644 // connection->sendText(connection->username());
647 void MainWindow::sendBlankPassword() {
648 m_connection
->sendText("");
651 void MainWindow::prompt() {
652 if (!m_connection
->initialized()) {
653 m_connection
->startup();
654 m_connection
->setInitialized();
658 void MainWindow::newGame() {
659 StatePtr pos
= ui().position();
660 scoped_ptr
<NewGame
> dialog(new NewGame(this));
661 if (dialog
->exec() == QDialog::Accepted
) {
662 if (!newGame(dialog
->variant(), StatePtr(), dialog
->newTab()))
663 QMessageBox::information(this, i18n("Error"), i18n("Error creating game"));
667 void MainWindow::quit() {
671 void MainWindow::flipView() {
675 void MainWindow::toggleConsole() {
676 if (console_dock
->isVisible())
677 console_dock
->hide();
679 console_dock
->show();
680 console_dock
->setFocus(Qt::MouseFocusReason
681 /*Qt::ActiveWindowFocusReason*/ /*Qt::OtherFocusReason*/);
685 void MainWindow::toggleMoveList() {
686 if (movelist_dock
->isVisible())
687 movelist_dock
->hide();
689 movelist_dock
->show();
690 movelist_dock
->setFocus(Qt::OtherFocusReason
);
695 void MainWindow::displayMessage(const QString
& msg
) {
696 Q_UNUSED(msg
); // TODO
697 // statusBar()->message(msg, 2000);
700 void MainWindow::displayErrorMessage(ErrorCode code
) {
703 displayMessage(i18n("Illegal move"));
708 void MainWindow::flash() {
709 if( !isAncestorOf(QApplication::focusWidget()) )
714 void MainWindow::prefHighlight() {
715 PrefHighlight dialog
;
716 int result
= dialog
.exec();
717 if (result
== QDialog::Accepted
) {
723 void MainWindow::preferences() {
724 Preferences
dialog(ui().currentVariant());
725 int result
= dialog
.exec();
726 if (result
== QDialog::Accepted
)
730 void MainWindow::settingsChanged() {
731 ui().reloadSettings();