File indexing completed on 2024-05-12 04:04:34

0001 /*
0002     Copyright (C) 2002-2005, Jason Katz-Brown <jasonkb@mit.edu>
0003     Copyright 2010 Stefan Majewsky <majewsky@gmx.net>
0004 
0005     This program is free software; you can redistribute it and/or modify
0006     it under the terms of the GNU General Public License as published by
0007     the Free Software Foundation; either version 2 of the License, or
0008     (at your option) any later version.
0009 
0010     This program is distributed in the hope that it will be useful,
0011     but WITHOUT ANY WARRANTY; without even the implied warranty of
0012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013     GNU General Public License for more details.
0014 
0015     You should have received a copy of the GNU General Public License
0016     along with this program; if not, write to the Free Software
0017     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0018 */
0019 
0020 #include "obstacles.h"
0021 #include "ball.h"
0022 #include "game.h"
0023 #include "shape.h"
0024 
0025 #include <QCheckBox>
0026 #include <QGridLayout>
0027 #include <QLabel>
0028 #include <QRandomGenerator>
0029 #include <QSlider>
0030 #include <QTimer>
0031 #include <KConfigGroup>
0032 #include <KLineEdit>
0033 #include <KLocalizedString>
0034 #include <KLazyLocalizedString>
0035 
0036 //BEGIN Kolf::Bumper
0037 
0038 Kolf::Bumper::Bumper(QGraphicsItem* parent, b2World* world)
0039     : EllipticalCanvasItem(false, QStringLiteral("bumper_off"), parent, world)
0040 {
0041     const int diameter = 20;
0042     setSize(QSizeF(diameter, diameter));
0043     setZBehavior(CanvasItem::IsRaisedByStrut, 4);
0044     setSimulationType(CanvasItem::NoSimulation);
0045 }
0046 
0047 bool Kolf::Bumper::collision(Ball* ball)
0048 {
0049     const double maxSpeed = ball->getMaxBumperBounceSpeed();
0050     const double speed = qMin(maxSpeed, 1.8 + Vector(ball->velocity()).magnitude() * .9);
0051     ball->reduceMaxBumperBounceSpeed();
0052 
0053     Vector betweenVector(ball->pos() - pos());
0054     betweenVector.setMagnitudeDirection(speed,
0055         // add some randomness so we don't go indefinitely
0056         betweenVector.direction() + deg2rad(QRandomGenerator::global()->bounded(3) - 1)
0057     );
0058 
0059     ball->setVelocity(betweenVector);
0060     ball->setState(Rolling);
0061 
0062     game->playSound(Sound::Bumper);
0063 
0064     setSpriteKey(QStringLiteral("bumper_on"));
0065     QTimer::singleShot(100, this, &Kolf::Bumper::turnBumperOff);
0066     return true;
0067 }
0068 
0069 void Kolf::Bumper::turnBumperOff()
0070 {
0071     setSpriteKey(QStringLiteral("bumper_off"));
0072 }
0073 
0074 Kolf::Overlay* Kolf::Bumper::createOverlay()
0075 {
0076     return new Kolf::Overlay(this, this);
0077 }
0078 
0079 //END Kolf::Bumper
0080 //BEGIN Kolf::Wall
0081 
0082 Kolf::Wall::Wall(QGraphicsItem* parent, b2World* world)
0083     : QGraphicsLineItem(QLineF(-15, 10, 15, -5), parent)
0084     , CanvasItem(world)
0085 {
0086     setPen(QPen(Qt::darkRed, 3));
0087     setData(0, Rtti_NoCollision);
0088     //see also KolfGame::addBorderWall()
0089     setZBehavior(CanvasItem::FixedZValue, 5);
0090 
0091     m_shape = new Kolf::LineShape(line());
0092     addShape(m_shape);
0093 }
0094 
0095 void Kolf::Wall::load(KConfigGroup* cfgGroup)
0096 {
0097     const QPoint start = cfgGroup->readEntry("startPoint", QPoint(-15, 10));
0098     const QPoint end = cfgGroup->readEntry("endPoint", QPoint(15, -5));
0099     setLine(QLineF(start, end));
0100 }
0101 
0102 void Kolf::Wall::save(KConfigGroup* cfgGroup)
0103 {
0104     const QLineF line = this->line();
0105     cfgGroup->writeEntry("startPoint", line.p1().toPoint());
0106     cfgGroup->writeEntry("endPoint", line.p2().toPoint());
0107 }
0108 
0109 void Kolf::Wall::setVisible(bool visible)
0110 {
0111     QGraphicsLineItem::setVisible(visible);
0112     setSimulationType(visible ? CanvasItem::CollisionSimulation : CanvasItem::NoSimulation);
0113 }
0114 
0115 void Kolf::Wall::setLine(const QLineF& line)
0116 {
0117     QGraphicsLineItem::setLine(line);
0118     m_shape->setLine(line);
0119     propagateUpdate();
0120 }
0121 
0122 void Kolf::Wall::moveBy(double dx, double dy)
0123 {
0124     QGraphicsLineItem::moveBy(dx, dy);
0125     CanvasItem::moveBy(dx, dy);
0126 }
0127 
0128 QPointF Kolf::Wall::getPosition() const
0129 {
0130     return QGraphicsItem::pos();
0131 }
0132 
0133 Kolf::Overlay* Kolf::Wall::createOverlay()
0134 {
0135     return new Kolf::WallOverlay(this);
0136 }
0137 
0138 //END Kolf::Wall
0139 //BEGIN Kolf::WallOverlay
0140 
0141 Kolf::WallOverlay::WallOverlay(Kolf::Wall* wall)
0142     : Kolf::Overlay(wall, wall)
0143     , m_handle1(new Kolf::OverlayHandle(Kolf::OverlayHandle::SquareShape, this))
0144     , m_handle2(new Kolf::OverlayHandle(Kolf::OverlayHandle::SquareShape, this))
0145 {
0146     addHandle(m_handle1);
0147     addHandle(m_handle2);
0148     connect(m_handle1, &Kolf::OverlayHandle::moveRequest, this, &Kolf::WallOverlay::moveHandle);
0149     connect(m_handle2, &Kolf::OverlayHandle::moveRequest, this, &Kolf::WallOverlay::moveHandle);
0150 }
0151 
0152 void Kolf::WallOverlay::update()
0153 {
0154     Kolf::Overlay::update();
0155     const QLineF line = dynamic_cast<Kolf::Wall*>(qitem())->line();
0156     m_handle1->setPos(line.p1());
0157     m_handle2->setPos(line.p2());
0158 }
0159 
0160 void Kolf::WallOverlay::moveHandle(const QPointF& handleScenePos)
0161 {
0162     //TODO: code duplication to Kolf::FloaterOverlay
0163     QPointF handlePos = mapFromScene(handleScenePos);
0164     const QObject* handle = sender();
0165     //get handle positions
0166     QPointF handle1Pos = m_handle1->pos();
0167     QPointF handle2Pos = m_handle2->pos();
0168     if (handle == m_handle1)
0169         handle1Pos = handlePos;
0170     else if (handle == m_handle2)
0171         handle2Pos = handlePos;
0172     //ensure minimum length
0173     static const qreal minLength = Kolf::Overlay::MinimumObjectDimension;
0174     const QPointF posDiff = handle1Pos - handle2Pos;
0175     const qreal length = QLineF(QPointF(), posDiff).length();
0176     if (length < minLength)
0177     {
0178         const QPointF additionalExtent = posDiff * (minLength / length - 1);
0179         if (handle == m_handle1)
0180             handle1Pos += additionalExtent;
0181         else if (handle == m_handle2)
0182             handle2Pos -= additionalExtent;
0183     }
0184     //apply to item
0185     dynamic_cast<Kolf::Wall*>(qitem())->setLine(QLineF(handle1Pos, handle2Pos));
0186 }
0187 
0188 //END Kolf::WallOverlay
0189 //BEGIN Kolf::RectangleItem
0190 
0191 Kolf::RectangleItem::RectangleItem(const QString& type, QGraphicsItem* parent, b2World* world)
0192     : Tagaro::SpriteObjectItem(Kolf::renderer(), type, parent)
0193     , CanvasItem(world)
0194     , m_wallPen(QColor("#92772D").darker(), 3)
0195     , m_wallAllowed(Kolf::RectangleWallCount, true)
0196     , m_walls(Kolf::RectangleWallCount, nullptr)
0197     , m_shape(new Kolf::RectShape(QRectF(0, 0, 1, 1)))
0198 {
0199     addShape(m_shape);
0200     setSimulationType(CanvasItem::NoSimulation);
0201     //default size
0202     setSize(type == QLatin1String("sign") ? QSize(110, 40) : QSize(80, 40));
0203 }
0204 
0205 Kolf::RectangleItem::~RectangleItem()
0206 {
0207     qDeleteAll(m_walls);
0208 }
0209 
0210 bool Kolf::RectangleItem::hasWall(Kolf::WallIndex index) const
0211 {
0212     return (bool) m_walls[index];
0213 }
0214 
0215 bool Kolf::RectangleItem::isWallAllowed(Kolf::WallIndex index) const
0216 {
0217     return m_wallAllowed[index];
0218 }
0219 
0220 void Kolf::RectangleItem::setWall(Kolf::WallIndex index, bool hasWall)
0221 {
0222     const bool oldHasWall = (bool) m_walls[index];
0223     if (oldHasWall == hasWall)
0224         return;
0225     if (hasWall && !m_wallAllowed[index])
0226         return;
0227     if (hasWall)
0228     {
0229         Kolf::Wall* wall = m_walls[index] = new Kolf::Wall(parentItem(), world());
0230         wall->setPos(pos());
0231         applyWallStyle(wall);
0232         updateWallPosition();
0233     }
0234     else
0235     {
0236         delete m_walls[index];
0237         m_walls[index] = nullptr;
0238     }
0239     propagateUpdate();
0240     Q_EMIT wallChanged(index, hasWall, m_wallAllowed[index]);
0241 }
0242 
0243 void Kolf::RectangleItem::setWallAllowed(Kolf::WallIndex index, bool wallAllowed)
0244 {
0245     m_wallAllowed[index] = wallAllowed;
0246     //delete wall if one exists at this position currently
0247     if (!wallAllowed)
0248         setWall(index, false);
0249     Q_EMIT wallChanged(index, hasWall(index), wallAllowed);
0250 }
0251 
0252 void Kolf::RectangleItem::updateWallPosition()
0253 {
0254     const QRectF rect(QPointF(), size());
0255     Kolf::Wall* const topWall = m_walls[Kolf::TopWallIndex];
0256     Kolf::Wall* const leftWall = m_walls[Kolf::LeftWallIndex];
0257     Kolf::Wall* const rightWall = m_walls[Kolf::RightWallIndex];
0258     Kolf::Wall* const bottomWall = m_walls[Kolf::BottomWallIndex];
0259     if (topWall)
0260         topWall->setLine(QLineF(rect.topLeft(), rect.topRight()));
0261     if (leftWall)
0262         leftWall->setLine(QLineF(rect.topLeft(), rect.bottomLeft()));
0263     if (rightWall)
0264         rightWall->setLine(QLineF(rect.topRight(), rect.bottomRight()));
0265     if (bottomWall)
0266         bottomWall->setLine(QLineF(rect.bottomLeft(), rect.bottomRight()));
0267 }
0268 
0269 void Kolf::RectangleItem::setSize(const QSizeF& size)
0270 {
0271     Tagaro::SpriteObjectItem::setSize(size);
0272     m_shape->setRect(QRectF(QPointF(), size));
0273     updateWallPosition();
0274     propagateUpdate();
0275 }
0276 
0277 QPointF Kolf::RectangleItem::getPosition() const
0278 {
0279     return QGraphicsItem::pos();
0280 }
0281 
0282 void Kolf::RectangleItem::moveBy(double dx, double dy)
0283 {
0284     Tagaro::SpriteObjectItem::moveBy(dx, dy);
0285     //move myself
0286     const QPointF pos = this->pos();
0287     for (Kolf::Wall* wall : std::as_const(m_walls))
0288         if (wall)
0289             wall->setPos(pos);
0290     //update Z order
0291     CanvasItem::moveBy(dx, dy);
0292     const auto collidingItems = this->collidingItems();
0293     for (QGraphicsItem* qitem : collidingItems) {
0294         CanvasItem* citem = dynamic_cast<CanvasItem*>(qitem);
0295         if (citem)
0296             citem->updateZ(qitem);
0297     }
0298 }
0299 
0300 void Kolf::RectangleItem::setWallColor(const QColor& color)
0301 {
0302     m_wallPen = QPen(color.darker(), 3);
0303     for (Kolf::Wall* wall : std::as_const(m_walls))
0304         applyWallStyle(wall);
0305 }
0306 
0307 void Kolf::RectangleItem::applyWallStyle(Kolf::Wall* wall, bool adjustPainting)
0308 {
0309     if (!wall) //explicitly allowed, see e.g. setWallColor()
0310         return;
0311     if (adjustPainting)
0312         wall->setPen(m_wallPen);
0313     wall->setZBehavior(CanvasItem::IsRaisedByStrut, 3);
0314     wall->setStaticStrut(this);
0315 }
0316 
0317 static const char* wallPropNames[] = { "topWallVisible", "leftWallVisible", "rightWallVisible", "botWallVisible" };
0318 
0319 void Kolf::RectangleItem::load(KConfigGroup* group)
0320 {
0321     QSize size = Tagaro::SpriteObjectItem::size().toSize();
0322     size.setWidth(group->readEntry("width", size.width()));
0323     size.setHeight(group->readEntry("height", size.height()));
0324     setSize(size);
0325     for (int i = 0; i < Kolf::RectangleWallCount; ++i)
0326     {
0327         bool hasWall = this->hasWall((Kolf::WallIndex) i);
0328         hasWall = group->readEntry(wallPropNames[i], hasWall);
0329         setWall((Kolf::WallIndex) i, hasWall);
0330     }
0331 }
0332 
0333 void Kolf::RectangleItem::save(KConfigGroup* group)
0334 {
0335     const QSize size = Tagaro::SpriteObjectItem::size().toSize();
0336     group->writeEntry("width", size.width());
0337     group->writeEntry("height", size.height());
0338     for (int i = 0; i < Kolf::RectangleWallCount; ++i)
0339     {
0340         const bool hasWall = this->hasWall((Kolf::WallIndex) i);
0341         group->writeEntry(wallPropNames[i], hasWall);
0342     }
0343 }
0344 
0345 Config* Kolf::RectangleItem::config(QWidget* parent)
0346 {
0347     return new Kolf::RectangleConfig(this, parent);
0348 }
0349 
0350 Kolf::Overlay* Kolf::RectangleItem::createOverlay()
0351 {
0352     return new Kolf::RectangleOverlay(this);
0353 }
0354 
0355 //END Kolf::RectangleItem
0356 //BEGIN Kolf::RectangleOverlay
0357 
0358 Kolf::RectangleOverlay::RectangleOverlay(Kolf::RectangleItem* item)
0359     : Kolf::Overlay(item, item)
0360 {
0361     //TODO: code duplication to Kolf::LandscapeOverlay and Kolf::SlopeOverlay
0362     for (int i = 0; i < 4; ++i)
0363     {
0364         Kolf::OverlayHandle* handle = new Kolf::OverlayHandle(Kolf::OverlayHandle::CircleShape, this);
0365         m_handles << handle;
0366         addHandle(handle);
0367         connect(handle, &Kolf::OverlayHandle::moveRequest, this, &Kolf::RectangleOverlay::moveHandle);
0368     }
0369 }
0370 
0371 void Kolf::RectangleOverlay::update()
0372 {
0373     Kolf::Overlay::update();
0374     const QRectF rect = qitem()->boundingRect();
0375     m_handles[0]->setPos(rect.topLeft());
0376     m_handles[1]->setPos(rect.topRight());
0377     m_handles[2]->setPos(rect.bottomLeft());
0378     m_handles[3]->setPos(rect.bottomRight());
0379 }
0380 
0381 void Kolf::RectangleOverlay::moveHandle(const QPointF& handleScenePos)
0382 {
0383     Kolf::OverlayHandle* handle = qobject_cast<Kolf::OverlayHandle*>(sender());
0384     const int handleIndex = m_handles.indexOf(handle);
0385     Kolf::RectangleItem* item = dynamic_cast<Kolf::RectangleItem*>(qitem());
0386     const QPointF handlePos = mapFromScene(handleScenePos);
0387     //modify bounding rect using new handlePos
0388     QRectF rect(QPointF(), item->size());
0389     if (handleIndex % 2 == 0)
0390         rect.setLeft(qMin(handlePos.x(), rect.right()));
0391     else
0392         rect.setRight(qMax(handlePos.x(), rect.left()));
0393     if (handleIndex < 2)
0394         rect.setTop(qMin(handlePos.y(), rect.bottom()));
0395     else
0396         rect.setBottom(qMax(handlePos.y(), rect.top()));
0397     item->moveBy(rect.x(), rect.y());
0398     item->setSize(rect.size());
0399 }
0400 
0401 //END Kolf::RectangleOverlay
0402 //BEGIN Kolf::RectangleConfig
0403 
0404 
0405 Kolf::RectangleConfig::RectangleConfig(Kolf::RectangleItem* item, QWidget* parent)
0406     : Config(parent)
0407     , m_layout(new QGridLayout(this))
0408     , m_wallCheckBoxes(Kolf::RectangleWallCount, nullptr)
0409     , m_item(item)
0410 {
0411     static const KLazyLocalizedString captions[] =
0412         { kli18n("&Top"), kli18n("&Left"), kli18n("&Right"), kli18n("&Bottom") };
0413     for (int i = 0; i < Kolf::RectangleWallCount; ++i)
0414     {
0415         QCheckBox* checkBox = m_wallCheckBoxes[i] = new QCheckBox(captions[i].toString(), this);
0416         checkBox->setEnabled(item->isWallAllowed((Kolf::WallIndex) i));
0417         checkBox->setChecked(item->hasWall((Kolf::WallIndex) i));
0418         connect(checkBox, &QCheckBox::toggled, this, &Kolf::RectangleConfig::setWall);
0419     }
0420     connect(item, &Kolf::RectangleItem::wallChanged, this, &Kolf::RectangleConfig::wallChanged);
0421     m_layout->addWidget(new QLabel(i18n("Walls on:")), 0, 0);
0422     m_layout->addWidget(m_wallCheckBoxes[0], 0, 1);
0423     m_layout->addWidget(m_wallCheckBoxes[1], 1, 0);
0424     m_layout->addWidget(m_wallCheckBoxes[2], 1, 2);
0425     m_layout->addWidget(m_wallCheckBoxes[3], 1, 1);
0426     m_layout->setRowStretch(2, 10);
0427     //Kolf::Sign does not have a special Config class
0428     Kolf::Sign* sign = qobject_cast<Kolf::Sign*>(item);
0429     if (sign)
0430     {
0431         m_layout->addWidget(new QLabel(i18n("Sign HTML:")), 3, 0, 1, 3);
0432         KLineEdit* edit = new KLineEdit(sign->text(), this);
0433         m_layout->addWidget(edit, 4, 0, 1, 3);
0434         connect(edit, &KLineEdit::textChanged, sign, &Kolf::Sign::setText);
0435     }
0436     //Kolf::Windmill does not have a special Config class
0437     Kolf::Windmill* windmill = qobject_cast<Kolf::Windmill*>(item);
0438     if (windmill)
0439     {
0440         QCheckBox* checkBox = new QCheckBox(i18n("Windmill on top"), this);
0441         m_layout->addWidget(checkBox, 4, 0, 1, 3);
0442         checkBox->setChecked(windmill->guardAtTop());
0443         connect(checkBox, &QCheckBox::toggled, windmill, &Kolf::Windmill::setGuardAtTop);
0444         QHBoxLayout* hlayout = new QHBoxLayout;
0445         m_layout->addLayout(hlayout, 5, 0, 1, 3);
0446         QLabel* label1 = new QLabel(i18n("Slow"), this);
0447         hlayout->addWidget(label1);
0448         QSlider* slider = new QSlider(Qt::Horizontal, this);
0449         hlayout->addWidget(slider);
0450         QLabel* label2 = new QLabel(i18n("Fast"), this);
0451         hlayout->addWidget(label2);
0452         slider->setRange(1, 10);
0453         slider->setPageStep(1);
0454         slider->setValue(windmill->speed());
0455         connect(slider, &QSlider::valueChanged, windmill, &Kolf::Windmill::setSpeed);
0456     }
0457     //Kolf::Floater does not have a special Config class
0458     Kolf::Floater* floater = qobject_cast<Kolf::Floater*>(item);
0459     if (floater)
0460     {
0461         m_layout->addWidget(new QLabel(i18n("Moving speed"), this), 4, 0, 1, 3);
0462         QHBoxLayout* hlayout = new QHBoxLayout;
0463         m_layout->addLayout(hlayout, 5, 0, 1, 3);
0464         QLabel* label1 = new QLabel(i18n("Slow"), this);
0465         hlayout->addWidget(label1);
0466         QSlider* slider = new QSlider(Qt::Horizontal, this);
0467         hlayout->addWidget(slider);
0468         QLabel* label2 = new QLabel(i18n("Fast"), this);
0469         hlayout->addWidget(label2);
0470         slider->setRange(0, 20);
0471         slider->setPageStep(2);
0472         slider->setValue(floater->speed());
0473         connect(slider, &QSlider::valueChanged, floater, &Kolf::Floater::setSpeed);
0474     }
0475 }
0476 
0477 void Kolf::RectangleConfig::setWall(bool hasWall)
0478 {
0479     const int wallIndex = m_wallCheckBoxes.indexOf(qobject_cast<QCheckBox*>(sender()));
0480     if (wallIndex >= 0)
0481     {
0482         m_item->setWall((Kolf::WallIndex) wallIndex, hasWall);
0483         changed();
0484     }
0485 }
0486 
0487 void Kolf::RectangleConfig::wallChanged(Kolf::WallIndex index, bool hasWall, bool wallAllowed)
0488 {
0489     m_wallCheckBoxes[index]->setEnabled(wallAllowed);
0490     m_wallCheckBoxes[index]->setChecked(hasWall);
0491 }
0492 
0493 //END Kolf::RectangleConfig
0494 //BEGIN Kolf::Bridge
0495 
0496 Kolf::Bridge::Bridge(QGraphicsItem* parent, b2World* world)
0497     : Kolf::RectangleItem(QStringLiteral("bridge"), parent, world)
0498 {
0499     setZBehavior(CanvasItem::IsStrut, 0);
0500 }
0501 
0502 bool Kolf::Bridge::collision(Ball* ball)
0503 {
0504     ball->setFrictionMultiplier(.63);
0505     return false;
0506 }
0507 
0508 //END Kolf::Bridge
0509 //BEGIN Kolf::Floater
0510 
0511 Kolf::Floater::Floater(QGraphicsItem* parent, b2World* world)
0512     : Kolf::RectangleItem(QStringLiteral("floater"), parent, world)
0513     , m_motionLine(QLineF(200, 200, 100, 100))
0514     , m_speed(0)
0515     , m_velocity(0)
0516     , m_position(0)
0517     , m_moveByMovesMotionLine(true)
0518     , m_animated(true)
0519 {
0520     setMlPosition(m_position);
0521     setZBehavior(CanvasItem::IsStrut, 0);
0522 }
0523 
0524 void Kolf::Floater::editModeChanged(bool editing)
0525 {
0526     Kolf::RectangleItem::editModeChanged(editing);
0527     m_animated = !editing;
0528     if (editing)
0529         setMlPosition(0);
0530 }
0531 
0532 void Kolf::Floater::moveBy(double dx, double dy)
0533 {
0534     moveItemsOnStrut(QPointF(dx, dy));
0535     Kolf::RectangleItem::moveBy(dx, dy);
0536     if (m_moveByMovesMotionLine)
0537         m_motionLine.translate(dx, dy);
0538     propagateUpdate();
0539 }
0540 
0541 QLineF Kolf::Floater::motionLine() const
0542 {
0543     return m_motionLine;
0544 }
0545 
0546 void Kolf::Floater::setMotionLine(const QLineF& motionLine)
0547 {
0548     m_motionLine = motionLine;
0549     setMlPosition(m_position);
0550     propagateUpdate();
0551 }
0552 
0553 void Kolf::Floater::setMlPosition(qreal position)
0554 {
0555     m_moveByMovesMotionLine = false;
0556     setPosition(m_motionLine.pointAt(position));
0557     m_position = position;
0558     m_moveByMovesMotionLine = true;
0559 }
0560 
0561 int Kolf::Floater::speed() const
0562 {
0563     return m_speed;
0564 }
0565 
0566 void Kolf::Floater::setSpeed(int speed)
0567 {
0568     m_speed = speed;
0569     const qreal velocity = speed / 3.5;
0570     m_velocity = (m_velocity < 0) ? -velocity : velocity;
0571     propagateUpdate();
0572 }
0573 
0574 void Kolf::Floater::advance(int phase)
0575 {
0576     if (phase != 1 || !m_animated)
0577         return;
0578     //determine movement step
0579     const qreal mlLength = m_motionLine.length();
0580     const qreal parameterDiff = m_velocity / mlLength;
0581     //determine new position (mirror on end point if end point passed)
0582     m_position += parameterDiff;
0583     if (m_position < 0)
0584     {
0585         m_velocity = qAbs(m_velocity);
0586         m_position = -m_position;
0587     }
0588     else if (m_position > 1)
0589     {
0590         m_velocity = -qAbs(m_velocity);
0591         m_position = 2 - m_position;
0592     }
0593     //apply position
0594     setMlPosition(m_position);
0595 }
0596 
0597 void Kolf::Floater::load(KConfigGroup* group)
0598 {
0599     Kolf::RectangleItem::load(group);
0600     QLineF motionLine = m_motionLine;
0601     motionLine.setP1(group->readEntry("startPoint", m_motionLine.p1()));
0602     motionLine.setP2(group->readEntry("endPoint", m_motionLine.p2()));
0603     setMotionLine(motionLine);
0604     setSpeed(group->readEntry("speed", m_speed));
0605 }
0606 
0607 void Kolf::Floater::save(KConfigGroup* group)
0608 {
0609     Kolf::RectangleItem::save(group);
0610     group->writeEntry("startPoint", m_motionLine.p1());
0611     group->writeEntry("endPoint", m_motionLine.p2());
0612     group->writeEntry("speed", m_speed);
0613 }
0614 
0615 Kolf::Overlay* Kolf::Floater::createOverlay()
0616 {
0617     return new Kolf::FloaterOverlay(this);
0618 }
0619 
0620 //END Kolf::Floater
0621 //BEGIN Kolf::FloaterOverlay
0622 
0623 Kolf::FloaterOverlay::FloaterOverlay(Kolf::Floater* floater)
0624     : Kolf::RectangleOverlay(floater)
0625     , m_handle1(new Kolf::OverlayHandle(Kolf::OverlayHandle::SquareShape, this))
0626     , m_handle2(new Kolf::OverlayHandle(Kolf::OverlayHandle::SquareShape, this))
0627     , m_motionLineItem(new QGraphicsLineItem(this))
0628 {
0629     addHandle(m_handle1);
0630     addHandle(m_handle2);
0631     connect(m_handle1, &Kolf::OverlayHandle::moveRequest, this, &Kolf::FloaterOverlay::moveMotionLineHandle);
0632     connect(m_handle2, &Kolf::OverlayHandle::moveRequest, this, &Kolf::FloaterOverlay::moveMotionLineHandle);
0633     addHandle(m_motionLineItem);
0634     QPen pen = m_motionLineItem->pen();
0635     pen.setStyle(Qt::DashLine);
0636     m_motionLineItem->setPen(pen);
0637 }
0638 
0639 void Kolf::FloaterOverlay::update()
0640 {
0641     Kolf::RectangleOverlay::update();
0642     const QLineF line = dynamic_cast<Kolf::Floater*>(qitem())->motionLine().translated(-qitem()->pos());
0643     m_handle1->setPos(line.p1());
0644     m_handle2->setPos(line.p2());
0645     m_motionLineItem->setLine(line);
0646 }
0647 
0648 void Kolf::FloaterOverlay::moveMotionLineHandle(const QPointF& handleScenePos)
0649 {
0650     //TODO: code duplication to Kolf::WallOverlay
0651     QPointF handlePos = mapFromScene(handleScenePos) + qitem()->pos();
0652     const QObject* handle = sender();
0653     //get handle positions
0654     QPointF handle1Pos = m_handle1->pos() + qitem()->pos();
0655     QPointF handle2Pos = m_handle2->pos() + qitem()->pos();
0656     if (handle == m_handle1)
0657         handle1Pos = handlePos;
0658     else if (handle == m_handle2)
0659         handle2Pos = handlePos;
0660     //ensure minimum length
0661     static const qreal minLength = Kolf::Overlay::MinimumObjectDimension;
0662     const QPointF posDiff = handle1Pos - handle2Pos;
0663     const qreal length = QLineF(QPointF(), posDiff).length();
0664     if (length < minLength)
0665     {
0666         const QPointF additionalExtent = posDiff * (minLength / length - 1);
0667         if (handle == m_handle1)
0668             handle1Pos += additionalExtent;
0669         else if (handle == m_handle2)
0670             handle2Pos -= additionalExtent;
0671     }
0672     //apply to item
0673     dynamic_cast<Kolf::Floater*>(qitem())->setMotionLine(QLineF(handle1Pos, handle2Pos));
0674 }
0675 
0676 //END Kolf::FloaterOverlay
0677 //BEGIN Kolf::Sign
0678 
0679 Kolf::Sign::Sign(QGraphicsItem* parent, b2World* world)
0680     : Kolf::RectangleItem(QStringLiteral("sign"), parent, world)
0681     , m_text(i18n("New Text"))
0682     , m_textItem(new QGraphicsTextItem(m_text, this))
0683 {
0684     setZBehavior(CanvasItem::FixedZValue, 3);
0685     setWallColor(Qt::black);
0686     for (int i = 0; i < Kolf::RectangleWallCount; ++i)
0687         setWall((Kolf::WallIndex) i, true);
0688     //Z value 1 should be enough to keep text above overlay
0689     m_textItem->setZValue(1);
0690     m_textItem->setAcceptedMouseButtons(Qt::NoButton);
0691     //TODO: activate QGraphicsItem::ItemClipsChildrenToShape flag after
0692     //refactoring (only after it is clear that the text is the only child)
0693 }
0694 
0695 QString Kolf::Sign::text() const
0696 {
0697     return m_text;
0698 }
0699 
0700 void Kolf::Sign::setText(const QString& text)
0701 {
0702     m_text = text;
0703     m_textItem->setHtml(text);
0704 }
0705 
0706 void Kolf::Sign::setSize(const QSizeF& size)
0707 {
0708     Kolf::RectangleItem::setSize(size);
0709     m_textItem->setTextWidth(size.width());
0710 }
0711 
0712 void Kolf::Sign::load(KConfigGroup* group)
0713 {
0714     Kolf::RectangleItem::load(group);
0715     setText(group->readEntry("Comment", m_text));
0716 }
0717 
0718 void Kolf::Sign::save(KConfigGroup* group)
0719 {
0720     Kolf::RectangleItem::save(group);
0721     group->writeEntry("Comment", m_text);
0722 }
0723 
0724 //END Kolf::Sign
0725 //BEGIN Kolf::Windmill
0726 
0727 Kolf::Windmill::Windmill(QGraphicsItem* parent, b2World* world)
0728     : Kolf::RectangleItem(QStringLiteral("windmill"), parent, world)
0729       , m_leftWall(new Kolf::Wall(parent, world))
0730       , m_rightWall(new Kolf::Wall(parent, world))
0731       , m_guardWall(new Kolf::Wall(parent, world))
0732       , m_guardAtTop(false)
0733       , m_speed(0), m_velocity(0)
0734 {
0735     setZBehavior(CanvasItem::IsStrut, 0);
0736     setSpeed(5); //initialize m_speed and m_velocity properly
0737     applyWallStyle(m_leftWall);
0738     applyWallStyle(m_rightWall);
0739     applyWallStyle(m_guardWall, false); //Z-ordering!
0740     m_guardWall->setPen(QPen(Qt::black, 5));
0741     setWall(Kolf::TopWallIndex, false);
0742     setWall(Kolf::LeftWallIndex, true);
0743     setWall(Kolf::RightWallIndex, true);
0744     setWallAllowed(Kolf::BottomWallIndex, false);
0745     m_guardWall->setLine(QLineF());
0746     updateWallPosition();
0747 }
0748 
0749 Kolf::Windmill::~Windmill()
0750 {
0751     delete m_leftWall;
0752     delete m_rightWall;
0753     delete m_guardWall;
0754 }
0755 
0756 bool Kolf::Windmill::guardAtTop() const
0757 {
0758     return m_guardAtTop;
0759 }
0760 
0761 void Kolf::Windmill::setGuardAtTop(bool guardAtTop)
0762 {
0763     if (m_guardAtTop == guardAtTop)
0764         return;
0765     m_guardAtTop = guardAtTop;
0766     //exchange top and bottom walls
0767     if (guardAtTop)
0768     {
0769         const bool hasWall = this->hasWall(Kolf::TopWallIndex);
0770         setWallAllowed(Kolf::BottomWallIndex, true);
0771         setWallAllowed(Kolf::TopWallIndex, false);
0772         setWall(Kolf::BottomWallIndex, hasWall);
0773     }
0774     else
0775     {
0776         const bool hasWall = this->hasWall(Kolf::BottomWallIndex);
0777         setWallAllowed(Kolf::BottomWallIndex, false);
0778         setWallAllowed(Kolf::TopWallIndex, true);
0779         setWall(Kolf::TopWallIndex, hasWall);
0780     }
0781     //recalculate position of guard walls etc.
0782     updateWallPosition();
0783     propagateUpdate();
0784 }
0785 
0786 int Kolf::Windmill::speed() const
0787 {
0788     return m_speed;
0789 }
0790 
0791 void Kolf::Windmill::setSpeed(int speed)
0792 {
0793     m_speed = speed;
0794     const qreal velocity = speed / 3.0;
0795     m_velocity = (m_velocity < 0) ? -velocity : velocity;
0796     propagateUpdate();
0797 }
0798 
0799 void Kolf::Windmill::advance(int phase)
0800 {
0801     if (phase == 1)
0802     {
0803         QLineF guardLine = m_guardWall->line().translated(m_velocity, 0);
0804         const qreal maxX = qMax(guardLine.x1(), guardLine.x2());
0805         const qreal minX = qMin(guardLine.x1(), guardLine.x2());
0806         QRectF rect(QPointF(), size());
0807         if (minX < rect.left())
0808         {
0809             guardLine.translate(rect.left() - minX, 0);
0810             m_velocity = qAbs(m_velocity);
0811         }
0812         else if (maxX > rect.right())
0813         {
0814             guardLine.translate(rect.right() - maxX, 0);
0815             m_velocity = -qAbs(m_velocity);
0816         }
0817         m_guardWall->setLine(guardLine);
0818     }
0819 }
0820 
0821 void Kolf::Windmill::moveBy(double dx, double dy)
0822 {
0823     Kolf::RectangleItem::moveBy(dx, dy);
0824     const QPointF pos = this->pos();
0825     m_leftWall->setPos(pos);
0826     m_rightWall->setPos(pos);
0827     m_guardWall->setPos(pos);
0828 }
0829 
0830 void Kolf::Windmill::updateWallPosition()
0831 {
0832     Kolf::RectangleItem::updateWallPosition();
0833     //parametrize position of guard relative to old rect
0834     qreal t = 0.5;
0835     if (!m_guardWall->line().isNull())
0836     {
0837         //this branch is taken unless this method gets called from the ctor
0838         const qreal oldLeft = m_leftWall->line().x1();
0839         const qreal oldRight = m_rightWall->line().x1();
0840         const qreal oldGCenter = m_guardWall->line().pointAt(0.5).x();
0841         t = (oldGCenter - oldLeft) / (oldRight - oldLeft);
0842     }
0843     //set new positions
0844     const QRectF rect(QPointF(), size());
0845     const QPointF leftEnd = m_guardAtTop ? rect.topLeft() : rect.bottomLeft();
0846     const QPointF rightEnd = m_guardAtTop ? rect.topRight() : rect.bottomRight();
0847     const QPointF wallExtent(rect.width() / 4, 0);
0848     m_leftWall->setLine(QLineF(leftEnd, leftEnd + wallExtent));
0849     m_rightWall->setLine(QLineF(rightEnd, rightEnd - wallExtent));
0850     //set position of guard to the same relative coordinate as before
0851     const qreal gWidth = wallExtent.x() / 1.07 - 2;
0852     const qreal gY = m_guardAtTop ? rect.top() - 4 : rect.bottom() + 4;
0853     QLineF gLine(rect.left(), gY, rect.left() + gWidth, gY);
0854     const qreal currentGCenter = gLine.pointAt(0.5).x();
0855     const qreal desiredGCenter = rect.left() + t * rect.width();
0856     gLine.translate(desiredGCenter - currentGCenter, 0);
0857     m_guardWall->setLine(gLine);
0858 }
0859 
0860 void Kolf::Windmill::load(KConfigGroup* group)
0861 {
0862     Kolf::RectangleItem::load(group);
0863     setSpeed(group->readEntry("speed", m_speed));
0864     setGuardAtTop(!group->readEntry("bottom", !m_guardAtTop));
0865 }
0866 
0867 void Kolf::Windmill::save(KConfigGroup* group)
0868 {
0869     Kolf::RectangleItem::save(group);
0870     group->writeEntry("speed", m_speed);
0871     group->writeEntry("bottom", !m_guardAtTop);
0872 }
0873 
0874 //END Kolf::Windmill
0875 
0876 #include "moc_obstacles.cpp"