1 #include "deckview.h"
2
3 #include "carddatabase.h"
4 #include "decklist.h"
5 #include "main.h"
6 #include "settingscache.h"
7 #include "thememanager.h"
8
9 #include <QApplication>
10 #include <QGraphicsSceneMouseEvent>
11 #include <QMouseEvent>
12 #include <algorithm>
13 #include <math.h>
14
DeckViewCardDragItem(DeckViewCard * _item,const QPointF & _hotSpot,AbstractCardDragItem * parentDrag)15 DeckViewCardDragItem::DeckViewCardDragItem(DeckViewCard *_item,
16 const QPointF &_hotSpot,
17 AbstractCardDragItem *parentDrag)
18 : AbstractCardDragItem(_item, _hotSpot, parentDrag), currentZone(0)
19 {
20 }
21
updatePosition(const QPointF & cursorScenePos)22 void DeckViewCardDragItem::updatePosition(const QPointF &cursorScenePos)
23 {
24 QList<QGraphicsItem *> colliding = scene()->items(cursorScenePos);
25
26 DeckViewCardContainer *cursorZone = 0;
27 for (int i = colliding.size() - 1; i >= 0; i--)
28 if ((cursorZone = qgraphicsitem_cast<DeckViewCardContainer *>(colliding.at(i))))
29 break;
30 if (!cursorZone)
31 return;
32 currentZone = cursorZone;
33
34 QPointF newPos = cursorScenePos;
35 if (newPos != pos()) {
36 for (int i = 0; i < childDrags.size(); i++)
37 childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
38 setPos(newPos);
39 }
40 }
41
handleDrop(DeckViewCardContainer * target)42 void DeckViewCardDragItem::handleDrop(DeckViewCardContainer *target)
43 {
44 DeckViewCard *card = static_cast<DeckViewCard *>(item);
45 DeckViewCardContainer *start = static_cast<DeckViewCardContainer *>(item->parentItem());
46 start->removeCard(card);
47 target->addCard(card);
48 card->setParentItem(target);
49 }
50
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)51 void DeckViewCardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
52 {
53 setCursor(Qt::OpenHandCursor);
54 DeckViewScene *sc = static_cast<DeckViewScene *>(scene());
55 sc->removeItem(this);
56
57 if (currentZone) {
58 handleDrop(currentZone);
59 for (int i = 0; i < childDrags.size(); i++) {
60 DeckViewCardDragItem *c = static_cast<DeckViewCardDragItem *>(childDrags[i]);
61 c->handleDrop(currentZone);
62 sc->removeItem(c);
63 }
64
65 sc->updateContents();
66 }
67
68 event->accept();
69 }
70
DeckViewCard(const QString & _name,const QString & _originZone,QGraphicsItem * parent)71 DeckViewCard::DeckViewCard(const QString &_name, const QString &_originZone, QGraphicsItem *parent)
72 : AbstractCardItem(_name, 0, -1, parent), originZone(_originZone), dragItem(0)
73 {
74 setAcceptHoverEvents(true);
75 }
76
~DeckViewCard()77 DeckViewCard::~DeckViewCard()
78 {
79 delete dragItem;
80 }
81
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)82 void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
83 {
84 AbstractCardItem::paint(painter, option, widget);
85
86 painter->save();
87 QPen pen;
88 pen.setWidth(3);
89 pen.setJoinStyle(Qt::MiterJoin);
90 pen.setColor(originZone == DECK_ZONE_MAIN ? Qt::green : Qt::red);
91 painter->setPen(pen);
92 painter->drawRect(QRectF(1, 1, CARD_WIDTH - 2, CARD_HEIGHT - 2.5));
93 painter->restore();
94 }
95
mouseMoveEvent(QGraphicsSceneMouseEvent * event)96 void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
97 {
98 if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() <
99 2 * QApplication::startDragDistance())
100 return;
101
102 if (static_cast<DeckViewScene *>(scene())->getLocked())
103 return;
104
105 delete dragItem;
106 dragItem = new DeckViewCardDragItem(this, event->pos());
107 scene()->addItem(dragItem);
108 dragItem->updatePosition(event->scenePos());
109 dragItem->grabMouse();
110
111 QList<QGraphicsItem *> sel = scene()->selectedItems();
112 int j = 0;
113 for (int i = 0; i < sel.size(); i++) {
114 DeckViewCard *c = static_cast<DeckViewCard *>(sel.at(i));
115 if (c == this)
116 continue;
117 ++j;
118 QPointF childPos = QPointF(j * CARD_WIDTH / 2, 0);
119 DeckViewCardDragItem *drag = new DeckViewCardDragItem(c, childPos, dragItem);
120 drag->setPos(dragItem->pos() + childPos);
121 scene()->addItem(drag);
122 }
123 setCursor(Qt::OpenHandCursor);
124 }
125
mouseDoubleClickEvent(QMouseEvent * event)126 void DeckView::mouseDoubleClickEvent(QMouseEvent *event)
127 {
128 if (static_cast<DeckViewScene *>(scene())->getLocked())
129 return;
130
131 if (event->button() == Qt::LeftButton) {
132 QList<MoveCard_ToZone> result;
133 QList<QGraphicsItem *> sel = scene()->selectedItems();
134
135 for (int i = 0; i < sel.size(); i++) {
136 DeckViewCard *c = static_cast<DeckViewCard *>(sel.at(i));
137 DeckViewCardContainer *zone = static_cast<DeckViewCardContainer *>(c->parentItem());
138 MoveCard_ToZone m;
139 m.set_card_name(c->getName().toStdString());
140 m.set_start_zone(zone->getName().toStdString());
141
142 if (zone->getName() == DECK_ZONE_MAIN)
143 m.set_target_zone(DECK_ZONE_SIDE);
144 else if (zone->getName() == DECK_ZONE_SIDE)
145 m.set_target_zone(DECK_ZONE_MAIN);
146 else // Trying to move from another zone
147 m.set_target_zone(zone->getName().toStdString());
148
149 result.append(m);
150 }
151
152 deckViewScene->applySideboardPlan(result);
153 deckViewScene->rearrangeItems();
154 emit deckViewScene->sideboardPlanChanged();
155 }
156 }
157
hoverEnterEvent(QGraphicsSceneHoverEvent * event)158 void DeckViewCard::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
159 {
160 event->accept();
161 processHoverEvent();
162 }
163
DeckViewCardContainer(const QString & _name)164 DeckViewCardContainer::DeckViewCardContainer(const QString &_name) : QGraphicsItem(), name(_name), width(0), height(0)
165 {
166 setCacheMode(DeviceCoordinateCache);
167 }
168
boundingRect() const169 QRectF DeckViewCardContainer::boundingRect() const
170 {
171 return QRectF(0, 0, width, height);
172 }
173
paint(QPainter * painter,const QStyleOptionGraphicsItem *,QWidget *)174 void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
175 {
176 qreal totalTextWidth = getCardTypeTextWidth();
177
178 painter->fillRect(boundingRect(), themeManager->getTableBgBrush());
179 painter->setPen(QColor(255, 255, 255, 100));
180 painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY));
181
182 painter->setPen(QColor(Qt::white));
183 QFont f("Serif");
184 f.setStyleHint(QFont::Serif);
185 f.setPixelSize(24);
186 f.setWeight(QFont::Bold);
187 painter->setFont(f);
188 painter->drawText(10, 0, width - 20, separatorY, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine,
189 InnerDecklistNode::visibleNameFromName(name) + QString(": %1").arg(cards.size()));
190
191 f.setPixelSize(16);
192 painter->setFont(f);
193 QList<QString> cardTypeList = cardsByType.uniqueKeys();
194 qreal yUntilNow = separatorY + paddingY;
195 for (int i = 0; i < cardTypeList.size(); ++i) {
196 if (i != 0) {
197 painter->setPen(QColor(255, 255, 255, 100));
198 painter->drawLine(QPointF(0, yUntilNow - paddingY / 2), QPointF(width, yUntilNow - paddingY / 2));
199 }
200 qreal thisRowHeight = CARD_HEIGHT * currentRowsAndCols[i].first;
201 QRectF textRect(0, yUntilNow, totalTextWidth, thisRowHeight);
202 yUntilNow += thisRowHeight + paddingY;
203
204 QString displayString = QString("%1\n(%2)").arg(cardTypeList[i]).arg(cardsByType.count(cardTypeList[i]));
205
206 painter->setPen(Qt::white);
207 painter->drawText(textRect, Qt::AlignHCenter | Qt::AlignVCenter, displayString);
208 }
209 }
210
addCard(DeckViewCard * card)211 void DeckViewCardContainer::addCard(DeckViewCard *card)
212 {
213 cards.append(card);
214 cardsByType.insert(card->getInfo() ? card->getInfo()->getMainCardType() : "", card);
215 }
216
removeCard(DeckViewCard * card)217 void DeckViewCardContainer::removeCard(DeckViewCard *card)
218 {
219 cards.removeAt(cards.indexOf(card));
220 cardsByType.remove(card->getInfo() ? card->getInfo()->getMainCardType() : "", card);
221 }
222
getRowsAndCols() const223 QList<QPair<int, int>> DeckViewCardContainer::getRowsAndCols() const
224 {
225 QList<QPair<int, int>> result;
226 QList<QString> cardTypeList = cardsByType.uniqueKeys();
227 for (int i = 0; i < cardTypeList.size(); ++i)
228 result.append(QPair<int, int>(1, cardsByType.count(cardTypeList[i])));
229 return result;
230 }
231
getCardTypeTextWidth() const232 int DeckViewCardContainer::getCardTypeTextWidth() const
233 {
234 QFont f("Serif");
235 f.setStyleHint(QFont::Serif);
236 f.setPixelSize(16);
237 f.setWeight(QFont::Bold);
238 QFontMetrics fm(f);
239
240 int maxCardTypeWidth = 0;
241 QMapIterator<QString, DeckViewCard *> i(cardsByType);
242 while (i.hasNext()) {
243 int cardTypeWidth = fm.size(Qt::TextSingleLine, i.next().key()).width();
244 if (cardTypeWidth > maxCardTypeWidth)
245 maxCardTypeWidth = cardTypeWidth;
246 }
247
248 return maxCardTypeWidth + 15;
249 }
250
calculateBoundingRect(const QList<QPair<int,int>> & rowsAndCols) const251 QSizeF DeckViewCardContainer::calculateBoundingRect(const QList<QPair<int, int>> &rowsAndCols) const
252 {
253 qreal totalHeight = 0;
254 qreal totalWidth = 0;
255
256 // Calculate space needed for cards
257 for (int i = 0; i < rowsAndCols.size(); ++i) {
258 totalHeight += CARD_HEIGHT * rowsAndCols[i].first + paddingY;
259 if (CARD_WIDTH * rowsAndCols[i].second > totalWidth)
260 totalWidth = CARD_WIDTH * rowsAndCols[i].second;
261 }
262
263 return QSizeF(getCardTypeTextWidth() + totalWidth, totalHeight + separatorY + paddingY);
264 }
265
sortCardsByName(DeckViewCard * c1,DeckViewCard * c2)266 bool DeckViewCardContainer::sortCardsByName(DeckViewCard *c1, DeckViewCard *c2)
267 {
268 if (c1 && c2)
269 return c1->getName() < c2->getName();
270 return false;
271 }
272
rearrangeItems(const QList<QPair<int,int>> & rowsAndCols)273 void DeckViewCardContainer::rearrangeItems(const QList<QPair<int, int>> &rowsAndCols)
274 {
275 currentRowsAndCols = rowsAndCols;
276
277 int totalCols = 0, totalRows = 0;
278 qreal yUntilNow = separatorY + paddingY;
279 qreal x = (qreal)getCardTypeTextWidth();
280 for (int i = 0; i < rowsAndCols.size(); ++i) {
281 const int tempRows = rowsAndCols[i].first;
282 const int tempCols = rowsAndCols[i].second;
283 totalRows += tempRows;
284 if (tempCols > totalCols)
285 totalCols = tempCols;
286
287 QList<QString> cardTypeList = cardsByType.uniqueKeys();
288 QList<DeckViewCard *> row = cardsByType.values(cardTypeList[i]);
289 std::sort(row.begin(), row.end(), DeckViewCardContainer::sortCardsByName);
290 for (int j = 0; j < row.size(); ++j) {
291 DeckViewCard *card = row[j];
292 card->setPos(x + (j % tempCols) * CARD_WIDTH, yUntilNow + (j / tempCols) * CARD_HEIGHT);
293 }
294 yUntilNow += tempRows * CARD_HEIGHT + paddingY;
295 }
296
297 prepareGeometryChange();
298 QSizeF bRect = calculateBoundingRect(rowsAndCols);
299 width = bRect.width();
300 height = bRect.height();
301 }
302
setWidth(qreal _width)303 void DeckViewCardContainer::setWidth(qreal _width)
304 {
305 prepareGeometryChange();
306 width = _width;
307 update();
308 }
309
DeckViewScene(QObject * parent)310 DeckViewScene::DeckViewScene(QObject *parent) : QGraphicsScene(parent), locked(true), deck(0), optimalAspectRatio(1.0)
311 {
312 }
313
~DeckViewScene()314 DeckViewScene::~DeckViewScene()
315 {
316 clearContents();
317 delete deck;
318 }
319
clearContents()320 void DeckViewScene::clearContents()
321 {
322 QMapIterator<QString, DeckViewCardContainer *> i(cardContainers);
323 while (i.hasNext())
324 delete i.next().value();
325 cardContainers.clear();
326 }
327
setDeck(const DeckList & _deck)328 void DeckViewScene::setDeck(const DeckList &_deck)
329 {
330 if (deck)
331 delete deck;
332
333 deck = new DeckList(_deck);
334 rebuildTree();
335 applySideboardPlan(deck->getCurrentSideboardPlan());
336 rearrangeItems();
337 }
338
rebuildTree()339 void DeckViewScene::rebuildTree()
340 {
341 clearContents();
342
343 if (!deck)
344 return;
345
346 InnerDecklistNode *listRoot = deck->getRoot();
347 for (int i = 0; i < listRoot->size(); i++) {
348 InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
349
350 DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0);
351 if (!container) {
352 container = new DeckViewCardContainer(currentZone->getName());
353 cardContainers.insert(currentZone->getName(), container);
354 addItem(container);
355 }
356
357 for (int j = 0; j < currentZone->size(); j++) {
358 DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
359 if (!currentCard)
360 continue;
361
362 for (int k = 0; k < currentCard->getNumber(); ++k) {
363 DeckViewCard *newCard = new DeckViewCard(currentCard->getName(), currentZone->getName(), container);
364 container->addCard(newCard);
365 emit newCardAdded(newCard);
366 }
367 }
368 }
369 }
370
applySideboardPlan(const QList<MoveCard_ToZone> & plan)371 void DeckViewScene::applySideboardPlan(const QList<MoveCard_ToZone> &plan)
372 {
373 for (int i = 0; i < plan.size(); ++i) {
374 const MoveCard_ToZone &m = plan[i];
375 DeckViewCardContainer *start = cardContainers.value(QString::fromStdString(m.start_zone()));
376 DeckViewCardContainer *target = cardContainers.value(QString::fromStdString(m.target_zone()));
377 if (!start || !target)
378 continue;
379
380 DeckViewCard *card = 0;
381 const QList<DeckViewCard *> &cardList = start->getCards();
382 for (int j = 0; j < cardList.size(); ++j)
383 if (cardList[j]->getName() == QString::fromStdString(m.card_name())) {
384 card = cardList[j];
385 break;
386 }
387 if (!card)
388 continue;
389
390 start->removeCard(card);
391 target->addCard(card);
392 card->setParentItem(target);
393 }
394 }
395
rearrangeItems()396 void DeckViewScene::rearrangeItems()
397 {
398 const int spacing = CARD_HEIGHT / 3;
399 QList<DeckViewCardContainer *> contList = cardContainers.values();
400
401 // Initialize space requirements
402 QList<QList<QPair<int, int>>> rowsAndColsList;
403 QList<QList<int>> cardCountList;
404 for (int i = 0; i < contList.size(); ++i) {
405 QList<QPair<int, int>> rowsAndCols = contList[i]->getRowsAndCols();
406 rowsAndColsList.append(rowsAndCols);
407
408 cardCountList.append(QList<int>());
409 for (int j = 0; j < rowsAndCols.size(); ++j)
410 cardCountList[i].append(rowsAndCols[j].second);
411 }
412
413 qreal totalHeight, totalWidth;
414 for (;;) {
415 // Calculate total size before this iteration
416 totalHeight = -spacing;
417 totalWidth = 0;
418 for (int i = 0; i < contList.size(); ++i) {
419 QSizeF contSize = contList[i]->calculateBoundingRect(rowsAndColsList[i]);
420 totalHeight += contSize.height() + spacing;
421 if (contSize.width() > totalWidth)
422 totalWidth = contSize.width();
423 }
424
425 // We're done when the aspect ratio shifts from too high to too low.
426 if (totalWidth / totalHeight <= optimalAspectRatio)
427 break;
428
429 // Find category with highest column count
430 int maxIndex1 = -1, maxIndex2 = -1, maxCols = 0;
431 for (int i = 0; i < rowsAndColsList.size(); ++i)
432 for (int j = 0; j < rowsAndColsList[i].size(); ++j)
433 if (rowsAndColsList[i][j].second > maxCols) {
434 maxIndex1 = i;
435 maxIndex2 = j;
436 maxCols = rowsAndColsList[i][j].second;
437 }
438
439 // Add row to category
440 const int maxRows = rowsAndColsList[maxIndex1][maxIndex2].first;
441 const int maxCardCount = cardCountList[maxIndex1][maxIndex2];
442 rowsAndColsList[maxIndex1][maxIndex2] =
443 QPair<int, int>(maxRows + 1, (int)ceil((qreal)maxCardCount / (qreal)(maxRows + 1)));
444 }
445
446 totalHeight = -spacing;
447 for (int i = 0; i < contList.size(); ++i) {
448 DeckViewCardContainer *c = contList[i];
449 c->rearrangeItems(rowsAndColsList[i]);
450 c->setPos(0, totalHeight + spacing);
451 totalHeight += c->boundingRect().height() + spacing;
452 }
453
454 totalWidth = totalHeight * optimalAspectRatio;
455 for (int i = 0; i < contList.size(); ++i)
456 contList[i]->setWidth(totalWidth);
457
458 setSceneRect(QRectF(0, 0, totalWidth, totalHeight));
459 }
460
updateContents()461 void DeckViewScene::updateContents()
462 {
463 rearrangeItems();
464 emit sideboardPlanChanged();
465 }
466
getSideboardPlan() const467 QList<MoveCard_ToZone> DeckViewScene::getSideboardPlan() const
468 {
469 QList<MoveCard_ToZone> result;
470 QMapIterator<QString, DeckViewCardContainer *> containerIterator(cardContainers);
471 while (containerIterator.hasNext()) {
472 DeckViewCardContainer *cont = containerIterator.next().value();
473 const QList<DeckViewCard *> cardList = cont->getCards();
474 for (int i = 0; i < cardList.size(); ++i)
475 if (cardList[i]->getOriginZone() != cont->getName()) {
476 MoveCard_ToZone m;
477 m.set_card_name(cardList[i]->getName().toStdString());
478 m.set_start_zone(cardList[i]->getOriginZone().toStdString());
479 m.set_target_zone(cont->getName().toStdString());
480 result.append(m);
481 }
482 }
483 return result;
484 }
485
resetSideboardPlan()486 void DeckViewScene::resetSideboardPlan()
487 {
488 rebuildTree();
489 rearrangeItems();
490 }
491
DeckView(QWidget * parent)492 DeckView::DeckView(QWidget *parent) : QGraphicsView(parent)
493 {
494 deckViewScene = new DeckViewScene(this);
495
496 setBackgroundBrush(QBrush(QColor(0, 0, 0)));
497 setDragMode(RubberBandDrag);
498 setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing /* | QPainter::SmoothPixmapTransform*/);
499 setScene(deckViewScene);
500
501 connect(deckViewScene, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(updateSceneRect(const QRectF &)));
502 connect(deckViewScene, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *)));
503 connect(deckViewScene, SIGNAL(sideboardPlanChanged()), this, SIGNAL(sideboardPlanChanged()));
504 }
505
resizeEvent(QResizeEvent * event)506 void DeckView::resizeEvent(QResizeEvent *event)
507 {
508 QGraphicsView::resizeEvent(event);
509 deckViewScene->setOptimalAspectRatio((qreal)width() / (qreal)height());
510 deckViewScene->rearrangeItems();
511 }
512
updateSceneRect(const QRectF & rect)513 void DeckView::updateSceneRect(const QRectF &rect)
514 {
515 fitInView(rect, Qt::KeepAspectRatio);
516 }
517
setDeck(const DeckList & _deck)518 void DeckView::setDeck(const DeckList &_deck)
519 {
520 deckViewScene->setDeck(_deck);
521 }
522
resetSideboardPlan()523 void DeckView::resetSideboardPlan()
524 {
525 deckViewScene->resetSideboardPlan();
526 }
527