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