Merge branch 'master' into 6269/ci/strip-fat-bins

This commit is contained in:
Bruno Alexandre Rosa 2025-11-17 19:41:40 -03:00
commit dcafc7c28e
49 changed files with 906 additions and 253 deletions

View file

@ -156,6 +156,7 @@ function ccachestatsverbose() {
# Compile # Compile
if [[ $RUNNER_OS == macOS ]]; then if [[ $RUNNER_OS == macOS ]]; then
if [[ $TARGET_MACOS_VERSION ]]; then if [[ $TARGET_MACOS_VERSION ]]; then
# CMAKE_OSX_DEPLOYMENT_TARGET is a vanilla cmake flag needed to compile to target macOS version # CMAKE_OSX_DEPLOYMENT_TARGET is a vanilla cmake flag needed to compile to target macOS version
flags+=("-DCMAKE_OSX_DEPLOYMENT_TARGET=$TARGET_MACOS_VERSION") flags+=("-DCMAKE_OSX_DEPLOYMENT_TARGET=$TARGET_MACOS_VERSION")
@ -213,6 +214,7 @@ done' >"$hdiutil_script"
chmod +x "$hdiutil_script" chmod +x "$hdiutil_script"
flags+=(-DCPACK_COMMAND_HDIUTIL="$hdiutil_script") flags+=(-DCPACK_COMMAND_HDIUTIL="$hdiutil_script")
fi fi
elif [[ $RUNNER_OS == Windows ]]; then elif [[ $RUNNER_OS == Windows ]]; then
# Enable MTT, see https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/ # Enable MTT, see https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
# and https://devblogs.microsoft.com/cppblog/cpp-build-throughput-investigation-and-tune-up/#multitooltask-mtt # and https://devblogs.microsoft.com/cppblog/cpp-build-throughput-investigation-and-tune-up/#multitooltask-mtt
@ -242,6 +244,19 @@ if [[ $USE_CCACHE ]]; then
echo "::endgroup::" echo "::endgroup::"
fi fi
if [[ $RUNNER_OS == macOS ]]; then
echo "::group::Inspect Mach-O binaries"
for app in cockatrice oracle servatrice dbconverter; do
binary="$GITHUB_WORKSPACE/build/$app/$app.app/Contents/MacOS/$app"
echo "Inspecting $app..."
vtool -show-build "$binary"
file "$binary"
lipo -info "$binary"
echo ""
done
echo "::endgroup::"
fi
if [[ $MAKE_TEST ]]; then if [[ $MAKE_TEST ]]; then
echo "::group::Run tests" echo "::group::Run tests"
ctest -C "$BUILDTYPE" --output-on-failure ctest -C "$BUILDTYPE" --output-on-failure
@ -256,7 +271,6 @@ fi
if [[ $MAKE_PACKAGE ]]; then if [[ $MAKE_PACKAGE ]]; then
echo "::group::Create package" echo "::group::Create package"
cmake --build . --target package --config "$BUILDTYPE" cmake --build . --target package --config "$BUILDTYPE"
echo "::endgroup::" echo "::endgroup::"

View file

@ -11,6 +11,9 @@ on:
- 'doxygen_style.css' - 'doxygen_style.css'
workflow_dispatch: workflow_dispatch:
env:
COCKATRICE_REF: ${{ github.ref_name }} # Tag name if the commit is tagged, otherwise branch name
jobs: jobs:
docs: docs:
name: Doxygen name: Doxygen

View file

@ -1,4 +1,5 @@
# Doxyfile 1.14.0 # Doxyfile 1.14.0
# Doxygen Docs: https://www.doxygen.nl/manual
# This file describes the settings to be used by the documentation system # This file describes the settings to be used by the documentation system
# Doxygen (www.doxygen.org) for a project. # Doxygen (www.doxygen.org) for a project.
@ -48,13 +49,13 @@ PROJECT_NAME = "Cockatrice"
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = 2.11 PROJECT_NUMBER = $(COCKATRICE_REF)
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewers a # for a project that appears at the top of each page and should give viewers a
# quick idea about the purpose of the project. Keep the description short. # quick idea about the purpose of the project. Keep the description short.
PROJECT_BRIEF = PROJECT_BRIEF = A cross-platform virtual tabletop for multiplayer card games
# With the PROJECT_LOGO tag one can specify a logo or an icon that is included # With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55 # in the documentation. The maximum height of the logo should not exceed 55
@ -839,7 +840,7 @@ FILE_VERSION_FILTER =
# DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE # DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE
# tag is left empty. # tag is left empty.
LAYOUT_FILE = DoxygenLayout.xml LAYOUT_FILE = doc/doxygen/DoxygenLayout.xml
# The CITE_BIB_FILES tag can be used to specify one or more bib files containing # The CITE_BIB_FILES tag can be used to specify one or more bib files containing
# the reference definitions. This must be a list of .bib files. The .bib # the reference definitions. This must be a list of .bib files. The .bib
@ -1438,7 +1439,7 @@ HTML_EXTRA_FILES = doc/doxygen/js/graph_toggle.js
# The default value is: AUTO_LIGHT. # The default value is: AUTO_LIGHT.
# This tag requires that the tag GENERATE_HTML is set to YES. # This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE = DARK HTML_COLORSTYLE = AUTO_DARK
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to # will adjust the colors in the style sheet and background images according to
@ -2021,7 +2022,7 @@ EXTRA_SEARCH_MAPPINGS =
# If the GENERATE_LATEX tag is set to YES, Doxygen will generate LaTeX output. # If the GENERATE_LATEX tag is set to YES, Doxygen will generate LaTeX output.
# The default value is: YES. # The default value is: YES.
GENERATE_LATEX = YES GENERATE_LATEX = NO
# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of

View file

@ -180,6 +180,7 @@ set(cockatrice_SOURCES
src/interface/widgets/replay/replay_timeline_widget.cpp src/interface/widgets/replay/replay_timeline_widget.cpp
src/interface/widgets/server/chat_view/chat_view.cpp src/interface/widgets/server/chat_view/chat_view.cpp
src/interface/widgets/server/game_selector.cpp src/interface/widgets/server/game_selector.cpp
src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp
src/interface/widgets/server/games_model.cpp src/interface/widgets/server/games_model.cpp
src/interface/widgets/server/handle_public_servers.cpp src/interface/widgets/server/handle_public_servers.cpp
src/interface/widgets/server/remote/remote_decklist_tree_widget.cpp src/interface/widgets/server/remote/remote_decklist_tree_widget.cpp

View file

@ -48,7 +48,7 @@ public:
} }
loader->getDeckList()->loadFromStream_Plain(outStream, false); loader->getDeckList()->loadFromStream_Plain(outStream, false);
loader->resolveSetNameAndNumberToProviderID(); DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList());
return loader; return loader;
} }
@ -95,7 +95,7 @@ public:
} }
loader->getDeckList()->loadFromStream_Plain(outStream, false); loader->getDeckList()->loadFromStream_Plain(outStream, false);
loader->resolveSetNameAndNumberToProviderID(); DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList());
QJsonObject commandersObj = obj.value("commanders").toObject(); QJsonObject commandersObj = obj.value("commanders").toObject();
if (!commandersObj.isEmpty()) { if (!commandersObj.isEmpty()) {

View file

@ -60,7 +60,8 @@ void AbstractCardItem::refreshCardInfo()
exactCard = CardDatabaseManager::query()->getCard(cardRef); exactCard = CardDatabaseManager::query()->getCard(cardRef);
if (!exactCard && !cardRef.name.isEmpty()) { if (!exactCard && !cardRef.name.isEmpty()) {
auto info = CardInfo::newInstance(cardRef.name, "", true, {}, {}, {}, {}, false, false, -1, false); CardInfo::UiAttributes attributes = {.tableRow = -1};
auto info = CardInfo::newInstance(cardRef.name, "", true, {}, {}, {}, {}, attributes);
exactCard = ExactCard(info); exactCard = ExactCard(info);
} }
if (exactCard) { if (exactCard) {

View file

@ -238,8 +238,8 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
if (startZone->getName().compare("hand") == 0) { if (startZone->getName().compare("hand") == 0) {
startCard->playCard(false); startCard->playCard(false);
CardInfoPtr ci = startCard->getCard().getCardPtr(); CardInfoPtr ci = startCard->getCard().getCardPtr();
if (ci && ((!SettingsCache::instance().getPlayToStack() && ci->getTableRow() == 3) || if (ci && ((!SettingsCache::instance().getPlayToStack() && ci->getUiAttributes().tableRow == 3) ||
(SettingsCache::instance().getPlayToStack() && ci->getTableRow() != 0 && (SettingsCache::instance().getPlayToStack() && ci->getUiAttributes().tableRow != 0 &&
startCard->getZone()->getName().toStdString() != "stack"))) startCard->getZone()->getName().toStdString() != "stack")))
cmd.set_start_zone("stack"); cmd.set_start_zone("stack");
else else

View file

@ -59,7 +59,7 @@ void PlayerActions::playCard(CardItem *card, bool faceDown)
const CardInfo &info = exactCard.getInfo(); const CardInfo &info = exactCard.getInfo();
int tableRow = info.getTableRow(); int tableRow = info.getUiAttributes().tableRow;
bool playToStack = SettingsCache::instance().getPlayToStack(); bool playToStack = SettingsCache::instance().getPlayToStack();
QString currentZone = card->getZone()->getName(); QString currentZone = card->getZone()->getName();
if (currentZone == "stack" && tableRow == 3) { if (currentZone == "stack" && tableRow == 3) {
@ -72,13 +72,13 @@ void PlayerActions::playCard(CardItem *card, bool faceDown)
cmd.set_x(-1); cmd.set_x(-1);
cmd.set_y(0); cmd.set_y(0);
} else { } else {
tableRow = faceDown ? 2 : info.getTableRow(); tableRow = faceDown ? 2 : info.getUiAttributes().tableRow;
QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - tableRow)); QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - tableRow));
cardToMove->set_face_down(faceDown); cardToMove->set_face_down(faceDown);
if (!faceDown) { if (!faceDown) {
cardToMove->set_pt(info.getPowTough().toStdString()); cardToMove->set_pt(info.getPowTough().toStdString());
} }
cardToMove->set_tapped(!faceDown && info.getCipt()); cardToMove->set_tapped(!faceDown && info.getUiAttributes().cipt);
if (tableRow != 3) if (tableRow != 3)
cmd.set_target_zone("table"); cmd.set_target_zone("table");
cmd.set_x(gridPoint.x()); cmd.set_x(gridPoint.x());
@ -111,7 +111,7 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown)
const CardInfo &info = exactCard.getInfo(); const CardInfo &info = exactCard.getInfo();
int tableRow = faceDown ? 2 : info.getTableRow(); int tableRow = faceDown ? 2 : info.getUiAttributes().tableRow;
// default instant/sorcery cards to the noncreatures row // default instant/sorcery cards to the noncreatures row
if (tableRow > 2) { if (tableRow > 2) {
tableRow = 1; tableRow = 1;
@ -122,7 +122,7 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown)
if (!faceDown) { if (!faceDown) {
cardToMove->set_pt(info.getPowTough().toStdString()); cardToMove->set_pt(info.getPowTough().toStdString());
} }
cardToMove->set_tapped(!faceDown && info.getCipt()); cardToMove->set_tapped(!faceDown && info.getUiAttributes().cipt);
cmd.set_target_zone("table"); cmd.set_target_zone("table");
cmd.set_x(gridPoint.x()); cmd.set_x(gridPoint.x());
cmd.set_y(gridPoint.y()); cmd.set_y(gridPoint.y());
@ -859,7 +859,7 @@ void PlayerActions::actCreateToken()
ExactCard correctedCard = CardDatabaseManager::query()->guessCard({lastTokenInfo.name, lastTokenInfo.providerId}); ExactCard correctedCard = CardDatabaseManager::query()->guessCard({lastTokenInfo.name, lastTokenInfo.providerId});
if (correctedCard) { if (correctedCard) {
lastTokenInfo.name = correctedCard.getName(); lastTokenInfo.name = correctedCard.getName();
lastTokenTableRow = TableZone::clampValidTableRow(2 - correctedCard.getInfo().getTableRow()); lastTokenTableRow = TableZone::clampValidTableRow(2 - correctedCard.getInfo().getUiAttributes().tableRow);
if (lastTokenInfo.pt.isEmpty()) { if (lastTokenInfo.pt.isEmpty()) {
lastTokenInfo.pt = correctedCard.getInfo().getPowTough(); lastTokenInfo.pt = correctedCard.getInfo().getPowTough();
} }
@ -910,7 +910,7 @@ void PlayerActions::setLastToken(CardInfoPtr cardInfo)
.providerId = .providerId =
SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardInfo->getName())}; SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardInfo->getName())};
lastTokenTableRow = TableZone::clampValidTableRow(2 - cardInfo->getTableRow()); lastTokenTableRow = TableZone::clampValidTableRow(2 - cardInfo->getUiAttributes().tableRow);
utilityMenu->setAndEnableCreateAnotherTokenAction(tr("C&reate another %1 token").arg(lastTokenInfo.name)); utilityMenu->setAndEnableCreateAnotherTokenAction(tr("C&reate another %1 token").arg(lastTokenInfo.name));
} }
@ -1080,7 +1080,7 @@ void PlayerActions::createCard(const CardItem *sourceCard,
// get the target token's location // get the target token's location
// TODO: Define this QPoint into its own function along with the one below // TODO: Define this QPoint into its own function along with the one below
QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - cardInfo->getTableRow())); QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - cardInfo->getUiAttributes().tableRow));
// create the token for the related card // create the token for the related card
Command_CreateToken cmd; Command_CreateToken cmd;

View file

@ -18,7 +18,7 @@ void TableZoneLogic::addCardImpl(CardItem *card, int _x, int _y)
if (!card->getFaceDown() && card->getPT().isEmpty()) { if (!card->getFaceDown() && card->getPT().isEmpty()) {
card->setPT(card->getCardInfo().getPowTough()); card->setPT(card->getCardInfo().getPowTough());
} }
if (card->getCardInfo().getCipt() && card->getCardInfo().getLandscapeOrientation()) { if (card->getCardInfo().getUiAttributes().cipt && card->getCardInfo().getUiAttributes().landscapeOrientation) {
card->setDoesntUntap(true); card->setDoesntUntap(true);
} }
card->setGridPoint(QPoint(_x, _y)); card->setGridPoint(QPoint(_x, _y));

View file

@ -154,7 +154,7 @@ void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image)
qCDebug(CardPictureLoaderLog) << "Caching NULL pixmap for" << card.getName(); qCDebug(CardPictureLoaderLog) << "Caching NULL pixmap for" << card.getName();
QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap()); QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap());
} else { } else {
if (card.getInfo().getUpsideDownArt()) { if (card.getInfo().getUiAttributes().upsideDownArt) {
#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0))
QImage mirrorImage = image.flipped(Qt::Horizontal | Qt::Vertical); QImage mirrorImage = image.flipped(Qt::Horizontal | Qt::Vertical);
#else #else

View file

@ -276,9 +276,11 @@ static QString toDecklistExportString(const DecklistCardNode *card)
/** /**
* Export deck to decklist function, called to format the deck in a way to be sent to a server * Export deck to decklist function, called to format the deck in a way to be sent to a server
*
* @param deckList The decklist to export
* @param website The website we're sending the deck to * @param website The website we're sending the deck to
*/ */
QString DeckLoader::exportDeckToDecklist(DecklistWebsite website) QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website)
{ {
// Add the base url // Add the base url
QString deckString = "https://" + getDomainForWebsite(website) + "/?"; QString deckString = "https://" + getDomainForWebsite(website) + "/?";
@ -345,8 +347,10 @@ struct SetProviderIdToPreferred
/** /**
* This function iterates through each card in the decklist and sets the providerId * This function iterates through each card in the decklist and sets the providerId
* on each card based on its set name and collector number. * on each card based on its set name and collector number.
*
* @param deckList The decklist to modify
*/ */
void DeckLoader::setProviderIdToPreferredPrinting() void DeckLoader::setProviderIdToPreferredPrinting(const DeckList *deckList)
{ {
// Set up the struct to call. // Set up the struct to call.
SetProviderIdToPreferred setProviderIdToPreferred; SetProviderIdToPreferred setProviderIdToPreferred;
@ -357,8 +361,10 @@ void DeckLoader::setProviderIdToPreferredPrinting()
/** /**
* Sets the providerId on each card in the decklist based on its set name and collector number. * Sets the providerId on each card in the decklist based on its set name and collector number.
*
* @param deckList The decklist to modify
*/ */
void DeckLoader::resolveSetNameAndNumberToProviderID() void DeckLoader::resolveSetNameAndNumberToProviderID(const DeckList *deckList)
{ {
auto setProviderId = [](const auto node, const auto card) { auto setProviderId = [](const auto node, const auto card) {
Q_UNUSED(node); Q_UNUSED(node);
@ -397,8 +403,10 @@ struct ClearSetNameNumberAndProviderId
/** /**
* Clears the set name and numbers on each card in the decklist. * Clears the set name and numbers on each card in the decklist.
*
* @param deckList The decklist to modify
*/ */
void DeckLoader::clearSetNamesAndNumbers() void DeckLoader::clearSetNamesAndNumbers(const DeckList *deckList)
{ {
auto clearSetNameAndNumber = [](const auto node, auto card) { auto clearSetNameAndNumber = [](const auto node, auto card) {
Q_UNUSED(node) Q_UNUSED(node)
@ -419,19 +427,22 @@ DeckLoader::FileFormat DeckLoader::getFormatFromName(const QString &fileName)
return PlainTextFormat; return PlainTextFormat;
} }
void DeckLoader::saveToClipboard(bool addComments, bool addSetNameAndNumber) const void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, bool addSetNameAndNumber)
{ {
QString buffer; QString buffer;
QTextStream stream(&buffer); QTextStream stream(&buffer);
saveToStream_Plain(stream, addComments, addSetNameAndNumber); saveToStream_Plain(stream, deckList, addComments, addSetNameAndNumber);
QApplication::clipboard()->setText(buffer, QClipboard::Clipboard); QApplication::clipboard()->setText(buffer, QClipboard::Clipboard);
QApplication::clipboard()->setText(buffer, QClipboard::Selection); QApplication::clipboard()->setText(buffer, QClipboard::Selection);
} }
bool DeckLoader::saveToStream_Plain(QTextStream &out, bool addComments, bool addSetNameAndNumber) const bool DeckLoader::saveToStream_Plain(QTextStream &out,
const DeckList *deckList,
bool addComments,
bool addSetNameAndNumber)
{ {
if (addComments) { if (addComments) {
saveToStream_DeckHeader(out); saveToStream_DeckHeader(out, deckList);
} }
// loop zones // loop zones
@ -447,7 +458,7 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out, bool addComments, bool add
return true; return true;
} }
void DeckLoader::saveToStream_DeckHeader(QTextStream &out) const void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList)
{ {
if (!deckList->getName().isEmpty()) { if (!deckList->getName().isEmpty()) {
out << "// " << deckList->getName() << "\n\n"; out << "// " << deckList->getName() << "\n\n";
@ -465,7 +476,7 @@ void DeckLoader::saveToStream_DeckHeader(QTextStream &out) const
void DeckLoader::saveToStream_DeckZone(QTextStream &out, void DeckLoader::saveToStream_DeckZone(QTextStream &out,
const InnerDecklistNode *zoneNode, const InnerDecklistNode *zoneNode,
bool addComments, bool addComments,
bool addSetNameAndNumber) const bool addSetNameAndNumber)
{ {
// group cards by card type and count the subtotals // group cards by card type and count the subtotals
QMultiMap<QString, DecklistCardNode *> cardsByType; QMultiMap<QString, DecklistCardNode *> cardsByType;
@ -513,7 +524,7 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
const InnerDecklistNode *zoneNode, const InnerDecklistNode *zoneNode,
QList<DecklistCardNode *> cards, QList<DecklistCardNode *> cards,
bool addComments, bool addComments,
bool addSetNameAndNumber) const bool addSetNameAndNumber)
{ {
// QMultiMap sorts values in reverse order // QMultiMap sorts values in reverse order
for (int i = cards.size() - 1; i >= 0; --i) { for (int i = cards.size() - 1; i >= 0; --i) {
@ -589,7 +600,7 @@ bool DeckLoader::convertToCockatriceFormat(QString fileName)
return result; return result;
} }
QString DeckLoader::getCardZoneFromName(QString cardName, QString currentZoneName) QString DeckLoader::getCardZoneFromName(const QString &cardName, QString currentZoneName)
{ {
CardInfoPtr card = CardDatabaseManager::query()->getCardInfo(cardName); CardInfoPtr card = CardDatabaseManager::query()->getCardInfo(cardName);
@ -600,7 +611,7 @@ QString DeckLoader::getCardZoneFromName(QString cardName, QString currentZoneNam
return currentZoneName; return currentZoneName;
} }
QString DeckLoader::getCompleteCardName(const QString &cardName) const QString DeckLoader::getCompleteCardName(const QString &cardName)
{ {
if (CardDatabaseManager::getInstance()) { if (CardDatabaseManager::getInstance()) {
ExactCard temp = CardDatabaseManager::query()->guessCard({cardName}); ExactCard temp = CardDatabaseManager::query()->guessCard({cardName});
@ -672,7 +683,7 @@ void DeckLoader::printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node)
cursor->movePosition(QTextCursor::End); cursor->movePosition(QTextCursor::End);
} }
void DeckLoader::printDeckList(QPrinter *printer) void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList)
{ {
QTextDocument doc; QTextDocument doc;

View file

@ -21,13 +21,6 @@ signals:
void deckLoaded(); void deckLoaded();
void loadFinished(bool success); void loadFinished(bool success);
public slots:
/**
* @brief Prints the decklist to the provided QPrinter.
* @param printer The printer to render the decklist to.
*/
void printDeckList(QPrinter *printer);
public: public:
enum FileFormat enum FileFormat
{ {
@ -84,7 +77,7 @@ public:
return getLastFileName().isEmpty() && getLastRemoteDeckId() == -1; return getLastFileName().isEmpty() && getLastRemoteDeckId() == -1;
} }
void clearSetNamesAndNumbers(); static void clearSetNamesAndNumbers(const DeckList *deckList);
static FileFormat getFormatFromName(const QString &fileName); static FileFormat getFormatFromName(const QString &fileName);
bool loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest = false); bool loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest = false);
@ -92,15 +85,25 @@ public:
bool loadFromRemote(const QString &nativeString, int remoteDeckId); bool loadFromRemote(const QString &nativeString, int remoteDeckId);
bool saveToFile(const QString &fileName, FileFormat fmt); bool saveToFile(const QString &fileName, FileFormat fmt);
bool updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt); bool updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt);
QString exportDeckToDecklist(DecklistWebsite website);
void setProviderIdToPreferredPrinting();
void resolveSetNameAndNumberToProviderID(); static QString exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website);
void saveToClipboard(bool addComments = true, bool addSetNameAndNumber = true) const; static void setProviderIdToPreferredPrinting(const DeckList *deckList);
static void resolveSetNameAndNumberToProviderID(const DeckList *deckList);
static void saveToClipboard(const DeckList *deckList, bool addComments = true, bool addSetNameAndNumber = true);
static bool saveToStream_Plain(QTextStream &out,
const DeckList *deckList,
bool addComments = true,
bool addSetNameAndNumber = true);
/**
* @brief Prints the decklist to the provided QPrinter.
* @param printer The printer to render the decklist to.
* @param deckList
*/
static void printDeckList(QPrinter *printer, const DeckList *deckList);
// overload
bool saveToStream_Plain(QTextStream &out, bool addComments = true, bool addSetNameAndNumber = true) const;
bool convertToCockatriceFormat(QString fileName); bool convertToCockatriceFormat(QString fileName);
DeckList *getDeckList() DeckList *getDeckList()
@ -109,21 +112,21 @@ public:
} }
private: private:
void printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node); static void printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node);
static void saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList);
protected: static void saveToStream_DeckZone(QTextStream &out,
void saveToStream_DeckHeader(QTextStream &out) const;
void saveToStream_DeckZone(QTextStream &out,
const InnerDecklistNode *zoneNode, const InnerDecklistNode *zoneNode,
bool addComments = true, bool addComments = true,
bool addSetNameAndNumber = true) const; bool addSetNameAndNumber = true);
void saveToStream_DeckZoneCards(QTextStream &out, static void saveToStream_DeckZoneCards(QTextStream &out,
const InnerDecklistNode *zoneNode, const InnerDecklistNode *zoneNode,
QList<DecklistCardNode *> cards, QList<DecklistCardNode *> cards,
bool addComments = true, bool addComments = true,
bool addSetNameAndNumber = true) const; bool addSetNameAndNumber = true);
[[nodiscard]] QString getCardZoneFromName(QString cardName, QString currentZoneName);
[[nodiscard]] QString getCompleteCardName(const QString &cardName) const; [[nodiscard]] static QString getCardZoneFromName(const QString &cardName, QString currentZoneName);
[[nodiscard]] static QString getCompleteCardName(const QString &cardName);
}; };
#endif #endif

View file

@ -183,7 +183,7 @@ void CardInfoPictureWidget::paintEvent(QPaintEvent *event)
QPixmap transformedPixmap = resizedPixmap; // Default pixmap QPixmap transformedPixmap = resizedPixmap; // Default pixmap
if (SettingsCache::instance().getAutoRotateSidewaysLayoutCards()) { if (SettingsCache::instance().getAutoRotateSidewaysLayoutCards()) {
if (exactCard.getInfo().getLandscapeOrientation()) { if (exactCard.getInfo().getUiAttributes().landscapeOrientation) {
// Rotate pixmap 90 degrees to the left // Rotate pixmap 90 degrees to the left
QTransform transform; QTransform transform;
transform.rotate(90); transform.rotate(90);

View file

@ -102,15 +102,20 @@ void DlgCreateGame::sharedCtor()
startingLifeTotalEdit->setValue(20); startingLifeTotalEdit->setValue(20);
startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); startingLifeTotalLabel->setBuddy(startingLifeTotalEdit);
shareDecklistsOnLoadLabel = new QLabel(tr("Open decklists in lobby")); shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby"));
shareDecklistsOnLoadCheckBox = new QCheckBox();
shareDecklistsOnLoadLabel->setBuddy(shareDecklistsOnLoadCheckBox); createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge"));
auto *gameSetupOptionsLayout = new QGridLayout; auto *gameSetupOptionsLayout = new QGridLayout;
gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0); gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0);
gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1); gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1);
gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadLabel, 1, 0); gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0);
gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 1); if (room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 2, 0);
} else {
createGameAsJudgeCheckBox->setChecked(false);
createGameAsJudgeCheckBox->setHidden(true);
}
gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options")); gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"));
gameSetupOptionsGroupBox->setLayout(gameSetupOptionsLayout); gameSetupOptionsGroupBox->setLayout(gameSetupOptionsLayout);
@ -245,6 +250,7 @@ void DlgCreateGame::actReset()
startingLifeTotalEdit->setValue(20); startingLifeTotalEdit->setValue(20);
shareDecklistsOnLoadCheckBox->setChecked(false); shareDecklistsOnLoadCheckBox->setChecked(false);
createGameAsJudgeCheckBox->setChecked(false);
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes); QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
while (gameTypeCheckBoxIterator.hasNext()) { while (gameTypeCheckBoxIterator.hasNext()) {
@ -270,7 +276,7 @@ void DlgCreateGame::actOK()
cmd.set_spectators_need_password(spectatorsNeedPasswordCheckBox->isChecked()); cmd.set_spectators_need_password(spectatorsNeedPasswordCheckBox->isChecked());
cmd.set_spectators_can_talk(spectatorsCanTalkCheckBox->isChecked()); cmd.set_spectators_can_talk(spectatorsCanTalkCheckBox->isChecked());
cmd.set_spectators_see_everything(spectatorsSeeEverythingCheckBox->isChecked()); cmd.set_spectators_see_everything(spectatorsSeeEverythingCheckBox->isChecked());
cmd.set_join_as_judge(QApplication::keyboardModifiers() & Qt::ShiftModifier); cmd.set_join_as_judge(createGameAsJudgeCheckBox->isChecked());
cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked()); cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked());
cmd.set_starting_life_total(startingLifeTotalEdit->value()); cmd.set_starting_life_total(startingLifeTotalEdit->value());
cmd.set_share_decklists_on_load(shareDecklistsOnLoadCheckBox->isChecked()); cmd.set_share_decklists_on_load(shareDecklistsOnLoadCheckBox->isChecked());

View file

@ -46,7 +46,7 @@ private:
QSpinBox *maxPlayersEdit, *startingLifeTotalEdit; QSpinBox *maxPlayersEdit, *startingLifeTotalEdit;
QCheckBox *onlyBuddiesCheckBox, *onlyRegisteredCheckBox; QCheckBox *onlyBuddiesCheckBox, *onlyRegisteredCheckBox;
QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox, QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox,
*spectatorsSeeEverythingCheckBox, *createGameAsSpectatorCheckBox; *spectatorsSeeEverythingCheckBox, *createGameAsJudgeCheckBox, *createGameAsSpectatorCheckBox;
QCheckBox *shareDecklistsOnLoadCheckBox; QCheckBox *shareDecklistsOnLoadCheckBox;
QDialogButtonBox *buttonBox; QDialogButtonBox *buttonBox;
QPushButton *clearButton; QPushButton *clearButton;

View file

@ -165,8 +165,8 @@ void DlgEditTokens::actAddToken()
QString setName = CardSet::TOKENS_SETNAME; QString setName = CardSet::TOKENS_SETNAME;
SetToPrintingsMap sets; SetToPrintingsMap sets;
sets[setName].append(PrintingInfo(databaseModel->getDatabase()->getSet(setName))); sets[setName].append(PrintingInfo(databaseModel->getDatabase()->getSet(setName)));
CardInfoPtr card = CardInfo::newInstance(name, "", true, QVariantHash(), QList<CardRelation *>(), CardInfo::UiAttributes attributes = {.tableRow = -1};
QList<CardRelation *>(), sets, false, false, -1, false); CardInfoPtr card = CardInfo::newInstance(name, "", true, {}, {}, {}, sets, attributes);
card->setCardType("Token"); card->setCardType("Token");
databaseModel->getDatabase()->addCard(card); databaseModel->getDatabase()->addCard(card);

View file

@ -37,7 +37,7 @@ DlgFilterGames::DlgFilterGames(const QMap<int, QString> &_allGameTypes,
hideIgnoredUserGames = new QCheckBox(tr("Hide 'ignored user' games")); hideIgnoredUserGames = new QCheckBox(tr("Hide 'ignored user' games"));
hideIgnoredUserGames->setChecked(gamesProxyModel->getHideIgnoredUserGames()); hideIgnoredUserGames->setChecked(gamesProxyModel->getHideIgnoredUserGames());
hideNotBuddyCreatedGames = new QCheckBox(tr("Hide games not created by buddy")); hideNotBuddyCreatedGames = new QCheckBox(tr("Hide games not created by buddies"));
hideNotBuddyCreatedGames->setChecked(gamesProxyModel->getHideNotBuddyCreatedGames()); hideNotBuddyCreatedGames->setChecked(gamesProxyModel->getHideNotBuddyCreatedGames());
hideOpenDecklistGames = new QCheckBox(tr("Hide games with forced open decklists")); hideOpenDecklistGames = new QCheckBox(tr("Hide games with forced open decklists"));
@ -56,7 +56,7 @@ DlgFilterGames::DlgFilterGames(const QMap<int, QString> &_allGameTypes,
auto *gameNameFilterLabel = new QLabel(tr("Game &description:")); auto *gameNameFilterLabel = new QLabel(tr("Game &description:"));
gameNameFilterLabel->setBuddy(gameNameFilterEdit); gameNameFilterLabel->setBuddy(gameNameFilterEdit);
creatorNameFilterEdit = new QLineEdit; creatorNameFilterEdit = new QLineEdit;
creatorNameFilterEdit->setText(gamesProxyModel->getCreatorNameFilter()); creatorNameFilterEdit->setText(gamesProxyModel->getCreatorNameFilters().join(", "));
auto *creatorNameFilterLabel = new QLabel(tr("&Creator name:")); auto *creatorNameFilterLabel = new QLabel(tr("&Creator name:"));
creatorNameFilterLabel->setBuddy(creatorNameFilterEdit); creatorNameFilterLabel->setBuddy(creatorNameFilterEdit);
@ -232,9 +232,9 @@ QString DlgFilterGames::getGameNameFilter() const
return gameNameFilterEdit->text(); return gameNameFilterEdit->text();
} }
QString DlgFilterGames::getCreatorNameFilter() const QStringList DlgFilterGames::getCreatorNameFilters() const
{ {
return creatorNameFilterEdit->text(); return creatorNameFilterEdit->text().split(",", Qt::SkipEmptyParts);
} }
QSet<int> DlgFilterGames::getGameTypeFilter() const QSet<int> DlgFilterGames::getGameTypeFilter() const

View file

@ -71,7 +71,7 @@ public:
bool getHideNotBuddyCreatedGames() const; bool getHideNotBuddyCreatedGames() const;
QString getGameNameFilter() const; QString getGameNameFilter() const;
void setGameNameFilter(const QString &_gameNameFilter); void setGameNameFilter(const QString &_gameNameFilter);
QString getCreatorNameFilter() const; QStringList getCreatorNameFilters() const;
void setCreatorNameFilter(const QString &_creatorNameFilter); void setCreatorNameFilter(const QString &_creatorNameFilter);
QSet<int> getGameTypeFilter() const; QSet<int> getGameTypeFilter() const;
void setGameTypeFilter(const QSet<int> &_gameTypeFilter); void setGameTypeFilter(const QSet<int> &_gameTypeFilter);

View file

@ -82,9 +82,9 @@ bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckLoader *deckLoader) const
if (deckLoader->getDeckList()->loadFromStream_Plain(stream, true)) { if (deckLoader->getDeckList()->loadFromStream_Plain(stream, true)) {
if (loadSetNameAndNumberCheckBox->isChecked()) { if (loadSetNameAndNumberCheckBox->isChecked()) {
deckLoader->resolveSetNameAndNumberToProviderID(); DeckLoader::resolveSetNameAndNumberToProviderID(deckLoader->getDeckList());
} else { } else {
deckLoader->clearSetNamesAndNumbers(); DeckLoader::clearSetNamesAndNumbers(deckLoader->getDeckList());
} }
return true; return true;
} }
@ -154,17 +154,17 @@ DlgEditDeckInClipboard::DlgEditDeckInClipboard(const DeckLoader &deckList, bool
* @param addComments Whether to add annotations * @param addComments Whether to add annotations
* @return A QString * @return A QString
*/ */
static QString deckListToString(const DeckLoader *deckList, bool addComments) static QString deckListToString(const DeckList *deckList, bool addComments)
{ {
QString buffer; QString buffer;
QTextStream stream(&buffer); QTextStream stream(&buffer);
deckList->saveToStream_Plain(stream, addComments); DeckLoader::saveToStream_Plain(stream, deckList, addComments);
return buffer; return buffer;
} }
void DlgEditDeckInClipboard::actRefresh() void DlgEditDeckInClipboard::actRefresh()
{ {
setText(deckListToString(deckLoader, annotated)); setText(deckListToString(deckLoader->getDeckList(), annotated));
} }
void DlgEditDeckInClipboard::actOK() void DlgEditDeckInClipboard::actOK()

View file

@ -98,7 +98,7 @@ void DlgLoadDeckFromWebsite::accept()
DeckLoader *loader = new DeckLoader(this); DeckLoader *loader = new DeckLoader(this);
QTextStream stream(&deckText); QTextStream stream(&deckText);
loader->getDeckList()->loadFromStream_Plain(stream, false); loader->getDeckList()->loadFromStream_Plain(stream, false);
loader->resolveSetNameAndNumberToProviderID(); DeckLoader::resolveSetNameAndNumberToProviderID(loader->getDeckList());
deck = loader; deck = loader;
QDialog::accept(); QDialog::accept();

View file

@ -162,14 +162,14 @@ void DlgSelectSetForCards::actOK()
void DlgSelectSetForCards::actClear() void DlgSelectSetForCards::actClear()
{ {
qobject_cast<DeckLoader *>(model->getDeckList())->clearSetNamesAndNumbers(); DeckLoader::clearSetNamesAndNumbers(model->getDeckList());
accept(); accept();
} }
void DlgSelectSetForCards::actSetAllToPreferred() void DlgSelectSetForCards::actSetAllToPreferred()
{ {
qobject_cast<DeckLoader *>(model->getDeckList())->clearSetNamesAndNumbers(); DeckLoader::clearSetNamesAndNumbers(model->getDeckList());
qobject_cast<DeckLoader *>(model->getDeckList())->setProviderIdToPreferredPrinting(); DeckLoader::setProviderIdToPreferredPrinting(model->getDeckList());
accept(); accept();
} }

View file

@ -178,7 +178,7 @@ QGroupBox *HomeWidget::createButtons()
boxLayout->addSpacing(25); boxLayout->addSpacing(25);
connectButton = new HomeStyledButton("Connect/Play", gradientColors); connectButton = new HomeStyledButton("Connect/Play", gradientColors);
boxLayout->addWidget(connectButton, 1); boxLayout->addWidget(connectButton);
auto visualDeckEditorButton = new HomeStyledButton(tr("Create New Deck"), gradientColors); auto visualDeckEditorButton = new HomeStyledButton(tr("Create New Deck"), gradientColors);
connect(visualDeckEditorButton, &QPushButton::clicked, tabSupervisor, connect(visualDeckEditorButton, &QPushButton::clicked, tabSupervisor,

View file

@ -76,6 +76,12 @@ GameSelector::GameSelector(AbstractClient *_client,
gameListView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); gameListView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
if (showFilters && restoresettings) {
quickFilterToolBar = new GameSelectorQuickFilterToolBar(this, tabSupervisor, gameListProxyModel, gameTypeMap);
} else {
quickFilterToolBar = nullptr;
}
filterButton = new QPushButton; filterButton = new QPushButton;
filterButton->setIcon(QPixmap("theme:icons/search")); filterButton->setIcon(QPixmap("theme:icons/search"));
connect(filterButton, &QPushButton::clicked, this, &GameSelector::actSetFilter); connect(filterButton, &QPushButton::clicked, this, &GameSelector::actSetFilter);
@ -92,7 +98,9 @@ GameSelector::GameSelector(AbstractClient *_client,
createButton = nullptr; createButton = nullptr;
} }
joinButton = new QPushButton; joinButton = new QPushButton;
joinAsJudgeButton = new QPushButton;
spectateButton = new QPushButton; spectateButton = new QPushButton;
joinAsJudgeSpectatorButton = new QPushButton;
QHBoxLayout *buttonLayout = new QHBoxLayout; QHBoxLayout *buttonLayout = new QHBoxLayout;
if (showFilters) { if (showFilters) {
@ -103,10 +111,23 @@ GameSelector::GameSelector(AbstractClient *_client,
if (room) if (room)
buttonLayout->addWidget(createButton); buttonLayout->addWidget(createButton);
buttonLayout->addWidget(joinButton); buttonLayout->addWidget(joinButton);
if (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
buttonLayout->addWidget(joinAsJudgeButton);
} else {
joinAsJudgeButton->setHidden(true);
}
buttonLayout->addWidget(spectateButton); buttonLayout->addWidget(spectateButton);
if (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
buttonLayout->addWidget(joinAsJudgeSpectatorButton);
} else {
joinAsJudgeSpectatorButton->setHidden(true);
}
buttonLayout->setAlignment(Qt::AlignTop); buttonLayout->setAlignment(Qt::AlignTop);
QVBoxLayout *mainLayout = new QVBoxLayout; QVBoxLayout *mainLayout = new QVBoxLayout;
if (showFilters && restoresettings) {
mainLayout->addWidget(quickFilterToolBar);
}
mainLayout->addWidget(gameListView); mainLayout->addWidget(gameListView);
mainLayout->addLayout(buttonLayout); mainLayout->addLayout(buttonLayout);
@ -117,7 +138,9 @@ GameSelector::GameSelector(AbstractClient *_client,
setMinimumHeight(200); setMinimumHeight(200);
connect(joinButton, &QPushButton::clicked, this, &GameSelector::actJoin); connect(joinButton, &QPushButton::clicked, this, &GameSelector::actJoin);
connect(spectateButton, &QPushButton::clicked, this, &GameSelector::actSpectate); connect(joinAsJudgeButton, &QPushButton::clicked, this, &GameSelector::actJoinAsJudge);
connect(spectateButton, &QPushButton::clicked, this, &GameSelector::actJoinAsSpectator);
connect(joinAsJudgeSpectatorButton, &QPushButton::clicked, this, &GameSelector::actJoinAsJudgeSpectator);
connect(gameListView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, connect(gameListView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
&GameSelector::actSelectedGameChanged); &GameSelector::actSelectedGameChanged);
connect(gameListView, &QTreeView::activated, this, &GameSelector::actJoin); connect(gameListView, &QTreeView::activated, this, &GameSelector::actJoin);
@ -158,7 +181,7 @@ void GameSelector::actSetFilter()
gameListProxyModel->setGameFilters( gameListProxyModel->setGameFilters(
dlg.getHideBuddiesOnlyGames(), dlg.getHideIgnoredUserGames(), dlg.getHideFullGames(), dlg.getHideBuddiesOnlyGames(), dlg.getHideIgnoredUserGames(), dlg.getHideFullGames(),
dlg.getHideGamesThatStarted(), dlg.getHidePasswordProtectedGames(), dlg.getHideNotBuddyCreatedGames(), dlg.getHideGamesThatStarted(), dlg.getHidePasswordProtectedGames(), dlg.getHideNotBuddyCreatedGames(),
dlg.getHideOpenDecklistGames(), dlg.getGameNameFilter(), dlg.getCreatorNameFilter(), dlg.getGameTypeFilter(), dlg.getHideOpenDecklistGames(), dlg.getGameNameFilter(), dlg.getCreatorNameFilters(), dlg.getGameTypeFilter(),
dlg.getMaxPlayersFilterMin(), dlg.getMaxPlayersFilterMax(), dlg.getMaxGameAge(), dlg.getMaxPlayersFilterMin(), dlg.getMaxPlayersFilterMax(), dlg.getMaxGameAge(),
dlg.getShowOnlyIfSpectatorsCanWatch(), dlg.getShowSpectatorPasswordProtected(), dlg.getShowOnlyIfSpectatorsCanWatch(), dlg.getShowSpectatorPasswordProtected(),
dlg.getShowOnlyIfSpectatorsCanChat(), dlg.getShowOnlyIfSpectatorsCanSeeHands()); dlg.getShowOnlyIfSpectatorsCanChat(), dlg.getShowOnlyIfSpectatorsCanSeeHands());
@ -238,12 +261,30 @@ void GameSelector::checkResponse(const Response &response)
void GameSelector::actJoin() void GameSelector::actJoin()
{ {
return joinGame(false); joinGame();
} }
void GameSelector::actSpectate() void GameSelector::actJoinAsJudge()
{ {
return joinGame(true); if (!(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge)) {
joinGame();
} else {
joinGame(false, true);
}
}
void GameSelector::actJoinAsSpectator()
{
joinGame(true);
}
void GameSelector::actJoinAsJudgeSpectator()
{
if (!(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge)) {
joinGame(true);
} else {
joinGame(true, true);
}
} }
void GameSelector::customContextMenu(const QPoint &point) void GameSelector::customContextMenu(const QPoint &point)
@ -257,7 +298,7 @@ void GameSelector::customContextMenu(const QPoint &point)
connect(&joinGame, &QAction::triggered, this, &GameSelector::actJoin); connect(&joinGame, &QAction::triggered, this, &GameSelector::actJoin);
QAction spectateGame(tr("Spectate Game")); QAction spectateGame(tr("Spectate Game"));
connect(&spectateGame, &QAction::triggered, this, &GameSelector::actSpectate); connect(&spectateGame, &QAction::triggered, this, &GameSelector::actJoinAsSpectator);
QAction getGameInfo(tr("Game Information")); QAction getGameInfo(tr("Game Information"));
connect(&getGameInfo, &QAction::triggered, this, [=, this]() { connect(&getGameInfo, &QAction::triggered, this, [=, this]() {
@ -270,12 +311,25 @@ void GameSelector::customContextMenu(const QPoint &point)
QMenu menu; QMenu menu;
menu.addAction(&joinGame); menu.addAction(&joinGame);
if (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
QAction joinGameAsJudge(tr("Join Game as Judge"));
connect(&joinGameAsJudge, &QAction::triggered, this, &GameSelector::actJoinAsJudge);
menu.addAction(&joinGameAsJudge);
QAction spectateGameAsJudge(tr("Spectate Game as Judge"));
connect(&spectateGameAsJudge, &QAction::triggered, this, &GameSelector::actJoinAsJudgeSpectator);
menu.addAction(&spectateGameAsJudge);
}
menu.addAction(&spectateGame); menu.addAction(&spectateGame);
menu.addAction(&getGameInfo); menu.addAction(&getGameInfo);
menu.exec(gameListView->mapToGlobal(point)); menu.exec(gameListView->mapToGlobal(point));
} }
void GameSelector::joinGame(const bool isSpectator) void GameSelector::joinGame(const bool asSpectator, const bool asJudge)
{ {
QModelIndex ind = gameListView->currentIndex(); QModelIndex ind = gameListView->currentIndex();
if (!ind.isValid()) { if (!ind.isValid()) {
@ -287,7 +341,7 @@ void GameSelector::joinGame(const bool isSpectator)
return; return;
} }
bool spectator = isSpectator || game.player_count() == game.max_players(); bool spectator = asSpectator || game.player_count() == game.max_players();
bool overrideRestrictions = !tabSupervisor->getAdminLocked(); bool overrideRestrictions = !tabSupervisor->getAdminLocked();
QString password; QString password;
@ -304,7 +358,7 @@ void GameSelector::joinGame(const bool isSpectator)
cmd.set_password(password.toStdString()); cmd.set_password(password.toStdString());
cmd.set_spectator(spectator); cmd.set_spectator(spectator);
cmd.set_override_restrictions(overrideRestrictions); cmd.set_override_restrictions(overrideRestrictions);
cmd.set_join_as_judge((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0); cmd.set_join_as_judge(asJudge);
TabRoom *r = tabSupervisor->getRoomTabs().value(game.room_id()); TabRoom *r = tabSupervisor->getRoomTabs().value(game.room_id());
if (!r) { if (!r) {
@ -356,7 +410,9 @@ void GameSelector::retranslateUi()
if (createButton) if (createButton)
createButton->setText(tr("C&reate")); createButton->setText(tr("C&reate"));
joinButton->setText(tr("&Join")); joinButton->setText(tr("&Join"));
joinAsJudgeButton->setText(tr("Join as judge"));
spectateButton->setText(tr("J&oin as spectator")); spectateButton->setText(tr("J&oin as spectator"));
joinAsJudgeSpectatorButton->setText(tr("Join as judge spectator"));
updateTitle(); updateTitle();
} }

View file

@ -1,12 +1,7 @@
/**
* @file game_selector.h
* @ingroup Lobby
* @brief TODO: Document this.
*/
#ifndef GAMESELECTOR_H #ifndef GAMESELECTOR_H
#define GAMESELECTOR_H #define GAMESELECTOR_H
#include "game_selector_quick_filter_toolbar.h"
#include "game_type_map.h" #include "game_type_map.h"
#include <QGroupBox> #include <QGroupBox>
@ -26,46 +21,167 @@ class TabRoom;
class ServerInfo_Game; class ServerInfo_Game;
class Response; class Response;
/**
* @class GameSelector
* @ingroup Lobby
* @brief Provides a widget for displaying, filtering, joining, spectating, and creating games in a room.
*
* The GameSelector displays all available games in a QTreeView. It supports filtering,
* creating, joining, spectating, and viewing game details. Integrates with TabSupervisor
* and TabRoom for room and game management.
*/
class GameSelector : public QGroupBox class GameSelector : public QGroupBox
{ {
Q_OBJECT Q_OBJECT
private slots: private slots:
/**
* @brief Opens a dialog to set filters for the game list.
*
* Updates the proxy model with selected filter parameters and refreshes the displayed game list.
*/
void actSetFilter(); void actSetFilter();
/**
* @brief Clears all filters applied to the game list.
*
* Resets the proxy model to show all games.
*/
void actClearFilter(); void actClearFilter();
/**
* @brief Opens the dialog to create a new game in the current room.
*/
void actCreate(); void actCreate();
/**
* @brief Joins the currently selected game as a player.
*/
void actJoin(); void actJoin();
void actSpectate(); /**
* @brief Joins the currently selected game as a judge.
*/
void actJoinAsJudge();
/**
* @brief Joins the currently selected game as a spectator.
*/
void actJoinAsSpectator();
void actJoinAsJudgeSpectator();
/**
* @brief Shows the custom context menu for a game when right-clicked.
* @param point The point at which the context menu is requested.
*/
void customContextMenu(const QPoint &point); void customContextMenu(const QPoint &point);
/**
* @brief Slot called when the selected game changes.
* @param current The currently selected index.
* @param previous The previously selected index.
*
* Updates the enabled/disabled state of buttons depending on the selected game.
*/
void actSelectedGameChanged(const QModelIndex &current, const QModelIndex &previous); void actSelectedGameChanged(const QModelIndex &current, const QModelIndex &previous);
/**
* @brief Processes server responses for join or spectate commands.
* @param response The response from the server.
*
* Displays error messages for failed join/spectate attempts.
*/
void checkResponse(const Response &response); void checkResponse(const Response &response);
/**
* @brief Refreshes the game list when the ignore list is received from the server.
* @param _ignoreList The list of users being ignored.
*/
void ignoreListReceived(const QList<ServerInfo_User> &_ignoreList); void ignoreListReceived(const QList<ServerInfo_User> &_ignoreList);
/**
* @brief Processes events where a user is added to a list (e.g., ignore or buddy).
* @param event The event information.
*/
void processAddToListEvent(const Event_AddToList &event); void processAddToListEvent(const Event_AddToList &event);
/**
* @brief Processes events where a user is removed from a list (e.g., ignore or buddy).
* @param event The event information.
*/
void processRemoveFromListEvent(const Event_RemoveFromList &event); void processRemoveFromListEvent(const Event_RemoveFromList &event);
signals: signals:
/**
* @brief Emitted when a game has been successfully joined.
* @param gameId The ID of the joined game.
*/
void gameJoined(int gameId); void gameJoined(int gameId);
private: private:
AbstractClient *client; AbstractClient *client; /**< The network client used to communicate with the server. */
TabSupervisor *tabSupervisor; TabSupervisor *tabSupervisor; /**< Reference to TabSupervisor for managing tabs and rooms. */
TabRoom *room; TabRoom *room; /**< The current room. */
QTreeView *gameListView; QTreeView *gameListView; /**< View widget for displaying the game list. */
GamesModel *gameListModel; GamesModel *gameListModel; /**< Model containing all games. */
GamesProxyModel *gameListProxyModel; GamesProxyModel *gameListProxyModel; /**< Proxy model for filtering and sorting the game list. */
QPushButton *filterButton, *clearFilterButton, *createButton, *joinButton, *spectateButton;
const bool showFilters;
GameTypeMap gameTypeMap;
GameSelectorQuickFilterToolBar *quickFilterToolBar;
QPushButton *filterButton; /**< Button to open the filter dialog. */
QPushButton *clearFilterButton; /**< Button to clear active filters. */
QPushButton *createButton; /**< Button to create a new game (only if room is set). */
QPushButton *joinButton; /**< Button to join the selected game. */
QPushButton *joinAsJudgeButton; /**< Button to join the selected game as a judge. */
QPushButton *spectateButton; /**< Button to spectate the selected game. */
QPushButton *joinAsJudgeSpectatorButton; /**< Button to join the selected game as a spectating judge. */
const bool showFilters; /**< Determines whether filter buttons are displayed. */
GameTypeMap gameTypeMap; /**< Mapping of game types for the current room. */
/**
* @brief Updates the widget title to reflect the current number of displayed games.
*
* Shows the number of visible games versus total games if filters are enabled.
*/
void updateTitle(); void updateTitle();
/**
* @brief Disables create/join/spectate buttons.
*/
void disableButtons(); void disableButtons();
/**
* @brief Enables buttons for the currently selected game.
*/
void enableButtons(); void enableButtons();
/**
* @brief Enables buttons for a specific game index.
* @param current The index of the currently selected game.
*/
void enableButtonsForIndex(const QModelIndex &current); void enableButtonsForIndex(const QModelIndex &current);
void joinGame(const bool isSpectator);
/**
* @brief Performs the join or spectate action for the currently selected game.
* @param asSpectator True to join as a spectator, false to join as a player.
* @param asJudge True to join as a judge, false to join as a player.
*
* Handles password prompts, overrides, and sending the join command to the server.
*/
void joinGame(bool asSpectator = false, bool asJudge = false);
public: public:
/**
* @brief Constructs a GameSelector widget.
* @param _client The network client used to communicate with the server.
* @param _tabSupervisor Reference to TabSupervisor for managing tabs and rooms.
* @param _room Pointer to the current room; nullptr if no room is selected.
* @param _rooms Map of room IDs to room names.
* @param _gameTypes Map of room IDs to their available game types.
* @param restoresettings Whether to restore filter settings from previous sessions.
* @param _showfilters Whether to display filter buttons.
* @param parent Parent QWidget.
*/
GameSelector(AbstractClient *_client, GameSelector(AbstractClient *_client,
TabSupervisor *_tabSupervisor, TabSupervisor *_tabSupervisor,
TabRoom *_room, TabRoom *_room,
@ -74,7 +190,16 @@ public:
const bool restoresettings, const bool restoresettings,
const bool _showfilters, const bool _showfilters,
QWidget *parent = nullptr); QWidget *parent = nullptr);
/**
* @brief Updates UI text for translation/localization.
*/
void retranslateUi(); void retranslateUi();
/**
* @brief Updates or adds a game entry in the list.
* @param info The ServerInfo_Game object containing information about the game to update.
*/
void processGameInfo(const ServerInfo_Game &info); void processGameInfo(const ServerInfo_Game &info);
}; };

View file

@ -0,0 +1,110 @@
#include "game_selector_quick_filter_toolbar.h"
#include "games_model.h"
#include "user/user_list_manager.h"
#include <QCheckBox>
#include <QComboBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent,
TabSupervisor *_tabSupervisor,
GamesProxyModel *_model,
const QMap<int, QString> &allGameTypes)
: QWidget(parent), tabSupervisor(_tabSupervisor), model(_model)
{
mainLayout = new QHBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(5);
searchBar = new QLineEdit(this);
searchBar->setText(model->getCreatorNameFilters().join(", "));
connect(searchBar, &QLineEdit::textChanged, this, [this](const QString &text) { model->setGameNameFilter(text); });
hideGamesNotCreatedByBuddiesCheckBox = new QCheckBox(this);
hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideBuddiesOnlyGames());
connect(hideGamesNotCreatedByBuddiesCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
if (checked) {
QStringList buddyNames;
for (auto buddy : tabSupervisor->getUserListManager()->getBuddyList().values()) {
buddyNames << QString::fromStdString(buddy.name());
}
model->setCreatorNameFilters(buddyNames);
} else {
model->setCreatorNameFilters({});
}
});
hideFullGamesCheckBox = new QCheckBox(this);
hideFullGamesCheckBox->setChecked(model->getHideFullGames());
connect(hideFullGamesCheckBox, &QCheckBox::toggled, this,
[this](bool checked) { model->setHideFullGames(checked); });
hideStartedGamesCheckBox = new QCheckBox(this);
hideStartedGamesCheckBox->setChecked(model->getHideGamesThatStarted());
connect(hideStartedGamesCheckBox, &QCheckBox::toggled, this,
[this](bool checked) { model->setHideGamesThatStarted(checked); });
filterToFormatComboBox = new QComboBox(this);
// Add a "No filter" / "All types" option first
filterToFormatComboBox->addItem(tr("All types"), QVariant()); // empty QVariant = no filter
QMapIterator<int, QString> i(allGameTypes);
while (i.hasNext()) {
i.next();
filterToFormatComboBox->addItem(i.value(), i.key()); // text = name, data = type ID
}
QSet<int> currentTypes = model->getGameTypeFilter();
if (currentTypes.size() == 1) {
int typeId = *currentTypes.begin();
int index = filterToFormatComboBox->findData(typeId);
if (index >= 0)
filterToFormatComboBox->setCurrentIndex(index);
} else {
filterToFormatComboBox->setCurrentIndex(0); // "All types" by default
}
// Update proxy model on selection change
connect(filterToFormatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
QVariant data = filterToFormatComboBox->itemData(index);
if (!data.isValid()) {
model->setGameTypeFilter({}); // empty = no filter
} else {
int typeId = data.toInt();
model->setGameTypeFilter({typeId});
}
});
hideGamesNotCreatedByBuddiesCheckBox->setMinimumSize(20, 20);
hideFullGamesCheckBox->setMinimumSize(20, 20);
hideStartedGamesCheckBox->setMinimumSize(20, 20);
#if defined(Q_OS_MAC)
mainLayout->setSpacing(6);
#endif
mainLayout->addWidget(searchBar);
mainLayout->addWidget(filterToFormatComboBox);
mainLayout->addWidget(hideGamesNotCreatedByBuddiesCheckBox);
mainLayout->addSpacing(5);
mainLayout->addWidget(hideFullGamesCheckBox);
mainLayout->addSpacing(5);
mainLayout->addWidget(hideStartedGamesCheckBox);
setLayout(mainLayout);
retranslateUi();
}
void GameSelectorQuickFilterToolBar::retranslateUi()
{
searchBar->setPlaceholderText(tr("Filter by game name..."));
filterToFormatComboBox->setToolTip(tr("Filter by game type/format"));
hideGamesNotCreatedByBuddiesCheckBox->setText(tr("Hide games not created by buddies"));
hideFullGamesCheckBox->setText(tr("Hide full games"));
hideStartedGamesCheckBox->setText(tr("Hide started games"));
}

View file

@ -0,0 +1,39 @@
#ifndef COCKATRICE_GAME_SELECTOR_QUICK_FILTER_TOOLBAR_H
#define COCKATRICE_GAME_SELECTOR_QUICK_FILTER_TOOLBAR_H
#include "../tabs/tab_supervisor.h"
#include "games_model.h"
#include <QCheckBox>
#include <QComboBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QWidget>
class GameSelectorQuickFilterToolBar : public QWidget
{
Q_OBJECT
public:
explicit GameSelectorQuickFilterToolBar(QWidget *parent,
TabSupervisor *tabSupervisor,
GamesProxyModel *model,
const QMap<int, QString> &allGameTypes);
void retranslateUi();
private:
TabSupervisor *tabSupervisor;
GamesProxyModel *model;
QHBoxLayout *mainLayout;
QLineEdit *searchBar;
QCheckBox *hideGamesNotCreatedByBuddiesCheckBox;
QCheckBox *hideFullGamesCheckBox;
QCheckBox *hideStartedGamesCheckBox;
QComboBox *filterToFormatComboBox;
};
#endif // COCKATRICE_GAME_SELECTOR_QUICK_FILTER_TOOLBAR_H

View file

@ -294,7 +294,7 @@ void GamesProxyModel::setGameFilters(bool _hideBuddiesOnlyGames,
bool _hideNotBuddyCreatedGames, bool _hideNotBuddyCreatedGames,
bool _hideOpenDecklistGames, bool _hideOpenDecklistGames,
const QString &_gameNameFilter, const QString &_gameNameFilter,
const QString &_creatorNameFilter, const QStringList &_creatorNameFilters,
const QSet<int> &_gameTypeFilter, const QSet<int> &_gameTypeFilter,
int _maxPlayersFilterMin, int _maxPlayersFilterMin,
int _maxPlayersFilterMax, int _maxPlayersFilterMax,
@ -315,7 +315,7 @@ void GamesProxyModel::setGameFilters(bool _hideBuddiesOnlyGames,
hideNotBuddyCreatedGames = _hideNotBuddyCreatedGames; hideNotBuddyCreatedGames = _hideNotBuddyCreatedGames;
hideOpenDecklistGames = _hideOpenDecklistGames; hideOpenDecklistGames = _hideOpenDecklistGames;
gameNameFilter = _gameNameFilter; gameNameFilter = _gameNameFilter;
creatorNameFilter = _creatorNameFilter; creatorNameFilters = _creatorNameFilters;
gameTypeFilter = _gameTypeFilter; gameTypeFilter = _gameTypeFilter;
maxPlayersFilterMin = _maxPlayersFilterMin; maxPlayersFilterMin = _maxPlayersFilterMin;
maxPlayersFilterMax = _maxPlayersFilterMax; maxPlayersFilterMax = _maxPlayersFilterMax;
@ -348,15 +348,15 @@ int GamesProxyModel::getNumFilteredGames() const
void GamesProxyModel::resetFilterParameters() void GamesProxyModel::resetFilterParameters()
{ {
setGameFilters(false, false, false, false, false, false, false, QString(), QString(), {}, DEFAULT_MAX_PLAYERS_MIN, setGameFilters(false, false, false, false, false, false, false, QString(), QStringList(), {},
DEFAULT_MAX_PLAYERS_MAX, DEFAULT_MAX_GAME_AGE, false, false, false, false); DEFAULT_MAX_PLAYERS_MIN, DEFAULT_MAX_PLAYERS_MAX, DEFAULT_MAX_GAME_AGE, false, false, false, false);
} }
bool GamesProxyModel::areFilterParametersSetToDefaults() const bool GamesProxyModel::areFilterParametersSetToDefaults() const
{ {
return !hideFullGames && !hideGamesThatStarted && !hidePasswordProtectedGames && !hideBuddiesOnlyGames && return !hideFullGames && !hideGamesThatStarted && !hidePasswordProtectedGames && !hideBuddiesOnlyGames &&
!hideOpenDecklistGames && !hideIgnoredUserGames && !hideNotBuddyCreatedGames && gameNameFilter.isEmpty() && !hideOpenDecklistGames && !hideIgnoredUserGames && !hideNotBuddyCreatedGames && gameNameFilter.isEmpty() &&
creatorNameFilter.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN && creatorNameFilters.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN &&
maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX && maxGameAge == DEFAULT_MAX_GAME_AGE && maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX && maxGameAge == DEFAULT_MAX_GAME_AGE &&
!showOnlyIfSpectatorsCanWatch && !showSpectatorPasswordProtected && !showOnlyIfSpectatorsCanChat && !showOnlyIfSpectatorsCanWatch && !showSpectatorPasswordProtected && !showOnlyIfSpectatorsCanChat &&
!showOnlyIfSpectatorsCanSeeHands; !showOnlyIfSpectatorsCanSeeHands;
@ -379,7 +379,7 @@ void GamesProxyModel::loadFilterParameters(const QMap<int, QString> &allGameType
gameFilters.isHideFullGames(), gameFilters.isHideGamesThatStarted(), gameFilters.isHideFullGames(), gameFilters.isHideGamesThatStarted(),
gameFilters.isHidePasswordProtectedGames(), gameFilters.isHideNotBuddyCreatedGames(), gameFilters.isHidePasswordProtectedGames(), gameFilters.isHideNotBuddyCreatedGames(),
gameFilters.isHideOpenDecklistGames(), gameFilters.getGameNameFilter(), gameFilters.isHideOpenDecklistGames(), gameFilters.getGameNameFilter(),
gameFilters.getCreatorNameFilter(), newGameTypeFilter, gameFilters.getMinPlayers(), gameFilters.getCreatorNameFilters(), newGameTypeFilter, gameFilters.getMinPlayers(),
gameFilters.getMaxPlayers(), gameFilters.getMaxGameAge(), gameFilters.getMaxPlayers(), gameFilters.getMaxGameAge(),
gameFilters.isShowOnlyIfSpectatorsCanWatch(), gameFilters.isShowSpectatorPasswordProtected(), gameFilters.isShowOnlyIfSpectatorsCanWatch(), gameFilters.isShowSpectatorPasswordProtected(),
gameFilters.isShowOnlyIfSpectatorsCanChat(), gameFilters.isShowOnlyIfSpectatorsCanSeeHands()); gameFilters.isShowOnlyIfSpectatorsCanChat(), gameFilters.isShowOnlyIfSpectatorsCanSeeHands());
@ -396,7 +396,7 @@ void GamesProxyModel::saveFilterParameters(const QMap<int, QString> &allGameType
gameFilters.setHideNotBuddyCreatedGames(hideNotBuddyCreatedGames); gameFilters.setHideNotBuddyCreatedGames(hideNotBuddyCreatedGames);
gameFilters.setHideOpenDecklistGames(hideOpenDecklistGames); gameFilters.setHideOpenDecklistGames(hideOpenDecklistGames);
gameFilters.setGameNameFilter(gameNameFilter); gameFilters.setGameNameFilter(gameNameFilter);
gameFilters.setCreatorNameFilter(creatorNameFilter); gameFilters.setCreatorNameFilters(creatorNameFilters);
QMapIterator<int, QString> gameTypeIterator(allGameTypes); QMapIterator<int, QString> gameTypeIterator(allGameTypes);
while (gameTypeIterator.hasNext()) { while (gameTypeIterator.hasNext()) {
@ -459,9 +459,17 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const
if (!gameNameFilter.isEmpty()) if (!gameNameFilter.isEmpty())
if (!QString::fromStdString(game.description()).contains(gameNameFilter, Qt::CaseInsensitive)) if (!QString::fromStdString(game.description()).contains(gameNameFilter, Qt::CaseInsensitive))
return false; return false;
if (!creatorNameFilter.isEmpty()) if (!creatorNameFilters.isEmpty()) {
if (!QString::fromStdString(game.creator_info().name()).contains(creatorNameFilter, Qt::CaseInsensitive)) bool found = false;
for (const auto &createNameFilter : creatorNameFilters) {
if (QString::fromStdString(game.creator_info().name()).contains(createNameFilter, Qt::CaseInsensitive)) {
found = true;
}
}
if (!found) {
return false; return false;
}
}
QSet<int> gameTypes; QSet<int> gameTypes;
for (int i = 0; i < game.game_types_size(); ++i) for (int i = 0; i < game.game_types_size(); ++i)

View file

@ -1,9 +1,3 @@
/**
* @file games_model.h
* @ingroup Lobby
* @brief TODO: Document this.
*/
#ifndef GAMESMODEL_H #ifndef GAMESMODEL_H
#define GAMESMODEL_H #define GAMESMODEL_H
@ -19,60 +13,107 @@
class UserListProxy; class UserListProxy;
/**
* @class GamesModel
* @ingroup Lobby
* @brief Model storing all available games for display in a QTreeView or QTableView.
*
* Provides access to game information, supports sorting by different columns,
* and updates when new game data is received from the server.
*/
class GamesModel : public QAbstractTableModel class GamesModel : public QAbstractTableModel
{ {
Q_OBJECT Q_OBJECT
private: private:
QList<ServerInfo_Game> gameList; QList<ServerInfo_Game> gameList; /**< List of games currently displayed. */
QMap<int, QString> rooms; QMap<int, QString> rooms; /**< Map of room IDs to room names. */
QMap<int, GameTypeMap> gameTypes; QMap<int, GameTypeMap> gameTypes; /**< Map of room IDs to available game types. */
static const int NUM_COLS = 8; static const int NUM_COLS = 8; /**< Number of columns in the table. */
public: public:
static const int SORT_ROLE = Qt::UserRole + 1; static const int SORT_ROLE = Qt::UserRole + 1; /**< Role used for sorting. */
/**
* @brief Constructs a GamesModel.
* @param _rooms Mapping of room IDs to room names.
* @param _gameTypes Mapping of room IDs to their available game types.
* @param parent Parent QObject.
*/
GamesModel(const QMap<int, QString> &_rooms, const QMap<int, GameTypeMap> &_gameTypes, QObject *parent = nullptr); GamesModel(const QMap<int, QString> &_rooms, const QMap<int, GameTypeMap> &_gameTypes, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override int rowCount(const QModelIndex &parent = QModelIndex()) const override
{ {
return parent.isValid() ? 0 : gameList.size(); return parent.isValid() ? 0 : gameList.size();
} }
int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override
{ {
return NUM_COLS; return NUM_COLS;
} }
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
/**
* @brief Formats the game creation time into a human-readable string.
* @param secs Number of seconds since the game started.
* @return Short string representing game age (e.g., "new", ">5 min", ">2 hr").
*/
static const QString getGameCreatedString(const int secs); static const QString getGameCreatedString(const int secs);
/**
* @brief Returns a reference to a specific game by row index.
* @param row Row index in the table.
* @return Reference to the ServerInfo_Game at the given row.
*/
const ServerInfo_Game &getGame(int row); const ServerInfo_Game &getGame(int row);
/** /**
* Update game list with a (possibly new) game. * @brief Updates the game list with a new or updated game.
* @param game The ServerInfo_Game object to add or update.
*/ */
void updateGameList(const ServerInfo_Game &game); void updateGameList(const ServerInfo_Game &game);
/**
* @brief Returns the index of the room column.
*/
int roomColIndex() int roomColIndex()
{ {
return 0; return 0;
} }
/**
* @brief Returns the index of the start time column.
*/
int startTimeColIndex() int startTimeColIndex()
{ {
return 1; return 1;
} }
/**
* @brief Returns the map of game types per room.
*/
const QMap<int, GameTypeMap> &getGameTypes() const QMap<int, GameTypeMap> &getGameTypes()
{ {
return gameTypes; return gameTypes;
} }
}; };
class ServerInfo_User; /**
* @class GamesProxyModel
* @ingroup Lobby
* @brief Proxy model for filtering and sorting the GamesModel based on user preferences.
*
* Supports filtering games based on buddies-only, ignored users, password protection,
* game types, creator, age, player count, and spectator permissions.
*/
class GamesProxyModel : public QSortFilterProxyModel class GamesProxyModel : public QSortFilterProxyModel
{ {
Q_OBJECT Q_OBJECT
private: private:
const UserListProxy *userListProxy; const UserListProxy *userListProxy; /**< Proxy for checking user ignore/buddy lists. */
// If adding any additional filters, make sure to update: // If adding any additional filters, make sure to update:
// - GamesProxyModel() // - GamesProxyModel()
@ -88,16 +129,25 @@ private:
bool hidePasswordProtectedGames; bool hidePasswordProtectedGames;
bool hideNotBuddyCreatedGames; bool hideNotBuddyCreatedGames;
bool hideOpenDecklistGames; bool hideOpenDecklistGames;
QString gameNameFilter, creatorNameFilter; QString gameNameFilter;
QStringList creatorNameFilters;
QSet<int> gameTypeFilter; QSet<int> gameTypeFilter;
quint32 maxPlayersFilterMin, maxPlayersFilterMax; quint32 maxPlayersFilterMin, maxPlayersFilterMax;
QTime maxGameAge; QTime maxGameAge;
bool showOnlyIfSpectatorsCanWatch, showSpectatorPasswordProtected, showOnlyIfSpectatorsCanChat, bool showOnlyIfSpectatorsCanWatch;
showOnlyIfSpectatorsCanSeeHands; bool showSpectatorPasswordProtected;
bool showOnlyIfSpectatorsCanChat;
bool showOnlyIfSpectatorsCanSeeHands;
public: public:
/**
* @brief Constructs a GamesProxyModel.
* @param parent Parent QObject.
* @param _userListProxy Proxy for accessing ignore/buddy lists.
*/
explicit GamesProxyModel(QObject *parent = nullptr, const UserListProxy *_userListProxy = nullptr); explicit GamesProxyModel(QObject *parent = nullptr, const UserListProxy *_userListProxy = nullptr);
// Getters for filter parameters
bool getHideBuddiesOnlyGames() const bool getHideBuddiesOnlyGames() const
{ {
return hideBuddiesOnlyGames; return hideBuddiesOnlyGames;
@ -130,9 +180,9 @@ public:
{ {
return gameNameFilter; return gameNameFilter;
} }
QString getCreatorNameFilter() const QStringList getCreatorNameFilters() const
{ {
return creatorNameFilter; return creatorNameFilters;
} }
QSet<int> getGameTypeFilter() const QSet<int> getGameTypeFilter() const
{ {
@ -166,6 +216,10 @@ public:
{ {
return showOnlyIfSpectatorsCanSeeHands; return showOnlyIfSpectatorsCanSeeHands;
} }
/**
* @brief Sets all game filters at once.
*/
void setGameFilters(bool _hideBuddiesOnlyGames, void setGameFilters(bool _hideBuddiesOnlyGames,
bool _hideIgnoredUserGames, bool _hideIgnoredUserGames,
bool _hideFullGames, bool _hideFullGames,
@ -174,7 +228,7 @@ public:
bool _hideNotBuddyCreatedGames, bool _hideNotBuddyCreatedGames,
bool _hideOpenDecklistGames, bool _hideOpenDecklistGames,
const QString &_gameNameFilter, const QString &_gameNameFilter,
const QString &_creatorNameFilter, const QStringList &_creatorNameFilter,
const QSet<int> &_gameTypeFilter, const QSet<int> &_gameTypeFilter,
int _maxPlayersFilterMin, int _maxPlayersFilterMin,
int _maxPlayersFilterMax, int _maxPlayersFilterMax,
@ -184,11 +238,123 @@ public:
bool _showOnlyIfSpectatorsCanChat, bool _showOnlyIfSpectatorsCanChat,
bool _showOnlyIfSpectatorsCanSeeHands); bool _showOnlyIfSpectatorsCanSeeHands);
// Individual setters
void setHideBuddiesOnlyGames(bool value)
{
hideBuddiesOnlyGames = value;
refresh();
}
void setHideIgnoredUserGames(bool value)
{
hideIgnoredUserGames = value;
refresh();
}
void setHideFullGames(bool value)
{
hideFullGames = value;
refresh();
}
void setHideGamesThatStarted(bool value)
{
hideGamesThatStarted = value;
refresh();
}
void setHidePasswordProtectedGames(bool value)
{
hidePasswordProtectedGames = value;
refresh();
}
void setHideNotBuddyCreatedGames(bool value)
{
hideNotBuddyCreatedGames = value;
refresh();
}
void setHideOpenDecklistGames(bool value)
{
hideOpenDecklistGames = value;
refresh();
}
void setGameNameFilter(const QString &value)
{
gameNameFilter = value;
refresh();
}
void setCreatorNameFilters(const QStringList &values)
{
creatorNameFilters = values;
refresh();
}
void setGameTypeFilter(const QSet<int> &value)
{
gameTypeFilter = value;
refresh();
}
void setMaxPlayersFilterMin(int value)
{
maxPlayersFilterMin = value;
refresh();
}
void setMaxPlayersFilterMax(int value)
{
maxPlayersFilterMax = value;
refresh();
}
void setMaxGameAge(const QTime &value)
{
maxGameAge = value;
refresh();
}
void setShowOnlyIfSpectatorsCanWatch(bool value)
{
showOnlyIfSpectatorsCanWatch = value;
refresh();
}
void setShowSpectatorPasswordProtected(bool value)
{
showSpectatorPasswordProtected = value;
refresh();
}
void setShowOnlyIfSpectatorsCanChat(bool value)
{
showOnlyIfSpectatorsCanChat = value;
refresh();
}
void setShowOnlyIfSpectatorsCanSeeHands(bool value)
{
showOnlyIfSpectatorsCanSeeHands = value;
refresh();
}
/**
* @brief Returns the number of games filtered out by the current filter.
*/
int getNumFilteredGames() const; int getNumFilteredGames() const;
/**
* @brief Resets all filter parameters to default values.
*/
void resetFilterParameters(); void resetFilterParameters();
/**
* @brief Returns true if all filter parameters are set to their defaults.
*/
bool areFilterParametersSetToDefaults() const; bool areFilterParametersSetToDefaults() const;
/**
* @brief Loads filter parameters from persistent settings.
* @param allGameTypes Mapping of all game types by room ID.
*/
void loadFilterParameters(const QMap<int, QString> &allGameTypes); void loadFilterParameters(const QMap<int, QString> &allGameTypes);
/**
* @brief Saves filter parameters to persistent settings.
* @param allGameTypes Mapping of all game types by room ID.
*/
void saveFilterParameters(const QMap<int, QString> &allGameTypes); void saveFilterParameters(const QMap<int, QString> &allGameTypes);
/**
* @brief Refreshes the proxy model (re-applies filters).
*/
void refresh(); void refresh();
protected: protected:

View file

@ -510,32 +510,33 @@ void AbstractTabDeckEditor::actEditDeckInClipboardRaw()
/** @brief Saves deck to clipboard with set info and annotation. */ /** @brief Saves deck to clipboard with set info and annotation. */
void AbstractTabDeckEditor::actSaveDeckToClipboard() void AbstractTabDeckEditor::actSaveDeckToClipboard()
{ {
getDeckLoader()->saveToClipboard(true, true); DeckLoader::saveToClipboard(getDeckList(), true, true);
} }
/** @brief Saves deck to clipboard with annotation, without set info. */ /** @brief Saves deck to clipboard with annotation, without set info. */
void AbstractTabDeckEditor::actSaveDeckToClipboardNoSetInfo() void AbstractTabDeckEditor::actSaveDeckToClipboardNoSetInfo()
{ {
getDeckLoader()->saveToClipboard(true, false); DeckLoader::saveToClipboard(getDeckList(), true, false);
} }
/** @brief Saves deck to clipboard without annotations, with set info. */ /** @brief Saves deck to clipboard without annotations, with set info. */
void AbstractTabDeckEditor::actSaveDeckToClipboardRaw() void AbstractTabDeckEditor::actSaveDeckToClipboardRaw()
{ {
getDeckLoader()->saveToClipboard(false, true); DeckLoader::saveToClipboard(getDeckList(), false, true);
} }
/** @brief Saves deck to clipboard without annotations or set info. */ /** @brief Saves deck to clipboard without annotations or set info. */
void AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo() void AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo()
{ {
getDeckLoader()->saveToClipboard(false, false); DeckLoader::saveToClipboard(getDeckList(), false, false);
} }
/** @brief Prints the deck using a QPrintPreviewDialog. */ /** @brief Prints the deck using a QPrintPreviewDialog. */
void AbstractTabDeckEditor::actPrintDeck() void AbstractTabDeckEditor::actPrintDeck()
{ {
auto *dlg = new QPrintPreviewDialog(this); auto *dlg = new QPrintPreviewDialog(this);
connect(dlg, &QPrintPreviewDialog::paintRequested, deckDockWidget->getDeckLoader(), &DeckLoader::printDeckList); connect(dlg, &QPrintPreviewDialog::paintRequested, this,
[this](QPrinter *printer) { DeckLoader::printDeckList(printer, getDeckList()); });
dlg->exec(); dlg->exec();
} }
@ -569,7 +570,7 @@ void AbstractTabDeckEditor::actLoadDeckFromWebsite()
void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website) void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website)
{ {
if (DeckLoader *const deck = getDeckLoader()) { if (DeckLoader *const deck = getDeckLoader()) {
QString decklistUrlString = deck->exportDeckToDecklist(website); QString decklistUrlString = deck->exportDeckToDecklist(getDeckList(), website);
// Check to make sure the string isn't empty. // Check to make sure the string isn't empty.
if (decklistUrlString.isEmpty()) { if (decklistUrlString.isEmpty()) {
// Show an error if the deck is empty, and return. // Show an error if the deck is empty, and return.

View file

@ -326,13 +326,13 @@ QMenu *DeckPreviewWidget::createRightClickMenu()
auto saveToClipboardMenu = menu->addMenu(tr("Save Deck to Clipboard")); auto saveToClipboardMenu = menu->addMenu(tr("Save Deck to Clipboard"));
connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this, connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this,
[this] { deckLoader->saveToClipboard(true, true); }); [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), true, true); });
connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this, connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this,
[this] { deckLoader->saveToClipboard(true, false); }); [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), true, false); });
connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this, connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this,
[this] { deckLoader->saveToClipboard(false, true); }); [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), false, true); });
connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this, connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this,
[this] { deckLoader->saveToClipboard(false, false); }); [this] { DeckLoader::saveToClipboard(deckLoader->getDeckList(), false, false); });
menu->addSeparator(); menu->addSeparator();

View file

@ -3,3 +3,5 @@
@subpage editing_decks_classic @subpage editing_decks_classic
@subpage editing_decks_visual @subpage editing_decks_visual
@subpage editing_decks_printings

View file

@ -1 +1,122 @@
@page editing_decks_printings Printing Selector @page editing_decks_printings Printing Selector
\image html printing_selector.png
# Purpose
The PrintingSelector allows editing the PrintingInfo associated with CardInfo%s contained in DeckList%s.
As described in PrintingInfo, a CardInfo can have different variations contained within the same or different CardSet%s.
PrintingSelector allows the user to choose a variation, as well as pinning a variation to be used as the preferred
printing in all cases where the 'default' or 'most preferred' image should be shown.
# Pre-requisites (User Interface)
To use the printing selector, ensure that you are using the Post-ProviderID change behavior (Checkbox as shown in the
image).
\image html printing_selector_pre_providerid.png
\attention This is the default behavior, unless you have explicitly changed it, in which case you will have seen a
warning as follows. Once again, checking this box will *disable* the new behavior and revert you to the legacy system
without the printing selector.
Disabling the printing selector:
\image html printing_selector_enable.png
Enabling the printing selector:
\image html printing_selector_disable.png
# Pre-requisites (Card Database)
The PrintingSelector requires each underlying CardInfo object to have a <set> entry with a *unique* providerID attribute
for each variation that should be displayed.
Here is an example, that first defines three sets, and then the 'Ace of Hearts' card with three variations.
```xml
<set rarity="common" uuid="00000000-0000-0000-0000-000000000033" num="51" muid="AH">PKR</set>
<set rarity="common" uuid="00000000-0000-0000-0001-000000000033" num="51" muid="AHSTD">STD</set>
<set rarity="common" uuid="00000000-0000-0000-0002-000000000033" num="51" muid="AHNBR">NBR</set>
```
```xml
<?xml version='1.0' encoding='UTF-8'?>
<cockatrice_carddatabase version="4">
<sets>
<set>
<name>PKR</name>
<longname>Cockatrice Poker</longname>
<settype>Custom</settype>
<releasedate>2025-11-15</releasedate>
</set>
<set>
<name>STD</name>
<longname>Standard Poker</longname>
<settype>core</settype>
<releasedate>2025-11-14</releasedate>
</set>
<set>
<name>NBR</name>
<longname>Numbers</longname>
<settype>core</settype>
<releasedate>2025-11-14</releasedate>
</set>
</sets>
<cards>
<card>
<name>Ace of Hearts</name>
<text>Ace of Hearts</text>
<prop>
<layout>normal</layout>
<side>front</side>
<type>Ace</type>
<maintype>Ace</maintype>
<manacost>14</manacost>
<cmc>14</cmc>
<colors>H</colors>
<coloridentity>H</coloridentity>
<format-standard>legal</format-standard>
</prop>
<reverse-related>Joker</reverse-related>
<token>0</token>
<tablerow>1</tablerow>
<cipt>0</cipt>
<upsidedown>0</upsidedown>
<set rarity="common" uuid="00000000-0000-0000-0000-000000000033" num="51" muid="AH">PKR</set>
<set rarity="common" uuid="00000000-0000-0000-0001-000000000033" num="51" muid="AHSTD">STD</set>
<set rarity="common" uuid="00000000-0000-0000-0002-000000000033" num="51" muid="AHNBR">NBR</set>
</card>
</cards>
</cockatrice_carddatabase>
```
# Using the Printing Selector
To use the PrintingSelector, first select a card in the DeckEditorDeckDockWidget:
\image html deck_dock_deck_list.png
You may add a variation to the mainboard by clicking the + and - buttons in the top row. The bottom row will add the
variation to your sideboard.
\image html printing_selector.png
You can also right-click a variation to pin it, which will move it to the top of the variation list for easy access and
additionally, use it as the default printing whenever the printing isn't explicitly specified (such as adding a card
from the database in the deck editor).
Only one printing may be pinned at a time.
Additionally, the PrintingSelector offers a navigation bar at the bottom to navigate through cards in your deck, as well
as an option to modify the variations of cards in bulk via the 'Bulk Selection' option.
\image html printing_selector_navigation.png
The search bar at the top may be used to filter the displayed variations and the cogwheel may be clicked to open up
additional settings.
\image html printing_selector_options.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View file

@ -26,13 +26,9 @@ CardInfo::CardInfo(const QString &_name,
const QList<CardRelation *> &_relatedCards, const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards, const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets, SetToPrintingsMap _sets,
bool _cipt, const UiAttributes _uiAttributes)
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt)
: name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards), : name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards),
reverseRelatedCards(_reverseRelatedCards), setsToPrintings(std::move(_sets)), cipt(_cipt), reverseRelatedCards(_reverseRelatedCards), setsToPrintings(std::move(_sets)), uiAttributes(_uiAttributes)
landscapeOrientation(_landscapeOrientation), tableRow(_tableRow), upsideDownArt(_upsideDownArt)
{ {
simpleName = CardInfo::simplifyName(name); simpleName = CardInfo::simplifyName(name);
@ -41,8 +37,7 @@ CardInfo::CardInfo(const QString &_name,
CardInfoPtr CardInfo::newInstance(const QString &_name) CardInfoPtr CardInfo::newInstance(const QString &_name)
{ {
return newInstance(_name, QString(), false, QVariantHash(), QList<CardRelation *>(), QList<CardRelation *>(), return newInstance(_name, "", false, {}, {}, {}, {}, {});
SetToPrintingsMap(), false, false, 0, false);
} }
CardInfoPtr CardInfo::newInstance(const QString &_name, CardInfoPtr CardInfo::newInstance(const QString &_name,
@ -52,13 +47,10 @@ CardInfoPtr CardInfo::newInstance(const QString &_name,
const QList<CardRelation *> &_relatedCards, const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards, const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets, SetToPrintingsMap _sets,
bool _cipt, const UiAttributes _uiAttributes)
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt)
{ {
CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards, CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards,
_sets, _cipt, _landscapeOrientation, _tableRow, _upsideDownArt)); _sets, _uiAttributes));
ptr->setSmartPointer(ptr); ptr->setSmartPointer(ptr);
for (const auto &printings : _sets) { for (const auto &printings : _sets) {

View file

@ -47,6 +47,21 @@ class CardInfo : public QObject
{ {
Q_OBJECT Q_OBJECT
public:
/**
* @class CardInfo::UiAttributes
* @ingroup Cards
*
* @brief Attributes of the card that affect display and game logic.
*/
struct UiAttributes
{
bool cipt = false; ///< Positioning flag used by UI.
bool landscapeOrientation = false; ///< Orientation flag for rendering.
int tableRow = 0; ///< Row index in a table or visual representation.
bool upsideDownArt = false; ///< Whether artwork is flipped for visual purposes.
};
private: private:
/** @name Private Card Properties /** @name Private Card Properties
* @anchor PrivateCardProperties * @anchor PrivateCardProperties
@ -63,10 +78,7 @@ private:
QList<CardRelation *> reverseRelatedCardsToMe; ///< Cards that consider this card as related. QList<CardRelation *> reverseRelatedCardsToMe; ///< Cards that consider this card as related.
SetToPrintingsMap setsToPrintings; ///< Mapping from set names to printing variations. SetToPrintingsMap setsToPrintings; ///< Mapping from set names to printing variations.
QString setsNames; ///< Cached, human-readable list of set names. QString setsNames; ///< Cached, human-readable list of set names.
bool cipt; ///< Positioning flag used by UI. UiAttributes uiAttributes; ///< Attributes that affect display and game logic
bool landscapeOrientation; ///< Orientation flag for rendering.
int tableRow; ///< Row index in a table or visual representation.
bool upsideDownArt; ///< Whether artwork is flipped for visual purposes.
///@} ///@}
public: public:
@ -80,10 +92,7 @@ public:
* @param _relatedCards Forward references to related cards. * @param _relatedCards Forward references to related cards.
* @param _reverseRelatedCards Backward references to related cards. * @param _reverseRelatedCards Backward references to related cards.
* @param _sets Map of set names to printing information. * @param _sets Map of set names to printing information.
* @param _cipt UI positioning flag. * @param _uiAttributes Attributes that affect display and game logic
* @param _landscapeOrientation UI rendering orientation.
* @param _tableRow Row index for table placement.
* @param _upsideDownArt Whether the artwork should be displayed upside down.
*/ */
explicit CardInfo(const QString &_name, explicit CardInfo(const QString &_name,
const QString &_text, const QString &_text,
@ -92,10 +101,7 @@ public:
const QList<CardRelation *> &_relatedCards, const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards, const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets, SetToPrintingsMap _sets,
bool _cipt, UiAttributes _uiAttributes);
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt);
/** /**
* @brief Copy constructor for CardInfo. * @brief Copy constructor for CardInfo.
@ -108,8 +114,7 @@ public:
: QObject(other.parent()), name(other.name), simpleName(other.simpleName), text(other.text), : QObject(other.parent()), name(other.name), simpleName(other.simpleName), text(other.text),
isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards), isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards),
reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe), reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe),
setsToPrintings(other.setsToPrintings), setsNames(other.setsNames), cipt(other.cipt), setsToPrintings(other.setsToPrintings), setsNames(other.setsNames), uiAttributes(other.uiAttributes)
landscapeOrientation(other.landscapeOrientation), tableRow(other.tableRow), upsideDownArt(other.upsideDownArt)
{ {
} }
@ -133,10 +138,7 @@ public:
* @param _relatedCards Forward relationships. * @param _relatedCards Forward relationships.
* @param _reverseRelatedCards Reverse relationships. * @param _reverseRelatedCards Reverse relationships.
* @param _sets Printing information per set. * @param _sets Printing information per set.
* @param _cipt UI positioning flag. * @param _uiAttributes Attributes that affect display and game logic
* @param _landscapeOrientation UI rendering orientation.
* @param _tableRow Row index for table placement.
* @param _upsideDownArt Artwork orientation flag.
* @return Shared pointer to the new CardInfo instance. * @return Shared pointer to the new CardInfo instance.
*/ */
static CardInfoPtr newInstance(const QString &_name, static CardInfoPtr newInstance(const QString &_name,
@ -146,10 +148,7 @@ public:
const QList<CardRelation *> &_relatedCards, const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards, const QList<CardRelation *> &_reverseRelatedCards,
SetToPrintingsMap _sets, SetToPrintingsMap _sets,
bool _cipt, UiAttributes _uiAttributes);
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt);
/** /**
* @brief Clones the current CardInfo instance. * @brief Clones the current CardInfo instance.
@ -254,29 +253,14 @@ public:
//@} //@}
/** @name UI Positioning */ //@{ /** @name UI Positioning */ //@{
bool getCipt() const const UiAttributes &getUiAttributes() const
{ {
return cipt; return uiAttributes;
} }
bool getLandscapeOrientation() const
{
return landscapeOrientation;
}
int getTableRow() const
{
return tableRow;
}
void setTableRow(int _tableRow)
{
tableRow = _tableRow;
}
bool getUpsideDownArt() const
{
return upsideDownArt;
}
const QChar getColorChar() const;
//@} //@}
const QChar getColorChar() const;
/** @name Legacy/Convenience Property Accessors */ //@{ /** @name Legacy/Convenience Property Accessors */ //@{
const QString getCardType() const; const QString getCardType() const;
void setCardType(const QString &value); void setCardType(const QString &value);

View file

@ -282,9 +282,13 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml)
} }
properties.insert("colors", colors); properties.insert("colors", colors);
CardInfoPtr newCard =
CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt, CardInfo::UiAttributes attributes = {.cipt = cipt,
landscapeOrientation, tableRow, upsideDown); .landscapeOrientation = landscapeOrientation,
.tableRow = tableRow,
.upsideDownArt = upsideDown};
CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards,
reverseRelatedCards, _sets, attributes);
emit addCard(newCard); emit addCard(newCard);
} }
} }
@ -417,14 +421,15 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in
} }
// positioning // positioning
xml.writeTextElement("tablerow", QString::number(info->getTableRow())); const CardInfo::UiAttributes &attributes = info->getUiAttributes();
if (info->getCipt()) { xml.writeTextElement("tablerow", QString::number(attributes.tableRow));
if (attributes.cipt) {
xml.writeTextElement("cipt", "1"); xml.writeTextElement("cipt", "1");
} }
if (info->getLandscapeOrientation()) { if (attributes.landscapeOrientation) {
xml.writeTextElement("landscapeOrientation", "1"); xml.writeTextElement("landscapeOrientation", "1");
} }
if (info->getUpsideDownArt()) { if (attributes.upsideDownArt) {
xml.writeTextElement("upsidedown", "1"); xml.writeTextElement("upsidedown", "1");
} }

View file

@ -262,9 +262,12 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
continue; continue;
} }
CardInfoPtr newCard = CardInfo::UiAttributes attributes = {.cipt = cipt,
CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt, .landscapeOrientation = landscapeOrientation,
landscapeOrientation, tableRow, upsideDown); .tableRow = tableRow,
.upsideDownArt = upsideDown};
CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards,
reverseRelatedCards, _sets, attributes);
emit addCard(newCard); emit addCard(newCard);
} }
} }
@ -379,14 +382,15 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in
} }
// positioning // positioning
xml.writeTextElement("tablerow", QString::number(info->getTableRow())); const CardInfo::UiAttributes &attributes = info->getUiAttributes();
if (info->getCipt()) { xml.writeTextElement("tablerow", QString::number(attributes.tableRow));
if (attributes.cipt) {
xml.writeTextElement("cipt", "1"); xml.writeTextElement("cipt", "1");
} }
if (info->getLandscapeOrientation()) { if (attributes.landscapeOrientation) {
xml.writeTextElement("landscapeOrientation", "1"); xml.writeTextElement("landscapeOrientation", "1");
} }
if (info->getUpsideDownArt()) { if (attributes.upsideDownArt) {
xml.writeTextElement("upsidedown", "1"); xml.writeTextElement("upsidedown", "1");
} }

View file

@ -722,7 +722,7 @@ void DeckList::refreshDeckHash()
/** /**
* Calls a given function on each card in the deck. * Calls a given function on each card in the deck.
*/ */
void DeckList::forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func) void DeckList::forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func) const
{ {
// Support for this is only possible if the internal structure // Support for this is only possible if the internal structure
// doesn't get more complicated. // doesn't get more complicated.

View file

@ -315,7 +315,7 @@ public:
* *
* @param func Function taking (zone node, card node). * @param func Function taking (zone node, card node).
*/ */
void forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func); void forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func) const;
}; };
#endif #endif

View file

@ -25,7 +25,7 @@ void GameFiltersSettings::setHideBuddiesOnlyGames(bool hide)
bool GameFiltersSettings::isHideBuddiesOnlyGames() bool GameFiltersSettings::isHideBuddiesOnlyGames()
{ {
QVariant previous = getValue("hide_buddies_only_games"); QVariant previous = getValue("hide_buddies_only_games");
return previous == QVariant() || previous.toBool(); return previous == QVariant() ? false : previous.toBool();
} }
void GameFiltersSettings::setHideFullGames(bool hide) void GameFiltersSettings::setHideFullGames(bool hide)
@ -36,7 +36,7 @@ void GameFiltersSettings::setHideFullGames(bool hide)
bool GameFiltersSettings::isHideFullGames() bool GameFiltersSettings::isHideFullGames()
{ {
QVariant previous = getValue("hide_full_games"); QVariant previous = getValue("hide_full_games");
return !(previous == QVariant()) && previous.toBool(); return previous == QVariant() ? false : previous.toBool();
} }
void GameFiltersSettings::setHideGamesThatStarted(bool hide) void GameFiltersSettings::setHideGamesThatStarted(bool hide)
@ -47,7 +47,7 @@ void GameFiltersSettings::setHideGamesThatStarted(bool hide)
bool GameFiltersSettings::isHideGamesThatStarted() bool GameFiltersSettings::isHideGamesThatStarted()
{ {
QVariant previous = getValue("hide_games_that_started"); QVariant previous = getValue("hide_games_that_started");
return !(previous == QVariant()) && previous.toBool(); return previous == QVariant() ? false : previous.toBool();
} }
void GameFiltersSettings::setHidePasswordProtectedGames(bool hide) void GameFiltersSettings::setHidePasswordProtectedGames(bool hide)
@ -58,7 +58,7 @@ void GameFiltersSettings::setHidePasswordProtectedGames(bool hide)
bool GameFiltersSettings::isHidePasswordProtectedGames() bool GameFiltersSettings::isHidePasswordProtectedGames()
{ {
QVariant previous = getValue("hide_password_protected_games"); QVariant previous = getValue("hide_password_protected_games");
return previous == QVariant() || previous.toBool(); return previous == QVariant() ? false : previous.toBool();
} }
void GameFiltersSettings::setHideIgnoredUserGames(bool hide) void GameFiltersSettings::setHideIgnoredUserGames(bool hide)
@ -69,7 +69,7 @@ void GameFiltersSettings::setHideIgnoredUserGames(bool hide)
bool GameFiltersSettings::isHideIgnoredUserGames() bool GameFiltersSettings::isHideIgnoredUserGames()
{ {
QVariant previous = getValue("hide_ignored_user_games"); QVariant previous = getValue("hide_ignored_user_games");
return !(previous == QVariant()) && previous.toBool(); return previous == QVariant() ? true : previous.toBool();
} }
void GameFiltersSettings::setHideNotBuddyCreatedGames(bool hide) void GameFiltersSettings::setHideNotBuddyCreatedGames(bool hide)
@ -80,7 +80,7 @@ void GameFiltersSettings::setHideNotBuddyCreatedGames(bool hide)
bool GameFiltersSettings::isHideNotBuddyCreatedGames() bool GameFiltersSettings::isHideNotBuddyCreatedGames()
{ {
QVariant previous = getValue("hide_not_buddy_created_games"); QVariant previous = getValue("hide_not_buddy_created_games");
return !(previous == QVariant()) && previous.toBool(); return previous == QVariant() ? false : previous.toBool();
} }
void GameFiltersSettings::setHideOpenDecklistGames(bool hide) void GameFiltersSettings::setHideOpenDecklistGames(bool hide)
@ -91,7 +91,7 @@ void GameFiltersSettings::setHideOpenDecklistGames(bool hide)
bool GameFiltersSettings::isHideOpenDecklistGames() bool GameFiltersSettings::isHideOpenDecklistGames()
{ {
QVariant previous = getValue("hide_open_decklist_games"); QVariant previous = getValue("hide_open_decklist_games");
return !(previous == QVariant()) && previous.toBool(); return previous == QVariant() ? false : previous.toBool();
} }
void GameFiltersSettings::setGameNameFilter(QString gameName) void GameFiltersSettings::setGameNameFilter(QString gameName)
@ -104,14 +104,14 @@ QString GameFiltersSettings::getGameNameFilter()
return getValue("game_name_filter").toString(); return getValue("game_name_filter").toString();
} }
void GameFiltersSettings::setCreatorNameFilter(QString creatorName) void GameFiltersSettings::setCreatorNameFilters(QStringList creatorName)
{ {
setValue(creatorName, "creator_name_filter"); setValue(creatorName, "creator_name_filter");
} }
QString GameFiltersSettings::getCreatorNameFilter() QStringList GameFiltersSettings::getCreatorNameFilters()
{ {
return getValue("creator_name_filter").toString(); return getValue("creator_name_filter").toStringList();
} }
void GameFiltersSettings::setMinPlayers(int min) void GameFiltersSettings::setMinPlayers(int min)
@ -182,7 +182,7 @@ void GameFiltersSettings::setShowSpectatorPasswordProtected(bool show)
bool GameFiltersSettings::isShowSpectatorPasswordProtected() bool GameFiltersSettings::isShowSpectatorPasswordProtected()
{ {
QVariant previous = getValue("show_spectator_password_protected"); QVariant previous = getValue("show_spectator_password_protected");
return previous == QVariant() ? true : previous.toBool(); return previous == QVariant() ? false : previous.toBool();
} }
void GameFiltersSettings::setShowOnlyIfSpectatorsCanChat(bool show) void GameFiltersSettings::setShowOnlyIfSpectatorsCanChat(bool show)
@ -193,7 +193,7 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanChat(bool show)
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanChat() bool GameFiltersSettings::isShowOnlyIfSpectatorsCanChat()
{ {
QVariant previous = getValue("show_only_if_spectators_can_chat"); QVariant previous = getValue("show_only_if_spectators_can_chat");
return previous == QVariant() ? true : previous.toBool(); return previous == QVariant() ? false : previous.toBool();
} }
void GameFiltersSettings::setShowOnlyIfSpectatorsCanSeeHands(bool show) void GameFiltersSettings::setShowOnlyIfSpectatorsCanSeeHands(bool show)
@ -204,5 +204,5 @@ void GameFiltersSettings::setShowOnlyIfSpectatorsCanSeeHands(bool show)
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanSeeHands() bool GameFiltersSettings::isShowOnlyIfSpectatorsCanSeeHands()
{ {
QVariant previous = getValue("show_only_if_spectators_can_see_hands"); QVariant previous = getValue("show_only_if_spectators_can_see_hands");
return previous == QVariant() ? true : previous.toBool(); return previous == QVariant() ? false : previous.toBool();
} }

View file

@ -24,7 +24,7 @@ public:
bool isHideNotBuddyCreatedGames(); bool isHideNotBuddyCreatedGames();
bool isHideOpenDecklistGames(); bool isHideOpenDecklistGames();
QString getGameNameFilter(); QString getGameNameFilter();
QString getCreatorNameFilter(); QStringList getCreatorNameFilters();
int getMinPlayers(); int getMinPlayers();
int getMaxPlayers(); int getMaxPlayers();
QTime getMaxGameAge(); QTime getMaxGameAge();
@ -42,7 +42,7 @@ public:
void setHidePasswordProtectedGames(bool hide); void setHidePasswordProtectedGames(bool hide);
void setHideNotBuddyCreatedGames(bool hide); void setHideNotBuddyCreatedGames(bool hide);
void setGameNameFilter(QString gameName); void setGameNameFilter(QString gameName);
void setCreatorNameFilter(QString creatorName); void setCreatorNameFilters(QStringList creatorName);
void setMinPlayers(int min); void setMinPlayers(int min);
void setMaxPlayers(int max); void setMaxPlayers(int max);
void setMaxGameAge(const QTime &maxGameAge); void setMaxGameAge(const QTime &maxGameAge);

View file

@ -204,11 +204,11 @@ CardInfoPtr OracleImporter::addCard(QString name,
bool upsideDown = layout == "flip" && side == "back"; bool upsideDown = layout == "flip" && side == "back";
// insert the card and its properties // insert the card and its properties
QList<CardRelation *> reverseRelatedCards;
SetToPrintingsMap setsInfo; SetToPrintingsMap setsInfo;
setsInfo[printingInfo.getSet()->getShortName()].append(printingInfo); setsInfo[printingInfo.getSet()->getShortName()].append(printingInfo);
CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, CardInfo::UiAttributes attributes = {cipt, landscapeOrientation, tableRow, upsideDown};
setsInfo, cipt, landscapeOrientation, tableRow, upsideDown); CardInfoPtr newCard =
CardInfo::newInstance(name, text, isToken, properties, relatedCards, {}, setsInfo, attributes);
if (name.isEmpty()) { if (name.isEmpty()) {
qDebug() << "warning: an empty card was added to set" << printingInfo.getSet()->getShortName(); qDebug() << "warning: an empty card was added to set" << printingInfo.getSet()->getShortName();

View file

@ -12,7 +12,7 @@ const serverPropsFile = `${ROOT_DIR}/server-props.json`;
const masterProtoFile = `${ROOT_DIR}/proto-files.json`; const masterProtoFile = `${ROOT_DIR}/proto-files.json`;
const sharedFiles = [ const sharedFiles = [
['../common/pb', protoFilesDir], ['../libcockatrice_protocol/libcockatrice/protocol/pb', protoFilesDir],
['../cockatrice/resources/countries', `${ROOT_DIR}/images/countries`], ['../cockatrice/resources/countries', `${ROOT_DIR}/images/countries`],
]; ];