diff --git a/chessx.pro b/chessx.pro index a571311de..1bce35695 100644 --- a/chessx.pro +++ b/chessx.pro @@ -204,6 +204,7 @@ HEADERS += src/database/board.h \ src/database/filteroperator.h \ src/database/filtersearch.h \ src/database/gamecursor.h \ + src/database/gameevaluation.h \ src/database/gameid.h \ src/database/gameundocommand.h \ src/database/gamex.h \ @@ -308,7 +309,7 @@ HEADERS += src/database/board.h \ src/gui/gamelist.h \ src/gui/gamelistsortmodel.h \ src/gui/gamenotationwidget.h \ - src/gui/gametoolbar.h \ + src/gui/centipawngraph.h \ src/gui/gamewindow.h \ src/gui/helpbrowser.h \ src/gui/helpbrowsershell.h \ @@ -387,6 +388,7 @@ SOURCES += \ src/database/filtermodel.cpp \ src/database/filtersearch.cpp \ src/database/gamecursor.cpp \ + src/database/gameevaluation.cpp \ src/database/gamex.cpp \ src/database/historylist.cpp \ src/database/index.cpp \ @@ -477,7 +479,7 @@ SOURCES += \ src/gui/gamelist.cpp \ src/gui/gamelistsortmodel.cpp \ src/gui/gamenotationwidget.cpp \ - src/gui/gametoolbar.cpp \ + src/gui/centipawngraph.cpp \ src/gui/gamewindow.cpp \ src/gui/helpbrowser.cpp \ src/gui/helpbrowsershell.cpp \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 86b8a7d02..cee08d33c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -95,6 +95,8 @@ add_library(database-core STATIC database/gameid.h database/gamecursor.cpp database/gamecursor.h + database/gameevaluation.cpp + database/gameevaluation.h database/gamex.cpp database/gamex.h database/index.cpp @@ -339,6 +341,8 @@ add_library(gui STATIC gui/boardviewex.cpp gui/boardviewex.h gui/boardviewex.ui + gui/centipawngraph.cpp + gui/centipawngraph.h gui/chartwidget.cpp gui/chartwidget.h gui/chessbrowser.cpp @@ -380,8 +384,6 @@ add_library(gui STATIC gui/gamelistsortmodel.h gui/gamenotationwidget.cpp gui/gamenotationwidget.h - gui/gametoolbar.cpp - gui/gametoolbar.h gui/gamewindow.cpp gui/gamewindow.h gui/gamewindow.ui diff --git a/src/database/analysis.h b/src/database/analysis.h index e0d06e5c0..1220ff9ce 100644 --- a/src/database/analysis.h +++ b/src/database/analysis.h @@ -62,6 +62,8 @@ class Analysis /** Moves to mate. */ /** Convert analysis to formatted text. */ QString toString(const BoardX& board, bool hiddenLine=false) const; + /** If bestMove is true, then only the bestMove, and variation members + * are valid. */ void setBestMove(bool bestMove); bool bestMove() const; diff --git a/src/database/gameevaluation.cpp b/src/database/gameevaluation.cpp new file mode 100644 index 000000000..869c2830d --- /dev/null +++ b/src/database/gameevaluation.cpp @@ -0,0 +1,188 @@ +#include +#include "enginex.h" +#include "gamex.h" +#include "gameevaluation.h" + +GameEvaluation::GameEvaluation(int engineIndex, int msPerMove, GameX game) noexcept + : engineIndex{engineIndex} + , msPerMove{msPerMove} + , game{game} + , targetThreadCount{std::max(1, QThread::idealThreadCount())} + , moveNumbers{0} +{ + if (targetThreadCount > 4) + // We'll leave one logical core idle if we can afford it. + --targetThreadCount; + connect(&timer, &QTimer::timeout, this, &GameEvaluation::update); +} + +void GameEvaluation::start() +{ + if (running) + { + throw std::logic_error{"Game evaluation already running"}; + } + running = true; + game.moveToStart(); + workers.clear(); + timer.stop(); + timer.start(std::chrono::milliseconds{100}); + // We place the first worker for the starting position here. + workers.emplace_back(engineIndex, game.startingBoard(), game.board(), msPerMove, moveNumbers++); +} + +void GameEvaluation::update() noexcept +{ + std::unordered_map evaluations; + for (std::list::iterator i{workers.begin()}; i != workers.end(); ++i) + { + i->update(); + evaluations.emplace(i->moveNumber, i->getLastScore()); + if (!i->isRunning()) + { + // Erase *after* getting the value because erasing will destroy the worker + i = workers.erase(i); + --i; + } + } + emit evaluationChanged(evaluations); + while (static_cast(workers.size()) < targetThreadCount) + { + if (!game.forward()) + { + break; + } + try + { + int tempNum {moveNumbers++}; + workers.emplace_back(engineIndex, game.startingBoard(), game.board(), msPerMove, tempNum); + } + catch (...) + { + // Destroy any workers that have started. + workers.clear(); + break; + } + } + if (!workers.size()) + { + timer.stop(); + emit evaluationComplete(); + running = false; + } +} + +void GameEvaluation::stop() noexcept +{ + workers.clear(); + timer.stop(); + emit evaluationComplete(); +} + +GameEvaluation::~GameEvaluation() noexcept +{ + workers.clear(); + timer.stop(); +} + +GameEvaluationWorker::GameEvaluationWorker(int engineIndex, BoardX const & startPosition, BoardX const & currentPosition, + int msPerMove, int moveNumber) + : moveNumber{moveNumber} + , currentPosition{currentPosition} + , msPerMove{msPerMove} + , engine{EngineX::newEngine(engineIndex)} +{ + if (!engine) + { + throw std::runtime_error{"Failed to instantiate engine"}; + } + if (!engine->m_mapOptionValues.contains("Threads")) + { + throw std::runtime_error{"Could not set engine threads to 1."}; + } + engine->m_mapOptionValues["Threads"] = 1; + engine->setStartPos(startPosition); + connect(engine, &EngineX::activated, this, &GameEvaluationWorker::engineActivated); + connect(engine, &EngineX::analysisStarted, this, &GameEvaluationWorker::engineAnalysisStarted); + connect(engine, &EngineX::analysisUpdated, this, &GameEvaluationWorker::engineAnalysisUpdated); + engine->activate(); + running = true; +} + +GameEvaluationWorker::~GameEvaluationWorker() noexcept +{ + if (!engine) + { + return; + } + try + { + engine->deactivate(); + delete engine; + } + catch (...) + { + // I don't think we can do anything about this exception. + } +} + +void GameEvaluationWorker::engineActivated() +{ + EngineParameter parameters {msPerMove}; + engine->startAnalysis(currentPosition, 1, parameters, false, ""); +} + +void GameEvaluationWorker::engineAnalysisStarted() +{ + startTimestamp = QDateTime::currentDateTimeUtc(); +} + +void GameEvaluationWorker::engineAnalysisUpdated(Analysis const & analysis) +{ + if (analysis.bestMove()) + { + // When the engine reports a best move, no score is reported, so we skip it + return; + } + if (analysis.isMate()) + { + lastScore = static_cast(10); + if (std::signbit(analysis.score())) + lastScore *= -1; + // If it's black's turn and black is winning, analysis.score() returns a + // positive number in a "mating" condition. We need a score from white's + // perspective. + bool blacksTurn {static_cast(analysis.variation().size() % 2)}; + if (!blacksTurn) + lastScore *= -1; + } + else + { + lastScore = analysis.fscore(); + } +} + +double GameEvaluationWorker::getLastScore() const noexcept +{ + return lastScore; +} + +bool GameEvaluationWorker::isRunning() const noexcept +{ + return running; +} + +void GameEvaluationWorker::update() noexcept +{ + if (!startTimestamp) + { + // Maybe have a timeout and kill the engine if analysis still hasn't started? + return; + } + if (startTimestamp->msecsTo(QDateTime::currentDateTimeUtc()) < msPerMove) + return; + engine->deactivate(); + running = false; + // We'll let the destructor delete the engine since this is called during computation when + // processing time is more premium than later. +} diff --git a/src/database/gameevaluation.h b/src/database/gameevaluation.h new file mode 100644 index 000000000..eaae38495 --- /dev/null +++ b/src/database/gameevaluation.h @@ -0,0 +1,75 @@ +#ifndef GAME_EVALUATION_H_INCLUDED +#define GAME_EVALUATION_H_INCLUDED +#include +#include "board.h" +#include "gamex.h" +#include "enginex.h" + +/** @ingroup Core + The GameEvaluation class represents an algorith for evaluating the centipawn score at + every move in a game. +*/ + +class GameEvaluationWorker final : public QObject +{ + Q_OBJECT +public: + GameEvaluationWorker(int engineIndex, BoardX const & startPosition, BoardX const & currentPosition, + int msPerMove, int moveNumber); + // QObjects can't be copied or moved + GameEvaluationWorker & operator=(GameEvaluationWorker &&) = delete; + GameEvaluationWorker(GameEvaluationWorker&&) = delete; + ~GameEvaluationWorker() noexcept; + + double getLastScore() const noexcept; + bool isRunning() const noexcept; + void update() noexcept; + + int const moveNumber; + +private: + BoardX currentPosition; + int msPerMove; + EngineX * engine; + double lastScore{0}; + bool running{false}; + std::optional startTimestamp; + +private slots: + void engineActivated(); + void engineAnalysisStarted(); + void engineAnalysisUpdated(Analysis const &); +}; + +class GameEvaluation final : public QObject +{ + Q_OBJECT +public : + GameEvaluation(int engineIndex, int msPerMove, GameX) noexcept; + ~GameEvaluation() noexcept; + + // This function does not block, but uses a QT Timer to call the + // evaluationChanged signal periodically. + void start(); + void stop() noexcept; +signals: + void evaluationChanged(std::unordered_map const & blah); + void evaluationComplete(); + +private: + int const engineIndex; + int const msPerMove; + GameX game; + QTimer timer; + int targetThreadCount; + QString line; + int moveNumbers; + + bool running{false}; + std::list workers; + void update() noexcept; + +}; + +#endif // GAME_EVALUATION_H_INCLUDED + diff --git a/src/gui/centipawngraph.cpp b/src/gui/centipawngraph.cpp new file mode 100644 index 000000000..7aa84764b --- /dev/null +++ b/src/gui/centipawngraph.cpp @@ -0,0 +1,132 @@ +#include "centipawngraph.h" + +#include +#include +#include +#include +#include + +#include "chartwidget.h" +#include "gameevaluation.h" + +CentipawnGraph::CentipawnGraph(QWidget* parent) + : QWidget(parent) + , evaluation{nullptr} +{ + setObjectName("CentipawnGraph"); + QGridLayout * layout = new QGridLayout(this); + m_startAnalysis = new QPushButton{"Start Analysis"}; + m_startAnalysis->setDefault(true); + connect(m_startAnalysis, &QPushButton::clicked, this, &CentipawnGraph::analysisRequested); + m_engineList = new QComboBox; + setupEngineList(); + layout->addWidget(m_startAnalysis, /*row*/0, 0); + layout->addWidget(m_engineList, 0, 1); + m_chart = new ChartWidget(); + m_chart->setObjectName("ChartWidget"); + m_chart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(m_chart, 1, 0, 1, 2); + m_chart->show(); + connect(m_chart, &ChartWidget::halfMoveRequested, this, &CentipawnGraph::requestPly); + adjustSize(); +} + +void CentipawnGraph::setupEngineList() noexcept +{ + m_engineList->clear(); + EngineList enginesList; + enginesList.restore(); + QStringList names = enginesList.names(); + m_engineList->setEditable(false); + if (!names.size()) + { + m_engineList->addItem("No Engines Configured"); + m_engineList->setEnabled(false); + m_startAnalysis->setEnabled(false); + } + else + { + m_engineList->addItems(names); + m_engineList->setEnabled(true); + m_startAnalysis->setEnabled(true); + m_engineList->setToolTip("Select a pre-configured engine"); + } +} + +void CentipawnGraph::slotDisplayCurrentPly(int ply) +{ + updateGeometry(); + m_chart->setPly(ply); + m_chart->show(); + m_chart->show(); +} + +void CentipawnGraph::slotDisplayMaterial(const QList& material) +{ + m_chart->setValues(0, material); +} + +void CentipawnGraph::slotDisplayEvaluations(const QList& evaluations) +{ + // If we don't have a computed evaluation, use this one retrieved from annotations. + m_chart->setValues(1, evaluations); +} + +void CentipawnGraph::analysisRequested(bool /*checked*/) noexcept +{ + // In order to start analysis, we need the current game which we don't have. + // We need to emit a signal that the main window can handle so that the + // main window can call back with the current game to get analysis started. + emit startAnalysisRequested(); +} + +void CentipawnGraph::startAnalysis(GameX const & game) noexcept +{ + try + { + if (evaluation) + { + return; + } + m_startAnalysis->setEnabled(false); + m_engineList->setEnabled(false); + evaluation = std::make_unique(m_engineList->currentIndex(), 1000, game); + connect(evaluation.get(), &GameEvaluation::evaluationChanged, this, &CentipawnGraph::evaluationChanged); + connect(evaluation.get(), &GameEvaluation::evaluationComplete, this, &CentipawnGraph::evaluationComplete); + currentGame = game; + scores.clear(); + scores.reserve(currentGame.cursor().countMoves()+1); + for (int i{0}; i < currentGame.cursor().countMoves()+1; ++i) + scores << 0; + evaluation->start(); + } + catch (...) + { + // Failed to start evaluation; swallowing error + } +} + +void CentipawnGraph::evaluationComplete() noexcept +{ + evaluation = nullptr; + m_startAnalysis->setEnabled(true); + m_engineList->setEnabled(true); +} + +void CentipawnGraph::evaluationChanged(std::unordered_map const & scoreUpdates) noexcept +{ + GameX tempGame{currentGame}; + tempGame.moveToStart(); + for (std::pair const & score : scoreUpdates) + { + int moveNumber = score.first; + if (scores.size() <= moveNumber) + { + continue; + } + double newScore {std::clamp(score.second, -10.0, 10.0)}; + scores.replace(moveNumber, newScore); + } + m_chart->setValues(1, scores); +} diff --git a/src/gui/centipawngraph.h b/src/gui/centipawngraph.h new file mode 100644 index 000000000..7da7fe69d --- /dev/null +++ b/src/gui/centipawngraph.h @@ -0,0 +1,47 @@ +#ifndef CENTIPAWNGRAPH_H +#define CENTIPAWNGRAPH_H + +#include +#include +#include "piece.h" +#include "gamex.h" +#include "gameevaluation.h" + +class ChartWidget; +class QPushButton; +class QComboBox; + +class CentipawnGraph final : public QWidget +{ + Q_OBJECT + +public: + CentipawnGraph(QWidget* parent = nullptr); + void startAnalysis(GameX const &) noexcept; + +signals: + void requestPly(int); + void startAnalysisRequested(); + +public slots: + void slotDisplayCurrentPly(int ply); + void slotDisplayMaterial(const QList& material); + void slotDisplayEvaluations(const QList& evaluations); + void evaluationChanged(std::unordered_map const &) noexcept; + void evaluationComplete() noexcept; + +private: + ChartWidget* m_chart {nullptr}; + QPushButton* m_startAnalysis {nullptr}; + QComboBox* m_engineList {nullptr}; + std::unique_ptr evaluation; + GameX currentGame; + QList scores; + + void setupEngineList() noexcept; + +private slots: + void analysisRequested(bool) noexcept; +}; + +#endif diff --git a/src/gui/chartwidget.cpp b/src/gui/chartwidget.cpp index 603720c3e..1e8577bf8 100644 --- a/src/gui/chartwidget.cpp +++ b/src/gui/chartwidget.cpp @@ -29,10 +29,6 @@ ChartWidget::ChartWidget(QWidget *parent) : setUpdatesEnabled(true); } -ChartWidget::~ChartWidget() -{ -} - void ChartWidget::setValues(int line, const QList& values) { if (line >= m_values.size()) diff --git a/src/gui/chartwidget.h b/src/gui/chartwidget.h index 23d6380ba..2cec80310 100644 --- a/src/gui/chartwidget.h +++ b/src/gui/chartwidget.h @@ -15,7 +15,6 @@ class ChartWidget : public QWidget Q_OBJECT public: explicit ChartWidget(QWidget *parent = nullptr); - virtual ~ChartWidget(); void setValues(int line, const QList &values); void setPly(int ply); diff --git a/src/gui/gametoolbar.cpp b/src/gui/gametoolbar.cpp deleted file mode 100644 index a0150ef05..000000000 --- a/src/gui/gametoolbar.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "gametoolbar.h" - -#include - -#include "chartwidget.h" - -GameToolBar::GameToolBar(const QString& title, QWidget* parent) - : QToolBar(title, parent) - , m_clock1(nullptr) - , m_clock2(nullptr) - , m_chart(nullptr) -{ - setObjectName("GameToolBar"); - - m_clock1 = new QLCDNumber(7, this); - m_clock1->setSegmentStyle(QLCDNumber::Flat); - m_clock1->setObjectName("Clock0"); - m_clock1->display("1:00:00"); - addWidget(m_clock1); - - m_chart = new ChartWidget(this); - m_chart->setObjectName("ChartWidget"); - m_chart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - addWidget(m_chart); - - m_clock2 = new QLCDNumber(7, this); - m_clock2->setSegmentStyle(QLCDNumber::Flat); - m_clock2->setObjectName("Clock1"); - m_clock2->display("1:00:00"); - addWidget(m_clock2); - - connect(m_chart, &ChartWidget::halfMoveRequested, this, &GameToolBar::requestPly); -} - -void GameToolBar::slotDisplayCurrentPly(int ply) -{ - m_chart->setPly(ply); -} - -void GameToolBar::slotDisplayMaterial(const QList& material) -{ - m_chart->setValues(0, material); -} - -void GameToolBar::slotDisplayEvaluations(const QList& evaluations) -{ - m_chart->setValues(1, evaluations); -} - -void GameToolBar::slotDisplayTime(const QString& timeWhite, const QString &timeBlack) -{ - m_clock1->display(timeWhite); - m_clock2->display(timeBlack); -} - -void GameToolBar::slotDisplayTime(Color color, const QString& text) -{ - switch (color) - { - case White: - m_clock1->display(text); - break; - case Black: - m_clock2->display(text); - break; - default: - break; - } -} diff --git a/src/gui/gametoolbar.h b/src/gui/gametoolbar.h deleted file mode 100644 index 221772b37..000000000 --- a/src/gui/gametoolbar.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef GAMETOOLBAR_H -#define GAMETOOLBAR_H - -#include - -#include "piece.h" - -class QLCDNumber; -class ChartWidget; - -class GameToolBar : public QToolBar -{ - Q_OBJECT - -public: - GameToolBar(const QString& title, QWidget* parent = nullptr); - -signals: - void requestPly(int); - -public slots: - void slotDisplayCurrentPly(int ply); - void slotDisplayMaterial(const QList& material); - void slotDisplayEvaluations(const QList& evaluations); - void slotDisplayTime(const QString& timeWhite, const QString& timeBlack); - void slotDisplayTime(Color color, const QString& time); - -private: - QLCDNumber* m_clock1; - QLCDNumber* m_clock2; - ChartWidget* m_chart; -}; - -#endif diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 5416641e2..2b923bb76 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -13,6 +13,7 @@ #include "annotationwidget.h" #include "boardview.h" #include "boardviewex.h" +#include "centipawngraph.h" #include "chessxsettings.h" #include "clipboarddatabase.h" #include "commentdialog.h" @@ -32,7 +33,6 @@ #include "gamelist.h" #include "GameMimeData.h" #include "gamewindow.h" -#include "gametoolbar.h" #include "gamenotationwidget.h" #include "helpbrowsershell.h" #include "historylabel.h" @@ -93,7 +93,7 @@ MainWindow::MainWindow() : QMainWindow(), m_tabDragIndex(-1), m_pDragTabBar(nullptr), m_gameWindow(nullptr), - m_gameToolBar(0), + m_centipawnGraph(nullptr), m_operationFlag(0), m_currentFrom(InvalidSquare), m_currentTo(InvalidSquare), @@ -189,19 +189,25 @@ MainWindow::MainWindow() : QMainWindow(), connect(this, SIGNAL(reconfigure()), m_ficsConsole, SLOT(slotReconfigure())); m_ficsConsole->setEnabled(false); + DockWidgetEx* centipawnDock = new DockWidgetEx(tr("Centipawn Loss Graph"), this); + centipawnDock->setObjectName("CentipawnDock"); + centipawnDock->toggleViewAction()->setShortcut(Qt::CTRL | Qt::Key_W); + m_menuView->addAction(centipawnDock->toggleViewAction()); + m_centipawnGraph = new CentipawnGraph(centipawnDock); + centipawnDock->adjustSize(); + connect(m_centipawnGraph, &CentipawnGraph::requestPly, this, &MainWindow::slotGameMoveToPly); + addDockWidget(Qt::RightDockWidgetArea, centipawnDock); + centipawnDock->setVisible(AppSettings->getValue("/MainWindow/CentipawnGraph").toBool()); + centipawnDock->setWidget(m_centipawnGraph); + connect(m_centipawnGraph, &CentipawnGraph::startAnalysisRequested, this, &MainWindow::slotStartAnalysisRequested); + /* Game view */ DockWidgetEx* gameTextDock = new DockWidgetEx(tr("Notation"), this); gameTextDock->setObjectName("GameTextDock"); m_gameWindow = new GameWindow(gameTextDock); connect(this, SIGNAL(reconfigure()), m_gameWindow, SLOT(slotReconfigure())); - m_gameToolBar = new GameToolBar(tr("Game Time"), m_gameWindow); - m_gameToolBar->setMovable(false); - m_gameWindow->addToolBar(Qt::BottomToolBarArea, m_gameToolBar); - connect(m_gameToolBar, &GameToolBar::requestPly, this, &MainWindow::slotGameMoveToPly); - m_menuView->addAction(m_gameToolBar->toggleViewAction()); - m_gameToolBar->setVisible(AppSettings->getValue("/MainWindow/GameToolBar").toBool()); m_gameView = m_gameWindow->browser(); connect(m_gameView, &GameNotationWidget::anchorClicked, this, &MainWindow::slotGameViewLinkUrl); connect(m_gameView, &GameNotationWidget::actionRequested, this, &MainWindow::slotGameModify); @@ -659,7 +665,7 @@ void MainWindow::closeEvent(QCloseEvent* e) AppSettings->setLayout(this); AppSettings->beginGroup("/MainWindow/"); AppSettings->setValue("BoardSplit", m_boardSplitter->saveState()); - AppSettings->setValue("GameToolBar", m_gameToolBar->isVisible()); + AppSettings->setValue("CentipawnGraph", m_centipawnGraph->isVisible()); AppSettings->endGroup(); } else @@ -1850,7 +1856,6 @@ void MainWindow::setupActions() QMenu *gameMenu = menuBar()->addMenu(tr("&Game")); gameToolBar = addToolBar(tr("Game")); gameToolBar->setObjectName("GameToolBarMain"); - dbToolBar = addToolBar(tr("Database")); dbToolBar->setObjectName("DbToolBarMain"); @@ -1897,10 +1902,8 @@ void MainWindow::setupActions() gameToolBar->addSeparator(); autoGroup = new ExclusiveActionGroup(this); - m_match = createAction(tr("Match"), SLOT(slotToggleGameMode()), Qt::CTRL | Qt::Key_M, gameToolBar, ":/images/black_chess.png"); m_match->setCheckable(true); - autoGroup->addAction(m_match); gameMenu->addAction(m_match); gameMenu->addSeparator(); diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index cf809138b..e96a86b5f 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -47,7 +47,7 @@ class FicsClient; class FicsConsole; class GameList; class GameNotationWidget; -class GameToolBar; +class CentipawnGraph; class GameWindow; class HistoryLabel; class OpeningTreeWidget; @@ -609,6 +609,8 @@ private slots: void slotVersionFound(int major, int minor, int build); void slotUpdateOpeningTreeWidget(); void slotDatabaseEditTag(); + void slotStartAnalysisRequested(); + private: /** Create single menu action. */ QAction* createAction(QString name, const char* slot, const QKeySequence& key = QKeySequence(), @@ -703,7 +705,7 @@ private slots: QLabel* m_sliderText; QPointer m_comboEngine; GameWindow* m_gameWindow; - GameToolBar* m_gameToolBar; + CentipawnGraph* m_centipawnGraph; QTabWidget* m_tabWidget; AnnotationWidget* annotationWidget; /* Status */ diff --git a/src/gui/mainwindowactions.cpp b/src/gui/mainwindowactions.cpp index 4da9e781c..c56b047ba 100644 --- a/src/gui/mainwindowactions.cpp +++ b/src/gui/mainwindowactions.cpp @@ -38,7 +38,7 @@ #include "gameid.h" #include "gamelist.h" #include "gamenotationwidget.h" -#include "gametoolbar.h" +#include "centipawngraph.h" #include "gamewindow.h" #include "GameMimeData.h" #include "historylabel.h" @@ -393,8 +393,8 @@ void MainWindow::UpdateMaterialWidget() { if(databaseInfo()) { - m_gameToolBar->slotDisplayMaterial(databaseInfo()->material()); - m_gameToolBar->slotDisplayEvaluations(databaseInfo()->evaluations()); + m_centipawnGraph->slotDisplayMaterial(databaseInfo()->material()); + m_centipawnGraph->slotDisplayEvaluations(databaseInfo()->evaluations()); } } @@ -1329,22 +1329,12 @@ void MainWindow::moveChanged() auto timeThis = g.timeAnnotation(m, GameX::BeforeMove); auto timeThat = g.timeAnnotation(m, GameX::AfterMove); - if (g.board().toMove() == White) - { - m_gameToolBar->slotDisplayTime(White, timeThis); - m_gameToolBar->slotDisplayTime(Black, timeThat); - } - else - { - m_gameToolBar->slotDisplayTime(Black, timeThis); - m_gameToolBar->slotDisplayTime(White, timeThat); - } // Highlight current move m_gameView->showMove(m); if (g.isMainline()) { - m_gameToolBar->slotDisplayCurrentPly(g.ply()); + m_centipawnGraph->slotDisplayCurrentPly(g.ply()); } displayVariations(); @@ -4449,3 +4439,10 @@ void MainWindow::delaySpeechTimeout() } #endif + +void MainWindow::slotStartAnalysisRequested() +{ + if (!m_centipawnGraph) + return; + m_centipawnGraph->startAnalysis(game()); +}