Compare commits

..

14 commits

Author SHA1 Message Date
Lukas Brübach
e20846a88d [Security] Refuse KDF downgrade; trim profile name and warn on silent credential drop 2026-09-04 13:49:22 +02:00
Lukas Brübach
4252f69d2b [Security] Harden credential format acceptance and challenge nonce validation 2026-09-04 13:49:22 +02:00
Lukas Brübach
7a3067d4a8 [Security] Add challenge-response auth with scrypt verifiers and stop storing plaintext passwords
Challenge-response authentication: the client derives a scrypt verifier
(RFC 7914, EVP_PBE_scrypt, N=32768, r=8, p=1) and authenticates with
HMAC-SHA256(key, nonce), so neither the password nor its hash is
transmitted. The stored format becomes
"$scrypt$<n>$<r>$<p>$<salt>$<verifier>" and Response_PasswordSalt
now carries the cost parameters. Strict servers only accept scrypt
verifiers; legacy accounts are migrated after a successful login.

Fix #344 for challenge-response servers: a saved profile stores the
derived verifier under the password key instead of the plaintext password.
The connect dialog loads it without revealing it, autoconnect passes it
through, the change-password dialog no longer prefills the old password
field with it, and the client only persists the verifier when
"Save password" is checked.

Took 3 minutes

Took 1 minute

Took 10 seconds

Took 7 minutes
2026-09-04 13:49:22 +02:00
BruebachL
d6fbfb32a1
[Security] Use a CSPRNG for salts, tokens, and RNG seeding (#7192)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Security] Use a CSPRNG for salts, tokens, and RNG seeding

Password salts and activation tokens were generated with the global SFMT
RNG, which was seeded from a 32-bit timestamp, making registration
salts and activation tokens predictable. The game RNG used the same
timestamp seed across restarts.

Add CryptoUtil backed by OpenSSL RAND_bytes and use it for salt/token
generation and to seed RNG_SFMT with a 64-bit CSPRNG value in both the
client and server. Link libcockatrice_utility against OpenSSL::Crypto.

Took 30 seconds

Took 25 minutes

* Lint.

Took 4 minutes

Took 36 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 13:49:18 +02:00
BruebachL
3ec62df3e7
[Protocol] Remove duplicate event_game_state_changed.proto entry (#7234)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
The .proto file appeared twice in the PROTO_FILES list, causing protoc to
process it twice on every build. Keep a single entry.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 05:20:53 +02:00
BruebachL
fcfb14cf56
[Build] Use pipes for GCC/Clang compilation (#7233)
Pass -pipe so GCC/Clang transfer intermediate representation between
compiler stages over pipes instead of temporary files, reducing build I/O.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 05:20:53 +02:00
BruebachL
35ebae8d7f
[Build] Bump cmake_minimum_required from 3.10 to 3.16 (#7232)
3.16 is already required by Qt6 (and enforced at find_package time).
This unlocks native target_precompile_headers(), better AUTOMOC/AUTORCC
handling, and qt6_finalize_project() without a version guard.

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-04 05:20:52 +02:00
BruebachL
4e9d148163
[TabSupervisor] Initialize all tabs (#7231)
Some checks failed
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build Desktop / Configure (push) Has been cancelled
Build Docker / Servatrice (arm) (push) Has been cancelled
Build Docker / Servatrice (x86) (push) Has been cancelled
Build Desktop / Debian 13 (push) Has been cancelled
Build Desktop / Debian 12 (push) Has been cancelled
Build Desktop / Fedora 44 (push) Has been cancelled
Build Desktop / Fedora 43 (push) Has been cancelled
Build Desktop / Servatrice_Debian 12 (push) Has been cancelled
Build Desktop / Ubuntu 26.04 (push) Has been cancelled
Build Desktop / Ubuntu 24.04 (push) Has been cancelled
Build Desktop / Arch (push) Has been cancelled
Build Desktop / macOS 13 Intel (push) Has been cancelled
Build Desktop / macOS 14 (push) Has been cancelled
Build Desktop / macOS 15 (push) Has been cancelled
Build Desktop / macOS 26 Debug (push) Has been cancelled
Build Desktop / Windows 10 (push) Has been cancelled
Build Docker / Publish multi-platform Servatrice image (push) Has been cancelled
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-01 16:11:02 +02:00
RickyRister
425b16ea0d
[Game] Allow dropping cards at bottom of stack zone (#7230)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-09-01 12:11:31 +02:00
BruebachL
45c7ff6f87
[Mods] Properly close card art rules tab on disconnect (#7227)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-01 11:31:49 +02:00
BruebachL
d974501277
[GameScene] Sever connections properly. (#7191)
* [GameScene] Sever connections properly.

Took 2 minutes


Took 54 minutes

* [GameScene] Sever animated item destroy connections at teardown

Fix crash when a replay's board is closed (GameScene teardown abort).

The old QObject::disconnect(nullptr, nullptr, this, nullptr) is invalid per
Qt docs (the sender must never be nullptr), so it never severed the PMF
destroyed -> GameScene::removeAnimatedItem connections that fire when
QGraphicsScene::~QGraphicsScene -> clear() destroys the remaining items.

Store the QMetaObject::Connection handle for each animated item and
disconnect them all in ~GameScene via the connection-handle overload.
Dedup connections on the connection map rather than animatedItems, since
the animation timer clears animatedItems on completion, which let a
re-registered item (e.g. a life counter flashed repeatedly) accumulate
orphaned duplicate destroyed connections that survived teardown.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-01 11:31:11 +02:00
RickyRister
9bf2202739
[Game] Fix dragged card always placed on bottom of stack (#7228)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-08-31 07:31:02 -07:00
RickyRister
03de1af678
[VDS] Fix search filter not being applied on refresh (#7229)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
2026-08-31 10:28:50 +02:00
RickyRister
3dc9dba67a
[SettingsPage] Refactor: Clean up order of variables (#7184)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [SettingsPage] Refactor: Clean up order of variables

* fixes
2026-08-30 14:43:23 -07:00
20 changed files with 262 additions and 191 deletions

View file

@ -5,8 +5,8 @@
# This file sets all the variables shared between the projects
# like the installation path, compilation flags etc..
# cmake 3.16 is required if using qt6
cmake_minimum_required(VERSION 3.10)
# 3.16 required for Qt6 and target_precompile_headers()
cmake_minimum_required(VERSION 3.16)
# Use compiler cache (ccache)
option(USE_CCACHE "Cache the build results with ccache" OFF)
@ -184,6 +184,9 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${FLAG}")
endif()
endforeach()
# Reduce compiler I/O by using pipes between stages instead of temp files
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe")
else()
# other: osx/llvm, bsd/llvm
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
@ -192,6 +195,9 @@ else()
else()
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra")
endif()
# Reduce compiler I/O by using pipes between stages instead of temp files
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe")
endif()
# GNU systems need to define the Mersenne exponent for the RNG to compile w/o warning

View file

@ -44,11 +44,16 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
GameScene::~GameScene()
{
// Sever all incoming connections (animated item destroy-tracking) before the
// members below are destroyed: the base QGraphicsScene destructor destroys the
// remaining items, and their destroyed() signals must not reach slots that
// reference members that no longer exist.
QObject::disconnect(nullptr, nullptr, this, nullptr);
// Sever all destroyed->removeAnimatedItem connections before the members below
// are destroyed: the base QGraphicsScene destructor destroys the remaining items,
// and their destroyed() signals must not reach slots that reference members that
// no longer exist. The connection handle overload is used because the string-based
// disconnect(nullptr, nullptr, this, nullptr) is invalid (the sender must never be
// nullptr) and would otherwise fail to sever these pointer-to-member connections.
for (auto it = animationItemConnections.constBegin(); it != animationItemConnections.constEnd(); ++it) {
QObject::disconnect(*it);
}
animationItemConnections.clear();
delete animationTimer;
animationTimer = nullptr;
@ -777,8 +782,15 @@ void GameScene::registerAnimationItem(IAnimatedItem *item)
if (!object) {
return;
}
if (!animatedItems.contains(object)) {
connect(object, &QObject::destroyed, this, &GameScene::removeAnimatedItem);
// Guard against duplicate connections using the connection map, not
// animatedItems: the animation timer removes entries from animatedItems when an
// animation completes, but the destroyed->removeAnimatedItem connection must
// persist until the object is destroyed. Relying on animatedItems here would let
// a re-registered item (e.g. a life counter that flashes repeatedly) accumulate
// duplicate destroyed connections, the older ones of which would survive teardown.
if (!animationItemConnections.contains(object)) {
animationItemConnections.insert(object,
connect(object, &QObject::destroyed, this, &GameScene::removeAnimatedItem));
}
animatedItems.insert(object, item);
if (animationTimer && !animationTimer->isActive()) {
@ -797,6 +809,7 @@ void GameScene::unregisterAnimationItem(IAnimatedItem *item)
void GameScene::removeAnimatedItem(QObject *item)
{
animatedItems.remove(item);
animationItemConnections.remove(item);
if (animationTimer && animatedItems.isEmpty()) {
animationTimer->stop();
}

View file

@ -54,9 +54,11 @@ private:
QPointer<CardItem> hoveredCard; ///< Currently hovered card
QBasicTimer *animationTimer; ///< Timer for scene animations
QHash<QObject *, IAnimatedItem *> animatedItems; ///< Items currently animating
int playerRotation; ///< Rotation offset for player layout
bool rearranging = false; ///< Guard against re-entrant rearrange
bool needsReArrange = false; ///< Pending rearrange requested during a pass
QHash<QObject *, QMetaObject::Connection>
animationItemConnections; ///< destroyed->removeAnimatedItem handles per animated item
int playerRotation; ///< Rotation offset for player layout
bool rearranging = false; ///< Guard against re-entrant rearrange
bool needsReArrange = false; ///< Pending rearrange requested during a pass
/**
* @brief Updates which card is currently hovered based on scene coordinates.

View file

@ -41,7 +41,8 @@ void HandZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
}
}
} else {
x = calcDropIndexFromY(dropPoint.y());
bool sameZone = startZone == getLogic();
x = calcDropIndexFromY(dropPoint.y(), !sameZone);
}
Command_MoveCard cmd;

View file

@ -83,7 +83,7 @@ SelectZone::StackLayoutParams SelectZone::buildStackParams(qreal minOffset) cons
return {cardCount, boundingRect().height(), cardHeight, offset, minOffset};
}
int SelectZone::calcDropIndexFromY(qreal dropY, qreal minOffset) const
int SelectZone::calcDropIndexFromY(qreal dropY, bool allowCountExpand, qreal minOffset) const
{
const auto &cards = getLogic()->getCards();
if (cards.isEmpty()) {
@ -94,7 +94,8 @@ int SelectZone::calcDropIndexFromY(qreal dropY, qreal minOffset) const
if (effectiveOffset <= 0.0) {
return 0;
}
return qBound(0, qRound((dropY - start) / effectiveOffset), params.cardCount - 1);
int max = allowCountExpand ? params.cardCount : params.cardCount - 1;
return qBound(0, qRound((dropY - start) / effectiveOffset), max);
}
void SelectZone::restoreStaleEscapedCards()

View file

@ -104,8 +104,12 @@ protected:
/**
* @brief Computes the card index at a given y-coordinate within the zone's vertical layout.
* Returns 0 if the zone has no cards or the offset is zero.
*
* @param dropY The y-coordinate that the card was dropped at
* @param allowCountExpand If false, clamps the index at the number of cards minus 1
* @param minOffset Minimum offset to preserve
*/
int calcDropIndexFromY(qreal dropY, qreal minOffset = 0.0) const;
int calcDropIndexFromY(qreal dropY, bool allowCountExpand, qreal minOffset = 0.0) const;
/**
* @brief Positions cards vertically with alternating left/right x-offsets.

View file

@ -57,18 +57,14 @@ void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
return;
}
const auto &cards = getLogic()->getCards();
int index;
if (startZone == getLogic()) {
// Reordering within the zone: use drop position
index = calcDropIndexFromY(dropPoint.y(), MIN_CARD_VISIBLE);
bool sameZone = startZone == getLogic();
int index = calcDropIndexFromY(dropPoint.y(), !sameZone, MIN_CARD_VISIBLE);
if (sameZone) {
// Same-zone no-op: don't move a card onto itself
const auto &cards = getLogic()->getCards();
if (!cards.isEmpty() && cards.at(index)->getId() == dragItems.at(0)->getId()) {
return;
}
} else {
// Coming from another zone: append at end (top of stack, rendered on top)
index = static_cast<int>(cards.size());
}
Command_MoveCard cmd;

View file

@ -154,6 +154,48 @@ AppearanceSettingsPage::AppearanceSettingsPage()
homeTabGroupBox = new QGroupBox;
homeTabGroupBox->setLayout(homeTabGrid);
// Playmat settings
playmatVisibilityCombo.addItem(tr("Show all playmats"), PlaymatVisibilityAll);
playmatVisibilityCombo.addItem(tr("Show own playmat only"), PlaymatVisibilityOwnOnly);
playmatVisibilityCombo.addItem(tr("Don't use playmats"), PlaymatVisibilityNone);
int visIdx = playmatVisibilityCombo.findData(settings.userInterface().getPlaymatVisibility());
if (visIdx >= 0) {
playmatVisibilityCombo.setCurrentIndex(visIdx);
}
connect(&playmatVisibilityCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
SettingsCache::instance().userInterface().setPlaymatVisibility(playmatVisibilityCombo.itemData(index).toInt());
});
playmatVisibilityLabel.setBuddy(&playmatVisibilityCombo);
// Playmat mode: Override / Fallback / Deck-only
playmatModeCombo.addItem(tr("Override deck playmat"), PlaymatModeOverrideDeck);
playmatModeCombo.addItem(tr("Fallback if deck has none"), PlaymatModeFallback);
playmatModeCombo.addItem(tr("Deck only, ignore collection"), PlaymatModeDeckOnly);
int modeIdx = playmatModeCombo.findData(settings.userInterface().getPlaymatMode());
if (modeIdx >= 0) {
playmatModeCombo.setCurrentIndex(modeIdx);
}
connect(&playmatModeCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
SettingsCache::instance().userInterface().setPlaymatMode(playmatModeCombo.itemData(index).toInt());
});
playmatModeLabel.setBuddy(&playmatModeCombo);
// User-level playmat settings: fallback collection.
connect(&playmatDefaultEditButton, &QPushButton::clicked, this,
&AppearanceSettingsPage::openPlaymatCollectionDialog);
auto *playmatGrid = new QGridLayout;
playmatGrid->addWidget(&playmatVisibilityLabel, 0, 0, 1, 1);
playmatGrid->addWidget(&playmatVisibilityCombo, 0, 1, 1, 1);
playmatGrid->addWidget(&playmatModeLabel, 1, 0, 1, 1);
playmatGrid->addWidget(&playmatModeCombo, 1, 1, 1, 1);
playmatGrid->addWidget(&playmatDefaultLabel, 2, 0, 1, 1);
playmatGrid->addWidget(&playmatDefaultEditButton, 2, 1, 1, 1);
playmatGroupBox = new QGroupBox;
playmatGroupBox->setLayout(playmatGrid);
// Styling settings
styleUserListCheckBox.setChecked(settings.appearance().getStyleUserList());
connect(&styleUserListCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings.appearance(),
&AppearanceSettings::setStyleUserList);
@ -259,7 +301,6 @@ AppearanceSettingsPage::AppearanceSettingsPage()
cardLayoutGroupBox->setLayout(cardLayoutGrid);
// Card counter colors
auto *cardCounterColorsLayout = new QGridLayout;
cardCounterColorsLayout->setColumnStretch(1, 1);
cardCounterColorsLayout->setColumnStretch(3, 1);
@ -339,47 +380,6 @@ AppearanceSettingsPage::AppearanceSettingsPage()
tableGroupBox = new QGroupBox;
tableGroupBox->setLayout(tableGrid);
// Playmat settings
playmatVisibilityCombo.addItem(tr("Show all playmats"), PlaymatVisibilityAll);
playmatVisibilityCombo.addItem(tr("Show own playmat only"), PlaymatVisibilityOwnOnly);
playmatVisibilityCombo.addItem(tr("Don't use playmats"), PlaymatVisibilityNone);
int visIdx = playmatVisibilityCombo.findData(settings.userInterface().getPlaymatVisibility());
if (visIdx >= 0) {
playmatVisibilityCombo.setCurrentIndex(visIdx);
}
connect(&playmatVisibilityCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
SettingsCache::instance().userInterface().setPlaymatVisibility(playmatVisibilityCombo.itemData(index).toInt());
});
playmatVisibilityLabel.setBuddy(&playmatVisibilityCombo);
// Playmat mode: Override / Fallback / Deck-only
playmatModeCombo.addItem(tr("Override deck playmat"), PlaymatModeOverrideDeck);
playmatModeCombo.addItem(tr("Fallback if deck has none"), PlaymatModeFallback);
playmatModeCombo.addItem(tr("Deck only, ignore collection"), PlaymatModeDeckOnly);
int modeIdx = playmatModeCombo.findData(settings.userInterface().getPlaymatMode());
if (modeIdx >= 0) {
playmatModeCombo.setCurrentIndex(modeIdx);
}
connect(&playmatModeCombo, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
SettingsCache::instance().userInterface().setPlaymatMode(playmatModeCombo.itemData(index).toInt());
});
playmatModeLabel.setBuddy(&playmatModeCombo);
// User-level playmat settings: fallback collection.
connect(&playmatDefaultEditButton, &QPushButton::clicked, this,
&AppearanceSettingsPage::openPlaymatCollectionDialog);
auto *playmatGrid = new QGridLayout;
playmatGrid->addWidget(&playmatVisibilityLabel, 0, 0, 1, 1);
playmatGrid->addWidget(&playmatVisibilityCombo, 0, 1, 1, 1);
playmatGrid->addWidget(&playmatModeLabel, 1, 0, 1, 1);
playmatGrid->addWidget(&playmatModeCombo, 1, 1, 1, 1);
playmatGrid->addWidget(&playmatDefaultLabel, 2, 0, 1, 1);
playmatGrid->addWidget(&playmatDefaultEditButton, 2, 1, 1, 1);
playmatGroupBox = new QGroupBox;
playmatGroupBox->setLayout(playmatGrid);
// putting it all together
auto *mainLayout = new QVBoxLayout;
mainLayout->addWidget(themeGroupBox);
@ -512,6 +512,12 @@ void AppearanceSettingsPage::retranslateUi()
homeTabButtonColorSourceBox.setToolTip(
tr("Automatic: extract from background if present, otherwise use theme default"));
playmatGroupBox->setTitle(tr("Playmat settings"));
playmatVisibilityLabel.setText(tr("Playmat visibility:"));
playmatModeLabel.setText(tr("Default collection behavior:"));
playmatDefaultLabel.setText(tr("Default playmat collection:"));
playmatDefaultEditButton.setText(tr("Edit..."));
stylingGroupBox->setTitle(tr("Styling settings"));
styleUserListCheckBox.setText(tr("Style user list"));
@ -554,9 +560,4 @@ void AppearanceSettingsPage::retranslateUi()
tableGroupBox->setTitle(tr("Table grid layout"));
invertVerticalCoordinateCheckBox.setText(tr("Invert vertical coordinate"));
minPlayersForMultiColumnLayoutLabel.setText(tr("Minimum player count for multi-column layout:"));
playmatGroupBox->setTitle(tr("Playmat settings"));
playmatVisibilityLabel.setText(tr("Playmat visibility:"));
playmatModeLabel.setText(tr("Default collection behavior:"));
playmatDefaultLabel.setText(tr("Default playmat collection:"));
playmatDefaultEditButton.setText(tr("Edit..."));
}

View file

@ -44,46 +44,55 @@ private:
QLabel homeTabButtonColorSourceLabel;
QComboBox homeTabButtonColorSourceBox;
QCheckBox styleUserListCheckBox;
QCheckBox showShortcutsCheckBox;
QCheckBox showGameSelectorFilterToolbarCheckBox;
QLabel minPlayersForMultiColumnLayoutLabel;
QLabel maxFontSizeForCardsLabel;
QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox;
QCheckBox bumpSetsWithCardsInDeckToTopCheckBox;
QCheckBox displayCardNamesCheckBox;
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
QCheckBox cardScalingCheckBox;
QCheckBox roundCardCornersCheckBox;
QLabel verticalCardOverlapPercentLabel;
QSpinBox verticalCardOverlapPercentBox;
QLabel cardViewInitialRowsMaxLabel;
QSpinBox cardViewInitialRowsMaxBox;
QLabel cardViewExpandedRowsMaxLabel;
QSpinBox cardViewExpandedRowsMaxBox;
QCheckBox horizontalHandCheckBox;
QCheckBox leftJustifiedHandCheckBox;
QCheckBox invertVerticalCoordinateCheckBox;
QLabel playmatVisibilityLabel;
QComboBox playmatVisibilityCombo;
QLabel playmatModeLabel;
QComboBox playmatModeCombo;
QLabel playmatDefaultLabel;
QPushButton playmatDefaultEditButton;
QCheckBox styleUserListCheckBox;
QCheckBox showShortcutsCheckBox;
QCheckBox showGameSelectorFilterToolbarCheckBox;
QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox;
QCheckBox bumpSetsWithCardsInDeckToTopCheckBox;
QCheckBox displayCardNamesCheckBox;
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
QCheckBox cardScalingCheckBox;
QCheckBox roundCardCornersCheckBox;
QLabel maxFontSizeForCardsLabel;
QSpinBox maxFontSizeForCardsEdit;
QLabel verticalCardOverlapPercentLabel;
QSpinBox verticalCardOverlapPercentBox;
QLabel cardViewInitialRowsMaxLabel;
QSpinBox cardViewInitialRowsMaxBox;
QLabel cardViewExpandedRowsMaxLabel;
QSpinBox cardViewExpandedRowsMaxBox;
QList<QLabel *> cardCounterNames;
QCheckBox horizontalHandCheckBox;
QCheckBox leftJustifiedHandCheckBox;
QCheckBox invertVerticalCoordinateCheckBox;
QLabel minPlayersForMultiColumnLayoutLabel;
QSpinBox minPlayersForMultiColumnLayoutEdit;
QGroupBox *themeGroupBox;
QGroupBox *homeTabGroupBox;
QGroupBox *playmatGroupBox;
QGroupBox *stylingGroupBox;
QGroupBox *menuGroupBox;
QGroupBox *printingsGroupBox;
QGroupBox *cardsGroupBox;
QGroupBox *cardLayoutGroupBox;
QGroupBox *handGroupBox;
QGroupBox *playmatGroupBox;
QGroupBox *tableGroupBox;
QGroupBox *cardCountersGroupBox;
QList<QLabel *> cardCounterNames;
QSpinBox minPlayersForMultiColumnLayoutEdit;
QSpinBox maxFontSizeForCardsEdit;
QGroupBox *handGroupBox;
QGroupBox *tableGroupBox;
public:
AppearanceSettingsPage();

View file

@ -425,29 +425,28 @@ void GeneralSettingsPage::updateStartupServerControlsVisibility()
void GeneralSettingsPage::retranslateUi()
{
const auto &settings = SettingsCache::instance();
languageGroupBox->setTitle(tr("Language settings"));
languageLabel.setText(tr("Language:"));
versionGroupBox->setTitle(tr("Version settings"));
cardDatabaseGroupBox->setTitle(tr("Card database"));
startupGroupBox->setTitle(tr("Startup settings"));
if (SettingsCache::instance().getIsPortableBuild()) {
pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
} else {
pathsGroupBox->setTitle(tr("Paths"));
}
advertiseTranslationPageLabel.setText(
QString("<a href='%1'>%2</a>").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations")));
deckPathLabel.setText(tr("Decks directory:"));
filtersPathLabel.setText(tr("Filters directory:"));
replaysPathLabel.setText(tr("Replays directory:"));
picsPathLabel.setText(tr("Pictures directory:"));
cardDatabasePathLabel.setText(tr("Card database:"));
customCardDatabasePathLabel.setText(tr("Custom database directory:"));
tokenDatabasePathLabel.setText(tr("Token database:"));
versionGroupBox->setTitle(tr("Version settings"));
updateReleaseChannelLabel.setText(tr("Update channel"));
startupUpdateCheckCheckBox.setText(tr("Check for client updates on startup"));
updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice"));
// We can't change the strings after they're put into the QComboBox, so this is our workaround
int oldIndex = updateReleaseChannelBox.currentIndex();
updateReleaseChannelBox.clear();
for (ReleaseChannel *chan : settings.getUpdateReleaseChannels()) {
updateReleaseChannelBox.addItem(tr(chan->getName().toUtf8()));
}
updateReleaseChannelBox.setCurrentIndex(oldIndex);
cardDatabaseGroupBox->setTitle(tr("Card database"));
startupCardUpdateCheckBehaviorLabel.setText(tr("Check for card database updates on startup"));
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexNone, tr("Don't check"));
startupCardUpdateCheckBehaviorSelector.setItemText(startupCardUpdateCheckBehaviorIndexPrompt,
@ -456,8 +455,13 @@ void GeneralSettingsPage::retranslateUi()
tr("Always update in the background"));
cardUpdateCheckIntervalLabel.setText(tr("Check for card database updates every"));
cardUpdateCheckIntervalSpinBox.setSuffix(tr(" days"));
updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice"));
QDate lastCheckDate = settings.updates().getLastCardUpdateCheck();
int daysAgo = lastCheckDate.daysTo(QDate::currentDate());
lastCardUpdateCheckDateLabel.setText(
tr("Last update check on %1 (%2 days ago)").arg(lastCheckDate.toString()).arg(daysAgo));
startupGroupBox->setTitle(tr("Startup settings"));
showTipsOnStartup.setText(tr("Show tips on startup"));
startupTabLabel.setText(tr("Startup tab:"));
startupTabSelector.setItemText(StartupTab::StartupTabHome, tr("Home"));
@ -473,21 +477,18 @@ void GeneralSettingsPage::retranslateUi()
startupServerLabel.setText(tr("Server:"));
startupRoomLabel.setText(tr("Room:"));
startupRoomNameEdit->setPlaceholderText(tr("Room name"));
resetAllPathsButton->setText(tr("Reset all paths"));
const auto &settings = SettingsCache::instance();
QDate lastCheckDate = settings.updates().getLastCardUpdateCheck();
int daysAgo = lastCheckDate.daysTo(QDate::currentDate());
lastCardUpdateCheckDateLabel.setText(
tr("Last update check on %1 (%2 days ago)").arg(lastCheckDate.toString()).arg(daysAgo));
// We can't change the strings after they're put into the QComboBox, so this is our workaround
int oldIndex = updateReleaseChannelBox.currentIndex();
updateReleaseChannelBox.clear();
for (ReleaseChannel *chan : settings.getUpdateReleaseChannels()) {
updateReleaseChannelBox.addItem(tr(chan->getName().toUtf8()));
if (settings.getIsPortableBuild()) {
pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
} else {
pathsGroupBox->setTitle(tr("Paths"));
}
updateReleaseChannelBox.setCurrentIndex(oldIndex);
}
deckPathLabel.setText(tr("Decks directory:"));
filtersPathLabel.setText(tr("Filters directory:"));
replaysPathLabel.setText(tr("Replays directory:"));
picsPathLabel.setText(tr("Pictures directory:"));
cardDatabasePathLabel.setText(tr("Card database:"));
customCardDatabasePathLabel.setText(tr("Custom database directory:"));
tokenDatabasePathLabel.setText(tr("Token database:"));
resetAllPathsButton->setText(tr("Reset all paths"));
}

View file

@ -42,6 +42,37 @@ private:
QGroupBox *startupGroupBox;
QGroupBox *pathsGroupBox;
QLabel languageLabel;
QComboBox languageBox;
QLabel advertiseTranslationPageLabel;
QLabel updateReleaseChannelLabel;
QComboBox updateReleaseChannelBox;
QCheckBox startupUpdateCheckCheckBox;
QCheckBox updateNotificationCheckBox;
QCheckBox newVersionOracleCheckBox;
QLabel startupCardUpdateCheckBehaviorLabel;
QComboBox startupCardUpdateCheckBehaviorSelector;
QLabel cardUpdateCheckIntervalLabel;
QSpinBox cardUpdateCheckIntervalSpinBox;
QLabel lastCardUpdateCheckDateLabel;
QCheckBox showTipsOnStartup;
QLabel startupTabLabel;
QComboBox startupTabSelector;
QLabel startupServerLabel;
QComboBox startupServerSelector;
QLabel startupRoomLabel;
QLineEdit *startupRoomNameEdit;
QLabel deckPathLabel;
QLabel filtersPathLabel;
QLabel replaysPathLabel;
QLabel picsPathLabel;
QLabel cardDatabasePathLabel;
QLabel customCardDatabasePathLabel;
QLabel tokenDatabasePathLabel;
QLineEdit *deckPathEdit;
QLineEdit *filtersPathEdit;
QLineEdit *replaysPathEdit;
@ -51,33 +82,6 @@ private:
QLineEdit *tokenDatabasePathEdit;
QPushButton *resetAllPathsButton;
QLabel *allPathsResetLabel;
QComboBox languageBox;
QCheckBox startupUpdateCheckCheckBox;
QLabel startupCardUpdateCheckBehaviorLabel;
QComboBox startupCardUpdateCheckBehaviorSelector;
QLabel cardUpdateCheckIntervalLabel;
QSpinBox cardUpdateCheckIntervalSpinBox;
QLabel lastCardUpdateCheckDateLabel;
QCheckBox updateNotificationCheckBox;
QCheckBox newVersionOracleCheckBox;
QComboBox updateReleaseChannelBox;
QLabel languageLabel;
QLabel deckPathLabel;
QLabel filtersPathLabel;
QLabel replaysPathLabel;
QLabel picsPathLabel;
QLabel cardDatabasePathLabel;
QLabel customCardDatabasePathLabel;
QLabel tokenDatabasePathLabel;
QLabel updateReleaseChannelLabel;
QLabel advertiseTranslationPageLabel;
QCheckBox showTipsOnStartup;
QLabel startupTabLabel;
QComboBox startupTabSelector;
QLabel startupServerLabel;
QComboBox startupServerSelector;
QLabel startupRoomLabel;
QLineEdit *startupRoomNameEdit;
};
#endif // COCKATRICE_GENERAL_SETTINGS_PAGE_H

View file

@ -20,26 +20,7 @@ enum visualDeckStoragePromptForConversionIndex
UserInterfaceSettingsPage::UserInterfaceSettingsPage()
{
// general settings and notification settings
notificationsEnabledCheckBox.setChecked(SettingsCache::instance().userInterface().getNotificationsEnabled());
connect(&notificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
&InterfaceSettings::setNotificationsEnabled);
connect(&notificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
&UserInterfaceSettingsPage::setNotificationEnabled);
specNotificationsEnabledCheckBox.setChecked(
SettingsCache::instance().userInterface().getSpectatorNotificationsEnabled());
specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().userInterface().getNotificationsEnabled());
connect(&specNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
&InterfaceSettings::setSpectatorNotificationsEnabled);
buddyConnectNotificationsEnabledCheckBox.setChecked(
SettingsCache::instance().userInterface().getBuddyConnectNotificationsEnabled());
buddyConnectNotificationsEnabledCheckBox.setEnabled(
SettingsCache::instance().userInterface().getNotificationsEnabled());
connect(&buddyConnectNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED,
&SettingsCache::instance().userInterface(), &InterfaceSettings::setBuddyConnectNotificationsEnabled);
// general settings
doubleClickToPlayCheckBox.setChecked(SettingsCache::instance().userInterface().getDoubleClickToPlay());
connect(&doubleClickToPlayCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
&InterfaceSettings::setDoubleClickToPlay);
@ -103,6 +84,26 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
generalGroupBox = new QGroupBox;
generalGroupBox->setLayout(generalGrid);
// notification settings
notificationsEnabledCheckBox.setChecked(SettingsCache::instance().userInterface().getNotificationsEnabled());
connect(&notificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
&InterfaceSettings::setNotificationsEnabled);
connect(&notificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
&UserInterfaceSettingsPage::setNotificationEnabled);
specNotificationsEnabledCheckBox.setChecked(
SettingsCache::instance().userInterface().getSpectatorNotificationsEnabled());
specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().userInterface().getNotificationsEnabled());
connect(&specNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(),
&InterfaceSettings::setSpectatorNotificationsEnabled);
buddyConnectNotificationsEnabledCheckBox.setChecked(
SettingsCache::instance().userInterface().getBuddyConnectNotificationsEnabled());
buddyConnectNotificationsEnabledCheckBox.setEnabled(
SettingsCache::instance().userInterface().getNotificationsEnabled());
connect(&buddyConnectNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED,
&SettingsCache::instance().userInterface(), &InterfaceSettings::setBuddyConnectNotificationsEnabled);
auto *notificationsGrid = new QGridLayout;
notificationsGrid->addWidget(&notificationsEnabledCheckBox, 0, 0);
notificationsGrid->addWidget(&specNotificationsEnabledCheckBox, 1, 0);
@ -355,6 +356,7 @@ void UserInterfaceSettingsPage::retranslateUi()
notificationsEnabledCheckBox.setText(tr("Enable notifications in taskbar"));
specNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar for game events while you are spectating"));
buddyConnectNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar when users in your buddy list connect"));
animationGroupBox->setTitle(tr("Animation settings"));
enableAllAnimationsButton.setText(tr("&Enable all animations"));
disableAllAnimationsButton.setText(tr("&Disable all animations"));
@ -362,6 +364,7 @@ void UserInterfaceSettingsPage::retranslateUi()
arrowDrawAnimationCheckBox.setText(tr("&Arrow draw animation"));
lifeCounterAnimationsCheckBox.setText(tr("Life counter flash"));
battlefieldFlashCheckBox.setText(tr("Battlefield flash on damage"));
deckEditorGroupBox->setTitle(tr("Deck editor/storage settings"));
openDeckInNewTabCheckBox.setText(tr("Open deck in new tab by default"));
visualDeckStorageInGameCheckBox.setText(tr("Use visual deck storage in game lobby"));
@ -397,8 +400,8 @@ void UserInterfaceSettingsPage::retranslateUi()
0, CommanderBracketNames::CommanderSpellbookBracketNames);
commanderSpellbookIntegrationBracketNamingSelector.setItemText(
1, CommanderBracketNames::OfficialCommanderBracketNames);
commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setToolTip(CommanderBracketNames::Explainer);
replayGroupBox->setTitle(tr("Replay settings"));
rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:"));
rewindBufferingMsBox.setSuffix(" ms");

View file

@ -23,9 +23,6 @@ private slots:
void updateCommanderSpellbookUiState();
private:
QCheckBox notificationsEnabledCheckBox;
QCheckBox specNotificationsEnabledCheckBox;
QCheckBox buddyConnectNotificationsEnabledCheckBox;
QCheckBox doubleClickToPlayCheckBox;
QCheckBox clickPlaysAllSelectedCheckBox;
QCheckBox playToStackCheckBox;
@ -37,12 +34,18 @@ private:
QCheckBox showTotalSelectionCountCheckBox;
QCheckBox useTearOffMenusCheckBox;
QCheckBox keepGameChatFocusCheckBox;
QCheckBox notificationsEnabledCheckBox;
QCheckBox specNotificationsEnabledCheckBox;
QCheckBox buddyConnectNotificationsEnabledCheckBox;
QPushButton enableAllAnimationsButton;
QPushButton disableAllAnimationsButton;
QCheckBox tapAnimationCheckBox;
QCheckBox arrowDrawAnimationCheckBox;
QCheckBox lifeCounterAnimationsCheckBox;
QCheckBox battlefieldFlashCheckBox;
QCheckBox openDeckInNewTabCheckBox;
QLabel visualDeckStoragePromptForConversionLabel;
QComboBox visualDeckStoragePromptForConversionSelector;
@ -57,8 +60,10 @@ private:
QLabel commanderSpellbookIntegrationUseOfficialBracketNamesLabel;
QToolButton commanderSpellbookIntegrationUseOfficialBracketNamesExplainer;
QComboBox commanderSpellbookIntegrationBracketNamingSelector;
QLabel rewindBufferingMsLabel;
QSpinBox rewindBufferingMsBox;
QGroupBox *generalGroupBox;
QGroupBox *notificationsGroupBox;
QGroupBox *animationGroupBox;

View file

@ -116,9 +116,10 @@ void CloseButton::paintEvent(QPaintEvent * /*event*/)
}
TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *parent)
: QTabWidget(parent), userInfo(nullptr), client(_client), tabsMenu(tabsMenu), tabVisualDeckStorage(nullptr),
tabServer(nullptr), tabAccount(nullptr), tabDeckStorage(nullptr), tabReplays(nullptr), tabAdmin(nullptr),
tabLog(nullptr), tabReport(nullptr), tabModeration(nullptr), isLocalGame(false)
: QTabWidget(parent), userInfo(nullptr), client(_client), tabsMenu(tabsMenu), tabHome(nullptr),
tabVisualDeckStorage(nullptr), tabServer(nullptr), tabAccount(nullptr), tabDeckStorage(nullptr),
tabReplays(nullptr), tabAdmin(nullptr), tabCardArtRules(nullptr), tabLog(nullptr), tabReport(nullptr),
tabModeration(nullptr), isLocalGame(false)
{
setElideMode(Qt::ElideRight);
setMovable(true);
@ -245,6 +246,7 @@ void TabSupervisor::retranslateUi()
aTabLog->setText(tr("Logs"));
aTabReport->setText(tr("Report Queue"));
aTabModeration->setText(tr("Moderation"));
aTabCardArtRules->setText(tr("Card Art Rules"));
// tabs
QList<Tab *> tabs;
@ -256,6 +258,7 @@ void TabSupervisor::retranslateUi()
tabs.append(tabLog);
tabs.append(tabReport);
tabs.append(tabModeration);
tabs.append(tabCardArtRules);
QMapIterator<int, TabRoom *> roomIterator(roomTabs);
while (roomIterator.hasNext()) {
tabs.append(roomIterator.next().value());
@ -520,7 +523,9 @@ void TabSupervisor::start(const ServerInfo_User &_userInfo)
if (SettingsCache::instance().tabs().getTabModerationOpen()) {
openTabModeration();
}
openTabCardArtRules();
if (SettingsCache::instance().tabs().getTabCardArtRulesOpen()) {
openTabCardArtRules();
}
}
retranslateUi();
@ -582,6 +587,9 @@ void TabSupervisor::stop()
if (tabModeration) {
tabModeration->close();
}
if (tabCardArtRules) {
tabCardArtRules->close();
}
}
QList<Tab *> tabsToDelete;
@ -775,6 +783,7 @@ void TabSupervisor::openTabAdmin()
void TabSupervisor::actTabCardArtRules(bool checked)
{
SettingsCache::instance().tabs().setTabCardArtRulesOpen(checked);
if (checked && !tabCardArtRules) {
openTabCardArtRules();
setCurrentWidget(tabCardArtRules);

View file

@ -125,9 +125,7 @@ void VisualDeckStorageFolderDisplayWidget::continueDeckPass()
}
const bool matches = index.data(VisualDeckStorageRoles::FilterMatchRole).toBool();
if (matches == deckPreviewWidget->isHidden()) {
deckPreviewWidget->setVisible(matches);
}
deckPreviewWidget->setVisible(matches);
if (matches) {
++visibleDeckCount;
}

View file

@ -21,6 +21,7 @@ public:
[[nodiscard]] virtual bool getTabLogOpen() const = 0;
[[nodiscard]] virtual bool getTabReportOpen() const = 0;
[[nodiscard]] virtual bool getTabModerationOpen() const = 0;
[[nodiscard]] virtual bool getTabCardArtRulesOpen() const = 0;
};
#endif // COCKATRICE_INTERFACE_TABS_SETTINGS_PROVIDER_H

View file

@ -90,7 +90,6 @@ set(PROTO_FILES
event_game_log_notice.proto
event_game_say.proto
event_game_state_changed.proto
event_game_state_changed.proto
event_join.proto
event_join_room.proto
event_kicked.proto

View file

@ -106,6 +106,11 @@ bool TabsSettings::getTabModerationOpen() const
return getValue("moderation", QString(), QString(), false).toBool();
}
bool TabsSettings::getTabCardArtRulesOpen() const
{
return getValue("cardArtRules", QString(), QString(), false).toBool();
}
void TabsSettings::setTabVisualDeckStorageOpen(bool value)
{
setValue(value, "visualDeckStorage");
@ -150,3 +155,8 @@ void TabsSettings::setTabModerationOpen(bool value)
{
setValue(value, "moderation");
}
void TabsSettings::setTabCardArtRulesOpen(bool value)
{
setValue(value, "cardArtRules");
}

View file

@ -43,6 +43,7 @@ public:
[[nodiscard]] bool getTabLogOpen() const override;
[[nodiscard]] bool getTabReportOpen() const override;
[[nodiscard]] bool getTabModerationOpen() const override;
[[nodiscard]] bool getTabCardArtRulesOpen() const override;
void setStartupTabIndex(int value);
void setStartupServerHost(const QString &host);
@ -57,6 +58,7 @@ public:
void setTabLogOpen(bool value);
void setTabReportOpen(bool value);
void setTabModerationOpen(bool value);
void setTabCardArtRulesOpen(bool value);
signals:
void startupTabIndexChanged(int index);

View file

@ -238,6 +238,12 @@ TEST_F(SettingsDefaultsTest, Tabs_ModerationOpen_Default)
ASSERT_EQ(s.getTabModerationOpen(), false);
}
TEST_F(SettingsDefaultsTest, Tabs_CardArtRulesOpen_Default)
{
TabsSettings s(settingsPath, nullptr);
ASSERT_EQ(s.getTabCardArtRulesOpen(), false);
}
// --- ChatSettings ---
TEST_F(SettingsDefaultsTest, Chat_Mention_Default)