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"