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 | |
36 | Kolf::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 | |
45 | bool 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 | |
65 | void Kolf::Bumper::turnBumperOff() |
66 | { |
67 | setSpriteKey(QLatin1String("bumper_off" )); |
68 | } |
69 | |
70 | Kolf::Overlay* Kolf::Bumper::createOverlay() |
71 | { |
72 | return new Kolf::Overlay(this, this); |
73 | } |
74 | |
75 | //END Kolf::Bumper |
76 | //BEGIN Kolf::Wall |
77 | |
78 | Kolf::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 | |
91 | void 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 | |
98 | void 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 | |
105 | void Kolf::Wall::setVisible(bool visible) |
106 | { |
107 | QGraphicsLineItem::setVisible(visible); |
108 | setSimulationType(visible ? CanvasItem::CollisionSimulation : CanvasItem::NoSimulation); |
109 | } |
110 | |
111 | void Kolf::Wall::setLine(const QLineF& line) |
112 | { |
113 | QGraphicsLineItem::setLine(line); |
114 | m_shape->setLine(line); |
115 | propagateUpdate(); |
116 | } |
117 | |
118 | void Kolf::Wall::moveBy(double dx, double dy) |
119 | { |
120 | QGraphicsLineItem::moveBy(dx, dy); |
121 | CanvasItem::moveBy(dx, dy); |
122 | } |
123 | |
124 | QPointF Kolf::Wall::getPosition() const |
125 | { |
126 | return QGraphicsItem::pos(); |
127 | } |
128 | |
129 | Kolf::Overlay* Kolf::Wall::createOverlay() |
130 | { |
131 | return new Kolf::WallOverlay(this); |
132 | } |
133 | |
134 | //END Kolf::Wall |
135 | //BEGIN Kolf::WallOverlay |
136 | |
137 | Kolf::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 | |
148 | void 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 | |
156 | void 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 | |
187 | Kolf::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 | |
201 | Kolf::RectangleItem::~RectangleItem() |
202 | { |
203 | qDeleteAll(m_walls); |
204 | } |
205 | |
206 | bool Kolf::RectangleItem::hasWall(Kolf::WallIndex index) const |
207 | { |
208 | return (bool) m_walls[index]; |
209 | } |
210 | |
211 | bool Kolf::RectangleItem::isWallAllowed(Kolf::WallIndex index) const |
212 | { |
213 | return m_wallAllowed[index]; |
214 | } |
215 | |
216 | void 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 | |
239 | void 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 | |
248 | void 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 | |
265 | void 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 | |
273 | QPointF Kolf::RectangleItem::getPosition() const |
274 | { |
275 | return QGraphicsItem::pos(); |
276 | } |
277 | |
278 | void 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 | |
296 | void 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 | |
303 | void 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 | |
313 | static const char* wallPropNames[] = { "topWallVisible" , "leftWallVisible" , "rightWallVisible" , "botWallVisible" }; |
314 | |
315 | void 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 | |
329 | void 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 | |
341 | Config* Kolf::RectangleItem::config(QWidget* parent) |
342 | { |
343 | return new Kolf::RectangleConfig(this, parent); |
344 | } |
345 | |
346 | Kolf::Overlay* Kolf::RectangleItem::createOverlay() |
347 | { |
348 | return new Kolf::RectangleOverlay(this); |
349 | } |
350 | |
351 | //END Kolf::RectangleItem |
352 | //BEGIN Kolf::RectangleOverlay |
353 | |
354 | Kolf::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 | |
367 | void 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 | |
377 | void 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 | |
400 | Kolf::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 | |
471 | void 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 | |
481 | void 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 | |
490 | Kolf::Bridge::Bridge(QGraphicsItem* parent, b2World* world) |
491 | : Kolf::RectangleItem(QLatin1String("bridge" ), parent, world) |
492 | { |
493 | setZBehavior(CanvasItem::IsStrut, 0); |
494 | } |
495 | |
496 | bool Kolf::Bridge::collision(Ball* ball) |
497 | { |
498 | ball->setFrictionMultiplier(.63); |
499 | return false; |
500 | } |
501 | |
502 | //END Kolf::Bridge |
503 | //BEGIN Kolf::Floater |
504 | |
505 | Kolf::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 | |
518 | void Kolf::Floater::editModeChanged(bool editing) |
519 | { |
520 | Kolf::RectangleItem::editModeChanged(editing); |
521 | m_animated = !editing; |
522 | if (editing) |
523 | setMlPosition(0); |
524 | } |
525 | |
526 | void 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 | |
535 | QLineF Kolf::Floater::motionLine() const |
536 | { |
537 | return m_motionLine; |
538 | } |
539 | |
540 | void Kolf::Floater::setMotionLine(const QLineF& motionLine) |
541 | { |
542 | m_motionLine = motionLine; |
543 | setMlPosition(m_position); |
544 | propagateUpdate(); |
545 | } |
546 | |
547 | void 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 | |
555 | int Kolf::Floater::speed() const |
556 | { |
557 | return m_speed; |
558 | } |
559 | |
560 | void 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 | |
568 | void 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 | |
591 | void 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 | |
601 | void 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 | |
609 | Kolf::Overlay* Kolf::Floater::createOverlay() |
610 | { |
611 | return new Kolf::FloaterOverlay(this); |
612 | } |
613 | |
614 | //END Kolf::Floater |
615 | //BEGIN Kolf::FloaterOverlay |
616 | |
617 | Kolf::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 | |
633 | void 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 | |
642 | void 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 | |
673 | Kolf::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 | |
689 | QString Kolf::Sign::text() const |
690 | { |
691 | return m_text; |
692 | } |
693 | |
694 | void Kolf::Sign::setText(const QString& text) |
695 | { |
696 | m_text = text; |
697 | m_textItem->setHtml(text); |
698 | } |
699 | |
700 | void Kolf::Sign::setSize(const QSizeF& size) |
701 | { |
702 | Kolf::RectangleItem::setSize(size); |
703 | m_textItem->setTextWidth(size.width()); |
704 | } |
705 | |
706 | void Kolf::Sign::load(KConfigGroup* group) |
707 | { |
708 | Kolf::RectangleItem::load(group); |
709 | setText(group->readEntry("Comment" , m_text)); |
710 | } |
711 | |
712 | void 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 | |
721 | Kolf::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 | |
743 | Kolf::Windmill::~Windmill() |
744 | { |
745 | delete m_leftWall; |
746 | delete m_rightWall; |
747 | delete m_guardWall; |
748 | } |
749 | |
750 | bool Kolf::Windmill::guardAtTop() const |
751 | { |
752 | return m_guardAtTop; |
753 | } |
754 | |
755 | void 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 | |
780 | int Kolf::Windmill::speed() const |
781 | { |
782 | return m_speed; |
783 | } |
784 | |
785 | void 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 | |
793 | void 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 | |
815 | void 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 | |
824 | void 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 | |
854 | void 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 | |
861 | void 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 | |