/*
* Quackle -- Crossword game artificial intelligence and analysis tool
* Copyright (C) 2005-2014 Jason Katz-Brown and John O'Laughlin.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "geometry.h"
#include "movebox.h"
MoveBox::MoveBox(QWidget *parent)
: View(parent)
{
QVBoxLayout *vlayout = new QVBoxLayout(this);
Geometry::setupInnerLayout(vlayout);
vlayout->setSpacing(0);
m_treeWidget = new QTreeWidget(this);
m_treeWidget->setColumnCount(3);
m_treeWidget->setSelectionMode(QTreeWidget::ExtendedSelection);
QStringList headers;
headers << tr("Move") << tr("Score") << tr("Leave") << tr("Win %") << tr("Valuation");
m_treeWidget->setHeaderLabels(headers);
QHBoxLayout *buttonLayout = new QHBoxLayout;
Geometry::setupInnerLayout(buttonLayout);
m_removeButton = new QPushButton(tr("Remove"));
connect(m_removeButton, SIGNAL(clicked()), this, SLOT(removeMove()));
buttonLayout->addWidget(m_removeButton);
m_commitButton = new QPushButton(tr("Commit"));
connect(m_commitButton, SIGNAL(clicked()), this, SIGNAL(commit()));
buttonLayout->addWidget(m_commitButton);
setSelectionWatchingEnabled(true);
vlayout->addWidget(m_treeWidget);
vlayout->addLayout(buttonLayout);
}
void MoveBox::moveActivated(QTreeWidgetItem *item)
{
if (item == 0)
{
emit setCandidateMove(Quackle::Move::createNonmove());
return;
}
// do nothing if no item was clicked or if more than one item is selected
if (m_treeWidget->selectedItems().size() != 1)
return;
for (QMap::iterator it = m_moveMap.begin(); it != m_moveMap.end(); ++it)
{
if (it.value() == item)
{
emit setCandidateMove(it.key());
break;
}
}
}
void MoveBox::selectionChanged()
{
const bool moveSelected = !m_treeWidget->selectedItems().empty();
m_removeButton->setEnabled(moveSelected);
m_commitButton->setEnabled(moveSelected);
}
void MoveBox::removeMove()
{
QList selectedItems = m_treeWidget->selectedItems();
if (selectedItems.empty())
return;
Quackle::MoveList selectedMoves;
for (QList::iterator it = selectedItems.begin(); it != selectedItems.end(); ++it)
{
for (QMap::iterator mapIt = m_moveMap.begin(); mapIt != m_moveMap.end(); ++mapIt)
{
if (mapIt.value() == *it)
{
selectedMoves.push_back(mapIt.key());
break;
}
}
}
emit removeCandidateMoves(selectedMoves);
// TODO make this code work to select the next item
QTreeWidgetItem *prevLastSelection = m_moveMap.value(selectedMoves.back());
QTreeWidgetItem *nextSelection = 0;
const int numItems = m_treeWidget->topLevelItemCount();
for (int i = 0; i < numItems; ++i)
{
if (m_treeWidget->topLevelItem(i) == prevLastSelection)
{
if (i != numItems - 1)
{
nextSelection = m_treeWidget->topLevelItem(i + 1);
break;
}
}
}
if (nextSelection)
{
for (QMap::iterator mapIt = m_moveMap.begin(); mapIt != m_moveMap.end(); ++mapIt)
{
if (mapIt.value() == nextSelection)
{
emit setCandidateMove(mapIt.key());
break;
}
}
}
}
// This is complex as it tries to do as little as possible when
// the move list hasn't changed and is sorted the same way,
// so simulations can go as fast as possible.
// Nevertheless, TODO clean this up
void MoveBox::setMoves(const Quackle::MoveList &moves, const Quackle::Move &selectedMove)
{
bool resorted = false;
if (m_previousMoves.size() == moves.size())
{
Quackle::MoveList::const_iterator prevIt = m_previousMoves.begin();
const Quackle::MoveList::const_iterator end = moves.end();
for (Quackle::MoveList::const_iterator it = moves.begin(); it != end; ++it, ++prevIt)
{
if (!(*prevIt == *it))
{
resorted = true;
break;
}
}
}
else
{
resorted = true;
}
bool hasNewItems = false;
Quackle::MoveList::const_iterator end(moves.end());
for (Quackle::MoveList::const_iterator it = moves.begin(); it != end; ++it)
{
QMap::const_iterator mapEnd(m_moveMap.end());
for (QMap::const_iterator mapIt = m_moveMap.begin(); mapIt != mapEnd; ++mapIt)
{
if (mapIt.key() == *it)
{
mapIt.value()->setText(WinPercentageColumn, formatWinPercentage((*it).win));
mapIt.value()->setText(EquityColumn, formatValuation((*it).equity));
if (resorted)
{
m_treeWidget->addTopLevelItem(m_treeWidget->takeTopLevelItem(m_treeWidget->indexOfTopLevelItem(mapIt.value())));
}
goto foundFirstPass;
}
}
hasNewItems = true;
m_moveMap.insert(*it, createItem(*it));
foundFirstPass:
continue;
}
if (resorted)
{
for (QMutableMapIterator mapIt(m_moveMap); mapIt.hasNext(); )
{
mapIt.next();
for (Quackle::MoveList::const_iterator it = moves.begin(); it != end; ++it)
if (mapIt.key() == *it)
goto found;
delete mapIt.value();
mapIt.remove();
found:
continue;
}
}
if (moves.contains(selectedMove) && m_moveMap.contains(selectedMove))
{
m_treeWidget->setCurrentItem(m_moveMap.value(selectedMove));
}
selectionChanged();
if (hasNewItems)
QTimer::singleShot(0, this, SLOT(checkGeometry()));
m_previousMoves = moves;
m_previousSelection = selectedMove;
}
void MoveBox::checkGeometry()
{
m_treeWidget->resizeColumnToContents(EquityColumn);
m_treeWidget->resizeColumnToContents(WinPercentageColumn);
m_treeWidget->resizeColumnToContents(LeaveColumn);
m_treeWidget->resizeColumnToContents(ScoreColumn);
m_treeWidget->resizeColumnToContents(PlayColumn);
}
void MoveBox::positionChanged(const Quackle::GamePosition &position)
{
if (m_rack.tiles() != position.currentPlayer().rack().tiles())
{
for (QMap::iterator mapIt = m_moveMap.begin(); mapIt != m_moveMap.end(); ++mapIt)
delete mapIt.value();
m_moveMap.clear();
}
m_rack = position.currentPlayer().rack();
setMoves(position.moves(), position.moveMade());
}
void MoveBox::movesChanged(const Quackle::MoveList &moves)
{
setMoves(moves, m_previousSelection);
}
QTreeWidgetItem *MoveBox::createItem(const Quackle::Move &move)
{
QTreeWidgetItem *item = new QTreeWidgetItem(m_treeWidget);
item->setText(PlayColumn, QuackleIO::Util::moveToDetailedString(move));
item->setText(ScoreColumn, QString::number(move.effectiveScore()));
item->setText(LeaveColumn, QuackleIO::Util::letterStringToQString(QuackleIO::Util::arrangeLettersForUser(m_rack - move)));
item->setText(WinPercentageColumn, formatWinPercentage(move.win));
item->setText(EquityColumn, formatValuation(move.equity));
return item;
}
QString MoveBox::formatValuation(double valuation)
{
return QString::number(valuation, 'f', 1);
}
QString MoveBox::formatWinPercentage(double winPercentage)
{
return QString::number(winPercentage * 100.0, 'f', 2);
}
void MoveBox::setSelectionWatchingEnabled(bool enabled)
{
if (enabled)
{
connect(m_treeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged()));
// the former is single-click to select on all platforms,
// latter is always double-click to select on most platforms
connect(m_treeWidget, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(moveActivated(QTreeWidgetItem *)));
//connect(m_treeWidget, SIGNAL(itemActivated(QTreeWidgetItem *, int)), this, SLOT(moveActivated(QTreeWidgetItem *)));
// to allow the arrow keys to be used to select moves
// connect(m_treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(moveActivated(QTreeWidgetItem *)));
}
else
{
disconnect(m_treeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged()));
disconnect(m_treeWidget, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(moveActivated(QTreeWidgetItem *)));
// disconnect(m_treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(moveActivated(QTreeWidgetItem *)));
}
}