diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp index 64416e5ee..c4096ceeb 100644 --- a/cockatrice/src/client/settings/cache_settings.cpp +++ b/cockatrice/src/client/settings/cache_settings.cpp @@ -312,6 +312,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(); @@ -1372,6 +1373,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 b1197e267..72e1af32f 100644 --- a/cockatrice/src/client/settings/cache_settings.h +++ b/cockatrice/src/client/settings/cache_settings.h @@ -349,6 +349,7 @@ private: bool showStatusBar; bool showDragSelectionCount; bool showTotalSelectionCount; + bool showSubtypeSelectionCount; public: SettingsCache(); @@ -472,6 +473,10 @@ public: { return showTotalSelectionCount; } + [[nodiscard]] bool getShowSubtypeSelectionCount() const + { + return showSubtypeSelectionCount; + } [[nodiscard]] bool getNotificationsEnabled() const { return notificationsEnabled; @@ -1155,5 +1160,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 4ba41cffb..d0dd1774b 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. @@ -42,7 +45,7 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par connect(scene, &GameScene::sigStartRubberBand, this, &GameView::startRubberBand); connect(scene, &GameScene::sigResizeRubberBand, this, &GameView::resizeRubberBand); connect(scene, &GameScene::sigStopRubberBand, this, &GameView::stopRubberBand); - connect(scene, &QGraphicsScene::selectionChanged, this, [this]() { updateTotalSelectionCount(); }); + connect(scene, &QGraphicsScene::selectionChanged, this, [this]() { updateSelectionCount(); }); aCloseMostRecentZoneView = new QAction(this); @@ -53,21 +56,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) @@ -80,7 +92,7 @@ void GameView::resizeEvent(QResizeEvent *event) } updateSceneRect(scene()->sceneRect()); - updateTotalSelectionCount(event->size()); + updateSelectionCount(event->size()); } void GameView::updateSceneRect(const QRectF &rect) @@ -162,27 +174,182 @@ void GameView::refreshShortcuts() SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView")); } -void GameView::updateTotalSelectionCount(const QSize &viewSize) +/** @brief Extracts subtypes from a card face type string (e.g., "Creature — Human Wizard" -> ["Human", "Wizard"]) */ +static QStringList extractSubtypesFromFace(const QString &faceType) { - if (!SettingsCache::instance().getShowTotalSelectionCount()) { - totalCountLabel->hide(); - return; + 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::updateSelectionCount(const QSize &viewSize) +{ + 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 15abad9af..fb51ce230 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: @@ -30,7 +34,7 @@ private slots: void resizeRubberBand(const QPointF &cursorPoint, int selectedCount); void stopRubberBand(); void refreshShortcuts(); - void updateTotalSelectionCount(const QSize &viewSize = QSize()); + void updateSelectionCount(const QSize &viewSize = QSize()); public slots: void updateSceneRect(const QRectF &rect); 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 dfa736a1a..6816fc48d 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); }); @@ -82,7 +86,8 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() generalGrid->addWidget(&annotateTokensCheckBox, 6, 0); generalGrid->addWidget(&showDragSelectionCountCheckBox, 7, 0); generalGrid->addWidget(&showTotalSelectionCountCheckBox, 8, 0); - generalGrid->addWidget(&useTearOffMenusCheckBox, 9, 0); + generalGrid->addWidget(&showSubtypeSelectionCountCheckBox, 9, 0); + generalGrid->addWidget(&useTearOffMenusCheckBox, 10, 0); generalGroupBox = new QGroupBox; generalGroupBox->setLayout(generalGrid); @@ -206,6 +211,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")); notificationsGroupBox->setTitle(tr("Notifications settings")); notificationsEnabledCheckBox.setText(tr("Enable notifications in taskbar")); 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 6dd43ceae..fb8e9ff57 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 tapAnimationCheckBox; QCheckBox openDeckInNewTabCheckBox;