1/*
2 Copyright (C) 2002-2005, Jason Katz-Brown <jasonkb@mit.edu>
3 Copyright 2010 Stefan Majewsky <majewsky@gmx.net>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18*/
19
20#include "obstacles.h"
21#include "ball.h"
22#include "game.h"
23#include "shape.h"
24
25#include <QCheckBox>
26#include <QGridLayout>
27#include <QLabel>
28#include <QSlider>
29#include <QTimer>
30#include <KConfigGroup>
31#include <KLineEdit>
32#include <KRandom>
33
34//BEGIN Kolf::Bumper
35
36Kolf::Bumper::Bumper(QGraphicsItem* parent, b2World* world)
37 : EllipticalCanvasItem(false, QLatin1String("bumper_off"), parent, world)
38{
39 const int diameter = 20;
40 setSize(QSizeF(diameter, diameter));
41 setZBehavior(CanvasItem::IsRaisedByStrut, 4);
42 setSimulationType(CanvasItem::NoSimulation);
43}
44
45bool Kolf::Bumper::collision(Ball* ball)
46{
47 const double maxSpeed = ball->getMaxBumperBounceSpeed();
48 const double speed = qMin(maxSpeed, 1.8 + Vector(ball->velocity()).magnitude() * .9);
49 ball->reduceMaxBumperBounceSpeed();
50
51 Vector betweenVector(ball->pos() - pos());
52 betweenVector.setMagnitudeDirection(speed,
53 // add some randomness so we don't go indefinetely
54 betweenVector.direction() + deg2rad((KRandom::random() % 3) - 1)
55 );
56
57 ball->setVelocity(betweenVector);
58 ball->setState(Rolling);
59
60 setSpriteKey(QLatin1String("bumper_on"));
61 QTimer::singleShot(100, this, SLOT(turnBumperOff()));
62 return true;
63}
64
65void Kolf::Bumper::turnBumperOff()
66{
67 setSpriteKey(QLatin1String("bumper_off"));
68}
69
70Kolf::Overlay* Kolf::Bumper::createOverlay()
71{
72 return new Kolf::Overlay(this, this);
73}
74
75//END Kolf::Bumper
76//BEGIN Kolf::Wall
77
78Kolf::Wall::Wall(QGraphicsItem* parent, b2World* world)
79 : QGraphicsLineItem(QLineF(-15, 10, 15, -5), parent)
80 , CanvasItem(world)
81{
82 setPen(QPen(Qt::darkRed, 3));
83 setData(0, Rtti_NoCollision);
84 //see also KolfGame::addBorderWall()
85 setZBehavior(CanvasItem::FixedZValue, 5);
86
87 m_shape = new Kolf::LineShape(line());
88 addShape(m_shape);
89}
90
91void Kolf::Wall::load(KConfigGroup* cfgGroup)
92{
93 const QPoint start = cfgGroup->readEntry("startPoint", QPoint(-15, 10));
94 const QPoint end = cfgGroup->readEntry("endPoint", QPoint(15, -5));
95 setLine(QLineF(start, end));
96}
97
98void Kolf::Wall::save(KConfigGroup* cfgGroup)
99{
100 const QLineF line = this->line();
101 cfgGroup->writeEntry("startPoint", line.p1().toPoint());
102 cfgGroup->writeEntry("endPoint", line.p2().toPoint());
103}
104
105void Kolf::Wall::setVisible(bool visible)
106{
107 QGraphicsLineItem::setVisible(visible);
108 setSimulationType(visible ? CanvasItem::CollisionSimulation : CanvasItem::NoSimulation);
109}
110
111void Kolf::Wall::setLine(const QLineF& line)
112{
113 QGraphicsLineItem::setLine(line);
114 m_shape->setLine(line);
115 propagateUpdate();
116}
117
118void Kolf::Wall::moveBy(double dx, double dy)
119{
120 QGraphicsLineItem::moveBy(dx, dy);
121 CanvasItem::moveBy(dx, dy);
122}
123
124QPointF Kolf::Wall::getPosition() const
125{
126 return QGraphicsItem::pos();
127}
128
129Kolf::Overlay* Kolf::Wall::createOverlay()
130{
131 return new Kolf::WallOverlay(this);
132}
133
134//END Kolf::Wall
135//BEGIN Kolf::WallOverlay
136
137Kolf::WallOverlay::WallOverlay(Kolf::Wall* wall)
138 : Kolf::Overlay(wall, wall)
139 , m_handle1(new Kolf::OverlayHandle(Kolf::OverlayHandle::SquareShape, this))
140 , m_handle2(new Kolf::OverlayHandle(Kolf::OverlayHandle::SquareShape, this))
141{
142 addHandle(m_handle1);
143 addHandle(m_handle2);
144 connect(m_handle1, SIGNAL(moveRequest(QPointF)), this, SLOT(moveHandle(QPointF)));
145 connect(m_handle2, SIGNAL(moveRequest(QPointF)), this, SLOT(moveHandle(QPointF)));
146}
147
148void Kolf::WallOverlay::update()
149{
150 Kolf::Overlay::update();
151 const QLineF line = dynamic_cast<Kolf::Wall*>(qitem())->line();
152 m_handle1->setPos(line.p1());
153 m_handle2->setPos(line.p2());
154}
155
156void Kolf::WallOverlay::moveHandle(const QPointF& handleScenePos)
157{
158 //TODO: code duplication to Kolf::FloaterOverlay
159 QPointF handlePos = mapFromScene(handleScenePos);
160 const QObject* handle = sender();
161 //get handle positions
162 QPointF handle1Pos = m_handle1->pos();
163 QPointF handle2Pos = m_handle2->pos();
164 if (handle == m_handle1)
165 handle1Pos = handlePos;
166 else if (handle == m_handle2)
167 handle2Pos = handlePos;
168 //ensure minimum length
169 static const qreal minLength = Kolf::Overlay::MinimumObjectDimension;
170 const QPointF posDiff = handle1Pos - handle2Pos;
171 const qreal length = QLineF(QPointF(), posDiff).length();
172 if (length < minLength)
173 {
174 const QPointF additionalExtent = posDiff * (minLength / length - 1);
175 if (handle == m_handle1)
176 handle1Pos += additionalExtent;
177 else if (handle == m_handle2)
178 handle2Pos -= additionalExtent;
179 }
180 //apply to item
181 dynamic_cast<Kolf::Wall*>(qitem())->setLine(QLineF(handle1Pos, handle2Pos));
182}
183
184//END Kolf::WallOverlay
185//BEGIN Kolf::RectangleItem
186
187Kolf::RectangleItem::RectangleItem(const QString& type, QGraphicsItem* parent, b2World* world)
188 : Tagaro::SpriteObjectItem(Kolf::renderer(), type, parent)
189 , CanvasItem(world)
190 , m_wallPen(QColor("#92772D").darker(), 3)
191 , m_wallAllowed(Kolf::RectangleWallCount, true)
192 , m_walls(Kolf::RectangleWallCount, 0)
193 , m_shape(new Kolf::RectShape(QRectF(0, 0, 1, 1)))
194{
195 addShape(m_shape);
196 setSimulationType(CanvasItem::NoSimulation);
197 //default size
198 setSize(type == "sign" ? QSize(110, 40) : QSize(80, 40));
199}
200
201Kolf::RectangleItem::~RectangleItem()
202{
203 qDeleteAll(m_walls);
204}
205
206bool Kolf::RectangleItem::hasWall(Kolf::WallIndex index) const
207{
208 return (bool) m_walls[index];
209}
210
211bool Kolf::RectangleItem::isWallAllowed(Kolf::WallIndex index) const
212{
213 return m_wallAllowed[index];
214}
215
216void Kolf::RectangleItem::setWall(Kolf::WallIndex index, bool hasWall)
217{
218 const bool oldHasWall = (bool) m_walls[index];
219 if (oldHasWall == hasWall)
220 return;
221 if (hasWall && !m_wallAllowed[index])
222 return;
223 if (hasWall)
224 {
225 Kolf::Wall* wall = m_walls[index] = new Kolf::Wall(parentItem(), world());
226 wall->setPos(pos());
227 applyWallStyle(wall);
228 updateWallPosition();
229 }
230 else
231 {
232 delete m_walls[index];
233 m_walls[index] = 0;
234 }
235 propagateUpdate();
236 emit wallChanged(index, hasWall, m_wallAllowed[index]);
237}
238
239void Kolf::RectangleItem::setWallAllowed(Kolf::WallIndex index, bool wallAllowed)
240{
241 m_wallAllowed[index] = wallAllowed;
242 //delete wall if one exists at this position currently
243 if (!wallAllowed)
244 setWall(index, false);
245 emit wallChanged(index, hasWall(index), wallAllowed);
246}
247
248void Kolf::RectangleItem::updateWallPosition()
249{
250 const QRectF rect(QPointF(), size());
251 Kolf::Wall* const topWall = m_walls[Kolf::TopWallIndex];
252 Kolf::Wall* const leftWall = m_walls[Kolf::LeftWallIndex];
253 Kolf::Wall* const rightWall = m_walls[Kolf::RightWallIndex];
254 Kolf::Wall* const bottomWall = m_walls[Kolf::BottomWallIndex];
255 if (topWall)
256 topWall->setLine(QLineF(rect.topLeft(), rect.topRight()));
257 if (leftWall)
258 leftWall->setLine(QLineF(rect.topLeft(), rect.bottomLeft()));
259 if (rightWall)
260 rightWall->setLine(QLineF(rect.topRight(), rect.bottomRight()));
261 if (bottomWall)
262 bottomWall->setLine(QLineF(rect.bottomLeft(), rect.bottomRight()));
263}
264
265void Kolf::RectangleItem::setSize(const QSizeF& size)
266{
267 Tagaro::SpriteObjectItem::setSize(size);
268 m_shape->setRect(QRectF(QPointF(), size));
269 updateWallPosition();
270 propagateUpdate();
271}
272
273QPointF Kolf::RectangleItem::getPosition() const
274{
275 return QGraphicsItem::pos();
276}
277
278void Kolf::RectangleItem::moveBy(double dx, double dy)
279{
280 Tagaro::SpriteObjectItem::moveBy(dx, dy);
281 //move myself
282 const QPointF pos = this->pos();
283 foreach (Kolf::Wall* wall, m_walls)
284 if (wall)
285 wall->setPos(pos);
286 //update Z order
287 CanvasItem::moveBy(dx, dy);
288 foreach (QGraphicsItem* qitem, collidingItems())
289 {
290 CanvasItem* citem = dynamic_cast<CanvasItem*>(qitem);
291 if (citem)
292 citem->updateZ(qitem);
293 }
294}
295
296void Kolf::RectangleItem::setWallColor(const QColor& color)
297{
298 m_wallPen = QPen(color.darker(), 3);
299 foreach (Kolf::Wall* wall, m_walls)
300 applyWallStyle(wall);
301}
302
303void Kolf::RectangleItem::applyWallStyle(Kolf::Wall* wall, bool adjustPainting)
304{
305 if (!wall) //explicitly allowed, see e.g. setWallColor()
306 return;
307 if (adjustPainting)
308 wall->setPen(m_wallPen);
309 wall->setZBehavior(CanvasItem::IsRaisedByStrut, 3);
310 wall->setStaticStrut(this);
311}
312
313static const char* wallPropNames[] = { "topWallVisible", "leftWallVisible", "rightWallVisible", "botWallVisible" };
314
315void Kolf::RectangleItem::load(KConfigGroup* group)
316{
317 QSize size = Tagaro::SpriteObjectItem::size().toSize();
318 size.setWidth(group->readEntry("width", size.width()));
319 size.setHeight(group->readEntry("height", size.height()));
320 setSize(size);
321 for (int i = 0; i < Kolf::RectangleWallCount; ++i)
322 {
323 bool hasWall = this->hasWall((Kolf::WallIndex) i);
324 hasWall = group->readEntry(wallPropNames[i], hasWall);
325 setWall((Kolf::WallIndex) i, hasWall);
326 }
327}
328
329void Kolf::RectangleItem::save(KConfigGroup* group)
330{
331 const QSize size = Tagaro::SpriteObjectItem::size().toSize();
332 group->writeEntry("width", size.width());
333 group->writeEntry("height", size.height());
334 for (int i = 0; i < Kolf::RectangleWallCount; ++i)
335 {
336 const bool hasWall = this->hasWall((Kolf::WallIndex) i);
337 group->writeEntry(wallPropNames[i], hasWall);
338 }
339}
340
341Config* Kolf::RectangleItem::config(QWidget* parent)
342{
343 return new Kolf::RectangleConfig(this, parent);
344}
345
346Kolf::Overlay* Kolf::RectangleItem::createOverlay()
347{
348 return new Kolf::RectangleOverlay(this);
349}
350
351//END Kolf::RectangleItem
352//BEGIN Kolf::RectangleOverlay
353
354Kolf::RectangleOverlay::RectangleOverlay(Kolf::RectangleItem* item)
355 : Kolf::Overlay(item, item)
356{
357 //TODO: code duplication to Kolf::LandscapeOverlay and Kolf::SlopeOverlay
358 for (int i = 0; i < 4; ++i)
359 {
360 Kolf::OverlayHandle* handle = new Kolf::OverlayHandle(Kolf::OverlayHandle::CircleShape, this);
361 m_handles << handle;
362 addHandle(handle);
363 connect(handle, SIGNAL(moveRequest(QPointF)), this, SLOT(moveHandle(QPointF)));
364 }
365}
366
367void Kolf::RectangleOverlay::update()
368{
369 Kolf::Overlay::update();
370 const QRectF rect = qitem()->boundingRect();
371 m_handles[0]->setPos(rect.topLeft());
372 m_handles[1]->setPos(rect.topRight());
373 m_handles[2]->setPos(rect.bottomLeft());
374 m_handles[3]->setPos(rect.bottomRight());
375}
376
377void Kolf::RectangleOverlay::moveHandle(const QPointF& handleScenePos)
378{
379 Kolf::OverlayHandle* handle = qobject_cast<Kolf::OverlayHandle*>(sender());
380 const int handleIndex = m_handles.indexOf(handle);
381 Kolf::RectangleItem* item = dynamic_cast<Kolf::RectangleItem*>(qitem());
382 const QPointF handlePos = mapFromScene(handleScenePos);
383 //modify bounding rect using new handlePos
384 QRectF rect(QPointF(), item->size());
385 if (handleIndex % 2 == 0)
386 rect.setLeft(qMin(handlePos.x(), rect.right()));
387 else
388 rect.setRight(qMax(handlePos.x(), rect.left()));
389 if (handleIndex < 2)
390 rect.setTop(qMin(handlePos.y(), rect.bottom()));
391 else
392 rect.setBottom(qMax(handlePos.y(), rect.top()));
393 item->moveBy(rect.x(), rect.y());
394 item->setSize(rect.size());
395}
396
397//END Kolf::RectangleOverlay
398//BEGIN Kolf::RectangleConfig
399
400Kolf::RectangleConfig::RectangleConfig(Kolf::RectangleItem* item, QWidget* parent)
401 : Config(parent)
402 , m_layout(new QGridLayout(this))
403 , m_wallCheckBoxes(Kolf::RectangleWallCount, 0)
404 , m_item(item)
405{
406 static const char* captions[] = { I18N_NOOP("&Top"), I18N_NOOP("&Left"), I18N_NOOP("&Right"), I18N_NOOP("&Bottom") };
407 for (int i = 0; i < Kolf::RectangleWallCount; ++i)
408 {
409 QCheckBox* checkBox = m_wallCheckBoxes[i] = new QCheckBox(i18n(captions[i]), this);
410 checkBox->setEnabled(item->isWallAllowed((Kolf::WallIndex) i));
411 checkBox->setChecked(item->hasWall((Kolf::WallIndex) i));
412 connect(checkBox, SIGNAL(toggled(bool)), SLOT(setWall(bool)));
413 }
414 connect(item, SIGNAL(wallChanged(Kolf::WallIndex,bool,bool)), SLOT(wallChanged(Kolf::WallIndex,bool,bool)));
415 m_layout->addWidget(new QLabel(i18n("Walls on:")), 0, 0);
416 m_layout->addWidget(m_wallCheckBoxes[0], 0, 1);
417 m_layout->addWidget(m_wallCheckBoxes[1], 1, 0);
418 m_layout->addWidget(m_wallCheckBoxes[2], 1, 2);
419 m_layout->addWidget(m_wallCheckBoxes[3], 1, 1);
420 m_layout->setRowStretch(2, 10);
421 //Kolf::Sign does not have a special Config class
422 Kolf::Sign* sign = qobject_cast<Kolf::Sign*>(item);
423 if (sign)
424 {
425 m_layout->addWidget(new QLabel(i18n("Sign HTML:")), 3, 0, 1, 3);
426 KLineEdit* edit = new KLineEdit(sign->text(), this);
427 m_layout->addWidget(edit, 4, 0, 1, 3);
428 connect(edit, SIGNAL(textChanged(QString)), sign, SLOT(setText(QString)));
429 }
430 //Kolf::Windmill does not have a special Config class
431 Kolf::Windmill* windmill = qobject_cast<Kolf::Windmill*>(item);
432 if (windmill)
433 {
434 QCheckBox* checkBox = new QCheckBox(i18n("Windmill on top"), this);
435 m_layout->addWidget(checkBox, 4, 0, 1, 3);
436 checkBox->setChecked(windmill->guardAtTop());
437 connect(checkBox, SIGNAL(toggled(bool)), windmill, SLOT(setGuardAtTop(bool)));
438 QHBoxLayout* hlayout = new QHBoxLayout;
439 m_layout->addLayout(hlayout, 5, 0, 1, 3);
440 QLabel* label1 = new QLabel(i18n("Slow"), this);
441 hlayout->addWidget(label1);
442 QSlider* slider = new QSlider(Qt::Horizontal, this);
443 hlayout->addWidget(slider);
444 QLabel* label2 = new QLabel(i18n("Fast"), this);
445 hlayout->addWidget(label2);
446 slider->setRange(1, 10);
447 slider->setPageStep(1);
448 slider->setValue(windmill->speed());
449 connect(slider, SIGNAL(valueChanged(int)), windmill, SLOT(setSpeed(int)));
450 }
451 //Kolf::Floater does not have a special Config class
452 Kolf::Floater* floater = qobject_cast<Kolf::Floater*>(item);
453 if (floater)
454 {
455 m_layout->addWidget(new QLabel(i18n("Moving speed"), this), 4, 0, 1, 3);
456 QHBoxLayout* hlayout = new QHBoxLayout;
457 m_layout->addLayout(hlayout, 5, 0, 1, 3);
458 QLabel* label1 = new QLabel(i18n("Slow"), this);
459 hlayout->addWidget(label1);
460 QSlider* slider = new QSlider(Qt::Horizontal, this);
461 hlayout->addWidget(slider);
462 QLabel* label2 = new QLabel(i18n("Fast"), this);
463 hlayout->addWidget(label2);
464 slider->setRange(0, 20);
465 slider->setPageStep(2);
466 slider->setValue(floater->speed());
467 connect(slider, SIGNAL(valueChanged(int)), floater, SLOT(setSpeed(int)));
468 }
469}
470
471void Kolf::RectangleConfig::setWall(bool hasWall)
472{
473 const int wallIndex = m_wallCheckBoxes.indexOf(qobject_cast<QCheckBox*>(sender()));
474 if (wallIndex >= 0)
475 {
476 m_item->setWall((Kolf::WallIndex) wallIndex, hasWall);
477 changed();
478 }
479}
480
481void Kolf::RectangleConfig::wallChanged(Kolf::WallIndex index, bool hasWall, bool wallAllowed)
482{
483 m_wallCheckBoxes[index]->setEnabled(wallAllowed);
484 m_wallCheckBoxes[index]->setChecked(hasWall);
485}
486
487//END Kolf::RectangleConfig
488//BEGIN Kolf::Bridge
489
490Kolf::Bridge::Bridge(QGraphicsItem* parent, b2World* world)
491 : Kolf::RectangleItem(QLatin1String("bridge"), parent, world)
492{
493 setZBehavior(CanvasItem::IsStrut, 0);
494}
495
496bool Kolf::Bridge::collision(Ball* ball)
497{
498 ball->setFrictionMultiplier(.63);
499 return false;
500}
501
502//END Kolf::Bridge
503//BEGIN Kolf::Floater
504
505Kolf::Floater::Floater(QGraphicsItem* parent, b2World* world)
506 : Kolf::RectangleItem(QLatin1String("floater"), parent, world)
507 , m_motionLine(QLineF(200, 200, 100, 100))
508 , m_speed(0)
509 , m_velocity(0)
510 , m_position(0)
511 , m_moveByMovesMotionLine(true)
512 , m_animated(true)
513{
514 setMlPosition(m_position);
515 setZBehavior(CanvasItem::IsStrut, 0);
516}
517
518void Kolf::Floater::editModeChanged(bool editing)
519{
520 Kolf::RectangleItem::editModeChanged(editing);
521 m_animated = !editing;
522 if (editing)
523 setMlPosition(0);
524}
525
526void Kolf::Floater::moveBy(double dx, double dy)
527{
528 moveItemsOnStrut(QPointF(dx, dy));
529 Kolf::RectangleItem::moveBy(dx, dy);
530 if (m_moveByMovesMotionLine)
531 m_motionLine.translate(dx, dy);
532 propagateUpdate();
533}
534
535QLineF Kolf::Floater::motionLine() const
536{
537 return m_motionLine;
538}
539
540void Kolf::Floater::setMotionLine(const QLineF& motionLine)
541{
542 m_motionLine = motionLine;
543 setMlPosition(m_position);
544 propagateUpdate();
545}
546
547void Kolf::Floater::setMlPosition(qreal position)
548{
549 m_moveByMovesMotionLine = false;
550 setPosition(m_motionLine.pointAt(position));
551 m_position = position;
552 m_moveByMovesMotionLine = true;
553}
554
555int Kolf::Floater::speed() const
556{
557 return m_speed;
558}
559
560void Kolf::Floater::setSpeed(int speed)
561{
562 m_speed = speed;
563 const qreal velocity = speed / 3.5;
564 m_velocity = (m_velocity < 0) ? -velocity : velocity;
565 propagateUpdate();
566}
567
568void Kolf::Floater::advance(int phase)
569{
570 if (phase != 1 || !m_animated)
571 return;
572 //determine movement step
573 const qreal mlLength = m_motionLine.length();
574 const qreal parameterDiff = m_velocity / mlLength;
575 //determine new position (mirror on end point if end point passed)
576 m_position += parameterDiff;
577 if (m_position < 0)
578 {
579 m_velocity = qAbs(m_velocity);
580 m_position = -m_position;
581 }
582 else if (m_position > 1)
583 {
584 m_velocity = -qAbs(m_velocity);
585 m_position = 2 - m_position;
586 }
587 //apply position
588 setMlPosition(m_position);
589}
590
591void Kolf::Floater::load(KConfigGroup* group)
592{
593 Kolf::RectangleItem::load(group);
594 QLineF motionLine = m_motionLine;
595 motionLine.setP1(group->readEntry("startPoint", m_motionLine.p1()));
596 motionLine.setP2(group->readEntry("endPoint", m_motionLine.p2()));
597 setMotionLine(motionLine);
598 setSpeed(group->readEntry("speed", m_speed));
599}
600
601void Kolf::Floater::save(KConfigGroup* group)
602{
603 Kolf::RectangleItem::save(group);
604 group->writeEntry("startPoint", m_motionLine.p1());
605 group->writeEntry("endPoint", m_motionLine.p2());
606 group->writeEntry("speed", m_speed);
607}
608
609Kolf::Overlay* Kolf::Floater::createOverlay()
610{
611 return new Kolf::FloaterOverlay(this);
612}
613
614//END Kolf::Floater
615//BEGIN Kolf::FloaterOverlay
616
617Kolf::FloaterOverlay::FloaterOverlay(Kolf::Floater* floater)
618 : Kolf::RectangleOverlay(floater)
619 , m_handle1(new Kolf::OverlayHandle(Kolf::OverlayHandle::SquareShape, this))
620 , m_handle2(new Kolf::OverlayHandle(Kolf::OverlayHandle::SquareShape, this))
621 , m_motionLineItem(new QGraphicsLineItem(this))
622{
623 addHandle(m_handle1);
624 addHandle(m_handle2);
625 connect(m_handle1, SIGNAL(moveRequest(QPointF)), this, SLOT(moveMotionLineHandle(QPointF)));
626 connect(m_handle2, SIGNAL(moveRequest(QPointF)), this, SLOT(moveMotionLineHandle(QPointF)));
627 addHandle(m_motionLineItem);
628 QPen pen = m_motionLineItem->pen();
629 pen.setStyle(Qt::DashLine);
630 m_motionLineItem->setPen(pen);
631}
632
633void Kolf::FloaterOverlay::update()
634{
635 Kolf::RectangleOverlay::update();
636 const QLineF line = dynamic_cast<Kolf::Floater*>(qitem())->motionLine().translated(-qitem()->pos());
637 m_handle1->setPos(line.p1());
638 m_handle2->setPos(line.p2());
639 m_motionLineItem->setLine(line);
640}
641
642void Kolf::FloaterOverlay::moveMotionLineHandle(const QPointF& handleScenePos)
643{
644 //TODO: code duplication to Kolf::WallOverlay
645 QPointF handlePos = mapFromScene(handleScenePos) + qitem()->pos();
646 const QObject* handle = sender();
647 //get handle positions
648 QPointF handle1Pos = m_handle1->pos() + qitem()->pos();
649 QPointF handle2Pos = m_handle2->pos() + qitem()->pos();
650 if (handle == m_handle1)
651 handle1Pos = handlePos;
652 else if (handle == m_handle2)
653 handle2Pos = handlePos;
654 //ensure minimum length
655 static const qreal minLength = Kolf::Overlay::MinimumObjectDimension;
656 const QPointF posDiff = handle1Pos - handle2Pos;
657 const qreal length = QLineF(QPointF(), posDiff).length();
658 if (length < minLength)
659 {
660 const QPointF additionalExtent = posDiff * (minLength / length - 1);
661 if (handle == m_handle1)
662 handle1Pos += additionalExtent;
663 else if (handle == m_handle2)
664 handle2Pos -= additionalExtent;
665 }
666 //apply to item
667 dynamic_cast<Kolf::Floater*>(qitem())->setMotionLine(QLineF(handle1Pos, handle2Pos));
668}
669
670//END Kolf::FloaterOverlay
671//BEGIN Kolf::Sign
672
673Kolf::Sign::Sign(QGraphicsItem* parent, b2World* world)
674 : Kolf::RectangleItem(QLatin1String("sign"), parent, world)
675 , m_text(i18n("New Text"))
676 , m_textItem(new QGraphicsTextItem(m_text, this))
677{
678 setZBehavior(CanvasItem::FixedZValue, 3);
679 setWallColor(Qt::black);
680 for (int i = 0; i < Kolf::RectangleWallCount; ++i)
681 setWall((Kolf::WallIndex) i, true);
682 //Z value 1 should be enough to keep text above overlay
683 m_textItem->setZValue(1);
684 m_textItem->setAcceptedMouseButtons(0);
685 //TODO: activate QGraphicsItem::ItemClipsChildrenToShape flag after
686 //refactoring (only after it is clear that the text is the only child)
687}
688
689QString Kolf::Sign::text() const
690{
691 return m_text;
692}
693
694void Kolf::Sign::setText(const QString& text)
695{
696 m_text = text;
697 m_textItem->setHtml(text);
698}
699
700void Kolf::Sign::setSize(const QSizeF& size)
701{
702 Kolf::RectangleItem::setSize(size);
703 m_textItem->setTextWidth(size.width());
704}
705
706void Kolf::Sign::load(KConfigGroup* group)
707{
708 Kolf::RectangleItem::load(group);
709 setText(group->readEntry("Comment", m_text));
710}
711
712void Kolf::Sign::save(KConfigGroup* group)
713{
714 Kolf::RectangleItem::save(group);
715 group->writeEntry("Comment", m_text);
716}
717
718//END Kolf::Sign
719//BEGIN Kolf::Windmill
720
721Kolf::Windmill::Windmill(QGraphicsItem* parent, b2World* world)
722 : Kolf::RectangleItem(QLatin1String("windmill"), parent, world)
723 , m_leftWall(new Kolf::Wall(parent, world))
724 , m_rightWall(new Kolf::Wall(parent, world))
725 , m_guardWall(new Kolf::Wall(parent, world))
726 , m_guardAtTop(false)
727 , m_speed(0), m_velocity(0)
728{
729 setZBehavior(CanvasItem::IsStrut, 0);
730 setSpeed(5); //initialize m_speed and m_velocity properly
731 applyWallStyle(m_leftWall);
732 applyWallStyle(m_rightWall);
733 applyWallStyle(m_guardWall, false); //Z-ordering!
734 m_guardWall->setPen(QPen(Qt::black, 5));
735 setWall(Kolf::TopWallIndex, false);
736 setWall(Kolf::LeftWallIndex, true);
737 setWall(Kolf::RightWallIndex, true);
738 setWallAllowed(Kolf::BottomWallIndex, false);
739 m_guardWall->setLine(QLineF());
740 updateWallPosition();
741}
742
743Kolf::Windmill::~Windmill()
744{
745 delete m_leftWall;
746 delete m_rightWall;
747 delete m_guardWall;
748}
749
750bool Kolf::Windmill::guardAtTop() const
751{
752 return m_guardAtTop;
753}
754
755void Kolf::Windmill::setGuardAtTop(bool guardAtTop)
756{
757 if (m_guardAtTop == guardAtTop)
758 return;
759 m_guardAtTop = guardAtTop;
760 //exchange top and bottom walls
761 if (guardAtTop)
762 {
763 const bool hasWall = this->hasWall(Kolf::TopWallIndex);
764 setWallAllowed(Kolf::BottomWallIndex, true);
765 setWallAllowed(Kolf::TopWallIndex, false);
766 setWall(Kolf::BottomWallIndex, hasWall);
767 }
768 else
769 {
770 const bool hasWall = this->hasWall(Kolf::BottomWallIndex);
771 setWallAllowed(Kolf::BottomWallIndex, false);
772 setWallAllowed(Kolf::TopWallIndex, true);
773 setWall(Kolf::TopWallIndex, hasWall);
774 }
775 //recalculate position of guard walls etc.
776 updateWallPosition();
777 propagateUpdate();
778}
779
780int Kolf::Windmill::speed() const
781{
782 return m_speed;
783}
784
785void Kolf::Windmill::setSpeed(int speed)
786{
787 m_speed = speed;
788 const qreal velocity = speed / 3.0;
789 m_velocity = (m_velocity < 0) ? -velocity : velocity;
790 propagateUpdate();
791}
792
793void Kolf::Windmill::advance(int phase)
794{
795 if (phase == 1)
796 {
797 QLineF guardLine = m_guardWall->line().translated(m_velocity, 0);
798 const qreal maxX = qMax(guardLine.x1(), guardLine.x2());
799 const qreal minX = qMin(guardLine.x1(), guardLine.x2());
800 QRectF rect(QPointF(), size());
801 if (minX < rect.left())
802 {
803 guardLine.translate(rect.left() - minX, 0);
804 m_velocity = qAbs(m_velocity);
805 }
806 else if (maxX > rect.right())
807 {
808 guardLine.translate(rect.right() - maxX, 0);
809 m_velocity = -qAbs(m_velocity);
810 }
811 m_guardWall->setLine(guardLine);
812 }
813}
814
815void Kolf::Windmill::moveBy(double dx, double dy)
816{
817 Kolf::RectangleItem::moveBy(dx, dy);
818 const QPointF pos = this->pos();
819 m_leftWall->setPos(pos);
820 m_rightWall->setPos(pos);
821 m_guardWall->setPos(pos);
822}
823
824void Kolf::Windmill::updateWallPosition()
825{
826 Kolf::RectangleItem::updateWallPosition();
827 //parametrize position of guard relative to old rect
828 qreal t = 0.5;
829 if (!m_guardWall->line().isNull())
830 {
831 //this branch is taken unless this method gets called from the ctor
832 const qreal oldLeft = m_leftWall->line().x1();
833 const qreal oldRight = m_rightWall->line().x1();
834 const qreal oldGCenter = m_guardWall->line().pointAt(0.5).x();
835 t = (oldGCenter - oldLeft) / (oldRight - oldLeft);
836 }
837 //set new positions
838 const QRectF rect(QPointF(), size());
839 const QPointF leftEnd = m_guardAtTop ? rect.topLeft() : rect.bottomLeft();
840 const QPointF rightEnd = m_guardAtTop ? rect.topRight() : rect.bottomRight();
841 const QPointF wallExtent(rect.width() / 4, 0);
842 m_leftWall->setLine(QLineF(leftEnd, leftEnd + wallExtent));
843 m_rightWall->setLine(QLineF(rightEnd, rightEnd - wallExtent));
844 //set position of guard to the same relative coordinate as before
845 const qreal gWidth = wallExtent.x() / 1.07 - 2;
846 const qreal gY = m_guardAtTop ? rect.top() - 4 : rect.bottom() + 4;
847 QLineF gLine(rect.left(), gY, rect.left() + gWidth, gY);
848 const qreal currentGCenter = gLine.pointAt(0.5).x();
849 const qreal desiredGCenter = rect.left() + t * rect.width();
850 gLine.translate(desiredGCenter - currentGCenter, 0);
851 m_guardWall->setLine(gLine);
852}
853
854void Kolf::Windmill::load(KConfigGroup* group)
855{
856 Kolf::RectangleItem::load(group);
857 setSpeed(group->readEntry("speed", m_speed));
858 setGuardAtTop(!group->readEntry("bottom", !m_guardAtTop));
859}
860
861void Kolf::Windmill::save(KConfigGroup* group)
862{
863 Kolf::RectangleItem::save(group);
864 group->writeEntry("speed", m_speed);
865 group->writeEntry("bottom", !m_guardAtTop);
866}
867
868//END Kolf::Windmill
869
870#include "obstacles.moc"
871