servatrice/cockatrice/src/deckview.cpp
2017-05-04 23:28:51 -04:00

522 lines
17 KiB
C++

#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QMouseEvent>
#include <math.h>
#include "deckview.h"
#include "decklist.h"
#include "carddatabase.h"
#include "settingscache.h"
#include "thememanager.h"
#include "main.h"
DeckViewCardDragItem::DeckViewCardDragItem(DeckViewCard *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag)
: AbstractCardDragItem(_item, _hotSpot, parentDrag), currentZone(0)
{
}
void DeckViewCardDragItem::updatePosition(const QPointF &cursorScenePos)
{
QList<QGraphicsItem *> colliding = scene()->items(cursorScenePos);
DeckViewCardContainer *cursorZone = 0;
for (int i = colliding.size() - 1; i >= 0; i--)
if ((cursorZone = qgraphicsitem_cast<DeckViewCardContainer *>(colliding.at(i))))
break;
if (!cursorZone)
return;
currentZone = cursorZone;
QPointF newPos = cursorScenePos;
if (newPos != pos()) {
for (int i = 0; i < childDrags.size(); i++)
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
setPos(newPos);
}
}
void DeckViewCardDragItem::handleDrop(DeckViewCardContainer *target)
{
DeckViewCard *card = static_cast<DeckViewCard *>(item);
DeckViewCardContainer *start = static_cast<DeckViewCardContainer *>(item->parentItem());
start->removeCard(card);
target->addCard(card);
card->setParentItem(target);
}
void DeckViewCardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
setCursor(Qt::OpenHandCursor);
DeckViewScene *sc = static_cast<DeckViewScene *>(scene());
sc->removeItem(this);
if (currentZone) {
handleDrop(currentZone);
for (int i = 0; i < childDrags.size(); i++) {
DeckViewCardDragItem *c = static_cast<DeckViewCardDragItem *>(childDrags[i]);
c->handleDrop(currentZone);
sc->removeItem(c);
}
sc->updateContents();
}
event->accept();
}
DeckViewCard::DeckViewCard(const QString &_name, const QString &_originZone, QGraphicsItem *parent)
: AbstractCardItem(_name, 0, -1, parent), originZone(_originZone), dragItem(0)
{
setAcceptHoverEvents(true);
}
DeckViewCard::~DeckViewCard()
{
delete dragItem;
}
void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
AbstractCardItem::paint(painter, option, widget);
painter->save();
QPen pen;
pen.setWidth(3);
pen.setJoinStyle(Qt::MiterJoin);
pen.setColor(originZone == DECK_ZONE_MAIN ? Qt::green : Qt::red);
painter->setPen(pen);
painter->drawRect(QRectF(1, 1, CARD_WIDTH - 2, CARD_HEIGHT - 2.5));
painter->restore();
}
void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < 2 * QApplication::startDragDistance())
return;
if (static_cast<DeckViewScene *>(scene())->getLocked())
return;
delete dragItem;
dragItem = new DeckViewCardDragItem(this, event->pos());
scene()->addItem(dragItem);
dragItem->updatePosition(event->scenePos());
dragItem->grabMouse();
QList<QGraphicsItem *> sel = scene()->selectedItems();
int j = 0;
for (int i = 0; i < sel.size(); i++) {
DeckViewCard *c = static_cast<DeckViewCard *>(sel.at(i));
if (c == this)
continue;
++j;
QPointF childPos = QPointF(j * CARD_WIDTH / 2, 0);
DeckViewCardDragItem *drag = new DeckViewCardDragItem(c, childPos, dragItem);
drag->setPos(dragItem->pos() + childPos);
scene()->addItem(drag);
}
setCursor(Qt::OpenHandCursor);
}
void DeckView::mouseDoubleClickEvent(QMouseEvent *event)
{
if (static_cast<DeckViewScene *>(scene())->getLocked())
return;
if (event->button() == Qt::LeftButton)
{
QList<MoveCard_ToZone> result;
QList<QGraphicsItem *> sel = scene()->selectedItems();
for (int i = 0; i < sel.size(); i++) {
DeckViewCard *c = static_cast<DeckViewCard *>(sel.at(i));
DeckViewCardContainer *zone = static_cast<DeckViewCardContainer *>(c->parentItem());
MoveCard_ToZone m;
m.set_card_name(c->getName().toStdString());
m.set_start_zone(zone->getName().toStdString());
if (zone->getName() == DECK_ZONE_MAIN)
m.set_target_zone(DECK_ZONE_SIDE);
else if (zone->getName() == DECK_ZONE_SIDE)
m.set_target_zone(DECK_ZONE_MAIN);
else // Trying to move from another zone
m.set_target_zone(zone->getName().toStdString());
result.append(m);
}
deckViewScene->applySideboardPlan(result);
deckViewScene->rearrangeItems();
emit deckViewScene->sideboardPlanChanged();
}
}
void DeckViewCard::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
event->accept();
processHoverEvent();
}
DeckViewCardContainer::DeckViewCardContainer(const QString &_name)
: QGraphicsItem(), name(_name), width(0), height(0)
{
setCacheMode(DeviceCoordinateCache);
}
QRectF DeckViewCardContainer::boundingRect() const
{
return QRectF(0, 0, width, height);
}
void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{
qreal totalTextWidth = getCardTypeTextWidth();
painter->fillRect(boundingRect(), themeManager->getTableBgBrush());
painter->setPen(QColor(255, 255, 255, 100));
painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY));
painter->setPen(QColor(Qt::white));
QFont f("Serif");
f.setStyleHint(QFont::Serif);
f.setPixelSize(24);
f.setWeight(QFont::Bold);
painter->setFont(f);
painter->drawText(10, 0, width - 20, separatorY, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, InnerDecklistNode::visibleNameFromName(name) + QString(": %1").arg(cards.size()));
f.setPixelSize(16);
painter->setFont(f);
QList<QString> cardTypeList = cardsByType.uniqueKeys();
qreal yUntilNow = separatorY + paddingY;
for (int i = 0; i < cardTypeList.size(); ++i) {
if (i != 0) {
painter->setPen(QColor(255, 255, 255, 100));
painter->drawLine(QPointF(0, yUntilNow - paddingY / 2), QPointF(width, yUntilNow - paddingY / 2));
}
qreal thisRowHeight = CARD_HEIGHT * currentRowsAndCols[i].first;
QRectF textRect(0, yUntilNow, totalTextWidth, thisRowHeight);
yUntilNow += thisRowHeight + paddingY;
QString displayString = QString("%1\n(%2)").arg(cardTypeList[i]).arg(cardsByType.count(cardTypeList[i]));
painter->setPen(Qt::white);
painter->drawText(textRect, Qt::AlignHCenter | Qt::AlignVCenter, displayString);
}
}
void DeckViewCardContainer::addCard(DeckViewCard *card)
{
cards.append(card);
cardsByType.insert(card->getInfo() ? card->getInfo()->getMainCardType() : "", card);
}
void DeckViewCardContainer::removeCard(DeckViewCard *card)
{
cards.removeAt(cards.indexOf(card));
cardsByType.remove(card->getInfo() ? card->getInfo()->getMainCardType(): "", card);
}
QList<QPair<int, int> > DeckViewCardContainer::getRowsAndCols() const
{
QList<QPair<int, int> > result;
QList<QString> cardTypeList = cardsByType.uniqueKeys();
for (int i = 0; i < cardTypeList.size(); ++i)
result.append(QPair<int, int>(1, cardsByType.count(cardTypeList[i])));
return result;
}
int DeckViewCardContainer::getCardTypeTextWidth() const
{
QFont f("Serif");
f.setStyleHint(QFont::Serif);
f.setPixelSize(16);
f.setWeight(QFont::Bold);
QFontMetrics fm(f);
int maxCardTypeWidth = 0;
QMapIterator<QString, DeckViewCard *> i(cardsByType);
while (i.hasNext()) {
int cardTypeWidth = fm.size(Qt::TextSingleLine, i.next().key()).width();
if (cardTypeWidth > maxCardTypeWidth)
maxCardTypeWidth = cardTypeWidth;
}
return maxCardTypeWidth + 15;
}
QSizeF DeckViewCardContainer::calculateBoundingRect(const QList<QPair<int, int> > &rowsAndCols) const
{
qreal totalHeight = 0;
qreal totalWidth = 0;
// Calculate space needed for cards
for (int i = 0; i < rowsAndCols.size(); ++i) {
totalHeight += CARD_HEIGHT * rowsAndCols[i].first + paddingY;
if (CARD_WIDTH * rowsAndCols[i].second > totalWidth)
totalWidth = CARD_WIDTH * rowsAndCols[i].second;
}
return QSizeF(getCardTypeTextWidth() + totalWidth, totalHeight + separatorY + paddingY);
}
bool DeckViewCardContainer::sortCardsByName(DeckViewCard * c1, DeckViewCard * c2)
{
if (c1 && c2)
return c1->getName() < c2->getName();
return false;
}
void DeckViewCardContainer::rearrangeItems(const QList<QPair<int, int> > &rowsAndCols)
{
currentRowsAndCols = rowsAndCols;
int totalCols = 0, totalRows = 0;
qreal yUntilNow = separatorY + paddingY;
qreal x = (qreal) getCardTypeTextWidth();
for (int i = 0; i < rowsAndCols.size(); ++i) {
const int tempRows = rowsAndCols[i].first;
const int tempCols = rowsAndCols[i].second;
totalRows += tempRows;
if (tempCols > totalCols)
totalCols = tempCols;
QList<QString> cardTypeList = cardsByType.uniqueKeys();
QList<DeckViewCard *> row = cardsByType.values(cardTypeList[i]);
qSort( row.begin(), row.end(), DeckViewCardContainer::sortCardsByName);
for (int j = 0; j < row.size(); ++j) {
DeckViewCard *card = row[j];
card->setPos(x + (j % tempCols) * CARD_WIDTH, yUntilNow + (j / tempCols) * CARD_HEIGHT);
}
yUntilNow += tempRows * CARD_HEIGHT + paddingY;
}
prepareGeometryChange();
QSizeF bRect = calculateBoundingRect(rowsAndCols);
width = bRect.width();
height = bRect.height();
}
void DeckViewCardContainer::setWidth(qreal _width)
{
prepareGeometryChange();
width = _width;
update();
}
DeckViewScene::DeckViewScene(QObject *parent)
: QGraphicsScene(parent), locked(true), deck(0), optimalAspectRatio(1.0)
{
}
DeckViewScene::~DeckViewScene()
{
clearContents();
delete deck;
}
void DeckViewScene::clearContents()
{
QMapIterator<QString, DeckViewCardContainer *> i(cardContainers);
while (i.hasNext())
delete i.next().value();
cardContainers.clear();
}
void DeckViewScene::setDeck(const DeckList &_deck)
{
if (deck)
delete deck;
deck = new DeckList(_deck);
rebuildTree();
applySideboardPlan(deck->getCurrentSideboardPlan());
rearrangeItems();
}
void DeckViewScene::rebuildTree()
{
clearContents();
if (!deck)
return;
InnerDecklistNode *listRoot = deck->getRoot();
for (int i = 0; i < listRoot->size(); i++) {
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0);
if (!container) {
container = new DeckViewCardContainer(currentZone->getName());
cardContainers.insert(currentZone->getName(), container);
addItem(container);
}
for (int j = 0; j < currentZone->size(); j++) {
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
if (!currentCard)
continue;
for (int k = 0; k < currentCard->getNumber(); ++k) {
DeckViewCard *newCard = new DeckViewCard(currentCard->getName(), currentZone->getName(), container);
container->addCard(newCard);
emit newCardAdded(newCard);
}
}
}
}
void DeckViewScene::applySideboardPlan(const QList<MoveCard_ToZone> &plan)
{
for (int i = 0; i < plan.size(); ++i) {
const MoveCard_ToZone &m = plan[i];
DeckViewCardContainer *start = cardContainers.value(QString::fromStdString(m.start_zone()));
DeckViewCardContainer *target = cardContainers.value(QString::fromStdString(m.target_zone()));
if (!start || !target)
continue;
DeckViewCard *card = 0;
const QList<DeckViewCard *> &cardList = start->getCards();
for (int j = 0; j < cardList.size(); ++j)
if (cardList[j]->getName() == QString::fromStdString(m.card_name())) {
card = cardList[j];
break;
}
if (!card)
continue;
start->removeCard(card);
target->addCard(card);
card->setParentItem(target);
}
}
void DeckViewScene::rearrangeItems()
{
const int spacing = CARD_HEIGHT / 3;
QList<DeckViewCardContainer *> contList = cardContainers.values();
// Initialize space requirements
QList<QList<QPair<int, int> > > rowsAndColsList;
QList<QList<int> > cardCountList;
for (int i = 0; i < contList.size(); ++i) {
QList<QPair<int, int> > rowsAndCols = contList[i]->getRowsAndCols();
rowsAndColsList.append(rowsAndCols);
cardCountList.append(QList<int>());
for (int j = 0; j < rowsAndCols.size(); ++j)
cardCountList[i].append(rowsAndCols[j].second);
}
qreal totalHeight, totalWidth;
for (;;) {
// Calculate total size before this iteration
totalHeight = -spacing;
totalWidth = 0;
for (int i = 0; i < contList.size(); ++i) {
QSizeF contSize = contList[i]->calculateBoundingRect(rowsAndColsList[i]);
totalHeight += contSize.height() + spacing;
if (contSize.width() > totalWidth)
totalWidth = contSize.width();
}
// We're done when the aspect ratio shifts from too high to too low.
if (totalWidth / totalHeight <= optimalAspectRatio)
break;
// Find category with highest column count
int maxIndex1 = -1, maxIndex2 = -1, maxCols = 0;
for (int i = 0; i < rowsAndColsList.size(); ++i)
for (int j = 0; j < rowsAndColsList[i].size(); ++j)
if (rowsAndColsList[i][j].second > maxCols) {
maxIndex1 = i;
maxIndex2 = j;
maxCols = rowsAndColsList[i][j].second;
}
// Add row to category
const int maxRows = rowsAndColsList[maxIndex1][maxIndex2].first;
const int maxCardCount = cardCountList[maxIndex1][maxIndex2];
rowsAndColsList[maxIndex1][maxIndex2] = QPair<int, int>(maxRows + 1, (int) ceil((qreal) maxCardCount / (qreal) (maxRows + 1)));
}
totalHeight = -spacing;
for (int i = 0; i < contList.size(); ++i) {
DeckViewCardContainer *c = contList[i];
c->rearrangeItems(rowsAndColsList[i]);
c->setPos(0, totalHeight + spacing);
totalHeight += c->boundingRect().height() + spacing;
}
totalWidth = totalHeight * optimalAspectRatio;
for (int i = 0; i < contList.size(); ++i)
contList[i]->setWidth(totalWidth);
setSceneRect(QRectF(0, 0, totalWidth, totalHeight));
}
void DeckViewScene::updateContents()
{
rearrangeItems();
emit sideboardPlanChanged();
}
QList<MoveCard_ToZone> DeckViewScene::getSideboardPlan() const
{
QList<MoveCard_ToZone> result;
QMapIterator<QString, DeckViewCardContainer *> containerIterator(cardContainers);
while (containerIterator.hasNext()) {
DeckViewCardContainer *cont = containerIterator.next().value();
const QList<DeckViewCard *> cardList = cont->getCards();
for (int i = 0; i < cardList.size(); ++i)
if (cardList[i]->getOriginZone() != cont->getName()) {
MoveCard_ToZone m;
m.set_card_name(cardList[i]->getName().toStdString());
m.set_start_zone(cardList[i]->getOriginZone().toStdString());
m.set_target_zone(cont->getName().toStdString());
result.append(m);
}
}
return result;
}
void DeckViewScene::resetSideboardPlan()
{
rebuildTree();
rearrangeItems();
}
DeckView::DeckView(QWidget *parent)
: QGraphicsView(parent)
{
deckViewScene = new DeckViewScene(this);
setBackgroundBrush(QBrush(QColor(0, 0, 0)));
setDragMode(RubberBandDrag);
setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing/* | QPainter::SmoothPixmapTransform*/);
setScene(deckViewScene);
connect(deckViewScene, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(updateSceneRect(const QRectF &)));
connect(deckViewScene, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *)));
connect(deckViewScene, SIGNAL(sideboardPlanChanged()), this, SIGNAL(sideboardPlanChanged()));
}
void DeckView::resizeEvent(QResizeEvent *event)
{
QGraphicsView::resizeEvent(event);
deckViewScene->setOptimalAspectRatio((qreal) width() / (qreal) height());
deckViewScene->rearrangeItems();
}
void DeckView::updateSceneRect(const QRectF &rect)
{
fitInView(rect, Qt::KeepAspectRatio);
}
void DeckView::setDeck(const DeckList &_deck)
{
deckViewScene->setDeck(_deck);
}
void DeckView::resetSideboardPlan()
{
deckViewScene->resetSideboardPlan();
}