qcross/qcross/ccrossfieldwidget.cpp

546 lines
17 KiB
C++

/***************************************************************************
* Copyright (C) 2008 by Oliver Groß *
* z.o.gross@gmx.de *
* *
* 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 2 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, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "ccrossfieldwidget.h"
#include <libqnono/cnonogram.h>
#include <QPainter>
#include <QMouseEvent>
#include <QLCDNumber>
#include <QDebug>
#include <QFrame>
#include <QLabel>
#include <QHBoxLayout>
#include <QStyle>
#include <QApplication>
namespace qcross {
using namespace libqnono;
int min(int a, int b) {
return (a < b) ? a : b;
}
int max(int a, int b) {
return (a > b) ? a : b;
}
//public
CCrossFieldWidget::CCrossFieldWidget(QWidget * parent) : QWidget(parent),
m_MessageTimeoutId(-1),
m_Nonogram(new CNonogram(this)),
m_MouseMark(NonogramMarker::NONE),
m_MouseDown(false),
m_NumbersMarkable(true),
m_Clock(new QLCDNumber(8, this)),
m_LastErrorMark(-1, -1)
{
// m_Notifier = new QFrame(this);
// m_Notifier->setLineWidth(10);
// m_Notifier->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
QPalette notifierPalette(QApplication::palette().color(QPalette::ToolTipBase));
notifierPalette.setColor(QPalette::Text, QApplication::palette().color(QPalette::ToolTipText));
// m_Notifier->setPalette(notifierPalette);
// m_Notifier->setAutoFillBackground(true);
// QHBoxLayout * notifierLayout = new QHBoxLayout(m_Notifier);
// m_NotifierIcon = new QLabel(m_Notifier);
// m_NotifierText = new QLabel(m_Notifier);
// m_NotifierText->setWordWrap(true);
// notifierLayout->addWidget(m_NotifierIcon);
// notifierLayout->addWidget(m_NotifierText);
// notifierLayout->addStretch();
// m_Notifier->setLayout(notifierLayout);
// m_Notifier->hide();
m_Clock->setVisible(false);
setEnabled(false);
connect(m_Nonogram, SIGNAL(loaded()), SLOT(loaded()));
connect(m_Nonogram, SIGNAL(won(int)), SLOT(won(int)));
connect(m_Nonogram, SIGNAL(timeup()), SLOT(timeup()));
connect(m_Nonogram, SIGNAL(wrongMark(int,int,int)), SLOT(wrongMark(int,int,int)));
connect(m_Nonogram, SIGNAL(changedMark(int,int)), SLOT(changedMark(int,int)));
connect(m_Nonogram, SIGNAL(restarted()), SLOT(restarted()));
connect(m_Nonogram, SIGNAL(paused(int)), SLOT(paused(int)));
connect(m_Nonogram, SIGNAL(resumed(int)), SLOT(resumed(int)));
connect(m_Nonogram, SIGNAL(tick(int)), SLOT(tick(int)));
}
CCrossFieldWidget::~CCrossFieldWidget() {
qDebug("m_MessageTimeoutId = %i", m_MessageTimeoutId);
if (m_MessageTimeoutId != -1) {
killTimer(m_MessageTimeoutId);
m_MessageTimeoutId = -1;
}
}
void CCrossFieldWidget::showMessage(const QString message, int timeout, MessageType type) {
if (!message.isEmpty()) {
Message newMessage;
newMessage.type = type;
newMessage.text = message;
newMessage.timeout = timeout;
m_Messages.enqueue(newMessage);
}
if (m_MessageTimeoutId == -1)
nextMessage();
}
//public slots
void CCrossFieldWidget::setNumbersMarkable(bool value) {
m_NumbersMarkable = value;
}
//protected slots:
void CCrossFieldWidget::loaded() {
m_LastErrorMark = QPoint(-1, -1);
initialize();
updateTimeDisplay();
m_Messages.clear();
if (m_Nonogram->valid()) {
if (m_Nonogram->time() == m_Nonogram->timeout()) {
showMessage(tr("Game started!"), 1000);
} else {
showMessage(tr("Save game loaded!"), 1000);
}
setEnabled(true);
} else {
nextMessage();
setEnabled(false);
}
}
void CCrossFieldWidget::won(int score) {
showMessage(tr("Congratulations! You've solved the puzzle."));
}
void CCrossFieldWidget::timeup() {
showMessage(tr("Too bad! Time's up."), 0, CCrossFieldWidget::Critical);
}
void CCrossFieldWidget::wrongMark(int x, int y, int penaltyTime) {
m_LastErrorMark = QPoint(x, y);
showMessage(tr("Sorry this was not correct: -%1min").arg(penaltyTime/60), 1000, CCrossFieldWidget::Warning);
}
void CCrossFieldWidget::changedMark(int x, int y) {
update();
}
void CCrossFieldWidget::restarted() {
m_Messages.clear();
m_LastErrorMark = QPoint(-1, -1);
showMessage(tr("Game restarted."), 1000);
}
void CCrossFieldWidget::paused(int time) {
if (m_Message.type != Invalid) {
m_Messages.enqueue(m_Message);
if (m_MessageTimeoutId != -1) {
killTimer(m_MessageTimeoutId);
m_MessageTimeoutId = -1;
}
}
setEnabled(false);
showMessage(tr("Game paused."));
}
void CCrossFieldWidget::resumed(int time) {
setEnabled(true);
showMessage(tr("Game resumed."), 1000);
update();
}
void CCrossFieldWidget::tick(int time) {
updateTimeDisplay();
}
//protected
void CCrossFieldWidget::nextMessage() {
if (m_MessageTimeoutId != -1)
killTimer(m_MessageTimeoutId);
m_MessageTimeoutId = -1;
if (m_Messages.isEmpty()) {
m_Message.type = Invalid;
// m_Notifier->hide();
// updateMetrics();
update();
return;
}
m_Message = m_Messages.dequeue();
update();
/* switch (m_Message.type) {
case Information:
m_NotifierIcon->setPixmap(style()->standardIcon(QStyle::SP_MessageBoxInformation).pixmap(22, 22));
break;
case Warning:
m_NotifierIcon->setPixmap(style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(22, 22));
break;
case Critical:
m_NotifierIcon->setPixmap(style()->standardIcon(QStyle::SP_MessageBoxCritical).pixmap(22, 22));
break;
default:
break;
}
m_NotifierText->setText(m_Message.text);
m_Notifier->show();
updateMetrics();*/
if (m_Message.timeout)
m_MessageTimeoutId = startTimer(m_Message.timeout);
}
void CCrossFieldWidget::initialize() {
int minimumW = max(m_Nonogram->numbers().maximumNumberCount()+1, 5);
int minimumH = minimumW;
minimumW += m_Nonogram->width();
minimumH += m_Nonogram->height();
int minimumBoxSize = max(fontMetrics().width(QString::number(m_Nonogram->height())), fontMetrics().width(QString::number(m_Nonogram->width())));
minimumBoxSize = max(minimumBoxSize, fontInfo().pixelSize() * 1.5);
minimumW *= minimumBoxSize;
minimumH *= minimumBoxSize;
setMinimumSize(minimumW, minimumH);
updateMetrics();
}
void CCrossFieldWidget::timerEvent(QTimerEvent * event) {
if (event->timerId() == m_MessageTimeoutId) {
nextMessage();
}
}
void CCrossFieldWidget::updateTimeDisplay() {
m_Clock->setVisible(m_Nonogram->valid());
int secs = m_Nonogram->time();
quint8 seconds = secs % 60;
quint8 minutes = secs / 60;
quint8 hours = minutes / 60;
minutes %= 60;
QString time("");
if (hours)
time += QString::number(hours) + ':';
if (minutes < 10)
time += '0';
time += QString::number(minutes) + ':';
if (seconds < 10)
time += '0';
time += QString::number(seconds);
m_Clock->display(time);
}
void CCrossFieldWidget::paintEvent(QPaintEvent *) {
if (!m_Nonogram) {
return;
}
QPainter painter(this);
int originX = m_OffsetX + m_HeaderWidth;
int originY = m_OffsetY + m_HeaderHeight;
int gridWidth = m_RasterWidth - m_HeaderWidth;
int gridHeight = m_RasterHeight - m_HeaderHeight;
// draw background
{
const int delta = 5 * m_BoxSize;
int offsetX = 0;
int offsetY;
int deltaX = delta;
int deltaY;
bool useBaseX;
bool useBaseY = true;
while (offsetX < gridWidth) {
if (gridWidth - offsetX < delta)
deltaX = gridWidth - offsetX;
offsetY = 0;
deltaY = delta;
useBaseX = useBaseY;
while (offsetY < gridHeight) {
if (gridHeight - offsetY < delta)
deltaY = gridHeight - offsetY;
painter.fillRect(originX + offsetX, originY + offsetY, deltaX, deltaY,
palette().color(useBaseX ? QPalette::Base : QPalette::AlternateBase));
useBaseX = !useBaseX;
offsetY += deltaY;
}
useBaseY = !useBaseY;
offsetX += deltaX;
}
}
// draw markers and crosses
{
QPen pen;
pen.setWidth(m_MarkerSize / 8);
painter.setPen(pen);
QRectF markerRect(m_MarkerOffset, m_MarkerOffset, m_MarkerSize, m_MarkerSize);
QColor markerColor = palette().color(QPalette::Highlight);
QColor errorColor = markerColor;
errorColor.setAlpha(128);
painter.setBrush(markerColor);
for (int i = 0; i < m_Nonogram->width(); i++) {
originX = m_OffsetX + m_HeaderWidth + i * m_BoxSize;
markerRect.moveLeft(originX + m_MarkerOffset);
for (int j = 0; j < m_Nonogram->height(); j++) {
originY = m_OffsetY + m_HeaderHeight + j * m_BoxSize;
markerRect.moveTop(originY + m_MarkerOffset);
switch (m_Nonogram->marker().pixel(i, j)) {
case NonogramMarker::MARKED:
painter.fillRect(markerRect, painter.brush());
break;
case NonogramMarker::CROSSED:
if (m_Nonogram->isFinished())
break;
if (i == m_LastErrorMark.x() && j == m_LastErrorMark.y()) {
painter.setBrush(errorColor);
painter.fillRect(markerRect, painter.brush());
painter.setBrush(markerColor);
}
painter.drawLine(markerRect.topLeft(), markerRect.bottomRight());
painter.drawLine(markerRect.bottomLeft(), markerRect.topRight());
break;
default:
break;
}
}
}
}
// draw grid lines
painter.setPen(palette().color(QPalette::Dark));
for (int i = m_OffsetX + m_HeaderWidth; i < m_OffsetX + m_RasterWidth; i += m_BoxSize) {
painter.drawLine(i, m_OffsetY + m_HeaderHeight, i, m_OffsetY + m_RasterHeight-1);
}
for (int i = m_OffsetY + m_HeaderHeight; i < m_OffsetY + m_RasterHeight; i += m_BoxSize) {
painter.drawLine(m_OffsetX + m_HeaderWidth, i, m_OffsetX + m_RasterWidth-1, i);
}
// draw numbers area
if (m_Nonogram->isPaused())
painter.setPen(palette().color(QPalette::Shadow));
QFont font = painter.font();
font.setPixelSize(m_MarkerSize * 0.9);
painter.setFont(font);
for (int i = 0; i < m_Nonogram->width(); i++) {
originX = m_OffsetX + m_HeaderWidth + i * m_BoxSize;
painter.fillRect(originX, m_OffsetY, m_BoxSize, m_HeaderHeight, palette().color((i % 2) ? QPalette::AlternateBase : QPalette::Base));
if (!m_Nonogram->isPaused()) {
painter.setPen(palette().color(QPalette::WindowText));
int j = m_Nonogram->numbers().columns()[i].count();;
foreach (quint16 block, m_Nonogram->numbers().columns()[i]) {
originY = m_OffsetY + m_HeaderHeight - j * m_BoxSize;
painter.drawText(originX, originY, m_BoxSize, m_BoxSize,
Qt::AlignVCenter | Qt::AlignCenter, QString::number(block));
--j;
}
painter.setPen(palette().color(QPalette::Shadow));
}
if (!(i % 5))
painter.drawLine(originX, m_OffsetY, originX, m_OffsetY + m_RasterHeight-1);
}
// painter.drawLine(m_OffsetX + m_RasterWidth, m_OffsetY, m_OffsetX + m_RasterWidth, m_OffsetY + m_RasterHeight);
for (int i = 0; i < m_Nonogram->height(); i++) {
originY = m_OffsetY + m_HeaderHeight + i * m_BoxSize;
painter.fillRect(m_OffsetX, originY, m_HeaderWidth, m_BoxSize, palette().color((i % 2) ? QPalette::AlternateBase : QPalette::Base));
if (!m_Nonogram->isPaused()) {
painter.setPen(palette().color(QPalette::WindowText));
int j = m_Nonogram->numbers().rows()[i].count();
foreach (quint16 block, m_Nonogram->numbers().rows()[i]) {
originX = m_OffsetX + m_HeaderWidth - j * m_BoxSize;
painter.drawText(originX, originY, m_BoxSize, m_BoxSize,
Qt::AlignVCenter | Qt::AlignCenter, QString::number(block));
--j;
}
painter.setPen(palette().color(QPalette::Shadow));
}
if (!(i % 5))
painter.drawLine(m_OffsetX, originY, m_OffsetX + m_RasterWidth-1, originY);
}
// draw message if needed
if (m_Message.type != Invalid) {
QRect boxrect(m_HeaderWidth + m_OffsetX, m_HeaderHeight + m_OffsetY, m_RasterWidth - m_HeaderWidth, m_RasterHeight - m_HeaderHeight);
QColor fillColor = palette().color(QPalette::Base);
fillColor.setAlpha(220);
painter.fillRect(boxrect, fillColor);
font.setPixelSize(font.pixelSize() * 2);
painter.setFont(font);
painter.drawText(boxrect, Qt::AlignVCenter | Qt::AlignCenter | Qt::TextWordWrap, m_Message.text);
}
}
/* void CCrossFieldWidget::updateCell(int x, int y) {
QPainter painter(this);
int originX, originY;
originX = m_OffsetX + m_HeaderWidth + x * m_BoxSize;
originY = m_OffsetY + m_HeaderHeight + y * m_BoxSize;
painter.fillRect(originX + 1, originY + 1, m_BoxSize - 2, m_BoxSize - 2, palette().color(QPalette::Base));
switch (m_OverlayData[x][y]) {
case CMT_MARKED:
painter.drawRect(originX + 2, originY + 2, m_BoxSize - 4, m_BoxSize - 4);
break;
case CMT_CROSSED:
painter.drawLine(originX + 2, originY + 2, originX + m_BoxSize - 2, originY + m_BoxSize - 2);
painter.drawLine(originX + 2, originY + m_BoxSize - 2, originX + m_BoxSize - 2, originY + 2);
break;
default:
break;
}
}*/
void CCrossFieldWidget::resizeEvent(QResizeEvent * /*event*/) {
updateMetrics();
}
void CCrossFieldWidget::updateMetrics() {
m_HeaderWidth = max(m_Nonogram->numbers().maximumNumberCount()+1, 5);
/* m_HeaderWidth = max(m_Picture->width() / 2 + 1, m_Picture->height() / 2 + 1);
m_HeaderWidth = m_Picture->width() / 2 + 1;
m_HeaderHeight = m_Picture->height() / 2 + 1;*/
int fieldsize = min((m_Nonogram->width() + m_HeaderWidth), (m_Nonogram->height() + m_HeaderWidth));
int widgetsize = min(width(), height());
m_BoxSize = ((double)widgetsize / fieldsize);
/*min((double)width() / (m_Picture->width() + m_HeaderWidth), (double)height() / (m_Picture->height() + m_HeaderWidth));*/
m_MarkerSize = m_BoxSize * 2.0/3.0;
m_MarkerOffset = ((m_BoxSize - m_MarkerSize) / 2.0);
m_HeaderWidth *= m_BoxSize;
m_HeaderHeight = m_HeaderWidth;
m_RasterWidth = m_BoxSize * m_Nonogram->width() + m_HeaderWidth;
m_RasterHeight = m_BoxSize * m_Nonogram->height() + m_HeaderHeight;
m_OffsetX = (width() - m_RasterWidth) / 2;
m_OffsetY = (height() - m_RasterHeight) / 2;
int clockHeight = /*m_Notifier->isVisible() ? m_HeaderHeight - m_Notifier->sizeHint().height()*/ /* * 3.0 / 4.0 */ /*:*/ m_HeaderHeight;
if (m_Clock)
m_Clock->setGeometry(m_OffsetX, m_OffsetY, m_HeaderWidth, clockHeight);
// if (m_Notifier && m_Notifier->isVisible())
// m_Notifier->setGeometry(m_OffsetX, m_OffsetY + clockHeight, m_HeaderWidth, m_Notifier->sizeHint().height());
update();
}
void CCrossFieldWidget::mousePressEvent(QMouseEvent * event) {
int pressedX = event->x() - m_OffsetX;
int pressedY = event->y() - m_OffsetY;
if (pressedX < m_HeaderWidth || pressedY < m_HeaderHeight || pressedX >= m_RasterWidth || pressedY >= m_RasterHeight)
return;
pressedX = (pressedX - m_HeaderWidth) / m_BoxSize;
pressedY = (pressedY - m_HeaderHeight) / m_BoxSize;
m_MouseLastX = pressedX; m_MouseLastY = pressedY;
if (event->button() == Qt::RightButton)
m_MouseMark = NonogramMarker::CROSSED;
else
m_MouseMark = NonogramMarker::MARKED;
m_MouseDown = true;
if (m_Nonogram->marker().pixel(pressedX, pressedY) == m_MouseMark)
m_MouseMark = NonogramMarker::NONE;
m_Nonogram->setMark(pressedX, pressedY, m_MouseMark);
}
void CCrossFieldWidget::mouseMoveEvent(QMouseEvent * event) {
if (!m_MouseDown)
return;
int pressedX = event->x() - m_OffsetX;
int pressedY = event->y() - m_OffsetY;
if (pressedX < m_HeaderWidth || pressedY < m_HeaderHeight || pressedX >= m_RasterWidth || pressedY >= m_RasterHeight)
return;
pressedX = (pressedX - m_HeaderWidth) / m_BoxSize;
pressedY = (pressedY - m_HeaderHeight) / m_BoxSize;
if (pressedX == m_MouseLastX && pressedY == m_MouseLastY) return;
m_MouseLastX = pressedX; m_MouseLastY = pressedY;
m_Nonogram->setMark(pressedX, pressedY, m_MouseMark);
}
void CCrossFieldWidget::mouseReleaseEvent(QMouseEvent *) {
m_MouseDown = false;
}
}