/* QSoloCards is a collection of Solitaire card games written using Qt Copyright (C) 2009 Steve Moore This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "CardStack.h" #include #include #include #include #include #include #include "CardPixmaps.h" #include "CardAnimationLock.h" #include CardStackMap CardStack::m_cardStackMap; bool CardStack::m_lockUserInteraction=false; ///////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// CardStack::CardStack() :QObject(),QGraphicsPixmapItem(), m_hintHighlightIndex(-1), m_stackName(), m_cardVector(), m_highlighted(false), m_showRedealCircle(false), m_autoFaceUp(false), m_mouseMoved(false), m_dragStartPos(0,0), m_flipAni(), m_dragStack(this) { // set the name of this stack and add it to the map of stacks. m_stackName=QString::number((qlonglong)this); m_cardStackMap[this->stackName()]=this; // accept drops and mouse hover events this->setAcceptDrops(true); this->setAcceptHoverEvents(true); this->setZValue(1); this->setCursor(Qt::PointingHandCursor); this->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); // call update stack to set the initial view of the stack as empty. this->updateStack(); this->connect(&m_flipAni,SIGNAL(flipComplete(CardStack *)), this,SLOT(slotFlipComplete(CardStack *))); this->connect(&m_dragStack,SIGNAL(cardsMoved(const CardMoveRecord &)), this,SLOT(slotDragCardsMoved(const CardMoveRecord &))); } ///////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// CardStack::~CardStack() { // remove the pointer from the map of stacks to the class. m_cardStackMap.erase(this->stackName()); } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// bool CardStack::allCardsFaceUp()const { bool rc=true; unsigned int i; for(i=0;im_cardVector.size();i++) { if (!this->m_cardVector[i].isFaceUp()) { rc=false; break; } } return rc; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// bool CardStack::cardsAscendingTopToBottom()const { bool rc=true; const PlayingCardVector & cardVector=this->getCardVector(); int i; // see if the cards are in descending order from // index 0 to n. for (i=cardVector.size()-1;i>0;i--) { if (cardVector[i]>cardVector[i-1]) { rc=false; break; } } return rc; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// bool CardStack::cardsDecendingTopToBottom()const { bool rc=true; const PlayingCardVector & cardVector=this->getCardVector(); int i; // see if the cards are in descending order from // index 0 to n. for (i=cardVector.size()-1;i>0;i--) { if (cardVector[i]m_cardVector.size()>0 && faceUp!=this->m_cardVector[this->m_cardVector.size()-1].isFaceUp()) { flipCard(-1); } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// bool CardStack::flipCard(int index,bool aniIfEnabled) { bool rc=false; if (this->m_cardVector.size()>0) { // if index is less than 0 assume it is the last card. if (index<0) { index=this->m_cardVector.size()-1; } if (index<(int)this->m_cardVector.size()) { if (aniIfEnabled) { m_flipAni.flipCard(this); } this->m_cardVector[index].setFaceUp(!this->m_cardVector[index].isFaceUp()); this->updateStack(); rc=true; } } return rc; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// bool CardStack::flipCard(int index,CardMoveRecord & moveRecord,bool aniIfEnabled) { bool rc=flipCard(index,aniIfEnabled); if (rc) { // if index is less than 0 assume it is the last card. if (index<0) { index=this->m_cardVector.size()-1; } moveRecord.push_back(CardMoveRecordItem(this->stackName(),index)); } return rc; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CardStack::addCard(const PlayingCard & newCard) { m_cardVector.push_back(newCard); if (isFlipAniRunning()) { m_flipAni.stopAni(); this->updateStack(); } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CardStack::addCards(const PlayingCardVector & cardVector) { for(unsigned int i=0;iupdateStack(); } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CardStack::addCard(const PlayingCard & newCard,CardMoveRecord & moveRecord,bool justUpdateRec) { if (!justUpdateRec) { this->addCard(newCard); } PlayingCardVector cardVector; cardVector.push_back(newCard); moveRecord.push_back(CardMoveRecordItem(this->stackName(),CardMoveRecordItem::AddCards,cardVector)); } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CardStack::addCards(const PlayingCardVector & cardVector,CardMoveRecord & moveRecord,bool justUpdateRec) { if (!justUpdateRec) { this->addCards(cardVector); } moveRecord.push_back(CardMoveRecordItem(this->stackName(),CardMoveRecordItem::AddCards,cardVector)); } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// PlayingCard CardStack::removeTopCard() { PlayingCard card(PlayingCard::MaxSuit,PlayingCard::MaxCardIndex); if (!this->m_cardVector.empty()) { card=m_cardVector.back(); this->m_cardVector.pop_back(); } return card; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// PlayingCard CardStack::removeTopCard(CardMoveRecord & moveRecord) { PlayingCard card(PlayingCard::MaxSuit,PlayingCard::MaxCardIndex); card=this->removeTopCard(); // if the card is valid update the moveRecord if (card.isValid()) { PlayingCardVector cardVector; cardVector.push_back(card); moveRecord.push_back(CardMoveRecordItem(this->stackName(),CardMoveRecordItem::RemoveCards,cardVector)); } return card; } ///////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// bool CardStack::removeCardsStartingAt(unsigned int index,PlayingCardVector & removedCards) { bool rc=false; if (indexm_cardVector.size()) { rc=true; unsigned int i; removedCards.clear(); for(i=index;im_cardVector.size();i++) { removedCards.push_back(this->m_cardVector[i]); } while(m_cardVector.size()>index) { this->m_cardVector.pop_back(); } } return rc; } ///////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// bool CardStack::removeCardsStartingAt(unsigned int index, PlayingCardVector & removedCards, CardMoveRecord & moveRecord, bool justUpdateRec) { bool rc=false; if (!justUpdateRec) { rc=this->removeCardsStartingAt(index,removedCards); } else if (indexm_cardVector.size()) { rc=true; unsigned int i; removedCards.clear(); for(i=index;im_cardVector.size();i++) { removedCards.push_back(this->m_cardVector[i]); } } if (rc) { // ok now add the move records for removal of the cards from this stack // it will be a remove and then a flip of the card before if it is face down moveRecord.push_back(CardMoveRecordItem(this->stackName(),CardMoveRecordItem::RemoveCards,removedCards)); // also if we are in autoflip mode add that to the flip record. if (0!=index && this->m_autoFaceUp && !this->m_cardVector[index-1].isFaceUp()) { moveRecord.push_back(CardMoveRecordItem(this->stackName(),index-1)); } } return rc; } ///////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// void CardStack::removeAllCards() { // stop any flip animation we have going. m_flipAni.stopAni(); m_cardVector.clear(); } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// bool CardStack::getMovableCards(PlayingCardVector & cardVector, unsigned int & index) const { bool rc=false; if (this->m_cardVector.size()>0) { int moveIndex=-1; // ok let's go up the stack and find the last card that we can move for (int i=this->m_cardVector.size()-1;i>=0;i--) { if (this->canMoveCard((unsigned int)i)) { moveIndex=i; } else { break; } } // did we get an index of a card. if (moveIndex>=0) { rc=true; for(unsigned int j=(unsigned int)moveIndex;jm_cardVector.size();j++) { cardVector.push_back(this->m_cardVector[j]); } index=(unsigned int)moveIndex; } } return rc; } ///////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// void CardStack::updateStack() { QPixmap * pPixmap=NULL; if (this->isFlipAniRunning()) { PlayingCardVector cardVector(this->m_cardVector); // the stack should have at least one card ie the one being flipped // but make sure. if (this->m_cardVector.size()>0) { cardVector.pop_back(); } pPixmap=getStackPixmap(cardVector,m_highlighted,m_hintHighlightIndex); } else { pPixmap=getStackPixmap(this->m_cardVector,m_highlighted,m_hintHighlightIndex); } if (NULL!=pPixmap) { this->setPixmap(*pPixmap); delete pPixmap; } } ///////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// QPixmap * CardStack::getStackPixmap(const PlayingCardVector & cardVector, bool highlighted, int hintHighlightIndex) { bool hl=((hintHighlightIndex>=0 || (HintHighlightNoCards==hintHighlightIndex && 0==cardVector.size()))|| highlighted); QPixmap * pPixmap=NULL; // if the stack is not empty either show a card or the card back if (cardVector.size()>0) { PlayingCard card(cardVector[cardVector.size()-1]); if (card.isFaceUp()) { pPixmap=new QPixmap(CardPixmaps::getInst().getCardPixmap(card,hl)); } else { pPixmap=new QPixmap(CardPixmaps::getInst().getCardBackPixmap(hl)); } } // if the stack is empty show the empty pixmap. else { pPixmap=new QPixmap(CardPixmaps::getInst().getCardNonePixmap(hl,this->m_showRedealCircle)); } return pPixmap; } ///////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// void CardStack::updateAllStacks() { CardStackMap::iterator it; CardPixmaps::getInst().clearPixmapCache(); for (it=m_cardStackMap.begin();it!=m_cardStackMap.end();it++) { it->second->updateStack(); } } ///////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// void CardStack::clearAllStacks() { CardStackMap::iterator it; for (it=m_cardStackMap.begin();it!=m_cardStackMap.end();it++) { it->second->removeAllCards(); } } ///////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// CardStack * CardStack::getStackByName(const std::string & stackName) { CardStack * pStack=NULL; CardStackMap::iterator it=m_cardStackMap.find(stackName); if (m_cardStackMap.end()!=it) { pStack=it->second; } return pStack; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// bool CardStack::isCardStack(QGraphicsItem * pGraphicsItem) { CardStack * pCardStack=getStackByName(QString::number((qlonglong)pGraphicsItem).toStdString()); bool rc=false; if (NULL!=pCardStack) { rc=true; } return true; } ///////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// void CardStack::processCardMoveRecord(ProcessCardMoveRecordType type, CardMoveRecord moveRecord) { while(!moveRecord.empty()) { CardMoveRecordItem currItem(((CardStack::RedoMove==type)?moveRecord.front():moveRecord.back())); CardStack * pStack=CardStack::m_cardStackMap[currItem.stackName()]; const PlayingCardVector & cardVector=currItem.cardVector(); unsigned int i; CardMoveRecordItem::MoveType moveType=currItem.moveType(); // for undo we are actually undoing something that was done // so we need to AddCards if they were removed or RemoveCards // if they were added if (CardStack::UndoMove==type) { if (CardMoveRecordItem::RemoveCards==moveType) { moveType=CardMoveRecordItem::AddCards; } else if (CardMoveRecordItem::AddCards==moveType) { moveType=CardMoveRecordItem::RemoveCards; } } switch(moveType) { // remove cards to a stack case CardMoveRecordItem::RemoveCards: { for (i=0;iremoveTopCard(); } pStack->updateStack(); } break; // add cards to a stack case CardMoveRecordItem::AddCards: { pStack->addCards(cardVector); pStack->updateStack(); } break; // for this case we are just going to flip the card over case CardMoveRecordItem::FlipCard: { // in this case we just want to flip the last card if (pStack->m_cardVector.size()>0) { unsigned int flipIndex=0; bool isValid=false; if (currItem.flipIndex()<0) { flipIndex=pStack->m_cardVector.size()-1; isValid=true; } else if (currItem.flipIndex()<(int)(pStack->m_cardVector.size())) { flipIndex=(unsigned int)currItem.flipIndex(); isValid=true; } if (isValid) { pStack->m_cardVector[flipIndex].setFaceUp(!pStack->m_cardVector[flipIndex].isFaceUp()); pStack->updateStack(); } } } break; }; if (CardStack::RedoMove==type) { moveRecord.pop_front(); } else { moveRecord.pop_back(); } } } ///////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// void CardStack::showHint(CardStack * pSrc, unsigned int srcCardIndex, CardStack * pDst) { if (NULL!=pSrc && NULL!=pDst) { pSrc->slotHintHighlight(srcCardIndex); pDst->slotDelayedHintHighlight(); } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CardStack::slotDelayedHintHighlight() { QTimer::singleShot(1000,this,SLOT(slotHintHighlight())); } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CardStack::slotHintHighlight(int index) { // if the index is less than 0 then the last card should be highlighted // or the card pad if no cards are in the stack. if (index<0) { this->m_hintHighlightIndex=HintHighlightNoCards; if (this->m_cardVector.size()>0) { this->m_hintHighlightIndex=this->m_cardVector.size()-1; } } else if (index<(int)this->m_cardVector.size()) { this->m_hintHighlightIndex=index; } this->updateStack(); QTimer::singleShot(1000,this,SLOT(slotHintHighlightComplete())); } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CardStack::slotHintHighlightComplete() { this->m_hintHighlightIndex=-1; this->updateStack(); } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CardStack::slotFlipComplete(CardStack * pSrc) { Q_UNUSED(pSrc); this->updateStack(); } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CardStack::slotDragCardsMoved(const CardMoveRecord & initRecord) { CardMoveRecord moveRecord(initRecord); // if we are in autotop card up flip the top card. and emit a signal // that we moved the cards. if (!this->isEmpty() && this->isAutoTopCardUp()) { int lastIndex=this->m_cardVector.size()-1; if (!this->m_cardVector[lastIndex].isFaceUp()) { this->flipCard(lastIndex,moveRecord); } } emit cardsMovedByDragDrop(moveRecord); } ////////////////////////////////////////////////////////////////////////// // for this case when all cards are directly stacked on top of each other // if there are cards in the stack we will always just return the last card. ////////////////////////////////////////////////////////////////////////// bool CardStack::getCardIndex(const QPointF & pos,unsigned int & index) { Q_UNUSED(pos); bool rc=false; if (this->m_cardVector.size()>0) { index=this->m_cardVector.size()-1; rc=true; } return rc; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CardStack::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (m_lockUserInteraction || this->isFlipAniRunning() || CardAnimationLock::getInst().isDemoRunning()) { QGraphicsPixmapItem::mousePressEvent(event); return; } this->m_mouseMoved=false; this->m_dragStartPos=event->pos(); } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CardStack::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (m_lockUserInteraction || this->isFlipAniRunning() || CardAnimationLock::getInst().isDemoRunning()) { QGraphicsPixmapItem::mouseReleaseEvent(event); return; } if (!m_mouseMoved) { unsigned int index; if (this->isEmpty()) { emit padClicked(this); } else if (this->getCardIndex(event->pos(),index)) { if (this->canMoveCard(index)) { PlayingCardVector moveCards; CardMoveRecord moveRecord; // create a move record and a vector containing the cards // but don't remove them. if (CardStack::removeCardsStartingAt(index,moveCards, moveRecord,true)) { emit movableCardsClicked(this,moveCards,moveRecord); } } else { emit cardClicked(this,index); } } } else if (m_dragStack.cardDragStarted()) { m_dragStack.mouseReleaseEvent(event); } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CardStack::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (m_lockUserInteraction || this->isFlipAniRunning() || CardAnimationLock::getInst().isDemoRunning()) { QGraphicsPixmapItem::mouseMoveEvent(event); return; } QPointF posDiff=(event->pos()-this->m_dragStartPos); if ( (qAbs(posDiff.x()) + qAbs((int)posDiff.y()))< QApplication::startDragDistance()) { return; } m_mouseMoved=true; if (!m_dragStack.cardDragStarted()) { unsigned int index=0; bool haveCardIndex=this->getCardIndex(event->pos(),index); if (haveCardIndex && this->canMoveCard(index)) { unsigned int i; PlayingCardVector dragCardVector; for (i=index;im_cardVector.size();i++) { dragCardVector.push_back(this->m_cardVector[i]); } m_dragStack.startCardMove(event->scenePos(),index,dragCardVector); } } else { m_dragStack.mouseMoveEvent(event); } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void CardStack::hoverMoveEvent(QGraphicsSceneHoverEvent * event) { unsigned int index; if (this->getCardIndex(event->pos(),index) && this->canMoveCard(index)) { this->setCursor(Qt::OpenHandCursor); } else { this->setCursor(Qt::PointingHandCursor); } QGraphicsPixmapItem::hoverMoveEvent(event); }