diff --git a/cockatrice/src/dialogs/dlg_settings.cpp b/cockatrice/src/dialogs/dlg_settings.cpp index f90fe8d4a..82c9f5ff3 100644 --- a/cockatrice/src/dialogs/dlg_settings.cpp +++ b/cockatrice/src/dialogs/dlg_settings.cpp @@ -492,6 +492,10 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&doubleClickToPlayCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setDoubleClickToPlay); + clickPlaysAllSelectedCheckBox.setChecked(SettingsCache::instance().getClickPlaysAllSelected()); + connect(&clickPlaysAllSelectedCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setClickPlaysAllSelected); + playToStackCheckBox.setChecked(SettingsCache::instance().getPlayToStack()); connect(&playToStackCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setPlayToStack); @@ -506,9 +510,10 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() auto *generalGrid = new QGridLayout; generalGrid->addWidget(&doubleClickToPlayCheckBox, 0, 0); - generalGrid->addWidget(&playToStackCheckBox, 1, 0); - generalGrid->addWidget(&annotateTokensCheckBox, 2, 0); - generalGrid->addWidget(&useTearOffMenusCheckBox, 3, 0); + generalGrid->addWidget(&clickPlaysAllSelectedCheckBox, 1, 0); + generalGrid->addWidget(&playToStackCheckBox, 2, 0); + generalGrid->addWidget(&annotateTokensCheckBox, 3, 0); + generalGrid->addWidget(&useTearOffMenusCheckBox, 4, 0); generalGroupBox = new QGroupBox; generalGroupBox->setLayout(generalGrid); @@ -582,6 +587,7 @@ void UserInterfaceSettingsPage::retranslateUi() { generalGroupBox->setTitle(tr("General interface settings")); doubleClickToPlayCheckBox.setText(tr("&Double-click cards to play them (instead of single-click)")); + clickPlaysAllSelectedCheckBox.setText(tr("&Clicking plays all selected cards (instead of just the clicked card)")); playToStackCheckBox.setText(tr("&Play all nonlands onto the stack (not the battlefield) by default")); annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen")); diff --git a/cockatrice/src/dialogs/dlg_settings.h b/cockatrice/src/dialogs/dlg_settings.h index 2a3f7bf0b..9db288128 100644 --- a/cockatrice/src/dialogs/dlg_settings.h +++ b/cockatrice/src/dialogs/dlg_settings.h @@ -125,6 +125,7 @@ private: QCheckBox specNotificationsEnabledCheckBox; QCheckBox buddyConnectNotificationsEnabledCheckBox; QCheckBox doubleClickToPlayCheckBox; + QCheckBox clickPlaysAllSelectedCheckBox; QCheckBox playToStackCheckBox; QCheckBox annotateTokensCheckBox; QCheckBox useTearOffMenusCheckBox; diff --git a/cockatrice/src/game/cards/card_item.cpp b/cockatrice/src/game/cards/card_item.cpp index 0378c1909..49a428e30 100644 --- a/cockatrice/src/game/cards/card_item.cpp +++ b/cockatrice/src/game/cards/card_item.cpp @@ -383,8 +383,46 @@ void CardItem::playCard(bool faceDown) TableZone *tz = qobject_cast(zone); if (tz) tz->toggleTapped(); - else - zone->getPlayer()->playCard(this, faceDown, info ? info->getCipt() : false); + else { + if (SettingsCache::instance().getClickPlaysAllSelected()) { + faceDown ? zone->getPlayer()->actPlayFacedown() : zone->getPlayer()->actPlay(); + } else { + zone->getPlayer()->playCard(this, faceDown, info ? info->getCipt() : false); + } + } +} + +/** + * @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone + * is nullptr. + */ +static bool isUnwritableRevealZone(CardZone *zone) +{ + if (zone && zone->getIsView()) { + if (auto *view = static_cast(zone)) { + return view->getRevealZone() && !view->getWriteableRevealZone(); + } + } + return false; +} + +/** + * This method is called when a "click to play" is done on the card. + * This is either triggered by a single click or double click, depending on the settings. + * + * @param shiftHeld if the shift key was held during the click + */ +void CardItem::handleClickedToPlay(bool shiftHeld) +{ + if (isUnwritableRevealZone(zone)) { + if (SettingsCache::instance().getClickPlaysAllSelected()) { + zone->getPlayer()->actHide(); + } else { + zone->removeCard(this); + } + } else { + playCard(shiftHeld); + } } void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) @@ -396,17 +434,7 @@ void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) } } else if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) && (!SettingsCache::instance().getDoubleClickToPlay())) { - bool hideCard = false; - if (zone && zone->getIsView()) { - auto *view = static_cast(zone); - if (view->getRevealZone() && !view->getWriteableRevealZone()) - hideCard = true; - } - if (zone && hideCard) { - zone->removeCard(this); - } else { - playCard(event->modifiers().testFlag(Qt::ShiftModifier)); - } + handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier)); } if (owner != nullptr) { // cards without owner will be deleted @@ -417,12 +445,9 @@ void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { - if ((event->modifiers() != Qt::AltModifier) && (SettingsCache::instance().getDoubleClickToPlay()) && - (event->buttons() == Qt::LeftButton)) { - if (revealedCard) - zone->removeCard(this); - else - playCard(event->modifiers().testFlag(Qt::ShiftModifier)); + if ((event->modifiers() != Qt::AltModifier) && (event->buttons() == Qt::LeftButton) && + (SettingsCache::instance().getDoubleClickToPlay())) { + handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier)); } event->accept(); } diff --git a/cockatrice/src/game/cards/card_item.h b/cockatrice/src/game/cards/card_item.h index bb037ead9..1dd6e9921 100644 --- a/cockatrice/src/game/cards/card_item.h +++ b/cockatrice/src/game/cards/card_item.h @@ -37,6 +37,7 @@ private: QMenu *cardMenu, *ptMenu, *moveMenu; void prepareDelete(); + void handleClickedToPlay(bool shiftHeld); public slots: void deleteLater(); diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index 8fcc13fb4..ce8f6a0a2 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -3540,13 +3540,36 @@ static bool isUnwritableRevealZone(CardZone *zone) return false; } +void Player::playSelectedCards(const bool faceDown) +{ + QList selectedCards; + for (const auto &item : scene()->selectedItems()) { + auto *card = static_cast(item); + selectedCards.append(card); + } + + // CardIds will get shuffled downwards when cards leave the deck. + // We need to iterate through the cards in reverse order so cardIds don't get changed out from under us as we play + // out the cards one-by-one. + std::sort(selectedCards.begin(), selectedCards.end(), + [](const auto &card1, const auto &card2) { return card1->getId() > card2->getId(); }); + + for (auto &card : selectedCards) { + if (card && !isUnwritableRevealZone(card->getZone()) && card->getZone()->getName() != "table") { + const bool cipt = !faceDown && card->getInfo() ? card->getInfo()->getCipt() : false; + playCard(card, faceDown, cipt); + } + } +} + void Player::actPlay() { - auto *card = game->getActiveCard(); - if (card) { - const bool cipt = card->getInfo() ? card->getInfo()->getCipt() : false; - playCard(card, false, cipt); - } + playSelectedCards(false); +} + +void Player::actPlayFacedown() +{ + playSelectedCards(true); } void Player::actHide() @@ -3559,14 +3582,6 @@ void Player::actHide() } } -void Player::actPlayFacedown() -{ - auto *card = game->getActiveCard(); - if (card) { - playCard(card, true, false); - } -} - void Player::actReveal(QAction *action) { const int otherPlayerId = action->data().toInt(); diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index e8d149e42..f96ae97fd 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -162,6 +162,11 @@ public slots: void actDrawCards(); void actUndoDraw(); void actMulligan(); + + void actPlay(); + void actPlayFacedown(); + void actHide(); + void actMoveTopCardToPlay(); void actMoveTopCardToPlayFaceDown(); void actMoveTopCardToGrave(); @@ -223,9 +228,6 @@ private slots: void actFlowP(); void actFlowT(); void actSetAnnotation(); - void actPlay(); - void actHide(); - void actPlayFacedown(); void actReveal(QAction *action); void refreshShortcuts(); @@ -323,6 +325,8 @@ private: void addPlayerToList(QMenu *playerList, Player *player); static void removePlayerFromList(QMenu *playerList, Player *player); + void playSelectedCards(bool faceDown = false); + QRectF bRect; QMap counters; diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index 5c8d50e0b..538607f80 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -232,6 +232,7 @@ SettingsCache::SettingsCache() spectatorNotificationsEnabled = settings->value("interface/specnotificationsenabled", false).toBool(); buddyConnectNotificationsEnabled = settings->value("interface/buddyconnectnotificationsenabled", true).toBool(); doubleClickToPlay = settings->value("interface/doubleclicktoplay", true).toBool(); + clickPlaysAllSelected = settings->value("interface/clickPlaysAllSelected", true).toBool(); playToStack = settings->value("interface/playtostack", true).toBool(); startingHandSize = settings->value("interface/startinghandsize", 7).toInt(); annotateTokens = settings->value("interface/annotatetokens", false).toBool(); @@ -490,6 +491,12 @@ void SettingsCache::setDoubleClickToPlay(QT_STATE_CHANGED_T _doubleClickToPlay) settings->setValue("interface/doubleclicktoplay", doubleClickToPlay); } +void SettingsCache::setClickPlaysAllSelected(QT_STATE_CHANGED_T _clickPlaysAllSelected) +{ + clickPlaysAllSelected = static_cast(_clickPlaysAllSelected); + settings->setValue("interface/clickPlaysAllSelected", clickPlaysAllSelected); +} + void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T _playToStack) { playToStack = static_cast(_playToStack); diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h index 0f46adb41..5ea1cdf68 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -93,6 +93,7 @@ private: bool spectatorNotificationsEnabled; bool buddyConnectNotificationsEnabled; bool doubleClickToPlay; + bool clickPlaysAllSelected; bool playToStack; int startingHandSize; bool annotateTokens; @@ -275,6 +276,10 @@ public: { return doubleClickToPlay; } + bool getClickPlaysAllSelected() const + { + return clickPlaysAllSelected; + } bool getPlayToStack() const { return playToStack; @@ -572,6 +577,7 @@ public slots: void setSpectatorNotificationsEnabled(QT_STATE_CHANGED_T _spectatorNotificationsEnabled); void setBuddyConnectNotificationsEnabled(QT_STATE_CHANGED_T _buddyConnectNotificationsEnabled); void setDoubleClickToPlay(QT_STATE_CHANGED_T _doubleClickToPlay); + void setClickPlaysAllSelected(QT_STATE_CHANGED_T _clickPlaysAllSelected); void setPlayToStack(QT_STATE_CHANGED_T _playToStack); void setStartingHandSize(int _startingHandSize); void setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens); diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index 6419f267a..4f3624c73 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -139,6 +139,9 @@ void SettingsCache::setBuddyConnectNotificationsEnabled(QT_STATE_CHANGED_T /* _b void SettingsCache::setDoubleClickToPlay(QT_STATE_CHANGED_T /* _doubleClickToPlay */) { } +void SettingsCache::setClickPlaysAllSelected(QT_STATE_CHANGED_T /* _clickPlaysAllSelected */) +{ +} void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T /* _playToStack */) { } diff --git a/tests/carddatabase/mocks.cpp b/tests/carddatabase/mocks.cpp index 5e42ccc44..03437ae2a 100644 --- a/tests/carddatabase/mocks.cpp +++ b/tests/carddatabase/mocks.cpp @@ -143,6 +143,9 @@ void SettingsCache::setBuddyConnectNotificationsEnabled(QT_STATE_CHANGED_T /* _b void SettingsCache::setDoubleClickToPlay(QT_STATE_CHANGED_T /* _doubleClickToPlay */) { } +void SettingsCache::setClickPlaysAllSelected(QT_STATE_CHANGED_T /* _clickPlaysAllSelected */) +{ +} void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T /* _playToStack */) { }