From ebbab20b2a06a0a0e4dbfaf41e2e500455628953 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Fri, 15 May 2026 20:49:59 -0400 Subject: [PATCH 01/12] Add subtype breakdown counter for card selection Display a categorized count of creature subtypes (and other card type subtypes) when multiple cards are selected. The breakdown appears above the total selection counter in the bottom-right corner. Subtypes are grouped by main card type and sorted by frequency, with the most common subtypes positioned adjacent to the total count for quick reference. The feature can be toggled via a new checkbox in Settings > User Interface. --- .../src/client/settings/cache_settings.cpp | 7 + .../src/client/settings/cache_settings.h | 6 + cockatrice/src/game_graphics/game_view.cpp | 203 ++++++++++++++++-- cockatrice/src/game_graphics/game_view.h | 4 + cockatrice/src/interface/theme_manager.cpp | 3 + .../user_interface_settings_page.cpp | 10 +- .../user_interface_settings_page.h | 1 + 7 files changed, 214 insertions(+), 20 deletions(-) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index cc34e1707..5fe252618 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -313,6 +313,7 @@ SettingsCache::SettingsCache() showDragSelectionCount = settings->value("interface/showlassoselectioncount", true).toBool(); showTotalSelectionCount = settings->value("interface/showpersistentselectioncount", true).toBool(); + showSubtypeSelectionCount = settings->value("interface/showsubtypeselectioncount", true).toBool(); showShortcuts = settings->value("menu/showshortcuts", true).toBool(); showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool(); @@ -1387,6 +1388,12 @@ void SettingsCache::setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSele settings->setValue("interface/showpersistentselectioncount", showTotalSelectionCount); } +void SettingsCache::setShowSubtypeSelectionCount(QT_STATE_CHANGED_T _showSubtypeSelectionCount) +{ + showSubtypeSelectionCount = static_cast(_showSubtypeSelectionCount); + settings->setValue("interface/showsubtypeselectioncount", showSubtypeSelectionCount); +} + void SettingsCache::loadPaths() { QString dataPath = getDataPath(); diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index a166917c1..43c4a8271 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -353,6 +353,7 @@ private: bool showStatusBar; bool showDragSelectionCount; bool showTotalSelectionCount; + bool showSubtypeSelectionCount; public: SettingsCache(); @@ -476,6 +477,10 @@ public: { return showTotalSelectionCount; } + [[nodiscard]] bool getShowSubtypeSelectionCount() const + { + return showSubtypeSelectionCount; + } [[nodiscard]] bool getNotificationsEnabled() const { return notificationsEnabled; @@ -1169,5 +1174,6 @@ public slots: void setRoundCardCorners(bool _roundCardCorners); void setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount); void setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount); + void setShowSubtypeSelectionCount(QT_STATE_CHANGED_T _showSubtypeSelectionCount); }; #endif diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index 41befd9a4..7ebb3a59a 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -1,12 +1,15 @@ #include "game_view.h" #include "../client/settings/cache_settings.h" +#include "board/card_item.h" #include "game_scene.h" #include #include +#include #include #include +#include // QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings. // This subclass disables that behavior so dragCountLabel can appear above it. @@ -55,21 +58,30 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par refreshShortcuts(); rubberBand = new SelectionRubberBand(QRubberBand::Rectangle, this); - const QString countLabelStyle = "color: white; " - "font-size: 14px; " - "font-weight: bold; " - "background-color: rgba(0, 0, 0, 160); " - "border-radius: 3px; " - "padding: 1px 2px;"; + const QString baseProperties = "color: white; " + "font-family: monospace; " + "background-color: rgba(0, 0, 0, 160); " + "border-radius: 3px; " + "padding: 1px 2px; " + "white-space: pre;"; + + const QString dragCountLabelStyle = baseProperties + "font-size: 14px; font-weight: bold;"; + const QString totalCountLabelStyle = baseProperties + "font-size: 16px; font-weight: bold;"; + const QString subtypeCountLabelStyle = baseProperties + "font-size: 12px;"; dragCountLabel = new QLabel(this); - dragCountLabel->setStyleSheet(countLabelStyle); + dragCountLabel->setStyleSheet(dragCountLabelStyle); dragCountLabel->hide(); dragCountLabel->raise(); totalCountLabel = new QLabel(this); - totalCountLabel->setStyleSheet(countLabelStyle); + totalCountLabel->setStyleSheet(totalCountLabelStyle); totalCountLabel->hide(); + + subtypeCountLabel = new QLabel(this); + subtypeCountLabel->setStyleSheet(subtypeCountLabelStyle); + subtypeCountLabel->setTextFormat(Qt::RichText); + subtypeCountLabel->hide(); } void GameView::resizeEvent(QResizeEvent *event) @@ -164,29 +176,184 @@ void GameView::refreshShortcuts() SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView")); } +/** @brief Extracts subtypes from a card face type string (e.g., "Creature — Human Wizard" -> ["Human", "Wizard"]) */ +static QStringList extractSubtypesFromFace(const QString &faceType) +{ + QStringList parts = faceType.split(QStringLiteral(" — ")); + if (parts.size() > 1) { + return parts[1].split(QStringLiteral(" "), Qt::SkipEmptyParts); + } + return {}; +} + +QString GameView::buildSubtypeCountText() const +{ + GameScene *gameScene = dynamic_cast(scene()); + if (!gameScene) { + return QString(); + } + + // Map: main card type -> (subtype -> count) + QMap> subtypesByMainType; + // Track cards contributing subtypes per main type (for group ordering) + QMap cardCountPerMainType; + + for (CardItem *card : gameScene->selectedCards()) { + if (card->getFaceDown() || card->getCard().isEmpty()) { + continue; + } + + QString mainType = card->getCardInfo().getMainCardType(); + if (mainType.isEmpty()) { + mainType = QStringLiteral("Other"); + } + + QString cardType = card->getCardInfo().getCardType(); + QStringList cardFaces = cardType.split(QStringLiteral(" // ")); + + bool contributedSubtypes = false; + for (const QString &face : cardFaces) { + QStringList subtypes = extractSubtypesFromFace(face); + for (const QString &subtype : subtypes) { + subtypesByMainType[mainType][subtype]++; + contributedSubtypes = true; + } + } + + if (contributedSubtypes) { + cardCountPerMainType[mainType]++; + } + } + + if (subtypesByMainType.isEmpty()) { + return QString(); + } + + // Build groups with sorted subtypes + struct MainTypeGroup + { + QString mainType; + int cardCount; + QList> subtypes; + }; + + QList groups; + for (auto it = subtypesByMainType.constBegin(); it != subtypesByMainType.constEnd(); ++it) { + MainTypeGroup group; + group.mainType = it.key(); + group.cardCount = cardCountPerMainType.value(it.key(), 0); + + for (auto subIt = it.value().constBegin(); subIt != it.value().constEnd(); ++subIt) { + group.subtypes.append({subIt.key(), subIt.value()}); + } + + /** + * Sort subtypes: by count ascending (lower counts at top of the list), then alphabetically. + * Since the subtype list displays above the total count label (bottom-right corner), + * ascending order places the most common subtypes visually adjacent to the total. + */ + std::sort(group.subtypes.begin(), group.subtypes.end(), + [](const QPair &a, const QPair &b) { + if (a.second != b.second) { + return a.second < b.second; + } + return a.first < b.first; + }); + + groups.append(group); + } + + // Sort groups: by card count ascending, then alphabetically by main type + std::sort(groups.begin(), groups.end(), [](const MainTypeGroup &a, const MainTypeGroup &b) { + if (a.cardCount != b.cardCount) { + return a.cardCount < b.cardCount; + } + return a.mainType < b.mainType; + }); + + // Flatten to final ordered list + QList> sortedEntries; + for (const MainTypeGroup &group : groups) { + for (const auto &entry : group.subtypes) { + sortedEntries.append(entry); + } + } + + // Calculate padding widths + int maxNameLen = 0; + int maxCountLen = 0; + for (const auto &entry : sortedEntries) { + maxNameLen = qMax(maxNameLen, entry.first.length()); + maxCountLen = qMax(maxCountLen, QString::number(entry.second).length()); + } + + // Format output + QStringList lines; + for (const auto &entry : sortedEntries) { + QString name = entry.first.toHtmlEscaped(); + QString count = QString::number(entry.second); + + QString namePadding = QString(QStringLiteral(" ")).repeated(maxNameLen - entry.first.length()); + QString countPadding = QString(QStringLiteral(" ")).repeated(maxCountLen - count.length()); + + lines << QStringLiteral( + "%1%2 %3%4") + .arg(namePadding, name, countPadding, count); + } + + return lines.join(QStringLiteral("
")); +} + void GameView::updateTotalSelectionCount(const QSize &viewSize) { - if (!SettingsCache::instance().getShowTotalSelectionCount()) { - totalCountLabel->hide(); - return; - } + constexpr int kMarginInPixels = 10; + constexpr int kSpacingBetweenLabels = 4; + + int availableWidth = viewSize.isValid() ? viewSize.width() : viewport()->width(); + int availableHeight = viewSize.isValid() ? viewSize.height() : viewport()->height(); int count = scene()->selectedItems().count(); - if (count > 1) { + if (!SettingsCache::instance().getShowTotalSelectionCount() || count <= 1) { + totalCountLabel->hide(); + } else { totalCountLabel->setText(QString::number(count)); totalCountLabel->adjustSize(); - constexpr int kMarginInPixels = 10; - int availableWidth = viewSize.isValid() ? viewSize.width() : viewport()->width(); - int availableHeight = viewSize.isValid() ? viewSize.height() : viewport()->height(); int x = availableWidth - totalCountLabel->width() - kMarginInPixels; int y = availableHeight - totalCountLabel->height() - kMarginInPixels; totalCountLabel->move(x, y); totalCountLabel->show(); - } else { - totalCountLabel->hide(); } + + if (!SettingsCache::instance().getShowSubtypeSelectionCount() || count <= 1) { + subtypeCountLabel->hide(); + return; + } + + QString subtypeText = buildSubtypeCountText(); + + if (subtypeText.isEmpty()) { + subtypeCountLabel->hide(); + return; + } + + subtypeCountLabel->setText(subtypeText); + subtypeCountLabel->adjustSize(); + + int x = availableWidth - subtypeCountLabel->width() - kMarginInPixels; + int y; + + if (totalCountLabel->isVisible()) { + y = totalCountLabel->y() - subtypeCountLabel->height() - kSpacingBetweenLabels; + } else { + y = availableHeight - subtypeCountLabel->height() - kMarginInPixels; + } + + y = qMax(kMarginInPixels, y); + + subtypeCountLabel->move(x, y); + subtypeCountLabel->show(); } /** diff --git a/cockatrice/src/game_graphics/game_view.h b/cockatrice/src/game_graphics/game_view.h index 80e8e96b5..8f15ed878 100644 --- a/cockatrice/src/game_graphics/game_view.h +++ b/cockatrice/src/game_graphics/game_view.h @@ -21,8 +21,12 @@ private: QRubberBand *rubberBand; QLabel *dragCountLabel; QLabel *totalCountLabel; + QLabel *subtypeCountLabel; ///< Label displaying subtype breakdown for selected cards QPointF selectionOrigin; + /** @brief Builds formatted text showing subtype counts for all selected cards */ + QString buildSubtypeCountText() const; + protected: void resizeEvent(QResizeEvent *event) override; private slots: diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index 086845fe6..4ba35a00e 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -271,6 +271,9 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName, const PaletteConfig &palCfg, const QString &activeScheme) { +#if (QT_VERSION < QT_VERSION_CHECK(6, 5, 0)) + Q_UNUSED(activeScheme) +#endif QString styleName = themeCfg.styleName; if (styleName.isEmpty() || styleName.compare("Default", Qt::CaseInsensitive) == 0) { if (themeName == FUSION_THEME_NAME) { diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp index 6039e3758..9261ba011 100644 --- a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp @@ -68,6 +68,10 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&showTotalSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setShowTotalSelectionCount); + showSubtypeSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowSubtypeSelectionCount()); + connect(&showSubtypeSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowSubtypeSelectionCount); + useTearOffMenusCheckBox.setChecked(SettingsCache::instance().getUseTearOffMenus()); connect(&useTearOffMenusCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), [](const QT_STATE_CHANGED_T state) { SettingsCache::instance().setUseTearOffMenus(state == Qt::Checked); }); @@ -86,8 +90,9 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() generalGrid->addWidget(&annotateTokensCheckBox, 6, 0); generalGrid->addWidget(&showDragSelectionCountCheckBox, 7, 0); generalGrid->addWidget(&showTotalSelectionCountCheckBox, 8, 0); - generalGrid->addWidget(&useTearOffMenusCheckBox, 9, 0); - generalGrid->addWidget(&keepGameChatFocusCheckBox, 10, 0); + generalGrid->addWidget(&showSubtypeSelectionCountCheckBox, 9, 0); + generalGrid->addWidget(&useTearOffMenusCheckBox, 10, 0); + generalGrid->addWidget(&keepGameChatFocusCheckBox, 11, 0); generalGroupBox = new QGroupBox; generalGroupBox->setLayout(generalGrid); @@ -211,6 +216,7 @@ void UserInterfaceSettingsPage::retranslateUi() annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); showDragSelectionCountCheckBox.setText(tr("Show selection counter during drag selection")); showTotalSelectionCountCheckBox.setText(tr("Show total selection counter")); + showSubtypeSelectionCountCheckBox.setText(tr("Show subtype breakdown in selection counter")); useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen")); keepGameChatFocusCheckBox.setText( tr("Keep game chat focused when clicking in game (Note: disables card view search bar)")); diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h index e10ed2a06..cddd840fd 100644 --- a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h @@ -29,6 +29,7 @@ private: QCheckBox annotateTokensCheckBox; QCheckBox showDragSelectionCountCheckBox; QCheckBox showTotalSelectionCountCheckBox; + QCheckBox showSubtypeSelectionCountCheckBox; QCheckBox useTearOffMenusCheckBox; QCheckBox keepGameChatFocusCheckBox; QCheckBox tapAnimationCheckBox; From d950a03b708d0aecbb32ba432e67b756d4ee95d8 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Sat, 23 May 2026 23:18:38 -0400 Subject: [PATCH 02/12] Alignment fix --- cockatrice/src/game_graphics/game_view.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index 7ebb3a59a..b1d088658 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -301,7 +301,8 @@ QString GameView::buildSubtypeCountText() const .arg(namePadding, name, countPadding, count); } - return lines.join(QStringLiteral("
")); + return QStringLiteral("") + lines.join(QStringLiteral("
")) + + QStringLiteral("
"); } void GameView::updateTotalSelectionCount(const QSize &viewSize) From eb06c02862886e54e21674f20c446954041999ad Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Sun, 24 May 2026 01:08:38 -0400 Subject: [PATCH 03/12] Computation logic moved to helper funtction in separate file --- cockatrice/CMakeLists.txt | 1 + .../src/game/selection_subtype_counter.cpp | 129 ++++++++++++++++++ .../src/game/selection_subtype_counter.h | 31 +++++ cockatrice/src/game_graphics/game_view.cpp | 126 +---------------- 4 files changed, 163 insertions(+), 124 deletions(-) create mode 100644 cockatrice/src/game/selection_subtype_counter.cpp create mode 100644 cockatrice/src/game/selection_subtype_counter.h diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index bd99d08bf..bc2833b58 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -82,6 +82,7 @@ set(cockatrice_SOURCES src/game_graphics/game_scene.cpp src/game/game_state.cpp src/game_graphics/game_view.cpp + src/game/selection_subtype_counter.cpp src/game_graphics/hand_counter.cpp src/game_graphics/log/message_log_widget.cpp src/game/phase.cpp diff --git a/cockatrice/src/game/selection_subtype_counter.cpp b/cockatrice/src/game/selection_subtype_counter.cpp new file mode 100644 index 000000000..b30f9438b --- /dev/null +++ b/cockatrice/src/game/selection_subtype_counter.cpp @@ -0,0 +1,129 @@ +#include "selection_subtype_counter.h" + +#include "board/card_item.h" + +#include +#include + +namespace SelectionSubtypeCounter +{ + +QStringList extractSubtypesFromFace(const QString &faceType) +{ + QStringList parts = faceType.split(QStringLiteral(" — ")); + if (parts.size() > 1) { + return parts[1].split(QStringLiteral(" "), Qt::SkipEmptyParts); + } + return {}; +} + +QList countSubtypes(const QList &cards) +{ + QMap> subtypesByMainType; + QMap cardCountPerMainType; + + for (CardItem *card : cards) { + if (card->getFaceDown() || card->getCard().isEmpty()) { + continue; + } + + QString mainType = card->getCardInfo().getMainCardType(); + if (mainType.isEmpty()) { + mainType = QStringLiteral("Other"); + } + + QString cardType = card->getCardInfo().getCardType(); + QStringList cardFaces = cardType.split(QStringLiteral(" // ")); + + bool contributedSubtypes = false; + for (const QString &face : cardFaces) { + QStringList subtypes = extractSubtypesFromFace(face); + for (const QString &subtype : subtypes) { + subtypesByMainType[mainType][subtype]++; + contributedSubtypes = true; + } + } + + if (contributedSubtypes) { + cardCountPerMainType[mainType]++; + } + } + + QList groups; + for (auto it = subtypesByMainType.constBegin(); it != subtypesByMainType.constEnd(); ++it) { + MainTypeGroup group; + group.mainType = it.key(); + group.cardCount = cardCountPerMainType.value(it.key(), 0); + + for (auto subIt = it.value().constBegin(); subIt != it.value().constEnd(); ++subIt) { + group.subtypes.append({subIt.key(), subIt.value()}); + } + + // Sort subtypes: by count ascending, then alphabetically + std::sort(group.subtypes.begin(), group.subtypes.end(), [](const SubtypeEntry &a, const SubtypeEntry &b) { + if (a.count != b.count) { + return a.count < b.count; + } + return a.name < b.name; + }); + + groups.append(group); + } + + // Sort groups: by card count ascending, then alphabetically by main type + std::sort(groups.begin(), groups.end(), [](const MainTypeGroup &a, const MainTypeGroup &b) { + if (a.cardCount != b.cardCount) { + return a.cardCount < b.cardCount; + } + return a.mainType < b.mainType; + }); + + return groups; +} + +QString formatAsHtml(const QList &groups) +{ + // Flatten to final ordered list + QList sortedEntries; + for (const MainTypeGroup &group : groups) { + for (const auto &entry : group.subtypes) { + sortedEntries.append(entry); + } + } + + // Calculate padding widths + int maxNameLen = 0; + int maxCountLen = 0; + for (const auto &entry : sortedEntries) { + maxNameLen = qMax(maxNameLen, entry.name.length()); + maxCountLen = qMax(maxCountLen, QString::number(entry.count).length()); + } + + // Format output + QStringList lines; + for (const auto &entry : sortedEntries) { + QString name = entry.name.toHtmlEscaped(); + QString count = QString::number(entry.count); + + QString namePadding = QString(QStringLiteral(" ")).repeated(maxNameLen - entry.name.length()); + QString countPadding = QString(QStringLiteral(" ")).repeated(maxCountLen - count.length()); + + lines << QStringLiteral( + "%1%2 %3%4") + .arg(namePadding, name, countPadding, count); + } + + return QStringLiteral("") + lines.join(QStringLiteral("
")) + + QStringLiteral("
"); +} + +QString buildSubtypeCountText(const QList &cards) +{ + QList groups = countSubtypes(cards); + if (groups.isEmpty()) { + return QString(); + } + return formatAsHtml(groups); +} + +} // namespace SelectionSubtypeCounter diff --git a/cockatrice/src/game/selection_subtype_counter.h b/cockatrice/src/game/selection_subtype_counter.h new file mode 100644 index 000000000..08dc389a7 --- /dev/null +++ b/cockatrice/src/game/selection_subtype_counter.h @@ -0,0 +1,31 @@ +#ifndef SELECTION_SUBTYPE_COUNTER_H +#define SELECTION_SUBTYPE_COUNTER_H + +#include +#include +#include + +class CardItem; + +struct SubtypeEntry +{ + QString name; + int count; +}; + +struct MainTypeGroup +{ + QString mainType; + int cardCount; + QList subtypes; +}; + +namespace SelectionSubtypeCounter +{ +QStringList extractSubtypesFromFace(const QString &faceType); +QList countSubtypes(const QList &cards); +QString formatAsHtml(const QList &groups); +QString buildSubtypeCountText(const QList &cards); +} // namespace SelectionSubtypeCounter + +#endif diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index b1d088658..609bbac60 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -1,15 +1,13 @@ #include "game_view.h" #include "../client/settings/cache_settings.h" -#include "board/card_item.h" #include "game_scene.h" +#include "selection_subtype_counter.h" #include #include -#include #include #include -#include // QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings. // This subclass disables that behavior so dragCountLabel can appear above it. @@ -176,133 +174,13 @@ void GameView::refreshShortcuts() SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView")); } -/** @brief Extracts subtypes from a card face type string (e.g., "Creature — Human Wizard" -> ["Human", "Wizard"]) */ -static QStringList extractSubtypesFromFace(const QString &faceType) -{ - QStringList parts = faceType.split(QStringLiteral(" — ")); - if (parts.size() > 1) { - return parts[1].split(QStringLiteral(" "), Qt::SkipEmptyParts); - } - return {}; -} - QString GameView::buildSubtypeCountText() const { GameScene *gameScene = dynamic_cast(scene()); if (!gameScene) { return QString(); } - - // Map: main card type -> (subtype -> count) - QMap> subtypesByMainType; - // Track cards contributing subtypes per main type (for group ordering) - QMap cardCountPerMainType; - - for (CardItem *card : gameScene->selectedCards()) { - if (card->getFaceDown() || card->getCard().isEmpty()) { - continue; - } - - QString mainType = card->getCardInfo().getMainCardType(); - if (mainType.isEmpty()) { - mainType = QStringLiteral("Other"); - } - - QString cardType = card->getCardInfo().getCardType(); - QStringList cardFaces = cardType.split(QStringLiteral(" // ")); - - bool contributedSubtypes = false; - for (const QString &face : cardFaces) { - QStringList subtypes = extractSubtypesFromFace(face); - for (const QString &subtype : subtypes) { - subtypesByMainType[mainType][subtype]++; - contributedSubtypes = true; - } - } - - if (contributedSubtypes) { - cardCountPerMainType[mainType]++; - } - } - - if (subtypesByMainType.isEmpty()) { - return QString(); - } - - // Build groups with sorted subtypes - struct MainTypeGroup - { - QString mainType; - int cardCount; - QList> subtypes; - }; - - QList groups; - for (auto it = subtypesByMainType.constBegin(); it != subtypesByMainType.constEnd(); ++it) { - MainTypeGroup group; - group.mainType = it.key(); - group.cardCount = cardCountPerMainType.value(it.key(), 0); - - for (auto subIt = it.value().constBegin(); subIt != it.value().constEnd(); ++subIt) { - group.subtypes.append({subIt.key(), subIt.value()}); - } - - /** - * Sort subtypes: by count ascending (lower counts at top of the list), then alphabetically. - * Since the subtype list displays above the total count label (bottom-right corner), - * ascending order places the most common subtypes visually adjacent to the total. - */ - std::sort(group.subtypes.begin(), group.subtypes.end(), - [](const QPair &a, const QPair &b) { - if (a.second != b.second) { - return a.second < b.second; - } - return a.first < b.first; - }); - - groups.append(group); - } - - // Sort groups: by card count ascending, then alphabetically by main type - std::sort(groups.begin(), groups.end(), [](const MainTypeGroup &a, const MainTypeGroup &b) { - if (a.cardCount != b.cardCount) { - return a.cardCount < b.cardCount; - } - return a.mainType < b.mainType; - }); - - // Flatten to final ordered list - QList> sortedEntries; - for (const MainTypeGroup &group : groups) { - for (const auto &entry : group.subtypes) { - sortedEntries.append(entry); - } - } - - // Calculate padding widths - int maxNameLen = 0; - int maxCountLen = 0; - for (const auto &entry : sortedEntries) { - maxNameLen = qMax(maxNameLen, entry.first.length()); - maxCountLen = qMax(maxCountLen, QString::number(entry.second).length()); - } - - // Format output - QStringList lines; - for (const auto &entry : sortedEntries) { - QString name = entry.first.toHtmlEscaped(); - QString count = QString::number(entry.second); - - QString namePadding = QString(QStringLiteral(" ")).repeated(maxNameLen - entry.first.length()); - QString countPadding = QString(QStringLiteral(" ")).repeated(maxCountLen - count.length()); - - lines << QStringLiteral( - "%1%2 %3%4") - .arg(namePadding, name, countPadding, count); - } - - return QStringLiteral("") + lines.join(QStringLiteral("
")) + - QStringLiteral("
"); + return SelectionSubtypeCounter::buildSubtypeCountText(gameScene->selectedCards()); } void GameView::updateTotalSelectionCount(const QSize &viewSize) From 3c2a59ce6e78e91d58fcc564fb5719a0631a32f7 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Thu, 4 Jun 2026 19:35:05 -0400 Subject: [PATCH 04/12] Rename SubtypeCounter to SubtypeTally --- cockatrice/CMakeLists.txt | 2 +- ...subtype_counter.cpp => selection_subtype_tally.cpp} | 8 ++++---- ...ion_subtype_counter.h => selection_subtype_tally.h} | 10 +++++----- cockatrice/src/game_graphics/game_view.cpp | 8 ++++---- cockatrice/src/game_graphics/game_view.h | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) rename cockatrice/src/game/{selection_subtype_counter.cpp => selection_subtype_tally.cpp} (95%) rename cockatrice/src/game/{selection_subtype_counter.h => selection_subtype_tally.h} (67%) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index bc2833b58..9dfb81f3c 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -82,7 +82,7 @@ set(cockatrice_SOURCES src/game_graphics/game_scene.cpp src/game/game_state.cpp src/game_graphics/game_view.cpp - src/game/selection_subtype_counter.cpp + src/game/selection_subtype_tally.cpp src/game_graphics/hand_counter.cpp src/game_graphics/log/message_log_widget.cpp src/game/phase.cpp diff --git a/cockatrice/src/game/selection_subtype_counter.cpp b/cockatrice/src/game/selection_subtype_tally.cpp similarity index 95% rename from cockatrice/src/game/selection_subtype_counter.cpp rename to cockatrice/src/game/selection_subtype_tally.cpp index b30f9438b..0d83a1670 100644 --- a/cockatrice/src/game/selection_subtype_counter.cpp +++ b/cockatrice/src/game/selection_subtype_tally.cpp @@ -1,11 +1,11 @@ -#include "selection_subtype_counter.h" +#include "selection_subtype_tally.h" #include "board/card_item.h" #include #include -namespace SelectionSubtypeCounter +namespace SelectionSubtypeTally { QStringList extractSubtypesFromFace(const QString &faceType) @@ -117,7 +117,7 @@ QString formatAsHtml(const QList &groups) QStringLiteral(""); } -QString buildSubtypeCountText(const QList &cards) +QString buildSubtypeTallyText(const QList &cards) { QList groups = countSubtypes(cards); if (groups.isEmpty()) { @@ -126,4 +126,4 @@ QString buildSubtypeCountText(const QList &cards) return formatAsHtml(groups); } -} // namespace SelectionSubtypeCounter +} // namespace SelectionSubtypeTally diff --git a/cockatrice/src/game/selection_subtype_counter.h b/cockatrice/src/game/selection_subtype_tally.h similarity index 67% rename from cockatrice/src/game/selection_subtype_counter.h rename to cockatrice/src/game/selection_subtype_tally.h index 08dc389a7..3c2f2092b 100644 --- a/cockatrice/src/game/selection_subtype_counter.h +++ b/cockatrice/src/game/selection_subtype_tally.h @@ -1,5 +1,5 @@ -#ifndef SELECTION_SUBTYPE_COUNTER_H -#define SELECTION_SUBTYPE_COUNTER_H +#ifndef SELECTION_SUBTYPE_TALLY_H +#define SELECTION_SUBTYPE_TALLY_H #include #include @@ -20,12 +20,12 @@ struct MainTypeGroup QList subtypes; }; -namespace SelectionSubtypeCounter +namespace SelectionSubtypeTally { QStringList extractSubtypesFromFace(const QString &faceType); QList countSubtypes(const QList &cards); QString formatAsHtml(const QList &groups); -QString buildSubtypeCountText(const QList &cards); -} // namespace SelectionSubtypeCounter +QString buildSubtypeTallyText(const QList &cards); +} // namespace SelectionSubtypeTally #endif diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index 609bbac60..12684b41f 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -2,7 +2,7 @@ #include "../client/settings/cache_settings.h" #include "game_scene.h" -#include "selection_subtype_counter.h" +#include "selection_subtype_tally.h" #include #include @@ -174,13 +174,13 @@ void GameView::refreshShortcuts() SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView")); } -QString GameView::buildSubtypeCountText() const +QString GameView::buildSubtypeTallyText() const { GameScene *gameScene = dynamic_cast(scene()); if (!gameScene) { return QString(); } - return SelectionSubtypeCounter::buildSubtypeCountText(gameScene->selectedCards()); + return SelectionSubtypeTally::buildSubtypeTallyText(gameScene->selectedCards()); } void GameView::updateTotalSelectionCount(const QSize &viewSize) @@ -210,7 +210,7 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize) return; } - QString subtypeText = buildSubtypeCountText(); + QString subtypeText = buildSubtypeTallyText(); if (subtypeText.isEmpty()) { subtypeCountLabel->hide(); diff --git a/cockatrice/src/game_graphics/game_view.h b/cockatrice/src/game_graphics/game_view.h index 8f15ed878..dec4f76cc 100644 --- a/cockatrice/src/game_graphics/game_view.h +++ b/cockatrice/src/game_graphics/game_view.h @@ -24,8 +24,8 @@ private: QLabel *subtypeCountLabel; ///< Label displaying subtype breakdown for selected cards QPointF selectionOrigin; - /** @brief Builds formatted text showing subtype counts for all selected cards */ - QString buildSubtypeCountText() const; + /** @brief Builds formatted text showing subtype tally for all selected cards */ + QString buildSubtypeTallyText() const; protected: void resizeEvent(QResizeEvent *event) override; From 74381b5c0641e8946c8dbd13b18d3b1624e76bc5 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Thu, 4 Jun 2026 20:29:28 -0400 Subject: [PATCH 05/12] Fix subtype tally alignment by using grid layout instead of character padding --- .../src/game/selection_subtype_tally.cpp | 52 ++---------- cockatrice/src/game/selection_subtype_tally.h | 3 - cockatrice/src/game_graphics/game_view.cpp | 82 ++++++++++++++----- cockatrice/src/game_graphics/game_view.h | 9 +- .../libcockatrice/utility/qt_utils.h | 1 + 5 files changed, 74 insertions(+), 73 deletions(-) diff --git a/cockatrice/src/game/selection_subtype_tally.cpp b/cockatrice/src/game/selection_subtype_tally.cpp index 0d83a1670..c033d42c9 100644 --- a/cockatrice/src/game/selection_subtype_tally.cpp +++ b/cockatrice/src/game/selection_subtype_tally.cpp @@ -5,7 +5,7 @@ #include #include -namespace SelectionSubtypeTally +namespace { QStringList extractSubtypesFromFace(const QString &faceType) @@ -17,6 +17,11 @@ QStringList extractSubtypesFromFace(const QString &faceType) return {}; } +} // anonymous namespace + +namespace SelectionSubtypeTally +{ + QList countSubtypes(const QList &cards) { QMap> subtypesByMainType; @@ -81,49 +86,4 @@ QList countSubtypes(const QList &cards) return groups; } -QString formatAsHtml(const QList &groups) -{ - // Flatten to final ordered list - QList sortedEntries; - for (const MainTypeGroup &group : groups) { - for (const auto &entry : group.subtypes) { - sortedEntries.append(entry); - } - } - - // Calculate padding widths - int maxNameLen = 0; - int maxCountLen = 0; - for (const auto &entry : sortedEntries) { - maxNameLen = qMax(maxNameLen, entry.name.length()); - maxCountLen = qMax(maxCountLen, QString::number(entry.count).length()); - } - - // Format output - QStringList lines; - for (const auto &entry : sortedEntries) { - QString name = entry.name.toHtmlEscaped(); - QString count = QString::number(entry.count); - - QString namePadding = QString(QStringLiteral(" ")).repeated(maxNameLen - entry.name.length()); - QString countPadding = QString(QStringLiteral(" ")).repeated(maxCountLen - count.length()); - - lines << QStringLiteral( - "%1%2 %3%4") - .arg(namePadding, name, countPadding, count); - } - - return QStringLiteral("") + lines.join(QStringLiteral("
")) + - QStringLiteral("
"); -} - -QString buildSubtypeTallyText(const QList &cards) -{ - QList groups = countSubtypes(cards); - if (groups.isEmpty()) { - return QString(); - } - return formatAsHtml(groups); -} - } // namespace SelectionSubtypeTally diff --git a/cockatrice/src/game/selection_subtype_tally.h b/cockatrice/src/game/selection_subtype_tally.h index 3c2f2092b..a5014fafe 100644 --- a/cockatrice/src/game/selection_subtype_tally.h +++ b/cockatrice/src/game/selection_subtype_tally.h @@ -22,10 +22,7 @@ struct MainTypeGroup namespace SelectionSubtypeTally { -QStringList extractSubtypesFromFace(const QString &faceType); QList countSubtypes(const QList &cards); -QString formatAsHtml(const QList &groups); -QString buildSubtypeTallyText(const QList &cards); } // namespace SelectionSubtypeTally #endif diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index 12684b41f..304556a21 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -2,10 +2,13 @@ #include "../client/settings/cache_settings.h" #include "game_scene.h" +#include "libcockatrice/utility/qt_utils.h" #include "selection_subtype_tally.h" #include +#include #include +#include #include #include @@ -76,10 +79,12 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par totalCountLabel->setStyleSheet(totalCountLabelStyle); totalCountLabel->hide(); - subtypeCountLabel = new QLabel(this); - subtypeCountLabel->setStyleSheet(subtypeCountLabelStyle); - subtypeCountLabel->setTextFormat(Qt::RichText); - subtypeCountLabel->hide(); + subtypeCountContainer = new QWidget(this); + subtypeCountContainer->setStyleSheet(subtypeCountLabelStyle); + subtypeCountLayout = new QGridLayout(subtypeCountContainer); + subtypeCountLayout->setContentsMargins(2, 2, 2, 2); + subtypeCountLayout->setSpacing(2); + subtypeCountContainer->hide(); } void GameView::resizeEvent(QResizeEvent *event) @@ -174,13 +179,33 @@ void GameView::refreshShortcuts() SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView")); } -QString GameView::buildSubtypeTallyText() const +void GameView::clearSubtypeLabels() { - GameScene *gameScene = dynamic_cast(scene()); - if (!gameScene) { - return QString(); + QtUtils::clearLayoutRec(subtypeCountLayout); +} + +void GameView::rebuildSubtypeLabels(const QList &entries) +{ + clearSubtypeLabels(); + + const QString nameStyle = QStringLiteral("color: white; font-size: 12px; background: transparent;"); + const QString countStyle = + QStringLiteral("color: white; font-size: 14px; font-weight: bold; background: transparent;"); + + int row = 0; + for (const SubtypeEntry &entry : entries) { + auto *nameLabel = new QLabel(entry.name, subtypeCountContainer); + nameLabel->setStyleSheet(nameStyle); + nameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + subtypeCountLayout->addWidget(nameLabel, row, 0); + + auto *countLabel = new QLabel(QString::number(entry.count), subtypeCountContainer); + countLabel->setStyleSheet(countStyle); + countLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + subtypeCountLayout->addWidget(countLabel, row, 1); + + ++row; } - return SelectionSubtypeTally::buildSubtypeTallyText(gameScene->selectedCards()); } void GameView::updateTotalSelectionCount(const QSize &viewSize) @@ -206,33 +231,48 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize) } if (!SettingsCache::instance().getShowSubtypeSelectionCount() || count <= 1) { - subtypeCountLabel->hide(); + subtypeCountContainer->hide(); return; } - QString subtypeText = buildSubtypeTallyText(); - - if (subtypeText.isEmpty()) { - subtypeCountLabel->hide(); + GameScene *gameScene = dynamic_cast(scene()); + if (!gameScene) { + subtypeCountContainer->hide(); return; } - subtypeCountLabel->setText(subtypeText); - subtypeCountLabel->adjustSize(); + QList groups = SelectionSubtypeTally::countSubtypes(gameScene->selectedCards()); + if (groups.isEmpty()) { + subtypeCountContainer->hide(); + return; + } - int x = availableWidth - subtypeCountLabel->width() - kMarginInPixels; + QList entries; + for (const MainTypeGroup &group : groups) { + entries.append(group.subtypes); + } + + if (entries.isEmpty()) { + subtypeCountContainer->hide(); + return; + } + + rebuildSubtypeLabels(entries); + subtypeCountContainer->adjustSize(); + + int x = availableWidth - subtypeCountContainer->width() - kMarginInPixels; int y; if (totalCountLabel->isVisible()) { - y = totalCountLabel->y() - subtypeCountLabel->height() - kSpacingBetweenLabels; + y = totalCountLabel->y() - subtypeCountContainer->height() - kSpacingBetweenLabels; } else { - y = availableHeight - subtypeCountLabel->height() - kMarginInPixels; + y = availableHeight - subtypeCountContainer->height() - kMarginInPixels; } y = qMax(kMarginInPixels, y); - subtypeCountLabel->move(x, y); - subtypeCountLabel->show(); + subtypeCountContainer->move(x, y); + subtypeCountContainer->show(); } /** diff --git a/cockatrice/src/game_graphics/game_view.h b/cockatrice/src/game_graphics/game_view.h index dec4f76cc..38a4510b2 100644 --- a/cockatrice/src/game_graphics/game_view.h +++ b/cockatrice/src/game_graphics/game_view.h @@ -10,8 +10,10 @@ #include class GameScene; +class QGridLayout; class QLabel; class QRubberBand; +struct SubtypeEntry; class GameView : public QGraphicsView { @@ -21,11 +23,12 @@ private: QRubberBand *rubberBand; QLabel *dragCountLabel; QLabel *totalCountLabel; - QLabel *subtypeCountLabel; ///< Label displaying subtype breakdown for selected cards + QWidget *subtypeCountContainer; ///< Container widget for subtype tally display + QGridLayout *subtypeCountLayout; ///< Grid layout for subtype name/count pairs QPointF selectionOrigin; - /** @brief Builds formatted text showing subtype tally for all selected cards */ - QString buildSubtypeTallyText() const; + void rebuildSubtypeLabels(const QList &entries); + void clearSubtypeLabels(); protected: void resizeEvent(QResizeEvent *event) override; diff --git a/libcockatrice_utility/libcockatrice/utility/qt_utils.h b/libcockatrice_utility/libcockatrice/utility/qt_utils.h index 334e56027..8e5212031 100644 --- a/libcockatrice_utility/libcockatrice/utility/qt_utils.h +++ b/libcockatrice_utility/libcockatrice/utility/qt_utils.h @@ -1,5 +1,6 @@ #ifndef COCKATRICE_QT_UTILS_H #define COCKATRICE_QT_UTILS_H +#include #include namespace QtUtils From 568e253a7578b3aba916de62a2948287c328d247 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Thu, 4 Jun 2026 23:34:20 -0400 Subject: [PATCH 06/12] Rename count to tally in the subtype breakdown feature --- .../src/client/settings/cache_settings.cpp | 24 +++++++-------- .../src/client/settings/cache_settings.h | 24 +++++++-------- cockatrice/src/game_graphics/game_view.cpp | 6 ++-- .../user_interface_settings_page.cpp | 30 +++++++++---------- .../user_interface_settings_page.h | 6 ++-- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 5fe252618..0f725f388 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -311,9 +311,9 @@ SettingsCache::SettingsCache() focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool(); keepGameChatFocus = settings->value("interface/keepGameChatFocus", false).toBool(); - showDragSelectionCount = settings->value("interface/showlassoselectioncount", true).toBool(); - showTotalSelectionCount = settings->value("interface/showpersistentselectioncount", true).toBool(); - showSubtypeSelectionCount = settings->value("interface/showsubtypeselectioncount", true).toBool(); + showDragSelectionTally = settings->value("interface/showlassoselectiontally", true).toBool(); + showTotalSelectionTally = settings->value("interface/showpersistentselectiontally", true).toBool(); + showSubtypeSelectionTally = settings->value("interface/showsubtypeselectiontally", true).toBool(); showShortcuts = settings->value("menu/showshortcuts", true).toBool(); showGameSelectorFilterToolbar = settings->value("menu/showgameselectorfiltertoolbar", true).toBool(); @@ -1376,22 +1376,22 @@ void SettingsCache::setRoundCardCorners(bool _roundCardCorners) emit roundCardCornersChanged(roundCardCorners); } -void SettingsCache::setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount) +void SettingsCache::setShowDragSelectionTally(QT_STATE_CHANGED_T _showDragSelectionTally) { - showDragSelectionCount = static_cast(_showDragSelectionCount); - settings->setValue("interface/showlassoselectioncount", showDragSelectionCount); + showDragSelectionTally = static_cast(_showDragSelectionTally); + settings->setValue("interface/showlassoselectiontally", showDragSelectionTally); } -void SettingsCache::setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount) +void SettingsCache::setShowTotalSelectionTally(QT_STATE_CHANGED_T _showTotalSelectionTally) { - showTotalSelectionCount = static_cast(_showTotalSelectionCount); - settings->setValue("interface/showpersistentselectioncount", showTotalSelectionCount); + showTotalSelectionTally = static_cast(_showTotalSelectionTally); + settings->setValue("interface/showpersistentselectiontally", showTotalSelectionTally); } -void SettingsCache::setShowSubtypeSelectionCount(QT_STATE_CHANGED_T _showSubtypeSelectionCount) +void SettingsCache::setShowSubtypeSelectionTally(QT_STATE_CHANGED_T _showSubtypeSelectionTally) { - showSubtypeSelectionCount = static_cast(_showSubtypeSelectionCount); - settings->setValue("interface/showsubtypeselectioncount", showSubtypeSelectionCount); + showSubtypeSelectionTally = static_cast(_showSubtypeSelectionTally); + settings->setValue("interface/showsubtypeselectiontally", showSubtypeSelectionTally); } void SettingsCache::loadPaths() diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index 43c4a8271..378500f01 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -351,9 +351,9 @@ private: bool isPortableBuild; bool roundCardCorners; bool showStatusBar; - bool showDragSelectionCount; - bool showTotalSelectionCount; - bool showSubtypeSelectionCount; + bool showDragSelectionTally; + bool showTotalSelectionTally; + bool showSubtypeSelectionTally; public: SettingsCache(); @@ -469,17 +469,17 @@ public: { return showStatusBar; } - [[nodiscard]] bool getShowDragSelectionCount() const + [[nodiscard]] bool getShowDragSelectionTally() const { - return showDragSelectionCount; + return showDragSelectionTally; } - [[nodiscard]] bool getShowTotalSelectionCount() const + [[nodiscard]] bool getShowTotalSelectionTally() const { - return showTotalSelectionCount; + return showTotalSelectionTally; } - [[nodiscard]] bool getShowSubtypeSelectionCount() const + [[nodiscard]] bool getShowSubtypeSelectionTally() const { - return showSubtypeSelectionCount; + return showSubtypeSelectionTally; } [[nodiscard]] bool getNotificationsEnabled() const { @@ -1172,8 +1172,8 @@ public slots: void setUpdateReleaseChannelIndex(int value); void setMaxFontSize(int _max); void setRoundCardCorners(bool _roundCardCorners); - void setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount); - void setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount); - void setShowSubtypeSelectionCount(QT_STATE_CHANGED_T _showSubtypeSelectionCount); + void setShowDragSelectionTally(QT_STATE_CHANGED_T _showDragSelectionTally); + void setShowTotalSelectionTally(QT_STATE_CHANGED_T _showTotalSelectionTally); + void setShowSubtypeSelectionTally(QT_STATE_CHANGED_T _showSubtypeSelectionTally); }; #endif diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index 304556a21..9e5717249 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -128,7 +128,7 @@ void GameView::resizeRubberBand(const QPointF &cursorPoint, int selectedCount) QRect rect = QRect(mapFromScene(selectionOrigin), cursor).normalized(); rubberBand->setGeometry(rect); - if (!SettingsCache::instance().getShowDragSelectionCount()) { + if (!SettingsCache::instance().getShowDragSelectionTally()) { dragCountLabel->hide(); return; } @@ -218,7 +218,7 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize) int count = scene()->selectedItems().count(); - if (!SettingsCache::instance().getShowTotalSelectionCount() || count <= 1) { + if (!SettingsCache::instance().getShowTotalSelectionTally() || count <= 1) { totalCountLabel->hide(); } else { totalCountLabel->setText(QString::number(count)); @@ -230,7 +230,7 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize) totalCountLabel->show(); } - if (!SettingsCache::instance().getShowSubtypeSelectionCount() || count <= 1) { + if (!SettingsCache::instance().getShowSubtypeSelectionTally() || count <= 1) { subtypeCountContainer->hide(); return; } diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp index 9261ba011..1bccc0a7f 100644 --- a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp @@ -60,17 +60,17 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&annotateTokensCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setAnnotateTokens); - showDragSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowDragSelectionCount()); - connect(&showDragSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setShowDragSelectionCount); + showDragSelectionTallyCheckBox.setChecked(SettingsCache::instance().getShowDragSelectionTally()); + connect(&showDragSelectionTallyCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowDragSelectionTally); - showTotalSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowTotalSelectionCount()); - connect(&showTotalSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setShowTotalSelectionCount); + showTotalSelectionTallyCheckBox.setChecked(SettingsCache::instance().getShowTotalSelectionTally()); + connect(&showTotalSelectionTallyCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowTotalSelectionTally); - showSubtypeSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowSubtypeSelectionCount()); - connect(&showSubtypeSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setShowSubtypeSelectionCount); + showSubtypeSelectionTallyCheckBox.setChecked(SettingsCache::instance().getShowSubtypeSelectionTally()); + connect(&showSubtypeSelectionTallyCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowSubtypeSelectionTally); useTearOffMenusCheckBox.setChecked(SettingsCache::instance().getUseTearOffMenus()); connect(&useTearOffMenusCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), @@ -88,9 +88,9 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() generalGrid->addWidget(&closeEmptyCardViewCheckBox, 4, 0); generalGrid->addWidget(&focusCardViewSearchBarCheckBox, 5, 0); generalGrid->addWidget(&annotateTokensCheckBox, 6, 0); - generalGrid->addWidget(&showDragSelectionCountCheckBox, 7, 0); - generalGrid->addWidget(&showTotalSelectionCountCheckBox, 8, 0); - generalGrid->addWidget(&showSubtypeSelectionCountCheckBox, 9, 0); + generalGrid->addWidget(&showDragSelectionTallyCheckBox, 7, 0); + generalGrid->addWidget(&showTotalSelectionTallyCheckBox, 8, 0); + generalGrid->addWidget(&showSubtypeSelectionTallyCheckBox, 9, 0); generalGrid->addWidget(&useTearOffMenusCheckBox, 10, 0); generalGrid->addWidget(&keepGameChatFocusCheckBox, 11, 0); @@ -214,9 +214,9 @@ void UserInterfaceSettingsPage::retranslateUi() closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed")); focusCardViewSearchBarCheckBox.setText(tr("Auto focus search bar when card view window is opened")); annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); - showDragSelectionCountCheckBox.setText(tr("Show selection counter during drag selection")); - showTotalSelectionCountCheckBox.setText(tr("Show total selection counter")); - showSubtypeSelectionCountCheckBox.setText(tr("Show subtype breakdown in selection counter")); + showDragSelectionTallyCheckBox.setText(tr("Show selection tally during drag selection")); + showTotalSelectionTallyCheckBox.setText(tr("Show total selection tally")); + showSubtypeSelectionTallyCheckBox.setText(tr("Show subtype breakdown in selection tally")); useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen")); keepGameChatFocusCheckBox.setText( tr("Keep game chat focused when clicking in game (Note: disables card view search bar)")); diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h index cddd840fd..1bab87cb2 100644 --- a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h @@ -27,9 +27,9 @@ private: QCheckBox closeEmptyCardViewCheckBox; QCheckBox focusCardViewSearchBarCheckBox; QCheckBox annotateTokensCheckBox; - QCheckBox showDragSelectionCountCheckBox; - QCheckBox showTotalSelectionCountCheckBox; - QCheckBox showSubtypeSelectionCountCheckBox; + QCheckBox showDragSelectionTallyCheckBox; + QCheckBox showTotalSelectionTallyCheckBox; + QCheckBox showSubtypeSelectionTallyCheckBox; QCheckBox useTearOffMenusCheckBox; QCheckBox keepGameChatFocusCheckBox; QCheckBox tapAnimationCheckBox; From 4447214c7a82e1b338cad2c76974094656aa873b Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Mon, 8 Jun 2026 16:36:10 -0400 Subject: [PATCH 07/12] partial rename --- .../src/client/settings/cache_settings.cpp | 16 +++++++-------- .../src/client/settings/cache_settings.h | 16 +++++++-------- cockatrice/src/game_graphics/game_view.cpp | 4 ++-- .../user_interface_settings_page.cpp | 20 +++++++++---------- .../user_interface_settings_page.h | 4 ++-- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 0f725f388..6a804d8b5 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -311,8 +311,8 @@ SettingsCache::SettingsCache() focusCardViewSearchBar = settings->value("interface/focusCardViewSearchBar", true).toBool(); keepGameChatFocus = settings->value("interface/keepGameChatFocus", false).toBool(); - showDragSelectionTally = settings->value("interface/showlassoselectiontally", true).toBool(); - showTotalSelectionTally = settings->value("interface/showpersistentselectiontally", true).toBool(); + showDragSelectionCount = settings->value("interface/showlassoselectioncount", true).toBool(); + showTotalSelectionCount = settings->value("interface/showpersistentselectioncount", true).toBool(); showSubtypeSelectionTally = settings->value("interface/showsubtypeselectiontally", true).toBool(); showShortcuts = settings->value("menu/showshortcuts", true).toBool(); @@ -1376,16 +1376,16 @@ void SettingsCache::setRoundCardCorners(bool _roundCardCorners) emit roundCardCornersChanged(roundCardCorners); } -void SettingsCache::setShowDragSelectionTally(QT_STATE_CHANGED_T _showDragSelectionTally) +void SettingsCache::setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount) { - showDragSelectionTally = static_cast(_showDragSelectionTally); - settings->setValue("interface/showlassoselectiontally", showDragSelectionTally); + showDragSelectionCount = static_cast(_showDragSelectionCount); + settings->setValue("interface/showlassoselectioncount", showDragSelectionCount); } -void SettingsCache::setShowTotalSelectionTally(QT_STATE_CHANGED_T _showTotalSelectionTally) +void SettingsCache::setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount) { - showTotalSelectionTally = static_cast(_showTotalSelectionTally); - settings->setValue("interface/showpersistentselectiontally", showTotalSelectionTally); + showTotalSelectionCount = static_cast(_showTotalSelectionCount); + settings->setValue("interface/showpersistentselectioncount", showTotalSelectionCount); } void SettingsCache::setShowSubtypeSelectionTally(QT_STATE_CHANGED_T _showSubtypeSelectionTally) diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h index 378500f01..292252fd2 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -351,8 +351,8 @@ private: bool isPortableBuild; bool roundCardCorners; bool showStatusBar; - bool showDragSelectionTally; - bool showTotalSelectionTally; + bool showDragSelectionCount; + bool showTotalSelectionCount; bool showSubtypeSelectionTally; public: @@ -469,13 +469,13 @@ public: { return showStatusBar; } - [[nodiscard]] bool getShowDragSelectionTally() const + [[nodiscard]] bool getShowDragSelectionCount() const { - return showDragSelectionTally; + return showDragSelectionCount; } - [[nodiscard]] bool getShowTotalSelectionTally() const + [[nodiscard]] bool getShowTotalSelectionCount() const { - return showTotalSelectionTally; + return showTotalSelectionCount; } [[nodiscard]] bool getShowSubtypeSelectionTally() const { @@ -1172,8 +1172,8 @@ public slots: void setUpdateReleaseChannelIndex(int value); void setMaxFontSize(int _max); void setRoundCardCorners(bool _roundCardCorners); - void setShowDragSelectionTally(QT_STATE_CHANGED_T _showDragSelectionTally); - void setShowTotalSelectionTally(QT_STATE_CHANGED_T _showTotalSelectionTally); + void setShowDragSelectionCount(QT_STATE_CHANGED_T _showDragSelectionCount); + void setShowTotalSelectionCount(QT_STATE_CHANGED_T _showTotalSelectionCount); void setShowSubtypeSelectionTally(QT_STATE_CHANGED_T _showSubtypeSelectionTally); }; #endif diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index 9e5717249..d7d5bc7b0 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -128,7 +128,7 @@ void GameView::resizeRubberBand(const QPointF &cursorPoint, int selectedCount) QRect rect = QRect(mapFromScene(selectionOrigin), cursor).normalized(); rubberBand->setGeometry(rect); - if (!SettingsCache::instance().getShowDragSelectionTally()) { + if (!SettingsCache::instance().getShowDragSelectionCount()) { dragCountLabel->hide(); return; } @@ -218,7 +218,7 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize) int count = scene()->selectedItems().count(); - if (!SettingsCache::instance().getShowTotalSelectionTally() || count <= 1) { + if (!SettingsCache::instance().getShowTotalSelectionCount() || count <= 1) { totalCountLabel->hide(); } else { totalCountLabel->setText(QString::number(count)); diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp index 1bccc0a7f..44b30d29c 100644 --- a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp @@ -60,13 +60,13 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&annotateTokensCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setAnnotateTokens); - showDragSelectionTallyCheckBox.setChecked(SettingsCache::instance().getShowDragSelectionTally()); - connect(&showDragSelectionTallyCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setShowDragSelectionTally); + showDragSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowDragSelectionCount()); + connect(&showDragSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowDragSelectionCount); - showTotalSelectionTallyCheckBox.setChecked(SettingsCache::instance().getShowTotalSelectionTally()); - connect(&showTotalSelectionTallyCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - &SettingsCache::setShowTotalSelectionTally); + showTotalSelectionCountCheckBox.setChecked(SettingsCache::instance().getShowTotalSelectionCount()); + connect(&showTotalSelectionCountCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowTotalSelectionCount); showSubtypeSelectionTallyCheckBox.setChecked(SettingsCache::instance().getShowSubtypeSelectionTally()); connect(&showSubtypeSelectionTallyCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), @@ -88,8 +88,8 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() generalGrid->addWidget(&closeEmptyCardViewCheckBox, 4, 0); generalGrid->addWidget(&focusCardViewSearchBarCheckBox, 5, 0); generalGrid->addWidget(&annotateTokensCheckBox, 6, 0); - generalGrid->addWidget(&showDragSelectionTallyCheckBox, 7, 0); - generalGrid->addWidget(&showTotalSelectionTallyCheckBox, 8, 0); + generalGrid->addWidget(&showDragSelectionCountCheckBox, 7, 0); + generalGrid->addWidget(&showTotalSelectionCountCheckBox, 8, 0); generalGrid->addWidget(&showSubtypeSelectionTallyCheckBox, 9, 0); generalGrid->addWidget(&useTearOffMenusCheckBox, 10, 0); generalGrid->addWidget(&keepGameChatFocusCheckBox, 11, 0); @@ -214,8 +214,8 @@ void UserInterfaceSettingsPage::retranslateUi() closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed")); focusCardViewSearchBarCheckBox.setText(tr("Auto focus search bar when card view window is opened")); annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); - showDragSelectionTallyCheckBox.setText(tr("Show selection tally during drag selection")); - showTotalSelectionTallyCheckBox.setText(tr("Show total selection tally")); + showDragSelectionCountCheckBox.setText(tr("Show selection count during drag selection")); + showTotalSelectionCountCheckBox.setText(tr("Show total selection count")); showSubtypeSelectionTallyCheckBox.setText(tr("Show subtype breakdown in selection tally")); useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen")); keepGameChatFocusCheckBox.setText( diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h index 1bab87cb2..06f0e6b83 100644 --- a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h @@ -27,8 +27,8 @@ private: QCheckBox closeEmptyCardViewCheckBox; QCheckBox focusCardViewSearchBarCheckBox; QCheckBox annotateTokensCheckBox; - QCheckBox showDragSelectionTallyCheckBox; - QCheckBox showTotalSelectionTallyCheckBox; + QCheckBox showDragSelectionCountCheckBox; + QCheckBox showTotalSelectionCountCheckBox; QCheckBox showSubtypeSelectionTallyCheckBox; QCheckBox useTearOffMenusCheckBox; QCheckBox keepGameChatFocusCheckBox; From 4850bcf809df885ba0e121e0936db01e95b48059 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Mon, 8 Jun 2026 21:08:04 -0400 Subject: [PATCH 08/12] list position fixed --- cockatrice/src/game_graphics/game_view.cpp | 31 +++++++++++++++++----- cockatrice/src/game_graphics/game_view.h | 2 +- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index d7d5bc7b0..78c8087f7 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -184,7 +184,7 @@ void GameView::clearSubtypeLabels() QtUtils::clearLayoutRec(subtypeCountLayout); } -void GameView::rebuildSubtypeLabels(const QList &entries) +QSize GameView::rebuildSubtypeLabels(const QList &entries) { clearSubtypeLabels(); @@ -192,6 +192,10 @@ void GameView::rebuildSubtypeLabels(const QList &entries) const QString countStyle = QStringLiteral("color: white; font-size: 14px; font-weight: bold; background: transparent;"); + int totalHeight = 0; + int maxNameWidth = 0; + int maxCountWidth = 0; + int row = 0; for (const SubtypeEntry &entry : entries) { auto *nameLabel = new QLabel(entry.name, subtypeCountContainer); @@ -204,8 +208,23 @@ void GameView::rebuildSubtypeLabels(const QList &entries) countLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); subtypeCountLayout->addWidget(countLabel, row, 1); + QSize nameSize = nameLabel->sizeHint(); + QSize countSize = countLabel->sizeHint(); + maxNameWidth = qMax(maxNameWidth, nameSize.width()); + maxCountWidth = qMax(maxCountWidth, countSize.width()); + totalHeight += qMax(nameSize.height(), countSize.height()); + ++row; } + + int spacing = subtypeCountLayout->spacing(); + int margins = subtypeCountLayout->contentsMargins().left() + subtypeCountLayout->contentsMargins().right(); + int verticalMargins = subtypeCountLayout->contentsMargins().top() + subtypeCountLayout->contentsMargins().bottom(); + + int width = maxNameWidth + spacing + maxCountWidth + margins; + int height = totalHeight + (row - 1) * spacing + verticalMargins; + + return QSize(width, height); } void GameView::updateTotalSelectionCount(const QSize &viewSize) @@ -257,16 +276,16 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize) return; } - rebuildSubtypeLabels(entries); - subtypeCountContainer->adjustSize(); + QSize containerSize = rebuildSubtypeLabels(entries); + subtypeCountContainer->resize(containerSize); - int x = availableWidth - subtypeCountContainer->width() - kMarginInPixels; + int x = availableWidth - containerSize.width() - kMarginInPixels; int y; if (totalCountLabel->isVisible()) { - y = totalCountLabel->y() - subtypeCountContainer->height() - kSpacingBetweenLabels; + y = totalCountLabel->y() - containerSize.height() - kSpacingBetweenLabels; } else { - y = availableHeight - subtypeCountContainer->height() - kMarginInPixels; + y = availableHeight - containerSize.height() - kMarginInPixels; } y = qMax(kMarginInPixels, y); diff --git a/cockatrice/src/game_graphics/game_view.h b/cockatrice/src/game_graphics/game_view.h index 38a4510b2..d55849a2f 100644 --- a/cockatrice/src/game_graphics/game_view.h +++ b/cockatrice/src/game_graphics/game_view.h @@ -27,7 +27,7 @@ private: QGridLayout *subtypeCountLayout; ///< Grid layout for subtype name/count pairs QPointF selectionOrigin; - void rebuildSubtypeLabels(const QList &entries); + QSize rebuildSubtypeLabels(const QList &entries); void clearSubtypeLabels(); protected: From 192570eda618f3088e085cfc8b32d254050cccaf Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Mon, 8 Jun 2026 22:20:48 -0400 Subject: [PATCH 09/12] Clean up code and documentation --- .../src/game/selection_subtype_tally.cpp | 55 +++++-------------- cockatrice/src/game/selection_subtype_tally.h | 30 ++++++---- cockatrice/src/game_graphics/game_view.cpp | 37 +++++-------- cockatrice/src/game_graphics/game_view.h | 8 ++- 4 files changed, 54 insertions(+), 76 deletions(-) diff --git a/cockatrice/src/game/selection_subtype_tally.cpp b/cockatrice/src/game/selection_subtype_tally.cpp index c033d42c9..afc5bc382 100644 --- a/cockatrice/src/game/selection_subtype_tally.cpp +++ b/cockatrice/src/game/selection_subtype_tally.cpp @@ -8,8 +8,10 @@ namespace { +/** @brief Extracts subtypes from a single card face's type line. */ QStringList extractSubtypesFromFace(const QString &faceType) { + // Card type format: "Creature — Goblin Warrior" or "Legendary Enchantment — Saga" QStringList parts = faceType.split(QStringLiteral(" — ")); if (parts.size() > 1) { return parts[1].split(QStringLiteral(" "), Qt::SkipEmptyParts); @@ -22,68 +24,41 @@ QStringList extractSubtypesFromFace(const QString &faceType) namespace SelectionSubtypeTally { -QList countSubtypes(const QList &cards) +QList countSubtypes(const QList &cards) { - QMap> subtypesByMainType; - QMap cardCountPerMainType; + QMap subtypeCounts; for (CardItem *card : cards) { if (card->getFaceDown() || card->getCard().isEmpty()) { continue; } - QString mainType = card->getCardInfo().getMainCardType(); - if (mainType.isEmpty()) { - mainType = QStringLiteral("Other"); - } - QString cardType = card->getCardInfo().getCardType(); + // Handle double-faced cards: "Creature — Human // Creature — Werewolf" QStringList cardFaces = cardType.split(QStringLiteral(" // ")); - bool contributedSubtypes = false; for (const QString &face : cardFaces) { QStringList subtypes = extractSubtypesFromFace(face); for (const QString &subtype : subtypes) { - subtypesByMainType[mainType][subtype]++; - contributedSubtypes = true; + subtypeCounts[subtype]++; } } - - if (contributedSubtypes) { - cardCountPerMainType[mainType]++; - } } - QList groups; - for (auto it = subtypesByMainType.constBegin(); it != subtypesByMainType.constEnd(); ++it) { - MainTypeGroup group; - group.mainType = it.key(); - group.cardCount = cardCountPerMainType.value(it.key(), 0); - - for (auto subIt = it.value().constBegin(); subIt != it.value().constEnd(); ++subIt) { - group.subtypes.append({subIt.key(), subIt.value()}); - } - - // Sort subtypes: by count ascending, then alphabetically - std::sort(group.subtypes.begin(), group.subtypes.end(), [](const SubtypeEntry &a, const SubtypeEntry &b) { - if (a.count != b.count) { - return a.count < b.count; - } - return a.name < b.name; - }); - - groups.append(group); + QList entries; + for (auto it = subtypeCounts.constBegin(); it != subtypeCounts.constEnd(); ++it) { + entries.append({it.key(), it.value()}); } - // Sort groups: by card count ascending, then alphabetically by main type - std::sort(groups.begin(), groups.end(), [](const MainTypeGroup &a, const MainTypeGroup &b) { - if (a.cardCount != b.cardCount) { - return a.cardCount < b.cardCount; + // Sort by count ascending, then alphabetically (lowest counts at bottom of display) + std::sort(entries.begin(), entries.end(), [](const SubtypeEntry &a, const SubtypeEntry &b) { + if (a.count != b.count) { + return a.count < b.count; } - return a.mainType < b.mainType; + return a.name < b.name; }); - return groups; + return entries; } } // namespace SelectionSubtypeTally diff --git a/cockatrice/src/game/selection_subtype_tally.h b/cockatrice/src/game/selection_subtype_tally.h index a5014fafe..9038653f6 100644 --- a/cockatrice/src/game/selection_subtype_tally.h +++ b/cockatrice/src/game/selection_subtype_tally.h @@ -3,26 +3,34 @@ #include #include -#include class CardItem; +/** @brief A single subtype (e.g., "Goblin", "Warrior") with its occurrence count. */ struct SubtypeEntry { - QString name; - int count; -}; - -struct MainTypeGroup -{ - QString mainType; - int cardCount; - QList subtypes; + QString name; ///< The subtype name + int count; ///< Number of selected cards with this subtype + + bool operator==(const SubtypeEntry &other) const + { + return name == other.name && count == other.count; + } }; +/** + * @brief Extracts and tallies subtypes from selected cards. + */ namespace SelectionSubtypeTally { -QList countSubtypes(const QList &cards); +/** + * @brief Parses card type lines and counts each subtype occurrence. + * + * Skips face-down cards and cards without type info. + * @param cards The list of selected card items to analyze. + * @return Entries sorted by count ascending, then alphabetically. + */ +QList countSubtypes(const QList &cards); } // namespace SelectionSubtypeTally #endif diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index 78c8087f7..1095788d2 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -91,10 +91,8 @@ void GameView::resizeEvent(QResizeEvent *event) { QGraphicsView::resizeEvent(event); - GameScene *s = dynamic_cast(scene()); - if (s) { - s->processViewSizeChange(event->size()); - } + GameScene *s = static_cast(scene()); + s->processViewSizeChange(event->size()); updateSceneRect(scene()->sceneRect()); updateTotalSelectionCount(event->size()); @@ -251,33 +249,28 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize) if (!SettingsCache::instance().getShowSubtypeSelectionTally() || count <= 1) { subtypeCountContainer->hide(); + cachedSubtypeEntries.clear(); return; } - GameScene *gameScene = dynamic_cast(scene()); - if (!gameScene) { - subtypeCountContainer->hide(); - return; - } - - QList groups = SelectionSubtypeTally::countSubtypes(gameScene->selectedCards()); - if (groups.isEmpty()) { - subtypeCountContainer->hide(); - return; - } - - QList entries; - for (const MainTypeGroup &group : groups) { - entries.append(group.subtypes); - } + GameScene *gameScene = static_cast(scene()); + QList entries = SelectionSubtypeTally::countSubtypes(gameScene->selectedCards()); if (entries.isEmpty()) { subtypeCountContainer->hide(); + cachedSubtypeEntries.clear(); return; } - QSize containerSize = rebuildSubtypeLabels(entries); - subtypeCountContainer->resize(containerSize); + // Only rebuild labels if entries changed + QSize containerSize; + if (entries != cachedSubtypeEntries) { + cachedSubtypeEntries = entries; + containerSize = rebuildSubtypeLabels(entries); + subtypeCountContainer->resize(containerSize); + } else { + containerSize = subtypeCountContainer->size(); + } int x = availableWidth - containerSize.width() - kMarginInPixels; int y; diff --git a/cockatrice/src/game_graphics/game_view.h b/cockatrice/src/game_graphics/game_view.h index d55849a2f..04154ab1c 100644 --- a/cockatrice/src/game_graphics/game_view.h +++ b/cockatrice/src/game_graphics/game_view.h @@ -7,13 +7,14 @@ #ifndef GAMEVIEW_H #define GAMEVIEW_H +#include "selection_subtype_tally.h" + #include class GameScene; class QGridLayout; class QLabel; class QRubberBand; -struct SubtypeEntry; class GameView : public QGraphicsView { @@ -23,9 +24,10 @@ private: QRubberBand *rubberBand; QLabel *dragCountLabel; QLabel *totalCountLabel; - QWidget *subtypeCountContainer; ///< Container widget for subtype tally display - QGridLayout *subtypeCountLayout; ///< Grid layout for subtype name/count pairs + QWidget *subtypeCountContainer; + QGridLayout *subtypeCountLayout; QPointF selectionOrigin; + QList cachedSubtypeEntries; ///< Cached entries to avoid redundant rebuilds QSize rebuildSubtypeLabels(const QList &entries); void clearSubtypeLabels(); From 23fe7822e8a50633e2a07eff5d173d148507a721 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Tue, 9 Jun 2026 11:58:49 -0400 Subject: [PATCH 10/12] Rename subtypeCountLabelStyle to subtypeTallyLabelStyle and fix include ordering --- cockatrice/CMakeLists.txt | 2 +- cockatrice/src/game_graphics/game_view.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 9dfb81f3c..086e189a1 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -82,8 +82,8 @@ set(cockatrice_SOURCES src/game_graphics/game_scene.cpp src/game/game_state.cpp src/game_graphics/game_view.cpp - src/game/selection_subtype_tally.cpp src/game_graphics/hand_counter.cpp + src/game/selection_subtype_tally.cpp src/game_graphics/log/message_log_widget.cpp src/game/phase.cpp src/game_graphics/phases_toolbar.cpp diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index 1095788d2..91bbb11c9 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -2,7 +2,6 @@ #include "../client/settings/cache_settings.h" #include "game_scene.h" -#include "libcockatrice/utility/qt_utils.h" #include "selection_subtype_tally.h" #include @@ -11,6 +10,7 @@ #include #include #include +#include // QRubberBand calls raise() in showEvent() and changeEvent() to stay on top of siblings. // This subclass disables that behavior so dragCountLabel can appear above it. @@ -68,7 +68,7 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par const QString dragCountLabelStyle = baseProperties + "font-size: 14px; font-weight: bold;"; const QString totalCountLabelStyle = baseProperties + "font-size: 16px; font-weight: bold;"; - const QString subtypeCountLabelStyle = baseProperties + "font-size: 12px;"; + const QString subtypeTallyLabelStyle = baseProperties + "font-size: 12px;"; dragCountLabel = new QLabel(this); dragCountLabel->setStyleSheet(dragCountLabelStyle); @@ -80,7 +80,7 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par totalCountLabel->hide(); subtypeCountContainer = new QWidget(this); - subtypeCountContainer->setStyleSheet(subtypeCountLabelStyle); + subtypeCountContainer->setStyleSheet(subtypeTallyLabelStyle); subtypeCountLayout = new QGridLayout(subtypeCountContainer); subtypeCountLayout->setContentsMargins(2, 2, 2, 2); subtypeCountLayout->setSpacing(2); From 189d7e5e6ebb77f72523463458638be4ae820a04 Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Tue, 9 Jun 2026 12:21:53 -0400 Subject: [PATCH 11/12] Fix include path for selection_subtype_tally.h after file relocation --- cockatrice/src/game/selection_subtype_tally.cpp | 2 +- cockatrice/src/game_graphics/game_view.cpp | 2 +- cockatrice/src/game_graphics/game_view.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/game/selection_subtype_tally.cpp b/cockatrice/src/game/selection_subtype_tally.cpp index afc5bc382..e9f87fab9 100644 --- a/cockatrice/src/game/selection_subtype_tally.cpp +++ b/cockatrice/src/game/selection_subtype_tally.cpp @@ -1,6 +1,6 @@ #include "selection_subtype_tally.h" -#include "board/card_item.h" +#include "../game_graphics/board/card_item.h" #include #include diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index 91bbb11c9..19ace0b48 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -1,8 +1,8 @@ #include "game_view.h" #include "../client/settings/cache_settings.h" +#include "../game/selection_subtype_tally.h" #include "game_scene.h" -#include "selection_subtype_tally.h" #include #include diff --git a/cockatrice/src/game_graphics/game_view.h b/cockatrice/src/game_graphics/game_view.h index 04154ab1c..05cc3bb2b 100644 --- a/cockatrice/src/game_graphics/game_view.h +++ b/cockatrice/src/game_graphics/game_view.h @@ -7,7 +7,7 @@ #ifndef GAMEVIEW_H #define GAMEVIEW_H -#include "selection_subtype_tally.h" +#include "../game/selection_subtype_tally.h" #include From b58d3a14deb0e3811a9dabc367f5184c352c463b Mon Sep 17 00:00:00 2001 From: DawnFire42 Date: Fri, 19 Jun 2026 10:41:16 -0400 Subject: [PATCH 12/12] fixed count to tally rename inconsistencies --- cockatrice/src/game_graphics/game_view.cpp | 40 +++++++++++----------- cockatrice/src/game_graphics/game_view.h | 4 +-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/cockatrice/src/game_graphics/game_view.cpp b/cockatrice/src/game_graphics/game_view.cpp index 19ace0b48..c2d9b2b3b 100644 --- a/cockatrice/src/game_graphics/game_view.cpp +++ b/cockatrice/src/game_graphics/game_view.cpp @@ -79,12 +79,12 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par totalCountLabel->setStyleSheet(totalCountLabelStyle); totalCountLabel->hide(); - subtypeCountContainer = new QWidget(this); - subtypeCountContainer->setStyleSheet(subtypeTallyLabelStyle); - subtypeCountLayout = new QGridLayout(subtypeCountContainer); - subtypeCountLayout->setContentsMargins(2, 2, 2, 2); - subtypeCountLayout->setSpacing(2); - subtypeCountContainer->hide(); + subtypeTallyContainer = new QWidget(this); + subtypeTallyContainer->setStyleSheet(subtypeTallyLabelStyle); + subtypeTallyLayout = new QGridLayout(subtypeTallyContainer); + subtypeTallyLayout->setContentsMargins(2, 2, 2, 2); + subtypeTallyLayout->setSpacing(2); + subtypeTallyContainer->hide(); } void GameView::resizeEvent(QResizeEvent *event) @@ -179,7 +179,7 @@ void GameView::refreshShortcuts() void GameView::clearSubtypeLabels() { - QtUtils::clearLayoutRec(subtypeCountLayout); + QtUtils::clearLayoutRec(subtypeTallyLayout); } QSize GameView::rebuildSubtypeLabels(const QList &entries) @@ -196,15 +196,15 @@ QSize GameView::rebuildSubtypeLabels(const QList &entries) int row = 0; for (const SubtypeEntry &entry : entries) { - auto *nameLabel = new QLabel(entry.name, subtypeCountContainer); + auto *nameLabel = new QLabel(entry.name, subtypeTallyContainer); nameLabel->setStyleSheet(nameStyle); nameLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - subtypeCountLayout->addWidget(nameLabel, row, 0); + subtypeTallyLayout->addWidget(nameLabel, row, 0); - auto *countLabel = new QLabel(QString::number(entry.count), subtypeCountContainer); + auto *countLabel = new QLabel(QString::number(entry.count), subtypeTallyContainer); countLabel->setStyleSheet(countStyle); countLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); - subtypeCountLayout->addWidget(countLabel, row, 1); + subtypeTallyLayout->addWidget(countLabel, row, 1); QSize nameSize = nameLabel->sizeHint(); QSize countSize = countLabel->sizeHint(); @@ -215,9 +215,9 @@ QSize GameView::rebuildSubtypeLabels(const QList &entries) ++row; } - int spacing = subtypeCountLayout->spacing(); - int margins = subtypeCountLayout->contentsMargins().left() + subtypeCountLayout->contentsMargins().right(); - int verticalMargins = subtypeCountLayout->contentsMargins().top() + subtypeCountLayout->contentsMargins().bottom(); + int spacing = subtypeTallyLayout->spacing(); + int margins = subtypeTallyLayout->contentsMargins().left() + subtypeTallyLayout->contentsMargins().right(); + int verticalMargins = subtypeTallyLayout->contentsMargins().top() + subtypeTallyLayout->contentsMargins().bottom(); int width = maxNameWidth + spacing + maxCountWidth + margins; int height = totalHeight + (row - 1) * spacing + verticalMargins; @@ -248,7 +248,7 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize) } if (!SettingsCache::instance().getShowSubtypeSelectionTally() || count <= 1) { - subtypeCountContainer->hide(); + subtypeTallyContainer->hide(); cachedSubtypeEntries.clear(); return; } @@ -257,7 +257,7 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize) QList entries = SelectionSubtypeTally::countSubtypes(gameScene->selectedCards()); if (entries.isEmpty()) { - subtypeCountContainer->hide(); + subtypeTallyContainer->hide(); cachedSubtypeEntries.clear(); return; } @@ -267,9 +267,9 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize) if (entries != cachedSubtypeEntries) { cachedSubtypeEntries = entries; containerSize = rebuildSubtypeLabels(entries); - subtypeCountContainer->resize(containerSize); + subtypeTallyContainer->resize(containerSize); } else { - containerSize = subtypeCountContainer->size(); + containerSize = subtypeTallyContainer->size(); } int x = availableWidth - containerSize.width() - kMarginInPixels; @@ -283,8 +283,8 @@ void GameView::updateTotalSelectionCount(const QSize &viewSize) y = qMax(kMarginInPixels, y); - subtypeCountContainer->move(x, y); - subtypeCountContainer->show(); + subtypeTallyContainer->move(x, y); + subtypeTallyContainer->show(); } /** diff --git a/cockatrice/src/game_graphics/game_view.h b/cockatrice/src/game_graphics/game_view.h index 05cc3bb2b..4047c87ab 100644 --- a/cockatrice/src/game_graphics/game_view.h +++ b/cockatrice/src/game_graphics/game_view.h @@ -24,8 +24,8 @@ private: QRubberBand *rubberBand; QLabel *dragCountLabel; QLabel *totalCountLabel; - QWidget *subtypeCountContainer; - QGridLayout *subtypeCountLayout; + QWidget *subtypeTallyContainer; + QGridLayout *subtypeTallyLayout; QPointF selectionOrigin; QList cachedSubtypeEntries; ///< Cached entries to avoid redundant rebuilds