bovo/gui/scene.cc

404 lines
12 KiB
C++

/*******************************************************************
*
* Copyright 2007 Aron Boström <c02ab@efd.lth.se>
*
* Bovo 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 2, or (at your option)
* any later version.
*
* Bovo 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 Bovo; see the file COPYING. If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
********************************************************************/
#include "scene.h"
#include <QTime>
#include <QTimer>
#include <QPainter>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QSvgRenderer>
#include <KConfig>
#include <KConfigGroup>
#include <KDesktopFile>
#include "common.h"
#include "coord.h"
#include "game.h"
#include "hintitem.h"
#include "mark.h"
#include "move.h"
#include "theme.h"
using namespace bovo;
namespace gui {
Scene::Scene(const Theme& theme, bool animation)
: m_activate(false), m_game(nullptr), m_curCellSize(10.0), m_player(No), m_animation(animation),
m_paintMarker(false), m_showLast(false) {
/** @todo read theme from some configuration, I guess */
/** @todo read file names from from some configuration, I guess */
m_renderer = nullptr;
loadTheme(theme);
m_hintTimer = new QTimer(this);
m_hintTimer->setSingleShot(true);
m_hintItem = nullptr;
setSceneRect( 0, 0, m_curCellSize*(NUMCOLS+2), m_curCellSize*(NUMCOLS+2) );
}
Scene::~Scene() {
if (m_renderer) {
m_renderer->deleteLater();
}
if (m_hintItem) {
m_hintItem->deleteLater();
}
if (m_hintTimer) {
m_hintTimer->disconnect();
m_hintTimer->stop();
m_hintTimer->deleteLater();
}
}
qreal Scene::squareSize() const {
return m_curCellSize;
}
void Scene::loadTheme(const Theme& theme) {
m_fill = theme.fill();
QColor color(theme.backgroundColor());
QPalette bgPal;
const auto vs = views();
for (QGraphicsView* view : vs) {
bgPal.setColor(view->backgroundRole(), color.isValid() ? color : Qt::white);
view->setPalette(bgPal);
}
if (m_renderer == nullptr)
m_renderer = new QSvgRenderer(theme.svg());
else
m_renderer->load(theme.svg());
const QList<QGraphicsItem*> allMarks = items();
for (QGraphicsItem* item : allMarks) {
if (Mark* mark = qgraphicsitem_cast<Mark *>(item)) {
mark->setFill(m_fill);
mark->update(mark->boundingRect());
}
else if (auto hintItem = qgraphicsitem_cast<HintItem *>(item)) {
hintItem->setFill(m_fill);
hintItem->update(hintItem->boundingRect());
}
}
}
void Scene::setTheme(const Theme& theme) {
loadTheme(theme);
invalidate(0, 0, width(), height());
}
void Scene::activate(bool activate) {
m_activate = activate;
}
void Scene::setWin() {
if (!m_game->isGameOver()) {
return;
}
invalidate(0, 0, width(), height());
}
void Scene::setGame(Game* game) {
destroyHint();
m_winningMoves = QList<Move>();
m_game = game;
m_player = m_game->player();
connect(m_game, &Game::boardChanged, this, &Scene::updateBoard);
if (!m_game->demoMode()) {
connect(m_game, &Game::playerTurn, this, &Scene::slotPlayerTurn);
connect(m_game, &Game::oposerTurn, this, &Scene::slotOposerTurn);
}
connect(m_game, &Game::gameOver, this, &Scene::slotGameOver);
connect(this, &Scene::move, m_game, &Game::move);
qDeleteAll(items()); //remove all marks
m_paintMarker = false;
m_showLast = false;
invalidate(0, 0, width(), height());
}
void Scene::updateBoard(const Move& move) {
destroyHint();
if (move.valid()) {
setShowLast(move.x(), move.y());
Mark* mark = new Mark(this, move, m_animation, m_fill);
mark->setSharedRenderer(m_renderer);
addItem(mark);
} else if (move.player() == No) {
if (!m_game->isGameOver() && !m_winningMoves.empty()) {
m_winningMoves = QList<Move>();
invalidate(0, 0, width(), height());
}
removeShowLast();
const QList<QGraphicsItem*> allMarks = items();
for (QGraphicsItem* item : allMarks) {
if (Mark* mark = qgraphicsitem_cast<Mark *>(item)) {
if (mark->row() == move.y() && mark->col() == move.x()) {
if (m_animation) {
connect(mark, &Mark::killed, this, &Scene::killMark);
mark->kill();
} else {
removeItem(mark);
delete mark;
}
}
}
}
}
}
QPointF Scene::cellCenter(int x, int y) const {
return cellTopLeft(x, y) + QPointF(m_curCellSize/2.0, m_curCellSize/2.0);
}
QPointF Scene::cellTopLeft(int x, int y) const {
return {(x+1) * m_curCellSize, (y+1) * m_curCellSize};
}
void Scene::drawBackground(QPainter *p, const QRectF&) {
QRectF sRect = sceneRect();
int minSize = qMin(static_cast<int>(sRect.width()),
static_cast<int>(sRect.height()));
qreal shrinkSize = static_cast<qreal>(NUMCOLS) /
static_cast<qreal>(NUMCOLS+2);
qreal size = static_cast<qreal>(minSize) /
static_cast<qreal>(NUMCOLS+2);
/* sRect.setWidth(minSize*cellSize);
sRect.setHeight(minSize*cellSize);
sRect.setLeft(cellSize);*/
QRectF tmpRect(size, size, minSize*shrinkSize, minSize*shrinkSize);
m_renderer->render(p, QStringLiteral("background"));
m_renderer->render(p, QStringLiteral("grid"), tmpRect);
}
void Scene::drawForeground(QPainter *p, const QRectF& bounds) {
if (m_paintMarker) {
QRectF rect(cellTopLeft(m_col, m_row), QSizeF(m_curCellSize,
m_curCellSize));
qreal adjusting((1.0-m_fill)*m_curCellSize/2.0);
rect.adjust(adjusting, adjusting, -adjusting, -adjusting);
if (bounds.intersects(rect)) {
p->setOpacity(0.4);
m_renderer->render(p, QStringLiteral("x1"), rect);
p->setOpacity(1);
}
}
if (m_showLast) {
QRectF rect(cellTopLeft(m_lastCol, m_lastRow), QSizeF(m_curCellSize,
m_curCellSize));
if (bounds.intersects(rect)) {
m_renderer->render(p, QStringLiteral("last"), rect);
}
}
if (!m_winningMoves.empty()) {
QList<Move>::const_iterator it = m_winningMoves.constBegin();
QList<Move>::const_iterator end = m_winningMoves.constEnd();
while (it != end) {
QRectF tmpRect(cellTopLeft(it->x(), it->y()), QSizeF(m_curCellSize,
m_curCellSize));
if (bounds.intersects(tmpRect)) {
m_renderer->render(p, QStringLiteral("win"), tmpRect);
}
++it;
}
}
}
void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *ev) {
if (!m_game->isGameOver() && !m_game->computerTurn() && m_activate) {
QRectF boardRect(cellTopLeft(0, 0), QSizeF(m_curCellSize * NUMCOLS,
m_curCellSize * NUMCOLS));
if (!boardRect.contains(ev->scenePos())) {
removePaintMarker();
} else {
uint row = (uint)((ev->scenePos().y()-boardRect.y()) / m_curCellSize);
uint col = (uint)((ev->scenePos().x()-boardRect.x()) / m_curCellSize);
row = qMax(row, static_cast<uint>(0));
row = qMin(static_cast<uint>(NUMCOLS-1), row);
col = qMax(col, static_cast<uint>(0));
col = qMin(static_cast<uint>(NUMCOLS-1), col);
if (m_row != row || m_col != col || !m_paintMarker) {
if (m_game->player(Coord(col, row)) == No) {
setPaintMarker(col, row);
} else {
removePaintMarker();
}
}
}
}
}
void Scene::mousePressEvent( QGraphicsSceneMouseEvent* ev ) {
if (m_game->isGameOver() || m_game->computerTurn() || !m_activate) {
return;
}
QRectF boardRect(cellTopLeft(0, 0), QSizeF(m_curCellSize * NUMCOLS,
m_curCellSize * NUMCOLS));
if (!boardRect.contains(ev->scenePos())) {
return;
}
int row = (int)((ev->scenePos().y()-boardRect.y()) / m_curCellSize);
int col = (int)((ev->scenePos().x()-boardRect.x()) / m_curCellSize);
row = qMax(row, 0);
row = qMin(NUMCOLS-1, row);
col = qMax(col, 0);
col = qMin(NUMCOLS-1, col);
Q_EMIT move(Move(m_player, Coord(col, row)));
}
void Scene::removePaintMarker() {
if (!m_paintMarker) {
return;
}
m_paintMarker = false;
QRectF rectOld(cellTopLeft(m_col, m_row), QSizeF(m_curCellSize,
m_curCellSize));
qreal adjusting((1.0-m_fill)*m_curCellSize/2.0);
rectOld.adjust(adjusting, adjusting, -adjusting, -adjusting);
invalidate(rectOld, ForegroundLayer);
}
void Scene::setPaintMarker(uint col, uint row) {
if (m_paintMarker == true && m_col == col && m_row == row) {
return;
}
removePaintMarker();
m_paintMarker = true;
m_col = col;
m_row = row;
QRectF rect(cellTopLeft(m_col, m_row), QSizeF(m_curCellSize,
m_curCellSize));
qreal adjusting((1.0-m_fill)*m_curCellSize/2.0);
rect.adjust(adjusting, adjusting, -adjusting, -adjusting);
invalidate(rect, ForegroundLayer);
}
void Scene::removeShowLast() {
if (!m_showLast) {
return;
}
m_showLast = false;
QRectF rectOld(cellTopLeft(m_lastCol, m_lastRow), QSizeF(m_curCellSize,
m_curCellSize));
invalidate(rectOld, ForegroundLayer);
}
void Scene::setShowLast(uint col, uint row) {
if (m_showLast == true && m_lastCol == col && m_lastRow == row) {
return;
}
removeShowLast();
m_showLast = true;
m_lastCol = col;
m_lastRow = row;
QRectF rect(cellTopLeft(m_lastCol, m_lastRow), QSizeF(m_curCellSize,
m_curCellSize));
invalidate(rect, ForegroundLayer);
}
void Scene::slotPlayerTurn() {
activate(true);
}
void Scene::slotOposerTurn() {
removePaintMarker();
activate(false);
}
void Scene::slotGameOver(const QList<Move>& win) {
removePaintMarker();
removeShowLast();
m_winningMoves = win;
setWin();
activate(false);
}
void Scene::hint(const Move& hint) {
destroyHint();
m_hintItem = new HintItem(this, hint, m_animation, m_fill);
m_hintItem->setSharedRenderer(m_renderer);
addItem(m_hintItem);
connect(m_hintTimer, &QTimer::timeout, this, &Scene::hintTimeout);
m_hintTimer->start(2000);
}
void Scene::destroyHint() {
if (m_hintItem != nullptr) {
m_hintTimer->disconnect();
m_hintTimer->stop();
m_hintItem->killAnimation();
removeItem(m_hintItem);
m_hintItem->deleteLater();
m_hintItem = nullptr;
}
}
void Scene::hintTimeout() {
if (!m_animation) {
destroyHint();
} else if (m_hintItem != nullptr) {
connect(m_hintItem, &HintItem::killed, this, &Scene::destroyHint);
m_hintItem->kill();
}
}
void Scene::enableAnimation(bool enabled) {
m_animation = enabled;
if (!m_animation) {
killAnimations();
}
}
void Scene::killAnimations() {
if (m_hintItem != nullptr) {
m_hintItem->killAnimation();
}
}
void Scene::killMark(Mark* mark) {
removeItem(mark);
mark->deleteLater();
}
void Scene::replay() {
const QList<QGraphicsItem*> allMarks = items();
for (QGraphicsItem* mark : allMarks) {
removeItem(mark);
delete mark;
}
m_winningMoves = QList<Move>();
invalidate(0, 0, width(), height());
connect(m_game, &Game::boardChanged, this, &Scene::updateBoard);
}
bool Scene::event(QEvent *event) {
bool ret = QGraphicsScene::event(event);
if (event->type() == QEvent::Leave) {
removePaintMarker();
}
return ret;
}
} /* namespace gui */