Commit fc2a95f7 authored by Johannes Bergmeier's avatar Johannes Bergmeier

- Changed Graphs to provide more infos on cliques

- Added new view based on QGV and SVG
- Added a SVG-renderer
- Added a yet to improve sample theme

svn path=/trunk/KDE/kdegames/ksudoku/; revision=791472
parent 3d62b0ab
......@@ -34,6 +34,7 @@ ADD_SUBDIRECTORY( gui )
#ADD_SUBDIRECTORY( gui/export ) #TODO PORT
#ADD_SUBDIRECTORY( gui/export/draw ) #TODO PORT
ADD_SUBDIRECTORY( shapes )
add_subdirectory( themes )
#set(ksudoku_client_SRCS
......
......@@ -2,10 +2,12 @@
set(ksudoku_views_SRCS
ksview.cpp
ArcBall.cpp
renderer.cpp
roxdokuview.cpp
sudokuview.cpp
qsudokubutton.cpp
valuelistwidget.cpp
view2d.cpp
)
kde4_add_library(ksudoku_views ${ksudoku_views_SRCS})
......
......@@ -35,6 +35,7 @@
#include "sudokuview.h"
#include "roxdokuview.h"
#include "view2d.h"
namespace ksudoku{
......@@ -56,11 +57,13 @@ void KsView::createView() {
GameType type = m_game.puzzle()->gameType();
switch(type) {
case sudoku: {
setWidget(new SudokuView(0, m_game, false));
// setWidget(new SudokuView(0, m_game, false));
setWidget(new View2D(0, m_game));
break;
}
case custom: {
setWidget(new SudokuView(0, m_game, true));
// setWidget(new SudokuView(0, m_game, true));
setWidget(new View2D(0, m_game));
break;
}
case roxdoku: {
......
#include "renderer.h"
#include <KSvgRenderer>
#include <KStandardDirs>
#include <kpixmapcache.h>
#include <QPixmap>
#include <QPainter>
namespace ksudoku {
Renderer* Renderer::instance() {
static Renderer instance;
return &instance;
}
Renderer::Renderer() {
m_cache = new KPixmapCache("ksudoku-cache");
m_cache->setCacheLimit(3*1024);
m_renderer = new KSvgRenderer(KStandardDirs::locate("appdata", "themes/ksudoku_sample.svg"));
fillNameHashes();
}
Renderer::~Renderer() {
delete m_cache;
delete m_renderer;
}
void Renderer::fillNameHashes() {
m_borderNames = QVector<QString>();
m_borderNames << "";
m_borderNames << "1";
m_borderNames << "2";
m_borderNames << "12";
m_borderNames << "3";
m_borderNames << "13";
m_borderNames << "23";
m_borderNames << "123";
m_borderNames << "4";
m_borderNames << "14";
m_borderNames << "24";
m_borderNames << "124";
m_borderNames << "34";
m_borderNames << "134";
m_borderNames << "234";
m_borderNames << "1234";
m_borderTypes << QString();
m_borderTypes << "row";
m_borderTypes << "column";
m_borderTypes << "block";
m_borderTypes << "special";
m_borderTypes << "special";
m_borderTypes << "special";
m_borderTypes << "special";
m_borderTypes << QString();
m_borderTypes << "row_h";
m_borderTypes << "column_h";
m_borderTypes << "block_h";
m_borderTypes << "special_h";
m_borderTypes << "special_h";
m_borderTypes << "special_h";
m_borderTypes << "special_h";
m_specialNames << "cell";
m_specialNames << "cell_preset";
m_specialNames << "cell";
m_specialNames << "cell_mistake";
m_specialNames << "cursor";
}
QPixmap Renderer::renderBackground(const QSize& size) const {
if(!m_renderer->isValid() || size.isEmpty()) return QPixmap();
QPixmap pix;
QString cacheName = QString("background_%1x%2").arg(size.width()).arg(size.height());
if(!m_cache->find(cacheName, pix))
{
pix = QPixmap(size);
pix.fill(Qt::transparent);
QPainter p(&pix);
m_renderer->render(&p, "background");
p.end();
m_cache->insert(cacheName, pix);
}
return pix;
}
/** Moves a point from its relative position to the base rect (0,0,1,1) to a relative position to rect @p to */
QPointF fromBasetoRect(const QPointF& p, const QRectF& to) {
return QPointF(p.x()*to.width()+to.left(), p.y()*to.height()+to.top());
}
/** Moves a point from its relative position to rect @p from to a relative position to the base rect (0,0,1,1) */
QPointF fromRectToBase(const QRectF& p, const QRectF& from) {
return QPointF((p.x()-from.left())/from.width(), (p.y()-from.top())/from.height());
}
/** Moves a point from its relative position to rect @p from to a relative position to rect @p to */
QPointF fromRectToRect(const QPointF& p, const QRectF& from, const QRectF& to) {
return QPointF((p.x()-from.left())*to.width()/from.width()+to.left(),
(p.y()-from.top())*to.height()/from.height()+to.top());
}
QPixmap Renderer::renderSpecial(SpecialType type, int size) const {
if(!m_renderer->isValid() || size == 0) return QPixmap();
QString cacheName = QString("special_%1_%2").arg(m_specialNames[type]).arg(size);
QPixmap pix;
if(!m_cache->find(cacheName, pix)) {
pix = QPixmap(size, size);
pix.fill(Qt::transparent);
QPainter p(&pix);
// NOTE fix for Qt's QSvgRenderer size reporting bug
QRectF r(m_renderer->boundsOnElement(m_specialNames[type]));
QRectF from(r.adjusted(+0.5,+0.5,-0.5,-0.5));
QRectF to(QRectF(0,0,size,size));
r.setTopLeft(fromRectToRect(r.topLeft(), from, to));
r.setBottomRight(fromRectToRect(r.bottomRight(), from, to));
m_renderer->render(&p, m_specialNames[type], r);
p.end();
m_cache->insert(cacheName, pix);
}
return pix;
}
QPixmap Renderer::renderSymbol(int symbol, int size) const {
if(!m_renderer->isValid() || size == 0) return QPixmap();
QString cacheName = QString("symbol_%1_%2").arg(symbol).arg(size);
QPixmap pix;
if(!m_cache->find(cacheName, pix)) {
pix = QPixmap(size, size);
pix.fill(Qt::transparent);
QPainter p(&pix);
// NOTE fix for Qt's QSvgRenderer size reporting bug
QRectF r(m_renderer->boundsOnElement("symbol_1"));
QRectF from(m_renderer->boundsOnElement("cell_symbol"));
from.adjust(+0.5,+0.5,-0.5,-0.5); // << this is the fix
QRectF to(QRectF(0,0,size,size));
r.setTopLeft(fromRectToRect(r.topLeft(), from, to));
r.setBottomRight(fromRectToRect(r.bottomRight(), from, to));
m_renderer->render(&p, QString("symbol_%1").arg(symbol), r);
p.end();
m_cache->insert(cacheName, pix);
}
return pix;
}
QPixmap Renderer::renderSymbolOn(QPixmap pixmap, int symbol, int color) const {
int size = pixmap.width();
QPixmap symbolPixmap = renderSymbol(symbol, size);
if(color) {
QPainter p(&symbolPixmap);
p.setCompositionMode(QPainter::CompositionMode_Multiply);
p.setBrush(QBrush(QColor(128,128,128,255)));
p.drawRect(0, 0, size, size);
p.setCompositionMode(QPainter::CompositionMode_DestinationOver);
p.drawPixmap(0, 0, pixmap);
p.end();
return symbolPixmap;
} else {
QPainter p(&pixmap);
p.drawPixmap(0, 0, symbolPixmap);
p.end();
return pixmap;
}
}
QPixmap Renderer::renderBorder(int border, GroupTypes type, int size) const {
if(!m_renderer->isValid() || size == 0) return QPixmap();
QString cacheName = QString("contour_%1_%2_%3").arg(m_borderTypes[type]).arg(m_borderNames[border]).arg(size);
QPixmap pix;
if(!m_cache->find(cacheName, pix)) {
pix = QPixmap(size, size);
pix.fill(Qt::transparent);
QPainter p(&pix);
// NOTE fix for Qt's QSvgRenderer size reporting bug
QRectF r(m_renderer->boundsOnElement(QString("%1_%2").arg(m_borderTypes[type]).arg(m_borderNames[border])));
QRectF from(r.adjusted(+0.5,+0.5,-0.5,-0.5));
QRectF to(QRectF(0,0,size,size));
r.setTopLeft(fromRectToRect(r.topLeft(), from, to));
r.setBottomRight(fromRectToRect(r.bottomRight(), from, to));
m_renderer->render(&p, QString("%1_%2").arg(m_borderTypes[type]).arg(m_borderNames[border]), r);
p.end();
m_cache->insert(cacheName, pix);
}
return pix;
}
}
#ifndef _KSUDOKU_RENDERER_H_
#define _KSUDOKU_RENDERER_H_
#include <QVector>
// #include <QPixmap>
#include <QString>
class QPixmap;
class QSize;
class KSvgRenderer;
class KPixmapCache;
namespace ksudoku {
enum GroupType {
GroupNone = 0x00,
GroupRow = 0x01,
GroupColumn = 0x02,
GroupBlock = 0x03,
GroupSpecial = 0x04,
GroupUnhighlightedMask = 0x07,
GroupHighlight = 0x08
};
enum SpecialType {
SpecialCell = 0x00,
SpecialCellPreset = 0x01,
SpecialCellMarkers = 0x02,
SpecialCellMistake = 0x03,
SpecialCursor = 0x04
};
Q_DECLARE_FLAGS(GroupTypes, GroupType)
class Renderer {
enum SupportFlag {
HasRow = 0x0001,
HasColumn = 0x0002,
HasBlock = 0x0004,
HasSpecial = 0x0008,
HasRowHighlight = 0x0010,
HasColumnHighlight = 0x0020,
HasBlockHighlight = 0x0040,
HasSpecialHighlight = 0x0080,
HasBlockCenter = 0x0100,
HasSpecialCenter = 0x0200,
ContoursInBackground = 0x1000
};
public:
static Renderer* instance();
QPixmap renderBackground(const QSize& size) const;
QPixmap renderSpecial(SpecialType type, int size) const;
QPixmap renderBorder(int border, GroupTypes type, int size) const;
QPixmap renderSymbol(int symbol, int size) const;
QPixmap renderSymbolOn(QPixmap pixmap, int symbol, int color) const;
private:
Renderer();
~Renderer();
private:
void fillNameHashes();
private:
bool m_hasRowAndColumn : 1;
bool m_hasRowAndColumnHighlight : 1;
bool m_hasBlock : 1;
bool m_hasBlockHighlight : 1;
bool m_hasSpecial : 1;
bool m_hasSpecialHighlight : 1;
QVector<QString> m_borderNames;
QVector<QString> m_borderTypes;
QVector<QString> m_specialNames;
KSvgRenderer* m_renderer;
KPixmapCache* m_cache;
};
}
#endif
#include "view2d.h"
#include "view2d.moc"
#include <QGraphicsPixmapItem>
#include <QtDebug>
// #include <QFile> // TODO only for debug
// #include <QEvent> // TODO only for debug
#include "sudoku_solver.h"
#include "puzzle.h"
namespace ksudoku {
struct ColoredValue {
ColoredValue() : value(0), color(0) { }
ColoredValue(int v, int c) : value(v), color(c) { }
int value;
int color;
};
class CellGraphicsItem : public QGraphicsPixmapItem {
public:
CellGraphicsItem(QPoint pos, int id, View2DScene* scene);
public:
void resize(int gridSize);
QPoint pos() const { return m_pos; }
void showCursor(QGraphicsItem* cursor);
void setType(SpecialType type);
void setValues(QVector<ColoredValue> values);
protected:
void hoverEnterEvent(QGraphicsSceneHoverEvent* event);
private:
void updatePixmap();
private:
View2DScene* m_scene;
QPoint m_pos;
SpecialType m_type;
QVector<ColoredValue> m_values;
int m_id;
int m_size;
};
CellGraphicsItem::CellGraphicsItem(QPoint pos, int id, View2DScene* scene) {
setAcceptsHoverEvents(true);
setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
m_pos = pos;
m_size = 0;
m_scene = scene;
m_id = id;
m_type = SpecialCell;
}
void CellGraphicsItem::resize(int gridSize) {
m_size = gridSize * 2;
setPos(m_pos.x()*m_size, m_pos.y()*m_size);
updatePixmap();
}
void CellGraphicsItem::showCursor(QGraphicsItem* cursor) {
cursor->setParentItem(this);
cursor->setZValue(1);
}
void CellGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent* event) {
Q_UNUSED(event);
m_scene->hover(m_id);
}
void CellGraphicsItem::setType(SpecialType type) {
if(type == m_type) return;
m_type = type;
updatePixmap();
}
void CellGraphicsItem::setValues(QVector<ColoredValue> values) {
m_values = values;
updatePixmap();
}
void CellGraphicsItem::updatePixmap() {
if(m_size == 0) return;
hide();
QPixmap pic = Renderer::instance()->renderSpecial(m_type, m_size);
switch(m_type) {
case SpecialCell:
case SpecialCellMistake:
if(m_values.size() > 0) {
pic = Renderer::instance()->renderSymbolOn(pic, m_values[0].value, m_values[0].color);
}
break;
case SpecialCellPreset:
if(m_values.size() > 0) {
pic = Renderer::instance()->renderSymbolOn(pic, m_values[0].value, 0);
}
break;
case SpecialCellMarkers:
// TODO add marker rendering
break;
default: break; // TODO maybe assert as this is not allowed to occur
}
setPixmap(pic);
show();
}
struct GroupGraphicItemSegment {
QPoint pos;
int shape;
QGraphicsPixmapItem* standard;
QGraphicsPixmapItem* highlighted;
};
class GroupGraphicsItem : public QGraphicsItemGroup {
public:
GroupGraphicsItem(QVector<QPoint> cells);
~GroupGraphicsItem();
public:
void resize(int gridSize);
void setHighlight(bool highlight);
void setHighlight(const QPoint& pos);
private:
int border(int tl, int tr, int bl, int br, int given);
void detectType();
void createContour();
void createSegment(const QPoint& pos, int shape);
private:
GroupTypes m_type;
QVector<QPoint> m_cells;
QVector<GroupGraphicItemSegment> m_segments;
};
GroupGraphicsItem::GroupGraphicsItem(QVector<QPoint> cells) {
m_cells = cells;
setEnabled(false);
setAcceptedMouseButtons(0);
detectType();
createContour();
if(!m_cells.contains(QPoint(1,1))) setHighlight(false);
}
GroupGraphicsItem::~GroupGraphicsItem() {
QVector<GroupGraphicItemSegment>::iterator segment;
for(segment = m_segments.begin(); segment != m_segments.end(); ++segment) {
if(segment->highlighted) delete segment->highlighted;
if(segment->standard) delete segment->standard;
}
}
void GroupGraphicsItem::detectType() {
int x = m_cells[0].x();
int y = m_cells[0].y();
for(int i = 1; i < m_cells.size(); ++i) {
if(x != m_cells[i].x()) x = -1;
if(y != m_cells[i].y()) y = -1;
}
m_type = GroupHighlight;
if(x==-1) m_type |= GroupRow;
if(y==-1) m_type |= GroupColumn;
if(m_type == GroupSpecial) setZValue(0);
if(m_type == GroupColumn) setZValue(-1);
else if(m_type == GroupRow) setZValue(-2);
else if(m_type == GroupBlock) setZValue(-3);
}
void GroupGraphicsItem::createContour() {
for(int i = 0; i < m_cells.size(); ++i) {
int x = m_cells[i].x();
int y = m_cells[i].y();
int idx[9];
idx[0] = m_cells.indexOf(QPoint(x-1, y-1));
idx[1] = m_cells.indexOf(QPoint(x, y-1));
idx[2] = m_cells.indexOf(QPoint(x+1, y-1));
idx[3] = m_cells.indexOf(QPoint(x-1, y ));
idx[4] = i;
idx[5] = m_cells.indexOf(QPoint(x+1, y ));
idx[6] = m_cells.indexOf(QPoint(x-1, y+1));
idx[7] = m_cells.indexOf(QPoint(x, y+1));
idx[8] = m_cells.indexOf(QPoint(x+1, y+1));
// TODO FIX: this is a detection outsied of detectType
if(idx[1] == -1 && idx[3] == -1 && idx[5] == -1 && idx[7] == -1)
m_type |= GroupSpecial;
int b;
if((b = border(idx[0],idx[1],idx[3],idx[4],3))) {
createSegment(QPoint(x,y), b);
}
if((b = border(idx[1],idx[2],idx[4],idx[5],2))) {
createSegment(QPoint(x+1,y), b);
}
if((b = border(idx[3],idx[4],idx[6],idx[7],1))) {
createSegment(QPoint(x,y+1), b);
}
if((b = border(idx[4],idx[5],idx[7],idx[8],0))) {
createSegment(QPoint(x+1,y+1), b);
}
}
}
void GroupGraphicsItem::createSegment(const QPoint& pos, int shape) {
GroupGraphicItemSegment segment;
segment.pos = pos*2 - QPoint(1,1);
segment.shape = shape;
switch(m_type & GroupUnhighlightedMask) {
// TODO make this behaviour dependant on the availability of normal pixmaps
case GroupRow:
case GroupColumn:
segment.standard = 0;
break;
case GroupBlock:
default: // Special Group
segment.standard = new QGraphicsPixmapItem(this);
break;
}
segment.highlighted = new QGraphicsPixmapItem(this);
segment.highlighted->setVisible(false);
m_segments << segment;
}
int GroupGraphicsItem::border(int tl, int tr, int bl, int br, int given) {
switch(given) {
case 0: if(tr > tl || bl > tl || br > tl) return 0; break;
case 1: if(tl > tr || bl > tr || br > tr) return 0; break;
case 2: if(tl > bl || tr > bl || br > bl) return 0; break;
case 3: if(tl > br || tr > br || bl > br) return 0; break;
}
int b = ((tl!=-1)?1:0) | ((tr!=-1)?2:0) | ((bl!=-1)?4:0) | ((br!=-1)?8:0);
return b;
}
void GroupGraphicsItem::setHighlight(bool highlight) {
if(m_type & GroupHighlight == highlight) return;
QVector<GroupGraphicItemSegment>::iterator segment;
for(segment = m_segments.begin(); segment != m_segments.end(); ++segment) {
if(segment->highlighted) segment->highlighted->setVisible(highlight);
if(segment->standard) segment->standard->setVisible(!highlight);
}
}
void GroupGraphicsItem::setHighlight(const QPoint& pos) {
setHighlight(m_cells.contains(pos));
}
void GroupGraphicsItem::resize(int gridSize) {
int size = gridSize*2;
Renderer* r = Renderer::instance();
GroupTypes standard = m_type & GroupUnhighlightedMask;
GroupTypes highlighted = m_type | GroupHighlight;
QVector<GroupGraphicItemSegment>::iterator segment;
for(segment = m_segments.begin(); segment != m_segments.end(); ++segment) {
QPointF pos = segment->pos*gridSize;
if(segment->standard) {
QPixmap pic = r->renderBorder(segment->shape, standard, size);
segment->standard->setPixmap(pic);
segment->standard->setOffset(pos);
}
if(segment->highlighted) {
QPixmap pic = r->renderBorder(segment->shape, highlighted, size);
segment->highlighted->setPixmap(pic);
segment->highlighted->setOffset(pos);
}
}
}
View2DScene::View2DScene() {
m_background = 0;
m_groupLayer = 0;
m_cellLayer = 0;
m_cursor = 0;
}
View2DScene::~View2DScene() {
delete m_cursor; // needs to be deleted before cells
// groups need to be deleted before m_groupLayer
QVector<GroupGraphicsItem*>::iterator group;
for(group = m_groups.begin(); group != m_groups.end(); ++group) {
delete *group;
}