1/***************************************************************************
2 * Copyright 2010 Stefan Majewsky <majewsky@gmx.net> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU Library General Public License *
6 * version 2 as published by the Free Software Foundation *
7 * *
8 * This program is distributed in the hope that it will be useful, *
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
11 * GNU Library General Public License for more details. *
12 * *
13 * You should have received a copy of the GNU Library General Public *
14 * License along with this program; if not, write to the *
15 * Free Software Foundation, Inc., *
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
17 ***************************************************************************/
18
19#include "kgamerenderedobjectitem.h"
20#include "kgamerenderer.h"
21
22#include <QtCore/qmath.h>
23#include <QtGui/QGraphicsView>
24
25class KGameRenderedObjectItemPrivate : public QGraphicsPixmapItem
26{
27 public:
28 KGameRenderedObjectItemPrivate(KGameRenderedObjectItem* parent);
29 bool adjustRenderSize(); //returns whether an adjustment was made; WARNING: only call when m_primaryView != 0
30 void adjustTransform();
31
32 //QGraphicsItem reimplementations (see comment below for why we need all of this)
33 virtual bool contains(const QPointF& point) const;
34 virtual bool isObscuredBy(const QGraphicsItem* item) const;
35 virtual QPainterPath opaqueArea() const;
36 virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0);
37 virtual QPainterPath shape() const;
38 public:
39 KGameRenderedObjectItem* m_parent;
40 QGraphicsView* m_primaryView;
41 QSize m_correctRenderSize;
42 QSizeF m_fixedSize;
43};
44
45KGameRenderedObjectItemPrivate::KGameRenderedObjectItemPrivate(KGameRenderedObjectItem* parent)
46 : QGraphicsPixmapItem(parent)
47 , m_parent(parent)
48 , m_primaryView(0)
49 , m_correctRenderSize(0, 0)
50 , m_fixedSize(-1, -1)
51{
52}
53
54static inline int vectorLength(const QPointF& point)
55{
56 return qSqrt(point.x() * point.x() + point.y() * point.y());
57}
58
59bool KGameRenderedObjectItemPrivate::adjustRenderSize()
60{
61 Q_ASSERT(m_primaryView);
62 //create a polygon from the item's boundingRect
63 const QRectF itemRect = m_parent->boundingRect();
64 QPolygonF itemPolygon(3);
65 itemPolygon[0] = itemRect.topLeft();
66 itemPolygon[1] = itemRect.topRight();
67 itemPolygon[2] = itemRect.bottomLeft();
68 //determine correct render size
69 const QPolygonF scenePolygon = m_parent->sceneTransform().map(itemPolygon);
70 const QPolygon viewPolygon = m_primaryView->mapFromScene(scenePolygon);
71 m_correctRenderSize.setWidth(qMax(vectorLength(viewPolygon[1] - viewPolygon[0]), 1));
72 m_correctRenderSize.setHeight(qMax(vectorLength(viewPolygon[2] - viewPolygon[0]), 1));
73 //ignore fluctuations in the render size which result from rounding errors
74 const QSize diff = m_parent->renderSize() - m_correctRenderSize;
75 if (qAbs(diff.width()) <= 1 && qAbs(diff.height()) <= 1)
76 {
77 return false;
78 }
79 m_parent->setRenderSize(m_correctRenderSize);
80 adjustTransform();
81 return true;
82}
83
84void KGameRenderedObjectItemPrivate::adjustTransform()
85{
86 //calculate new transform for this item
87 QTransform t;
88 t.scale(m_fixedSize.width() / m_correctRenderSize.width(), m_fixedSize.height() / m_correctRenderSize.height());
89 //render item
90 m_parent->prepareGeometryChange();
91 setTransform(t);
92 m_parent->update();
93}
94
95KGameRenderedObjectItem::KGameRenderedObjectItem(KGameRenderer* renderer, const QString& spriteKey, QGraphicsItem* parent)
96 : QGraphicsObject(parent)
97 , KGameRendererClient(renderer, spriteKey)
98 , d(new KGameRenderedObjectItemPrivate(this))
99{
100 setPrimaryView(renderer->defaultPrimaryView());
101}
102
103KGameRenderedObjectItem::~KGameRenderedObjectItem()
104{
105 delete d;
106}
107
108QPointF KGameRenderedObjectItem::offset() const
109{
110 return d->pos();
111}
112
113void KGameRenderedObjectItem::setOffset(const QPointF& offset)
114{
115 if (d->pos() != offset)
116 {
117 prepareGeometryChange();
118 d->setPos(offset);
119 update();
120 }
121}
122
123void KGameRenderedObjectItem::setOffset(qreal x, qreal y)
124{
125 setOffset(QPointF(x, y));
126}
127
128QSizeF KGameRenderedObjectItem::fixedSize() const
129{
130 return d->m_fixedSize;
131}
132
133void KGameRenderedObjectItem::setFixedSize(const QSizeF& fixedSize)
134{
135 if (d->m_primaryView)
136 {
137 d->m_fixedSize = fixedSize.expandedTo(QSize(1, 1));
138 d->adjustTransform();
139 }
140}
141
142QGraphicsView* KGameRenderedObjectItem::primaryView() const
143{
144 return d->m_primaryView;
145}
146
147void KGameRenderedObjectItem::setPrimaryView(QGraphicsView* view)
148{
149 if (d->m_primaryView != view)
150 {
151 d->m_primaryView = view;
152 if (view)
153 {
154 if (!d->m_fixedSize.isValid())
155 {
156 d->m_fixedSize = QSize(1, 1);
157 }
158 //determine render size and adjust coordinate system
159 d->m_correctRenderSize = QSize(-10, -10); //force adjustment to be made
160 d->adjustRenderSize();
161 }
162 else
163 {
164 d->m_fixedSize = QSize(-1, -1);
165 //reset transform to make coordinate systems of this item and the private item equal
166 prepareGeometryChange();
167 d->setTransform(QTransform());
168 update();
169 }
170 }
171}
172
173void KGameRenderedObjectItem::receivePixmap(const QPixmap& pixmap)
174{
175 prepareGeometryChange();
176 d->setPixmap(pixmap);
177 update();
178}
179
180//We want to make sure that all interactional events are sent ot this item, and
181//not to the contained QGraphicsPixmapItem which provides the visual
182//representation (and the metrics calculations).
183//At the same time, we do not want the contained QGraphicsPixmapItem to slow
184//down operations like QGraphicsScene::collidingItems().
185//So the strategy is to use the QGraphicsPixmapItem implementation from
186//KGameRenderedObjectItemPrivate for KGameRenderedObjectItem.
187//Then the relevant methods in KGameRenderedObjectItemPrivate are reimplemented empty
188//to effectively clear the item and hide it from any collision detection. This
189//strategy allows us to use the nifty QGraphicsPixmapItem logic without exposing
190//a QGraphicsPixmapItem subclass (which would conflict with QGraphicsObject).
191
192//BEGIN QGraphicsItem reimplementation of KGameRenderedObjectItem
193
194QRectF KGameRenderedObjectItem::boundingRect() const
195{
196 return d->mapRectToParent(d->QGraphicsPixmapItem::boundingRect());
197}
198
199bool KGameRenderedObjectItem::contains(const QPointF& point) const
200{
201 return d->QGraphicsPixmapItem::contains(d->mapFromParent(point));
202}
203
204bool KGameRenderedObjectItem::isObscuredBy(const QGraphicsItem* item) const
205{
206 return d->QGraphicsPixmapItem::isObscuredBy(item);
207}
208
209QPainterPath KGameRenderedObjectItem::opaqueArea() const
210{
211 return d->mapToParent(d->QGraphicsPixmapItem::opaqueArea());
212}
213
214void KGameRenderedObjectItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
215{
216 Q_UNUSED(painter) Q_UNUSED(option) Q_UNUSED(widget)
217}
218
219QPainterPath KGameRenderedObjectItem::shape() const
220{
221 return d->mapToParent(d->QGraphicsPixmapItem::shape());
222}
223
224//END QGraphicsItem reimplementation of KGameRenderedObjectItem
225//BEGIN QGraphicsItem reimplementation of KGameRenderedObjectItemPrivate
226
227bool KGameRenderedObjectItemPrivate::contains(const QPointF& point) const
228{
229 Q_UNUSED(point)
230 return false;
231}
232
233bool KGameRenderedObjectItemPrivate::isObscuredBy(const QGraphicsItem* item) const
234{
235 Q_UNUSED(item)
236 return false;
237}
238
239QPainterPath KGameRenderedObjectItemPrivate::opaqueArea() const
240{
241 return QPainterPath();
242}
243
244void KGameRenderedObjectItemPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
245{
246 //Trivial stuff up to now. The fun stuff starts here. ;-)
247 //There is no way to get informed when the viewport's coordinate system
248 //(relative to this item's coordinate system) has changed, so we're checking
249 //the renderSize in each paintEvent coming from the primary view.
250 if (m_primaryView)
251 {
252 if (m_primaryView == widget || m_primaryView->isAncestorOf(widget))
253 {
254 const bool isSimpleTransformation = !painter->transform().isRotating();
255 //If an adjustment was made, do not paint now, but wait for the next
256 //painting. However, paint directly if the transformation is
257 //complex, in order to avoid flicker.
258 if (adjustRenderSize())
259 {
260 if (isSimpleTransformation)
261 {
262 return;
263 }
264 }
265 if (isSimpleTransformation)
266 {
267 //draw pixmap directly in physical coordinates
268 const QPoint basePos = painter->transform().map(QPointF()).toPoint();
269 painter->save();
270 painter->setTransform(QTransform());
271 painter->drawPixmap(basePos, pixmap());
272 painter->restore();
273 return;
274 }
275 }
276 }
277 QGraphicsPixmapItem::paint(painter, option, widget);
278}
279
280QPainterPath KGameRenderedObjectItemPrivate::shape() const
281{
282 return QPainterPath();
283}
284
285//END QGraphicsItem reimplementation of KGameRenderedObjectItemPrivate
286
287#include "kgamerenderedobjectitem.moc"
288