Factor canBeCaptured() out of Shogi legal() check for east reuse.
[tagua/yd.git] / src / mainwindow.cpp
blob02475a60f97a13cee9e6b83f8884430e8101d7cf
1 /*
2 Copyright (c) 2006-2007 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.
9 */
11 #include "mainwindow.h"
12 #include <boost/scoped_ptr.hpp>
13 #include <kaction.h>
14 #include <kstandardaction.h>
15 #include <kactioncollection.h>
16 #include <kicon.h>
17 #include <klocale.h>
18 #include <kurl.h>
19 #include <kfiledialog.h>
20 #include <kio/netaccess.h>
21 #include <kmessagebox.h>
22 #include <kmenubar.h>
23 #include <QKeySequence>
24 #include <QStackedWidget>
25 #include <QDockWidget>
26 #include <QCloseEvent>
27 #include <QTextStream>
28 #include <QTextCodec>
30 #include "actioncollection.h"
31 #include "chesstable.h"
32 #include "console.h"
33 #include "clock.h"
34 #include "newgame.h"
35 #include "variants.h"
36 #include "gameinfo.h"
37 #include "controllers/editgame.h"
38 #include "controllers/editposition.h"
39 #include "engineinfo.h"
40 #include "movelist_table.h"
41 #include "icsconnection.h"
42 #include "qconnect.h"
43 #include "mastersettings.h"
44 #include "flash.h"
45 #include "foreach.h"
46 #include "pgnparser.h"
47 #include "pref_highlight.h"
48 #include "pref_preferences.h"
49 #include "tabwidget.h"
51 using namespace Qt;
52 using namespace boost;
54 MainWindow::~MainWindow() {
55 delete console;
56 qApp->quit();
59 MainWindow::MainWindow(const QString& variant)
60 : KXmlGuiWindow(0) {
61 setObjectName("tagua_main");
62 m_main = new TabWidget(this);
63 m_main->setTabBarHidden(true);
64 setCentralWidget(m_main);
66 m_movelist_stack = new QStackedWidget;
68 connect(m_main, SIGNAL(currentChanged(int)),
69 this, SLOT(changeTab(int)));
70 connect(m_main, SIGNAL(closeTab()),
71 this, SLOT(closeTab()));
73 movelist_dock = new QDockWidget(this);
74 movelist_dock->setWidget(m_movelist_stack);
75 movelist_dock->setWindowTitle(i18n("Move list"));
76 movelist_dock->setObjectName("move_list");
77 addDockWidget(Qt::LeftDockWidgetArea, movelist_dock, Qt::Vertical);
78 movelist_dock->show();
80 ChessTable* board = new ChessTable;
82 board->setFocus();
84 console_dock = new QDockWidget(this);
85 console = new Console(0, i18n("FICS Connection"));
86 console_dock->setWidget(console);
87 console_dock->setFocusProxy(console);
88 console_dock->setWindowTitle(i18n("Console"));
89 console_dock->setObjectName("console");
90 addDockWidget(Qt::BottomDockWidgetArea, console_dock, Qt::Horizontal);
91 console_dock->setWindowFlags(console_dock->windowFlags() & ~Qt::WindowStaysOnTopHint);
92 console_dock->show();
94 settings().onChange(this, "settingsChanged");
96 connect(board, SIGNAL(error(ErrorCode)), this, SLOT(displayErrorMessage(ErrorCode)));
97 //BROKEN connect(board->clock(), SIGNAL(labelClicked(int)), &ui(), SLOT(setTurn(int)));
99 // start in edit game mode
100 newGame(variant, AbstractPosition::Ptr(), true);
102 setupActions();
103 setupGUI();
104 setupEngineMenu();
105 updateVariantActions();
108 ChessTable* MainWindow::table() {
109 return qobject_cast<ChessTable*>(m_main->currentWidget());
112 KAction* MainWindow::installRegularAction(const QString& name, const KIcon& icon, const QString& text, QObject* obj, const char* slot) {
113 KAction* temp = new KAction(icon, text, this);
114 actionCollection()->addAction(name, temp);
115 connect(temp, SIGNAL(triggered(bool)), obj, slot);
116 return temp;
119 void MainWindow::setupEngineMenu() {
120 QMenu* engine_menu = 0;
121 SettingArray engine_settings = settings().group("engines").array("engine");
122 foreach (Settings s, engine_settings) {
123 if (!engine_menu) {
124 // this way the menu is created only if there is at least one engine
125 engine_menu = menuBar()->addMenu(i18n("E&ngines"));
128 QString name;
129 EngineDetails engine_details;
130 s["name"] >> engine_details.name;
131 s["path"] >> engine_details.path;
132 engine_details.type = EngineDetails::typeFromName(s["type"].value<QString>());
133 if (s["work-path"])
134 s["work-path"] >> engine_details.workPath;
136 kDebug() << "creating engine " << engine_details.name << endl;
137 EngineInfo* engine = new EngineInfo(engine_details, ui());
139 m_engines.push_back(engine);
141 QMenu* menu = engine_menu->addMenu(engine_details.name);
144 KAction* play_white = new KAction(i18n("Play as &white"), this);
145 play_white->setCheckable(true);
146 connect(play_white, SIGNAL(triggered()), engine, SLOT(playAsWhite()));
147 menu->addAction(play_white);
150 KAction* play_black = new KAction(i18n("Play as &black"), this);
151 play_black->setCheckable(true);
152 connect(play_black, SIGNAL(triggered()), engine, SLOT(playAsBlack()));
153 menu->addAction(play_black);
155 #if 0 // disable analysis for the moment
157 KAction* analyze = new KAction(i18n("&Analyze"), this);
158 analyze->setCheckable(true);
159 connect(analyze, SIGNAL(triggered()), engine, SLOT(analyze()));
160 menu->addAction(analyze);
162 #endif
166 void MainWindow::setupActions() {
167 KStandardAction::openNew(this, SLOT(newGame()), actionCollection());
168 KStandardAction::open(this, SLOT(loadGame()), actionCollection());
169 KStandardAction::quit(this, SLOT(quit()), actionCollection());
171 installRegularAction("back", KIcon("go-previous"), i18n("&Back"), &ui(), SLOT(back()));
172 installRegularAction("forward", KIcon("go-next"), i18n("&Forward"), &ui(), SLOT(forward()));
173 installRegularAction("begin", KIcon("go-first"), i18n("Be&gin"), &ui(), SLOT(gotoFirst()));
174 installRegularAction("end", KIcon("go-last"), i18n("&End"), &ui(), SLOT(gotoLast()));
175 installRegularAction("connect", KIcon("connection-established"), i18n("&Connect"), this, SLOT(icsConnect()));
176 installRegularAction("disconnect", KIcon("connect-no"), i18n("&Disconnect"), this, SLOT(icsDisconnect()));
178 KStandardAction::undo(&ui(), SLOT(undo()), actionCollection());
179 KStandardAction::redo(&ui(), SLOT(redo()), actionCollection());
181 installRegularAction("pgnCopy", KIcon("edit-copy"), i18n("Copy PGN"), this, SLOT(pgnCopy()));
182 installRegularAction("pgnPaste", KIcon("edit-paste"), i18n("Paste PGN"), this, SLOT(pgnPaste()));
183 installRegularAction("editPosition", KIcon("edit"), i18n("&Edit position"), this, SLOT(editPosition()));
184 installRegularAction("clearBoard", KIcon("edit-delete"), i18n("&Clear board"), &ui(), SLOT(clearBoard()));
185 installRegularAction("setStartingPosition", KIcon("contents"), i18n("&Set starting position"),
186 &ui(), SLOT(setStartingPosition()));
187 installRegularAction("copyPosition", KIcon(), i18n("&Copy position"), &ui(), SLOT(copyPosition()));
188 installRegularAction("pastePosition", KIcon(), i18n("&Paste position"), &ui(), SLOT(pastePosition()));
189 installRegularAction("flip", KIcon("rotate"), i18n("&Flip view"), this, SLOT(flipView()));
190 installRegularAction("toggleConsole", KIcon("openterm"), i18n("Toggle &console"), this, SLOT(toggleConsole()));
191 installRegularAction("toggleMoveList", KIcon("view_text"), i18n("Toggle &move list"), this, SLOT(toggleMoveList()));
192 installRegularAction("configure", KIcon("configure"), i18n("&Configure Tagua..."), this, SLOT(preferences()));
195 void MainWindow::updateVariantActions() {
196 ActionCollection* variant_actions = m_ui.variantActions();
197 unplugActionList("variantActions");
198 if (variant_actions) {
199 plugActionList("variantActions", variant_actions->actions());
201 else {
202 WARNING("No variant actions");
206 void MainWindow::readSettings() { }
207 void MainWindow::writeSettings() { }
209 void MainWindow::closeEvent(QCloseEvent* e) {
210 e->accept();
211 writeSettings();
212 delete this;
216 void MainWindow::keyPressEvent(QKeyEvent* event) {
217 if (event->key() == Qt::Key_F5) {
218 ui().createCtrlAction();
222 void MainWindow::keyReleaseEvent(QKeyEvent* event) {
223 if (event->key() == Qt::Key_F5) {
224 ui().destroyCtrlAction();
228 void MainWindow::changeTab(int index) {
229 std::cout << "changing tab, widget = " << m_main->currentWidget() << std::endl;
230 m_ui.setCurrentTab(m_main->currentWidget());
231 m_movelist_stack->setCurrentIndex(index);
232 updateVariantActions();
235 void MainWindow::closeTab() {
236 if (m_main->count() > 1) {
237 int old_index = m_main->currentIndex();
238 ChessTable* old_board = table();
240 int new_index = old_index - 1;
241 if (new_index < 0)
242 new_index = old_index + 1;
243 m_main->setCurrentIndex(new_index);
245 m_main->removeTab(old_index);
246 m_movelist_stack->removeWidget(m_movelist_stack->widget(old_index));
247 m_ui.removeController(old_board);
249 if (m_main->count() <= 1) {
250 m_main->setTabBarHidden(true);
253 #if 0 // this doesn't work... why?
254 ChessTable* old_board = table();
255 m_ui.removeController(old_board);
256 m_movelist_stack->removeWidget(
257 m_movelist_stack->widget(m_main->currentIndex()));
258 m_main->removeTab(m_main->currentIndex());
260 delete old_board;
262 if (m_main->count() <= 1) {
263 m_main->hideTabBar();
266 // update ui controller (just in case...)
267 m_ui.setCurrentTab(m_main->currentWidget());
268 #endif
272 void MainWindow::createTab(ChessTable* board, const shared_ptr<Controller>& controller,
273 const QString& caption, int index) {
274 if (index == -1)
275 index = m_main->addTab(board, caption);
276 else
277 m_main->insertTab(index, board, caption);
279 m_ui.addController(board, controller);
280 m_ui.setCurrentTab(board);
281 m_movelist_stack->addWidget(board->moveListTable());
283 m_main->setCurrentIndex(index);
284 m_movelist_stack->setCurrentIndex(index);
286 if (m_main->count() > 1) m_main->setTabBarHidden(false);
288 std::cout << "created tab " << board << std::endl;
292 void MainWindow::cleanupGame(const QString& what, const QString& result) {
293 Q_UNUSED(what);
294 Q_UNUSED(result);
296 cleanupGame();
299 void MainWindow::cleanupGame() {
300 ui().detach();
301 ui().end();
305 bool MainWindow::newGame(const QString& variantName, AbstractPosition::Ptr startingPos,
306 bool newTab) {
307 VariantPtr variant = Variants::instance().get(variantName);
308 if (!variant) {
309 WARNING("no variant " << variantName << " found");
310 variant = Variants::instance().get("chess");
313 if (variant) {
314 ChessTable* board = newTab ? new ChessTable : table();
315 QString text = QString("Editing %1 game").arg(variant->name().toLower());
317 shared_ptr<Controller> controller(new EditGameController(
318 board, variant, startingPos));
319 if (newTab) {
320 createTab(board, controller, text);
322 else {
323 unplugActionList("variantActions");
324 ui().setController(controller);
325 table()->setPlayers(Player(), Player());
326 m_main->setTabText(m_main->currentIndex(), text);
328 return true;
330 else {
331 ERROR("Could not find the chess variant");
332 exit(1);
333 return false;
338 void MainWindow::editPosition() {
339 //BROKEN
340 #if 0
341 shared_ptr<Controller> controller(new EditPositionController(table(), ChessVariant::info()));
342 m_main->setTabText(m_main->currentIndex(), "Editing chess position");
343 ui().setController(controller);
344 #endif
347 void MainWindow::setupGame(const GameInfo* gameInfo, const PositionInfo& style12) {
348 QString variantCode = gameInfo->variant();
349 VariantPtr variant = Variants::instance().get(variantCode);
350 shared_ptr<EditGameController> controller(new EditGameController(
351 table(), variant));
352 Q_ASSERT(style12.relation == PositionInfo::NotMyMove ||
353 style12.relation == PositionInfo::MyMove);
355 // set opponent side
356 int side = (style12.relation == PositionInfo::MyMove ^ style12.turn == 0) ? 0 : 1;
358 if (controller->addICSPlayer(side, style12.gameNumber, m_connection)) {
359 ui().setController(controller);
360 table()->setPlayers(gameInfo->white(), gameInfo->black());
361 m_main->setTabText(m_main->currentIndex(),
362 QString("FICS Game - %1 vs %2").arg(style12.whitePlayer)
363 .arg(style12.blackPlayer));
367 void MainWindow::setupExaminedGame(const GameInfo* gameInfo, const PositionInfo& style12) {
368 shared_ptr<EditGameController> controller(
369 new EditGameController(table(), Variants::instance().get(gameInfo->variant())));
370 if (controller->setExaminationMode(style12.gameNumber, m_connection)) {
371 table()->setPlayers(Player(style12.whitePlayer, -1),
372 Player(style12.blackPlayer, -1));
373 ui().setController(controller);
374 m_main->setTabText(m_main->currentIndex(),
375 QString("Examining - %1 vs %2").arg(style12.whitePlayer)
376 .arg(style12.blackPlayer));
381 void MainWindow::setupObservedGame(const GameInfo* g, const PositionInfo& style12) {
382 std::auto_ptr<ChessTable> board(new ChessTable);
384 shared_ptr<EditGameController> controller(new EditGameController(
385 board.get(), Variants::instance().get(g->variant())));
386 if (controller->setObserveMode(style12.gameNumber, m_connection)) {
387 board->setPlayers(Player(style12.whitePlayer, -1),
388 Player(style12.blackPlayer, -1));
389 // ui().setController(controller);
390 //std::cout << "adding tab" << std::endl;
391 createTab(board.get(), controller,
392 QString("Observing - %1 vs %2").arg(style12.whitePlayer)
393 .arg(style12.blackPlayer));
394 board.release();
398 void MainWindow::setupPGN(const QString& s) {
399 PGN pgn(s);
401 std::map<QString, QString>::const_iterator var = pgn.m_tags.find("Variant");
402 VariantPtr variant;
404 if (var == pgn.m_tags.end()) {
405 variant = Variants::instance().get("chess");
407 else if (!(variant = Variants::instance().get(var->second))) {
408 std::cout << " --> MainWindow::setupPGN: Error, no such variant " << var->second << std::endl;
409 return;
412 shared_ptr<EditGameController> controller(new EditGameController(
413 table(), variant));
414 ui().setController(controller);
415 controller->loadPGN(pgn);
417 // table()->setPlayers(gameInfo->white(), gameInfo->black());
418 // m_main->setTabText(m_main->currentIndex(),
419 // QString("FICS Game - %1 vs %2").arg(style12.whitePlayer)
420 // .arg(style12.blackPlayer));
423 bool MainWindow::openFile(const QString& filename) {
424 QFileInfo info(filename);
426 if(info.isDir()) {
427 KMessageBox::sorry(this, i18n("You have specified a folder"), i18n("Error"));
428 return false;
431 if(!info.exists() || !info.isFile()) {
432 KMessageBox::sorry(this, i18n("The specified file does not exist"), i18n("Error"));
433 return false;
436 QFile file(filename);
438 if(!file.open(QIODevice::ReadOnly)) {
439 KMessageBox::sorry(this, i18n("You do not have read permission to this file."), i18n("Error"));
440 return false;
443 QTextStream stream(&file);
444 QTextCodec *codec;
445 codec = QTextCodec::codecForLocale();
446 stream.setCodec(codec);
448 setupPGN(stream.readAll());
449 //ui().pgnPaste(stream.readAll());
450 return true;
453 void MainWindow::loadGame() {
454 KUrl url = KFileDialog::getOpenUrl(KUrl(), "*.pgn", this, i18n("Open PGN file"));
456 if(url.isEmpty())
457 return;
459 QString tmp_file;
460 if (KIO::NetAccess::download(url, tmp_file, this)) {
461 openFile(tmp_file);
462 KIO::NetAccess::removeTempFile(tmp_file);
464 else
465 KMessageBox::error(this, KIO::NetAccess::lastErrorString());
468 void MainWindow::createConnection(const QString& username, const QString& password,
469 const QString& host, quint16 port,
470 const QString& timeseal, const QString& timeseal_cmd) {
471 m_connection = shared_ptr<ICSConnection>(new ICSConnection);
473 connect(m_connection.get(), SIGNAL(established()), this, SLOT(onEstablishConnection()));
474 connect(m_connection.get(), SIGNAL(hostLookup()), this, SLOT(onHostLookup()));
475 connect(m_connection.get(), SIGNAL(hostFound()), this, SLOT(onHostFound()));
477 connect(m_connection.get(), SIGNAL(receivedLine(QString, int)), console, SLOT(displayText(QString, int)));
478 connect(m_connection.get(), SIGNAL(receivedText(QString, int)), console, SLOT(displayText(QString, int)));
480 connect(console, SIGNAL(receivedInput(const QString&)), m_connection.get(), SLOT(sendText(const QString&)));
481 connect(console, SIGNAL(notify()), this, SLOT(flash()));
483 connect(m_connection.get(), SIGNAL(loginPrompt()), this, SLOT(sendLogin()));
484 connect(m_connection.get(), SIGNAL(registeredNickname()), this, SLOT(sendBlankPassword()));
485 connect(m_connection.get(), SIGNAL(prompt()), this, SLOT(prompt()));
488 connect(m_connection.get(), SIGNAL(creatingExaminedGame(const GameInfo*, const PositionInfo&)),
489 this, SLOT(setupExaminedGame(const GameInfo*, const PositionInfo&)));
490 connect(m_connection.get(), SIGNAL(endingExaminedGame(int)), this, SLOT(cleanupGame()));
492 connect(m_connection.get(), SIGNAL(creatingObservedGame(const GameInfo*, const PositionInfo&)),
493 this, SLOT(setupObservedGame(const GameInfo*, const PositionInfo&)));
494 connect(m_connection.get(), SIGNAL(endingObservedGame(int)), this, SLOT(cleanupGame()));
497 connect(m_connection.get(), SIGNAL(creatingGame(const GameInfo*, const PositionInfo&)),
498 this, SLOT(setupGame(const GameInfo*, const PositionInfo&)));
499 connect(m_connection.get(), SIGNAL(endingGame(const QString&, const QString&)),
500 this, SLOT(cleanupGame(const QString&, const QString&)));
501 connect(m_connection.get(), SIGNAL(notification()), this, SLOT(flash()));
503 m_connection->establish(host.toAscii().constData(), port, timeseal, timeseal_cmd);
504 console->show();
506 // send username / password combination
507 if (!username.isEmpty()) {
508 m_connection->sendText(username);
509 m_connection->sendText(password);
512 quickConnectDialog.reset();
515 void MainWindow::icsConnect() {
516 quickConnectDialog = shared_ptr<QConnect>(new QConnect(this));
517 connect(quickConnectDialog.get(),
518 SIGNAL(acceptConnection(const QString&,
519 const QString&,
520 const QString&,
521 quint16,
522 const QString&,
523 const QString&)),
524 this,
525 SLOT(createConnection(const QString&,
526 const QString&,
527 const QString&,
528 quint16,
529 const QString&,
530 const QString&)));
531 quickConnectDialog->show();
534 void MainWindow::destroyConnection() {
535 m_connection.reset();
538 void MainWindow::icsDisconnect() {
539 destroyConnection();
540 console->hide();
541 console->clear();
544 void MainWindow::testConnect() {
545 Settings s_ics = settings().group("ics");
546 if (s_ics["username"]) {
547 QString username = s_ics["username"].value<QString>();
548 QString password = (s_ics["password"] | "");
549 QString host = (s_ics["icsHost"] | "freechess.org");
550 quint16 port = (s_ics["icsPort"] | 5000);
551 createConnection(username, password, host, port, QString(), QString() );
553 else icsConnect();
557 void MainWindow::onEstablishConnection() {
558 // std::cout << "connection established" << std::endl;
561 void MainWindow::onConnectionError(int ) {
562 // std::cout << "connection error (" << code << ")" << std::endl;
565 void MainWindow::onHostLookup() {
566 // std::cout << "hostLookup..." << std::flush;
569 void MainWindow::onHostFound() {
570 // std::cout << "found" << std::endl;
574 void MainWindow::sendLogin() {
575 // std::cout << "sending username" << std::endl;
576 // connection->sendText(connection->username());
579 void MainWindow::sendBlankPassword() {
580 std::cout << "sending blank password" << std::endl;
581 m_connection->sendText("");
584 void MainWindow::prompt() {
585 if (!m_connection->initialized()) {
586 m_connection->startup();
587 m_connection->setInitialized();
591 void MainWindow::newGame() {
592 AbstractPosition::Ptr pos = ui().position();
593 scoped_ptr<NewGame> dialog(new NewGame(this));
594 if (dialog->exec() == QDialog::Accepted) {
595 if (!newGame(dialog->variant(), PositionPtr(), dialog->newTab()))
596 QMessageBox::information(this, i18n("Error"), i18n("Error creating game"));
600 void MainWindow::quit() {
601 delete this;
604 void MainWindow::flipView() {
605 table()->flip();
608 void MainWindow::toggleConsole() {
609 if (console_dock->isVisible())
610 console_dock->hide();
611 else {
612 console_dock->show();
613 console_dock->setFocus(Qt::MouseFocusReason
614 /*Qt::ActiveWindowFocusReason*/ /*Qt::OtherFocusReason*/);
618 void MainWindow::toggleMoveList() {
619 if (movelist_dock->isVisible())
620 movelist_dock->hide();
621 else {
622 movelist_dock->show();
623 movelist_dock->setFocus(Qt::OtherFocusReason);
628 void MainWindow::displayMessage(const QString& msg) {
629 Q_UNUSED(msg); // TODO
630 // statusBar()->message(msg, 2000);
633 void MainWindow::displayErrorMessage(ErrorCode code) {
634 switch(code) {
635 case InvalidMove:
636 displayMessage(i18n("Illegal move"));
637 break;
641 void MainWindow::flash() {
642 if( !isAncestorOf(QApplication::focusWidget()) )
643 Flash::flash(this);
646 #if 0
647 void MainWindow::prefHighlight() {
648 PrefHighlight dialog;
649 int result = dialog.exec();
650 if (result == QDialog::Accepted) {
651 dialog.apply();
654 #endif
656 void MainWindow::preferences() {
657 Preferences dialog(ui().currentVariant());
658 int result = dialog.exec();
659 if (result == QDialog::Accepted)
660 dialog.apply();
663 void MainWindow::settingsChanged() {
664 ui().reloadSettings();