1 #define _USE_MATH_DEFINES
2 #include "arrowitem.h"
3 
4 #include "carddatabase.h"
5 #include "carditem.h"
6 #include "cardzone.h"
7 #include "color.h"
8 #include "pb/command_attach_card.pb.h"
9 #include "pb/command_create_arrow.pb.h"
10 #include "pb/command_delete_arrow.pb.h"
11 #include "player.h"
12 #include "playertarget.h"
13 #include "settingscache.h"
14 
15 #include <QDebug>
16 #include <QGraphicsScene>
17 #include <QGraphicsSceneMouseEvent>
18 #include <QPainter>
19 #include <cmath>
20 
ArrowItem(Player * _player,int _id,ArrowTarget * _startItem,ArrowTarget * _targetItem,const QColor & _color)21 ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color)
22     : QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), color(_color),
23       fullColor(true)
24 {
25     qDebug() << "ArrowItem constructor: startItem=" << static_cast<QGraphicsItem *>(startItem);
26     setZValue(2000000005);
27 
28     if (startItem)
29         startItem->addArrowFrom(this);
30     if (targetItem)
31         targetItem->addArrowTo(this);
32 
33     if (startItem && targetItem)
34         updatePath();
35 }
36 
~ArrowItem()37 ArrowItem::~ArrowItem()
38 {
39     qDebug() << "ArrowItem destructor";
40 }
41 
delArrow()42 void ArrowItem::delArrow()
43 {
44     if (startItem) {
45         startItem->removeArrowFrom(this);
46         startItem = 0;
47     }
48 
49     if (targetItem) {
50         targetItem->setBeingPointedAt(false);
51         targetItem->removeArrowTo(this);
52         targetItem = 0;
53     }
54 
55     player->removeArrow(this);
56     deleteLater();
57 }
58 
updatePath()59 void ArrowItem::updatePath()
60 {
61     if (!targetItem)
62         return;
63 
64     QPointF endPoint = targetItem->mapToScene(
65         QPointF(targetItem->boundingRect().width() / 2, targetItem->boundingRect().height() / 2));
66     updatePath(endPoint);
67 }
68 
updatePath(const QPointF & endPoint)69 void ArrowItem::updatePath(const QPointF &endPoint)
70 {
71     const double arrowWidth = 15.0;
72     const double headWidth = 40.0;
73     const double headLength =
74         headWidth / pow(2, 0.5); // aka headWidth / sqrt (2) but this produces a compile error with MSVC++
75     const double phi = 15;
76 
77     if (!startItem)
78         return;
79 
80     QPointF startPoint =
81         startItem->mapToScene(QPointF(startItem->boundingRect().width() / 2, startItem->boundingRect().height() / 2));
82     QLineF line(startPoint, endPoint);
83     qreal lineLength = line.length();
84 
85     prepareGeometryChange();
86     if (lineLength < 30)
87         path = QPainterPath();
88     else {
89         QPointF c(lineLength / 2, tan(phi * M_PI / 180) * lineLength);
90 
91         QPainterPath centerLine;
92         centerLine.moveTo(0, 0);
93         centerLine.quadTo(c, QPointF(lineLength, 0));
94 
95         double percentage = 1 - headLength / lineLength;
96         QPointF arrowBodyEndPoint = centerLine.pointAtPercent(percentage);
97         QLineF testLine(arrowBodyEndPoint, centerLine.pointAtPercent(percentage + 0.001));
98         qreal alpha = testLine.angle() - 90;
99         QPointF endPoint1 =
100             arrowBodyEndPoint + arrowWidth / 2 * QPointF(cos(alpha * M_PI / 180), -sin(alpha * M_PI / 180));
101         QPointF endPoint2 =
102             arrowBodyEndPoint + arrowWidth / 2 * QPointF(-cos(alpha * M_PI / 180), sin(alpha * M_PI / 180));
103         QPointF point1 =
104             endPoint1 + (headWidth - arrowWidth) / 2 * QPointF(cos(alpha * M_PI / 180), -sin(alpha * M_PI / 180));
105         QPointF point2 =
106             endPoint2 + (headWidth - arrowWidth) / 2 * QPointF(-cos(alpha * M_PI / 180), sin(alpha * M_PI / 180));
107 
108         path = QPainterPath(-arrowWidth / 2 * QPointF(cos((phi - 90) * M_PI / 180), sin((phi - 90) * M_PI / 180)));
109         path.quadTo(c, endPoint1);
110         path.lineTo(point1);
111         path.lineTo(QPointF(lineLength, 0));
112         path.lineTo(point2);
113         path.lineTo(endPoint2);
114         path.quadTo(c, arrowWidth / 2 * QPointF(cos((phi - 90) * M_PI / 180), sin((phi - 90) * M_PI / 180)));
115         path.lineTo(-arrowWidth / 2 * QPointF(cos((phi - 90) * M_PI / 180), sin((phi - 90) * M_PI / 180)));
116     }
117 
118     setPos(startPoint);
119     setTransform(QTransform().rotate(-line.angle()));
120 }
121 
paint(QPainter * painter,const QStyleOptionGraphicsItem *,QWidget *)122 void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
123 {
124     QColor paintColor(color);
125     if (fullColor)
126         paintColor.setAlpha(200);
127     else
128         paintColor.setAlpha(150);
129     painter->setBrush(paintColor);
130     painter->drawPath(path);
131 }
132 
mousePressEvent(QGraphicsSceneMouseEvent * event)133 void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
134 {
135     if (!player->getLocal()) {
136         event->ignore();
137         return;
138     }
139 
140     QList<QGraphicsItem *> colliding = scene()->items(event->scenePos());
141     for (int i = 0; i < colliding.size(); ++i) {
142         if (qgraphicsitem_cast<CardItem *>(colliding[i])) {
143             event->ignore();
144             return;
145         }
146     }
147 
148     event->accept();
149     if (event->button() == Qt::RightButton) {
150         Command_DeleteArrow cmd;
151         cmd.set_arrow_id(id);
152         player->sendGameCommand(cmd);
153     }
154 }
155 
ArrowDragItem(Player * _owner,ArrowTarget * _startItem,const QColor & _color)156 ArrowDragItem::ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color)
157     : ArrowItem(_owner, -1, _startItem, 0, _color)
158 {
159 }
160 
addChildArrow(ArrowDragItem * childArrow)161 void ArrowDragItem::addChildArrow(ArrowDragItem *childArrow)
162 {
163     childArrows.append(childArrow);
164 }
165 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)166 void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
167 {
168     // This ensures that if a mouse move event happens after a call to delArrow(),
169     // the event will be discarded as it would create some stray pointers.
170     if (!startItem)
171         return;
172 
173     QPointF endPos = event->scenePos();
174 
175     QList<QGraphicsItem *> colliding = scene()->items(endPos);
176     ArrowTarget *cursorItem = 0;
177     qreal cursorItemZ = -1;
178     for (int i = colliding.size() - 1; i >= 0; i--) {
179         if (qgraphicsitem_cast<PlayerTarget *>(colliding.at(i)) || qgraphicsitem_cast<CardItem *>(colliding.at(i))) {
180             if (colliding.at(i)->zValue() > cursorItemZ) {
181                 cursorItem = static_cast<ArrowTarget *>(colliding.at(i));
182                 cursorItemZ = cursorItem->zValue();
183             }
184         }
185     }
186 
187     if ((cursorItem != targetItem) && targetItem) {
188         targetItem->setBeingPointedAt(false);
189         targetItem->removeArrowTo(this);
190     }
191     if (!cursorItem) {
192         fullColor = false;
193         targetItem = 0;
194         updatePath(endPos);
195     } else {
196         if (cursorItem != targetItem) {
197             fullColor = true;
198             if (cursorItem != startItem) {
199                 cursorItem->setBeingPointedAt(true);
200                 cursorItem->addArrowTo(this);
201             }
202             targetItem = cursorItem;
203         }
204         updatePath();
205     }
206     update();
207 
208     for (int i = 0; i < childArrows.size(); ++i) {
209         childArrows[i]->mouseMoveEvent(event);
210     }
211 }
212 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)213 void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
214 {
215     if (!startItem)
216         return;
217 
218     if (targetItem && (targetItem != startItem)) {
219         CardZone *startZone = static_cast<CardItem *>(startItem)->getZone();
220         // For now, we can safely assume that the start item is always a card.
221         // The target item can be a player as well.
222         CardItem *startCard = qgraphicsitem_cast<CardItem *>(startItem);
223         CardItem *targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
224 
225         Command_CreateArrow cmd;
226         cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(color));
227         cmd.set_start_player_id(startZone->getPlayer()->getId());
228         cmd.set_start_zone(startZone->getName().toStdString());
229         cmd.set_start_card_id(startCard->getId());
230 
231         if (targetCard) {
232             CardZone *targetZone = targetCard->getZone();
233             cmd.set_target_player_id(targetZone->getPlayer()->getId());
234             cmd.set_target_zone(targetZone->getName().toStdString());
235             cmd.set_target_card_id(targetCard->getId());
236         } else {
237             PlayerTarget *targetPlayer = qgraphicsitem_cast<PlayerTarget *>(targetItem);
238             cmd.set_target_player_id(targetPlayer->getOwner()->getId());
239         }
240         if (startZone->getName().compare("hand") == 0) {
241             startCard->playCard(false);
242             CardInfoPtr ci = startCard->getInfo();
243             if (ci && (((!SettingsCache::instance().getPlayToStack() && ci->getTableRow() == 3) ||
244                         ((SettingsCache::instance().getPlayToStack() && ci->getTableRow() != 0) &&
245                          startCard->getZone()->getName().toStdString() != "stack"))))
246                 cmd.set_start_zone("stack");
247             else
248                 cmd.set_start_zone(SettingsCache::instance().getPlayToStack() ? "stack" : "table");
249         }
250         player->sendGameCommand(cmd);
251     }
252     delArrow();
253 
254     for (int i = 0; i < childArrows.size(); ++i)
255         childArrows[i]->mouseReleaseEvent(event);
256 }
257 
ArrowAttachItem(ArrowTarget * _startItem)258 ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem)
259     : ArrowItem(_startItem->getOwner(), -1, _startItem, 0, Qt::green)
260 {
261 }
262 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)263 void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
264 {
265     if (!startItem)
266         return;
267 
268     QPointF endPos = event->scenePos();
269 
270     QList<QGraphicsItem *> colliding = scene()->items(endPos);
271     ArrowTarget *cursorItem = 0;
272     qreal cursorItemZ = -1;
273     for (int i = colliding.size() - 1; i >= 0; i--) {
274         if (qgraphicsitem_cast<CardItem *>(colliding.at(i))) {
275             if (colliding.at(i)->zValue() > cursorItemZ) {
276                 cursorItem = static_cast<ArrowTarget *>(colliding.at(i));
277                 cursorItemZ = cursorItem->zValue();
278             }
279         }
280     }
281 
282     if ((cursorItem != targetItem) && targetItem) {
283         targetItem->setBeingPointedAt(false);
284     }
285     if (!cursorItem) {
286         fullColor = false;
287         targetItem = 0;
288         updatePath(endPos);
289     } else {
290         fullColor = true;
291         if (cursorItem != startItem) {
292             cursorItem->setBeingPointedAt(true);
293         }
294         targetItem = cursorItem;
295         updatePath();
296     }
297     update();
298 }
299 
mouseReleaseEvent(QGraphicsSceneMouseEvent *)300 void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * /*event*/)
301 {
302     if (!startItem)
303         return;
304 
305     if (targetItem && (targetItem != startItem)) {
306         CardItem *startCard = qgraphicsitem_cast<CardItem *>(startItem);
307         CardZone *startZone = startCard->getZone();
308         CardItem *targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
309         CardZone *targetZone = targetCard->getZone();
310 
311         Command_AttachCard cmd;
312         cmd.set_start_zone(startZone->getName().toStdString());
313         cmd.set_card_id(startCard->getId());
314         cmd.set_target_player_id(targetZone->getPlayer()->getId());
315         cmd.set_target_zone(targetZone->getName().toStdString());
316         cmd.set_target_card_id(targetCard->getId());
317 
318         player->sendGameCommand(cmd);
319     }
320 
321     delArrow();
322 }
323