This commit is contained in:
RickyRister 2026-03-21 10:45:41 -03:00 committed by GitHub
commit c93f1d91e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 250 additions and 37 deletions

View File

@ -71,6 +71,7 @@ set(cockatrice_SOURCES
src/game/dialogs/dlg_create_token.cpp
src/game/dialogs/dlg_move_top_cards_until.cpp
src/game/dialogs/dlg_roll_dice.cpp
src/game/dialogs/dlg_select_all_matching.cpp
src/game/game.cpp
src/game/game_event_handler.cpp
src/game/game_meta_info.cpp

View File

@ -546,6 +546,10 @@ private:
{"Player/aSelectColumn", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Column"),
parseSequenceString("Ctrl+Shift+C"),
ShortcutGroup::Playing_Area)},
{"Player/aSelectAllMatching",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Zone Matching Expression"),
parseSequenceString(""),
ShortcutGroup::Playing_Area)},
{"Player/aRevealToAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Reveal Selected Cards to All Players"),
parseSequenceString(""),
ShortcutGroup::Playing_Area)},

View File

@ -12,8 +12,7 @@
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/filters/filter_string.h>
DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QStringList exprs, uint _numberOfHits, bool autoPlay)
: QDialog(parent)
DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, const MoveTopCardsUntilOptions &options) : QDialog(parent)
{
exprLabel = new QLabel(tr("Card name (or search expressions):"));
@ -21,13 +20,13 @@ DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QStringList exprs, u
exprComboBox->setFocus();
exprComboBox->setEditable(true);
exprComboBox->setInsertPolicy(QComboBox::InsertAtTop);
exprComboBox->insertItems(0, exprs);
exprComboBox->insertItems(0, options.exprs);
exprLabel->setBuddy(exprComboBox);
numberOfHitsLabel = new QLabel(tr("Number of hits:"));
numberOfHitsEdit = new QSpinBox(this);
numberOfHitsEdit->setRange(1, 99);
numberOfHitsEdit->setValue(_numberOfHits);
numberOfHitsEdit->setValue(options.numberOfHits);
numberOfHitsLabel->setBuddy(numberOfHitsEdit);
auto *grid = new QGridLayout;
@ -35,7 +34,7 @@ DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QStringList exprs, u
grid->addWidget(numberOfHitsEdit, 0, 1);
autoPlayCheckBox = new QCheckBox(tr("Auto play hits"));
autoPlayCheckBox->setChecked(autoPlay);
autoPlayCheckBox->setChecked(options.autoPlay);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgMoveTopCardsUntil::validateAndAccept);
@ -118,6 +117,13 @@ QString DlgMoveTopCardsUntil::getExpr() const
return exprComboBox->currentText();
}
MoveTopCardsUntilOptions DlgMoveTopCardsUntil::getOptions() const
{
return {.exprs = getExprs(),
.numberOfHits = numberOfHitsEdit->text().toInt(),
.autoPlay = autoPlayCheckBox->isChecked()};
}
QStringList DlgMoveTopCardsUntil::getExprs() const
{
QStringList exprs;
@ -125,14 +131,4 @@ QStringList DlgMoveTopCardsUntil::getExprs() const
exprs.append(exprComboBox->itemText(i));
}
return exprs;
}
uint DlgMoveTopCardsUntil::getNumberOfHits() const
{
return numberOfHitsEdit->text().toUInt();
}
bool DlgMoveTopCardsUntil::isAutoPlay() const
{
return autoPlayCheckBox->isChecked();
}
}

View File

@ -16,6 +16,13 @@
class FilterString;
struct MoveTopCardsUntilOptions
{
QStringList exprs = {};
int numberOfHits = 1;
bool autoPlay = false;
};
class DlgMoveTopCardsUntil : public QDialog
{
Q_OBJECT
@ -29,15 +36,12 @@ class DlgMoveTopCardsUntil : public QDialog
void validateAndAccept();
bool validateMatchExists(const FilterString &filterString);
public:
explicit DlgMoveTopCardsUntil(QWidget *parent = nullptr,
QStringList exprs = QStringList(),
uint numberOfHits = 1,
bool autoPlay = false);
[[nodiscard]] QString getExpr() const;
[[nodiscard]] QStringList getExprs() const;
[[nodiscard]] uint getNumberOfHits() const;
[[nodiscard]] bool isAutoPlay() const;
public:
explicit DlgMoveTopCardsUntil(QWidget *parent = nullptr, const MoveTopCardsUntilOptions &options = {});
[[nodiscard]] QString getExpr() const;
[[nodiscard]] MoveTopCardsUntilOptions getOptions() const;
};
#endif // DLG_MOVE_TOP_CARDS_UNTIL_H

View File

@ -0,0 +1,119 @@
#include "dlg_select_all_matching.h"
#include "libcockatrice/card/card_info.h"
#include "libcockatrice/card/database/card_database_manager.h"
#include "libcockatrice/filters/filter_string.h"
#include <QCheckBox>
#include <QComboBox>
#include <QLabel>
#include <QMessageBox>
#include <QVBoxLayout>
DlgSelectAllMatching::DlgSelectAllMatching(QWidget *parent, const SelectAllMatchingOptions &options) : QDialog(parent)
{
exprLabel = new QLabel(tr("Card name (or search expressions):"));
exprComboBox = new QComboBox(this);
exprComboBox->setFocus();
exprComboBox->setEditable(true);
exprComboBox->setInsertPolicy(QComboBox::InsertAtTop);
exprComboBox->insertItems(0, options.exprs);
exprLabel->setBuddy(exprComboBox);
clearSelectionCheckBox = new QCheckBox(tr("Clear existing selection first"));
clearSelectionCheckBox->setChecked(options.clearSelection);
toggleSelectedCheckBox = new QCheckBox(tr("Toggle already selected matches"));
toggleSelectedCheckBox->setChecked(options.toggleSelected);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgSelectAllMatching::validateAndAccept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
auto *mainLayout = new QVBoxLayout;
mainLayout->addWidget(exprLabel);
mainLayout->addWidget(exprComboBox);
mainLayout->addWidget(clearSelectionCheckBox);
mainLayout->addWidget(toggleSelectedCheckBox);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("Select all cards matching..."));
}
/**
* @brief Checks if a card matching the expr exists in the card database.
*
* @returns true if a card matching the expression exists.
*/
static bool matchExistsInDb(const FilterString &filterString)
{
const auto *cardDatabase = CardDatabaseManager::getInstance();
const auto &allCards = cardDatabase->getCardList();
const auto it = std::find_if(allCards.begin(), allCards.end(),
[&filterString](const CardInfoPtr &card) { return filterString.check(card); });
return it != allCards.end();
}
bool DlgSelectAllMatching::validateMatchExists(const FilterString &filterString)
{
if (matchExistsInDb(filterString)) {
return true;
}
const auto msg = tr("No cards matching the search expression exists in the card database. Proceed anyways?");
const auto res =
QMessageBox::warning(this, tr("Cockatrice"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (res == QMessageBox::No) {
return false;
}
return true;
}
void DlgSelectAllMatching::validateAndAccept()
{
auto movingCardsUntilFilter = FilterString(exprComboBox->currentText());
if (!movingCardsUntilFilter.valid()) {
QMessageBox::warning(this, tr("Invalid filter"), movingCardsUntilFilter.error(), QMessageBox::Ok);
return;
}
if (!validateMatchExists(movingCardsUntilFilter)) {
return;
}
// move currently selected text to top of history list
if (exprComboBox->currentIndex() != 0) {
QString currentExpr = exprComboBox->currentText();
exprComboBox->removeItem(exprComboBox->currentIndex());
exprComboBox->insertItem(0, currentExpr);
exprComboBox->setCurrentIndex(0);
}
accept();
}
QString DlgSelectAllMatching::getExpr() const
{
return exprComboBox->currentText();
}
SelectAllMatchingOptions DlgSelectAllMatching::getOptions() const
{
return {.exprs = getExprs(),
.clearSelection = clearSelectionCheckBox->isChecked(),
.toggleSelected = toggleSelectedCheckBox->isChecked()};
}
QStringList DlgSelectAllMatching::getExprs() const
{
QStringList exprs;
for (int i = 0; i < exprComboBox->count(); ++i) {
exprs.append(exprComboBox->itemText(i));
}
return exprs;
}

View File

@ -0,0 +1,42 @@
#ifndef COCKATRICE_DLG_SELECT_ALL_MATCHING_H
#define COCKATRICE_DLG_SELECT_ALL_MATCHING_H
#include <QDialog>
class FilterString;
class QCheckBox;
class QDialogButtonBox;
class QSpinBox;
class QComboBox;
class QLabel;
struct SelectAllMatchingOptions
{
QStringList exprs = {};
bool clearSelection = true;
bool toggleSelected = false;
};
class DlgSelectAllMatching : public QDialog
{
Q_OBJECT
QLabel *exprLabel;
QComboBox *exprComboBox;
QCheckBox *clearSelectionCheckBox;
QCheckBox *toggleSelectedCheckBox;
QDialogButtonBox *buttonBox;
void validateAndAccept();
bool validateMatchExists(const FilterString &filterString);
[[nodiscard]] QStringList getExprs() const;
public:
explicit DlgSelectAllMatching(QWidget *parent, const SelectAllMatchingOptions &options);
[[nodiscard]] QString getExpr() const;
[[nodiscard]] SelectAllMatchingOptions getOptions() const;
};
#endif // COCKATRICE_DLG_SELECT_ALL_MATCHING_H

View File

@ -59,6 +59,8 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive
connect(aSelectRow, &QAction::triggered, playerActions, &PlayerActions::actSelectRow);
aSelectColumn = new QAction(this);
connect(aSelectColumn, &QAction::triggered, playerActions, &PlayerActions::actSelectColumn);
aSelectAllMatching = new QAction(this);
connect(aSelectAllMatching, &QAction::triggered, playerActions, &PlayerActions::actSelectAllMatching);
aPlay = new QAction(this);
connect(aPlay, &QAction::triggered, playerActions, &PlayerActions::actPlay);
@ -111,6 +113,7 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addAction(aSelectAllMatching);
addAction(aSelectColumn);
addRelatedCardView();
} else {
@ -155,6 +158,7 @@ void CardMenu::createTableMenu(bool canModifyCard)
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addAction(aSelectAllMatching);
addAction(aSelectRow);
return;
}
@ -183,6 +187,7 @@ void CardMenu::createTableMenu(bool canModifyCard)
addMenu(new MoveMenu(player));
addSeparator();
addAction(aSelectAll);
addAction(aSelectAllMatching);
addAction(aSelectRow);
addSeparator();
@ -210,12 +215,14 @@ void CardMenu::createStackMenu(bool canModifyCard)
addMenu(new MoveMenu(player));
addSeparator();
addAction(aSelectAll);
addAction(aSelectAllMatching);
} else {
addAction(aDrawArrow);
addSeparator();
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addAction(aSelectAllMatching);
}
addRelatedCardView();
@ -234,6 +241,7 @@ void CardMenu::createGraveyardOrExileMenu(bool canModifyCard)
addMenu(new MoveMenu(player));
addSeparator();
addAction(aSelectAll);
addAction(aSelectAllMatching);
addAction(aSelectColumn);
addSeparator();
@ -243,6 +251,7 @@ void CardMenu::createGraveyardOrExileMenu(bool canModifyCard)
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addAction(aSelectAllMatching);
addAction(aSelectColumn);
addSeparator();
addAction(aDrawArrow);
@ -263,6 +272,7 @@ void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard)
addAction(aClone);
addSeparator();
addAction(aSelectAll);
addAction(aSelectAllMatching);
return;
}
@ -289,6 +299,7 @@ void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard)
addSeparator();
addAction(aSelectAll);
addAction(aSelectAllMatching);
if (qobject_cast<ZoneViewZoneLogic *>(card->getZone())) {
addAction(aSelectColumn);
}
@ -442,6 +453,7 @@ void CardMenu::retranslateUi()
aSelectAll->setText(tr("&Select All"));
aSelectRow->setText(tr("S&elect Row"));
aSelectColumn->setText(tr("S&elect Column"));
aSelectAllMatching->setText(tr("Select All Matching..."));
aPlay->setText(tr("&Play"));
aHide->setText(tr("&Hide"));
@ -498,6 +510,7 @@ void CardMenu::setShortcutsActive()
aSelectAll->setShortcuts(shortcuts.getShortcut("Player/aSelectAll"));
aSelectRow->setShortcuts(shortcuts.getShortcut("Player/aSelectRow"));
aSelectColumn->setShortcuts(shortcuts.getShortcut("Player/aSelectColumn"));
aSelectAllMatching->setShortcuts(shortcuts.getShortcut("Player/aSelectAllMatching"));
static const QStringList colorWords = {"Red", "Yellow", "Green", "Cyan", "Purple", "Magenta"};
for (int i = 0; i < aAddCounter.size(); i++) {

View File

@ -30,7 +30,7 @@ public:
QAction *aRevealToAll;
QAction *aHide;
QAction *aClone;
QAction *aSelectAll, *aSelectRow, *aSelectColumn;
QAction *aSelectAll, *aSelectRow, *aSelectColumn, *aSelectAllMatching;
QAction *aDrawArrow;
QAction *aTap, *aDoesntUntap;
QAction *aFlip, *aPeek;

View File

@ -5,6 +5,7 @@
#include "../board/card_item.h"
#include "../dialogs/dlg_move_top_cards_until.h"
#include "../dialogs/dlg_roll_dice.h"
#include "../dialogs/dlg_select_all_matching.h"
#include "../zones/hand_zone.h"
#include "../zones/logic/view_zone_logic.h"
#include "../zones/table_zone.h"
@ -485,22 +486,19 @@ void PlayerActions::actMoveTopCardsUntil()
{
stopMoveTopCardsUntil();
DlgMoveTopCardsUntil dlg(player->getGame()->getTab(), movingCardsUntilExprs, movingCardsUntilNumberOfHits,
movingCardsUntilAutoPlay);
DlgMoveTopCardsUntil dlg(player->getGame()->getTab(), movingCardsUntilOptions);
if (!dlg.exec()) {
return;
}
auto expr = dlg.getExpr();
movingCardsUntilExprs = dlg.getExprs();
movingCardsUntilNumberOfHits = dlg.getNumberOfHits();
movingCardsUntilAutoPlay = dlg.isAutoPlay();
movingCardsUntilOptions = dlg.getOptions();
if (player->getDeckZone()->getCards().empty()) {
stopMoveTopCardsUntil();
} else {
movingCardsUntilFilter = FilterString(expr);
movingCardsUntilCounter = movingCardsUntilNumberOfHits;
movingCardsUntilCounter = movingCardsUntilOptions.numberOfHits;
movingCardsUntil = true;
actMoveTopCardToPlay();
}
@ -512,7 +510,7 @@ void PlayerActions::moveOneCardUntil(CardItem *card)
const bool isMatch = card && movingCardsUntilFilter.check(card->getCard().getCardPtr());
if (isMatch && movingCardsUntilAutoPlay) {
if (isMatch && movingCardsUntilOptions.autoPlay) {
// Directly calling playCard will deadlock, since we are already in the middle of processing an event.
// Use QTimer::singleShot to queue up the playCard on the event loop.
QTimer::singleShot(0, this, [card, this] { playCard(card, false); });
@ -699,10 +697,12 @@ void PlayerActions::actMoveBottomCardToTop()
*
* @param zone The zone to select from
* @param filter A predicate to filter which cards are selected. Defaults to always returning true.
* @param toggleSelected If true, toggles the selection state if card is already selected
*/
static void selectCardsInZone(
const CardZoneLogic *zone,
std::function<bool(const CardItem *)> filter = [](const CardItem *) { return true; })
std::function<bool(const CardItem *)> filter = [](const CardItem *) { return true; },
bool toggleSelected = false)
{
if (!zone) {
return;
@ -710,7 +710,11 @@ static void selectCardsInZone(
for (auto &cardItem : zone->getCards()) {
if (cardItem && filter(cardItem)) {
cardItem->setSelected(true);
if (toggleSelected) {
cardItem->setSelected(!cardItem->isSelected());
} else {
cardItem->setSelected(true);
}
}
}
}
@ -749,6 +753,33 @@ void PlayerActions::actSelectColumn()
selectCardsInZone(card->getZone(), isSameColumn);
}
void PlayerActions::actSelectAllMatching()
{
const CardItem *card = player->getGame()->getActiveCard();
if (!card) {
return;
}
auto dlg = DlgSelectAllMatching(player->getGame()->getTab(), lastSelectAllMatchingOptions);
if (!dlg.exec()) {
return;
}
QString expr = dlg.getExpr();
lastSelectAllMatchingOptions = dlg.getOptions();
if (lastSelectAllMatchingOptions.clearSelection) {
player->getGameScene()->clearSelection();
}
auto filterString = FilterString(expr);
auto matches = [filterString](const CardItem *cardItem) {
return filterString.check(cardItem->getCard().getCardPtr());
};
selectCardsInZone(card->getZone(), matches, lastSelectAllMatchingOptions.toggleSelected);
}
void PlayerActions::actDrawBottomCard()
{
if (player->getDeckZone()->getCards().empty()) {

View File

@ -8,6 +8,8 @@
#ifndef COCKATRICE_PLAYER_ACTIONS_H
#define COCKATRICE_PLAYER_ACTIONS_H
#include "../dialogs/dlg_create_token.h"
#include "../dialogs/dlg_move_top_cards_until.h"
#include "../dialogs/dlg_select_all_matching.h"
#include "event_processing_options.h"
#include "player.h"
@ -118,6 +120,7 @@ public slots:
void actSelectAll();
void actSelectRow();
void actSelectColumn();
void actSelectAllMatching();
void actViewLibrary();
void actViewHand();
@ -178,11 +181,11 @@ private:
bool movingCardsUntil;
QTimer *moveTopCardTimer;
QStringList movingCardsUntilExprs = {};
int movingCardsUntilNumberOfHits = 1;
bool movingCardsUntilAutoPlay = false;
FilterString movingCardsUntilFilter;
int movingCardsUntilCounter = 0;
MoveTopCardsUntilOptions movingCardsUntilOptions;
SelectAllMatchingOptions lastSelectAllMatchingOptions;
void moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown);
void moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown);