Visual deck storage v2 (#5427)
Some checks are pending
Build Desktop / Configure (push) Waiting to run
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, 12) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Debian, DEB, skip, 11) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Fedora, RPM, 41) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Fedora, RPM, skip, 40) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Ubuntu, DEB, 24.04) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Ubuntu, DEB, skip, 20.04) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (Ubuntu, DEB, skip, 22.04) (push) Blocked by required conditions
Build Desktop / ${{matrix.distro}} ${{matrix.version}} (yes, Arch, skip) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (3, 1, macos-14, Apple, 14, Release, 15.4) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (3, 1, macos-15, Apple, 15, Release, 16.2) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (3, macos-15, Apple, 15, Debug, 16.2) (push) Blocked by required conditions
Build Desktop / macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} (4, 1, macos-13, Intel, 13, Release, 14.3.1) (push) Blocked by required conditions
Build Desktop / Windows ${{matrix.target}} (msvc2019_64, 5.15.*, 7) (push) Blocked by required conditions
Build Desktop / Windows ${{matrix.target}} (msvc2019_64, qtimageformats qtmultimedia qtwebsockets, 6.6.*, 10) (push) Blocked by required conditions

* Restore some button states (ready/sideboard locked) to sensible defaults when unloading a deck.

* Update last loaded timestamp in decklist file and then restore original last modified timestamp if a user requests a deck load.

* Add some todos.

* Loading a deck from local file dialog should swap out scenes, enable unload button.

* Lint.

* Shuffle some classes and signals around.

* More sort options, sort widgets directly.

* Banner cards should respect providerIds.

* Properly updateSortOrder on load.

* Add the color identity to the Deck Preview Widget.

* Properly set sort indices.

* Change replace visualDeckStorageWidget with deckView to be in deckSelectFinished so that it also works on remote deck load.

* Include settings for unused color identities display.

* Change opacity scaling.

* Overload for Qt.

* Lint.

* Lint.

* Include QMouseEvent

* Template because MacOs.

* Include a quick filter for color identities.

* Include a quick filter for color identities.

* Save some space.

* Refactor DeckPreviewWidgets to reside in their own folder.

* Add Deck Loader logging category.

* Introduce a tagging system.

* Add some more default tags.

* Even more default tags.

* Lint.

* Lint a comma.

* Remove extra set of braces.

* Lint some stuff.

* Refresh banner cards when tags are added.

* Lint.

* Wrestle with Qt Checkboxes.

* Lint.

* Adjust some sizes, relayout.

* Address comments.

* Lint.

* Reorder kindred types.

* Add a search bar for tags.

* Remove close button (for now) and change "Add tags ..." to "Edit tags ..."

* Retranslate window title for Deck Tag Manager Dialog.

* Style tag addition widget to be consistent.

* Lint.

* Override paintEvent.

* Override sizeHint

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2025-01-12 23:46:22 +01:00 committed by GitHub
parent 9bd024d39f
commit dd8ac14f99
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 1899 additions and 153 deletions

View File

@ -162,8 +162,18 @@ set(cockatrice_SOURCES
src/game/zones/view_zone.cpp
src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp
${VERSION_STRING_CPP}
)

View File

@ -1,2 +1,3 @@
[Rules]
picture_loader.debug = true
picture_loader.debug = true
deck_loader.debug = true

View File

@ -823,7 +823,7 @@ void TabDeckEditor::updateBannerCardComboBox()
bannerCardComboBox->clear();
// Prepare the new items with deduplication
QSet<QString> bannerCardSet;
QSet<QPair<QString, QString>> bannerCardSet;
InnerDecklistNode *listRoot = deckModel->getDeckList()->getRoot();
for (int i = 0; i < listRoot->size(); i++) {
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
@ -833,23 +833,30 @@ void TabDeckEditor::updateBannerCardComboBox()
continue;
for (int k = 0; k < currentCard->getNumber(); ++k) {
CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(currentCard->getName());
CardInfoPtr info = CardDatabaseManager::getInstance()->getCardByNameAndProviderId(
currentCard->getName(), currentCard->getCardProviderId());
if (info) {
bannerCardSet.insert(currentCard->getName());
bannerCardSet.insert(
QPair<QString, QString>(currentCard->getName(), currentCard->getCardProviderId()));
}
}
}
}
// Convert the QSet to a sorted QStringList
QStringList bannerCardChoices;
for (const QString &entry : bannerCardSet) {
bannerCardChoices.append(entry);
}
bannerCardChoices.sort(Qt::CaseInsensitive);
QList<QPair<QString, QString>> pairList = bannerCardSet.values();
// Populate the combo box with new items
bannerCardComboBox->addItems(bannerCardChoices);
// Sort QList by the first() element of the QPair
std::sort(pairList.begin(), pairList.end(), [](const QPair<QString, QString> &a, const QPair<QString, QString> &b) {
return a.first.toLower() < b.first.toLower();
});
for (const auto &pair : pairList) {
QVariantMap dataMap;
dataMap["name"] = pair.first;
dataMap["uuid"] = pair.second;
bannerCardComboBox->addItem(pair.first, dataMap);
}
// Try to restore the previous selection by finding the currentText
int restoredIndex = bannerCardComboBox->findText(currentText);
@ -857,7 +864,7 @@ void TabDeckEditor::updateBannerCardComboBox()
bannerCardComboBox->setCurrentIndex(restoredIndex);
} else {
// Add a placeholder "-" and set it as the current selection
int bannerIndex = bannerCardComboBox->findText(deckModel->getDeckList()->getBannerCard());
int bannerIndex = bannerCardComboBox->findText(deckModel->getDeckList()->getBannerCard().first);
if (bannerIndex != -1) {
bannerCardComboBox->setCurrentIndex(bannerIndex);
} else {
@ -872,8 +879,8 @@ void TabDeckEditor::updateBannerCardComboBox()
void TabDeckEditor::setBannerCard(int /* changedIndex */)
{
qDebug() << "Banner card was set to: " << bannerCardComboBox->currentText();
deckModel->getDeckList()->setBannerCard(bannerCardComboBox->currentText());
QVariantMap data = bannerCardComboBox->itemData(bannerCardComboBox->currentIndex()).toMap();
deckModel->getDeckList()->setBannerCard(QPair<QString, QString>(data["name"].toString(), data["uuid"].toString()));
}
void TabDeckEditor::updateCardInfo(CardInfoPtr _card)
@ -1043,11 +1050,11 @@ void TabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLocation d
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
auto *l = new DeckLoader;
if (l->loadFromFile(fileName, fmt)) {
if (l->loadFromFile(fileName, fmt, true)) {
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName);
updateBannerCardComboBox();
if (!l->getBannerCard().isEmpty()) {
bannerCardComboBox->setCurrentIndex(bannerCardComboBox->findText(l->getBannerCard()));
if (!l->getBannerCard().first.isEmpty()) {
bannerCardComboBox->setCurrentIndex(bannerCardComboBox->findText(l->getBannerCard().first));
}
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(l);
@ -1542,13 +1549,13 @@ void TabDeckEditor::actDecrement()
void TabDeckEditor::setDeck(DeckLoader *_deck)
{
qDebug() << " ORIGINAL BANNER CARD " << _deck->getBannerCard();
qDebug() << " ORIGINAL BANNER CARD " << _deck->getBannerCard().first;
deckModel->setDeckList(_deck);
nameEdit->setText(deckModel->getDeckList()->getName());
commentsEdit->setText(deckModel->getDeckList()->getComments());
qDebug() << deckModel->getDeckList()->getBannerCard() << " was the banner card";
bannerCardComboBox->setCurrentText(deckModel->getDeckList()->getBannerCard());
bannerCardComboBox->setCurrentText(deckModel->getDeckList()->getBannerCard().first);
updateBannerCardComboBox();
updateHash();
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());

View File

@ -203,7 +203,7 @@ void TabDeckStorage::actOpenLocalDeck()
QString filePath = localDirModel->filePath(curLeft);
DeckLoader deckLoader;
if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat))
if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat, true))
continue;
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(filePath);

View File

@ -145,7 +145,7 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
deckView->setVisible(false);
visualDeckStorageWidget = new VisualDeckStorageWidget(this);
connect(visualDeckStorageWidget, &VisualDeckStorageWidget::imageDoubleClicked, this,
connect(visualDeckStorageWidget, &VisualDeckStorageWidget::deckPreviewDoubleClicked, this,
&DeckViewContainer::replaceDeckStorageWithDeckView);
deckViewLayout = new QVBoxLayout;
@ -299,20 +299,12 @@ void TabGame::refreshShortcuts()
}
}
void DeckViewContainer::replaceDeckStorageWithDeckView(QMouseEvent *event, DeckPreviewCardPictureWidget *instance)
void DeckViewContainer::replaceDeckStorageWithDeckView(QMouseEvent *event, DeckPreviewWidget *instance)
{
Q_UNUSED(event);
QString fileName = instance->filePath;
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
QString deckString;
DeckLoader deck;
QString deckString = instance->deckLoader->writeToString_Native();
bool error = !deck.loadFromFile(fileName, fmt);
if (!error) {
deckString = deck.writeToString_Native();
error = deckString.length() > MAX_FILE_LENGTH;
}
if (error) {
if (deckString.length() > MAX_FILE_LENGTH) {
QMessageBox::critical(this, tr("Error"), tr("The selected file could not be loaded."));
return;
}
@ -335,6 +327,11 @@ void DeckViewContainer::unloadDeck()
visualDeckStorageWidget->setVisible(true);
deckViewLayout->update();
unloadDeckButton->setEnabled(false);
readyStartButton->setEnabled(false);
readyStartButton->setState(false);
sideboardLockButton->setEnabled(false);
sideboardLockButton->setState(false);
setReadyStart(false);
}
void DeckViewContainer::loadLocalDeck()
@ -352,7 +349,7 @@ void DeckViewContainer::loadDeckFromFile(const QString &filePath)
QString deckString;
DeckLoader deck;
bool error = !deck.loadFromFile(filePath, fmt);
bool error = !deck.loadFromFile(filePath, fmt, true);
if (!error) {
deckString = deck.writeToString_Native();
error = deckString.length() > MAX_FILE_LENGTH;
@ -390,6 +387,9 @@ void DeckViewContainer::deckSelectFinished(const Response &r)
// TODO CHANGE THIS TO BE SELECTED BY UUID
PictureLoader::cacheCardPixmaps(CardDatabaseManager::getInstance()->getCards(newDeck.getCardList()));
setDeck(newDeck);
deckView->setVisible(true);
visualDeckStorageWidget->setVisible(false);
unloadDeckButton->setEnabled(true);
}
void DeckViewContainer::readyStart()

View File

@ -95,7 +95,7 @@ private:
TabGame *parentGame;
int playerId;
private slots:
void replaceDeckStorageWithDeckView(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void replaceDeckStorageWithDeckView(QMouseEvent *event, DeckPreviewWidget *instance);
void loadLocalDeck();
void loadRemoteDeck();
void unloadDeck();

View File

@ -49,7 +49,7 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor, Abstra
leftToolBar->addAction(aDeleteLocalDeck);
visualDeckStorageWidget = new VisualDeckStorageWidget(this);
connect(visualDeckStorageWidget, &VisualDeckStorageWidget::imageDoubleClicked, this,
connect(visualDeckStorageWidget, &VisualDeckStorageWidget::deckPreviewDoubleClicked, this,
&TabDeckStorageVisual::actOpenLocalDeck);
// layout->addWidget(leftToolBar);
@ -74,11 +74,11 @@ QString TabDeckStorageVisual::getTargetPath() const
return {};
}
void TabDeckStorageVisual::actOpenLocalDeck(QMouseEvent *event, DeckPreviewCardPictureWidget *instance)
void TabDeckStorageVisual::actOpenLocalDeck(QMouseEvent *event, DeckPreviewWidget *instance)
{
(void)event;
DeckLoader deckLoader;
if (!deckLoader.loadFromFile(instance->filePath, DeckLoader::CockatriceFormat))
if (!deckLoader.loadFromFile(instance->filePath, DeckLoader::CockatriceFormat, true))
return;
emit openDeckEditor(&deckLoader);

View File

@ -33,8 +33,8 @@ public:
}
public slots:
void cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus);
void closeRequest(bool forced = false) override;
void actOpenLocalDeck(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void closeRequest(bool forced) override;
void actOpenLocalDeck(QMouseEvent *event, DeckPreviewWidget *instance);
void actDeleteLocalDeck();
signals:
void openDeckEditor(const DeckLoader *deckLoader);

View File

@ -1,6 +1,7 @@
#include "deck_preview_card_picture_widget.h"
#include <QApplication>
#include <QFileInfo>
#include <QFontMetrics>
#include <QMouseEvent>
#include <QPainterPath>
@ -15,6 +16,7 @@
* @param outlineColor The color of the outline around the text.
* @param fontSize The font size of the overlay text.
* @param alignment The alignment of the text within the overlay.
* @param _deckLoader The Deck Loader holding the Deck associated with this preview.
*
* Sets the widget's size policy and default border style.
*/
@ -46,8 +48,3 @@ void DeckPreviewCardPictureWidget::mouseDoubleClickEvent(QMouseEvent *event)
emit imageDoubleClicked(lastMouseEvent, this);
}
}
void DeckPreviewCardPictureWidget::setFilePath(const QString &_filePath)
{
filePath = _filePath;
}

View File

@ -12,22 +12,17 @@ class DeckPreviewCardPictureWidget final : public CardInfoPictureWithTextOverlay
Q_OBJECT
public:
explicit DeckPreviewCardPictureWidget(QWidget *parent = nullptr,
explicit DeckPreviewCardPictureWidget(QWidget *parent,
bool hoverToZoomEnabled = false,
const QColor &textColor = Qt::white,
const QColor &outlineColor = Qt::black,
int fontSize = 12,
Qt::Alignment alignment = Qt::AlignCenter);
QString filePath;
signals:
void imageClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void imageDoubleClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
public slots:
void setFilePath(const QString &filePath);
private:
QTimer *singleClickTimer;
QMouseEvent *lastMouseEvent = nullptr; // Store the last mouse event

View File

@ -87,6 +87,11 @@ void FlowWidget::addWidget(QWidget *widget_to_add) const
flowLayout->addWidget(widget_to_add);
}
void FlowWidget::removeWidget(QWidget *widgetToRemove) const
{
flowLayout->removeWidget(widgetToRemove);
}
/**
* @brief Clears all widgets from the flow layout.
*

View File

@ -13,6 +13,7 @@ class FlowWidget final : public QWidget
public:
FlowWidget(QWidget *parent, Qt::ScrollBarPolicy horizontalPolicy, Qt::ScrollBarPolicy verticalPolicy);
void addWidget(QWidget *widget_to_add) const;
void removeWidget(QWidget *widgetToRemove) const;
void clearLayout();
[[nodiscard]] int count() const;
[[nodiscard]] QLayoutItem *itemAt(int index) const;

View File

@ -0,0 +1,201 @@
#include "deck_preview_color_identity_filter_widget.h"
#include "deck_preview_widget.h"
#include <QMouseEvent>
#include <QPainter>
DeckPreviewColorIdentityFilterCircleWidget::DeckPreviewColorIdentityFilterCircleWidget(QChar color, QWidget *parent)
: QWidget(parent), colorChar(color), isActive(false), circleDiameter(30)
{
setFixedSize(circleDiameter, circleDiameter);
}
void DeckPreviewColorIdentityFilterCircleWidget::setColorActive(bool active)
{
if (isActive != active) {
isActive = active;
update();
}
}
bool DeckPreviewColorIdentityFilterCircleWidget::isColorActive() const
{
return isActive;
}
QChar DeckPreviewColorIdentityFilterCircleWidget::getColorChar() const
{
return colorChar;
}
void DeckPreviewColorIdentityFilterCircleWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QColor circleColor;
switch (colorChar.unicode()) {
case 'W':
circleColor = Qt::white;
break;
case 'U':
circleColor = QColor(0, 115, 230);
break;
case 'B':
circleColor = QColor(50, 50, 50);
break;
case 'R':
circleColor = QColor(230, 30, 30);
break;
case 'G':
circleColor = QColor(30, 180, 30);
break;
default:
circleColor = Qt::transparent;
break;
}
if (!isActive) {
circleColor.setAlpha(100); // Dim inactive circles
}
painter.setBrush(circleColor);
painter.setPen(Qt::black);
painter.drawEllipse(rect());
if (isActive) {
QFont font = painter.font();
font.setBold(true);
font.setPointSize(circleDiameter / 3);
painter.setFont(font);
painter.setPen(colorChar.unicode() == 'B' ? Qt::white : Qt::black);
painter.drawText(rect(), Qt::AlignCenter, colorChar);
}
}
void DeckPreviewColorIdentityFilterCircleWidget::mousePressEvent(QMouseEvent *event)
{
Q_UNUSED(event);
isActive = !isActive;
emit colorToggled(colorChar, isActive);
update();
}
DeckPreviewColorIdentityFilterWidget::DeckPreviewColorIdentityFilterWidget(VisualDeckStorageWidget *parent)
: QWidget(parent), layout(new QHBoxLayout(this))
{
setLayout(layout);
layout->setSpacing(5);
layout->setContentsMargins(0, 0, 0, 0);
QString fullColorIdentity = "WUBRG";
for (const QChar &color : fullColorIdentity) {
auto *circle = new DeckPreviewColorIdentityFilterCircleWidget(color, this);
layout->addWidget(circle);
// Initialize the activeColors map
activeColors[color] = false;
// Connect the color toggled signal
connect(circle, &DeckPreviewColorIdentityFilterCircleWidget::colorToggled, this,
&DeckPreviewColorIdentityFilterWidget::handleColorToggled);
}
toggleButton = new QPushButton(this);
toggleButton->setCheckable(true);
layout->addWidget(toggleButton);
// Connect the button's toggled signal
connect(toggleButton, &QPushButton::toggled, this, &DeckPreviewColorIdentityFilterWidget::updateFilterMode);
connect(this, &DeckPreviewColorIdentityFilterWidget::activeColorsChanged, parent,
&VisualDeckStorageWidget::refreshBannerCards);
connect(this, &DeckPreviewColorIdentityFilterWidget::filterModeChanged, parent,
&VisualDeckStorageWidget::refreshBannerCards);
// Call retranslateUi to set the initial text
retranslateUi();
}
void DeckPreviewColorIdentityFilterWidget::retranslateUi()
{
// Set the toggle button text based on the current mode
toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes"));
}
void DeckPreviewColorIdentityFilterWidget::handleColorToggled(QChar color, bool active)
{
activeColors[color] = active;
emit activeColorsChanged();
}
void DeckPreviewColorIdentityFilterWidget::updateFilterMode(bool checked)
{
exactMatchMode = checked; // Toggle between modes
retranslateUi(); // Update the button text
emit filterModeChanged(exactMatchMode);
}
QList<DeckPreviewWidget *> DeckPreviewColorIdentityFilterWidget::filterWidgets(QList<DeckPreviewWidget *> &widgets)
{
QList<DeckPreviewWidget *> filteredWidgets;
// Check if no colors are active
bool noColorsActive = true;
for (auto it = activeColors.constBegin(); it != activeColors.constEnd(); ++it) {
if (it.value()) {
noColorsActive = false;
break;
}
}
// If no colors are active, return the unfiltered list of widgets
if (noColorsActive) {
return widgets;
}
for (const auto &widget : widgets) {
QString colorIdentity = widget->getColorIdentity();
bool matchesFilter = true;
if (exactMatchMode) {
// Exact match mode: active colors must exactly match colorIdentity
// Create a set of active colors
QSet<QChar> activeColorSet;
for (auto it = activeColors.constBegin(); it != activeColors.constEnd(); ++it) {
if (it.value()) {
activeColorSet.insert(it.key().toUpper()); // Use uppercase for uniformity
}
}
// Create a set of colors from the color identity string
QSet<QChar> colorIdentitySet;
for (const QChar &color : colorIdentity) {
colorIdentitySet.insert(color.toUpper()); // Ensure case uniformity
}
// Compare the sets: the sets must match exactly
if (activeColorSet != colorIdentitySet) {
matchesFilter = false;
}
} else {
// Includes mode: colorIdentity must contain all active colors
for (auto it = activeColors.constBegin(); it != activeColors.constEnd(); ++it) {
if (it.value() && !colorIdentity.contains(it.key())) {
matchesFilter = false;
break;
}
}
}
if (matchesFilter) {
filteredWidgets << widget;
}
}
return filteredWidgets;
}

View File

@ -0,0 +1,62 @@
#ifndef DECK_PREVIEW_COLOR_IDENTITY_FILTER_WIDGET_H
#define DECK_PREVIEW_COLOR_IDENTITY_FILTER_WIDGET_H
#include "../visual_deck_storage_widget.h"
#include <QChar>
#include <QHBoxLayout>
#include <QList>
#include <QPushButton>
#include <QWidget>
class DeckPreviewWidget;
class VisualDeckStorageWidget;
class DeckPreviewColorIdentityFilterCircleWidget : public QWidget
{
Q_OBJECT
public:
explicit DeckPreviewColorIdentityFilterCircleWidget(QChar color, QWidget *parent = nullptr);
void setColorActive(bool active);
bool isColorActive() const;
QChar getColorChar() const;
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
signals:
void colorToggled(QChar color, bool active);
private:
QChar colorChar;
bool isActive;
int circleDiameter;
};
class DeckPreviewColorIdentityFilterWidget : public QWidget
{
Q_OBJECT
public:
explicit DeckPreviewColorIdentityFilterWidget(VisualDeckStorageWidget *parent);
void retranslateUi();
QList<DeckPreviewWidget *> filterWidgets(QList<DeckPreviewWidget *> &widgets);
signals:
void filterModeChanged(bool exactMatchMode);
void activeColorsChanged();
private slots:
void handleColorToggled(QChar color, bool active);
void updateFilterMode(bool checked);
private:
QHBoxLayout *layout;
QPushButton *toggleButton;
QMap<QChar, bool> activeColors;
bool exactMatchMode = false; // Default to "includes" mode
};
#endif // DECK_PREVIEW_COLOR_IDENTITY_FILTER_WIDGET_H

View File

@ -0,0 +1,156 @@
#include "deck_preview_color_identity_widget.h"
#include "../../../../../settings/cache_settings.h"
#include <QPainter>
#include <QResizeEvent>
DeckPreviewColorCircleWidget::DeckPreviewColorCircleWidget(QChar color, QWidget *parent)
: QWidget(parent), colorChar(color), circleDiameter(0), isActive(false)
{
}
void DeckPreviewColorCircleWidget::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event);
// Get the parent of the DeckPreviewColorIdentityWidget
QWidget *identityParent = parentWidget() ? parentWidget()->parentWidget() : nullptr;
if (identityParent) {
// Calculate the circle diameter as 15% of the parent's height
int maxSize = identityParent->width() * 0.15;
circleDiameter = maxSize;
// Update the widget size based on the diameter
updateGeometry(); // Request a resize based on sizeHint()
}
update(); // Trigger a repaint
}
QSize DeckPreviewColorCircleWidget::sizeHint() const
{
// Return the size we calculated based on the parent widget
return QSize(circleDiameter, circleDiameter);
}
QSize DeckPreviewColorCircleWidget::minimumSizeHint() const
{
// Return the same value as sizeHint() for minimum size
return QSize(circleDiameter, circleDiameter);
}
void DeckPreviewColorCircleWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// Calculate the circle's bounding rectangle
int x = (width() - circleDiameter) / 2;
int y = (height() - circleDiameter) / 2;
QRect circleRect(x, y, circleDiameter, circleDiameter);
// Map color characters to their respective colors
QColor circleColor;
switch (colorChar.unicode()) {
case 'W':
circleColor = Qt::white;
break;
case 'U':
circleColor = QColor(0, 115, 230);
break; // Blue
case 'B':
circleColor = QColor(50, 50, 50);
break; // Black
case 'R':
circleColor = QColor(230, 30, 30);
break; // Red
case 'G':
circleColor = QColor(30, 180, 30);
break; // Green
default:
circleColor = Qt::transparent;
break; // Fallback
}
if (SettingsCache::instance().getVisualDeckStorageDrawUnusedColorIdentities() || isActive) {
// Make the circle faint if it is not active
if (!isActive) {
circleColor.setAlpha(SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity() / 100.0 *
255.0);
}
// Draw the circle
painter.setBrush(circleColor);
painter.setPen(Qt::black);
painter.drawEllipse(circleRect);
}
// Draw the color character only if the circle is active
if (isActive) {
QFont font = painter.font();
font.setBold(true);
font.setPointSize(circleDiameter * 0.4); // Adjust font size relative to circle diameter
painter.setFont(font);
if (colorChar.unicode() == 'B') {
painter.setPen(Qt::white);
} else {
painter.setPen(Qt::black);
}
// Center the text within the circle
painter.drawText(circleRect, Qt::AlignCenter, colorChar);
}
}
void DeckPreviewColorCircleWidget::setColorActive(bool active)
{
isActive = active;
update(); // Redraw the circle with the new active state
}
QChar DeckPreviewColorCircleWidget::getColorChar() const
{
return colorChar;
}
DeckPreviewColorIdentityWidget::DeckPreviewColorIdentityWidget(const QString &colorIdentity, QWidget *parent)
: QWidget(parent)
{
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setSpacing(5);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
// Define the full WUBRG set (White, Blue, Black, Red, Green)
QString fullColorIdentity = "WUBRG";
// Create and add a DeckPreviewColorCircleWidget for each color in WUBRG
for (const QChar &color : fullColorIdentity) {
auto *circle = new DeckPreviewColorCircleWidget(color, this);
layout->addWidget(circle);
}
// Set any active colors from the input colorIdentity
for (const QChar &color : colorIdentity) {
for (DeckPreviewColorCircleWidget *circle : findChildren<DeckPreviewColorCircleWidget *>()) {
if (circle->getColorChar() == color) {
circle->setColorActive(true); // Mark the color as active
}
}
}
}
void DeckPreviewColorIdentityWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
// Notify child widgets to update their sizes based on the new parent size
for (auto *circle : findChildren<DeckPreviewColorCircleWidget *>()) {
circle->updateGeometry(); // Request each circle to resize
}
}

View File

@ -0,0 +1,42 @@
#ifndef DECK_PREVIEW_COLOR_IDENTITY_WIDGET_H
#define DECK_PREVIEW_COLOR_IDENTITY_WIDGET_H
#include <QChar>
#include <QHBoxLayout>
#include <QSize>
#include <QWidget>
class DeckPreviewColorCircleWidget : public QWidget
{
Q_OBJECT
public:
explicit DeckPreviewColorCircleWidget(QChar color, QWidget *parent = nullptr);
void setColorActive(bool active);
QChar getColorChar() const;
protected:
void resizeEvent(QResizeEvent *event) override;
void paintEvent(QPaintEvent *event) override;
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
private:
QChar colorChar;
int circleDiameter;
bool isActive;
};
class DeckPreviewColorIdentityWidget : public QWidget
{
Q_OBJECT
public:
explicit DeckPreviewColorIdentityWidget(const QString &colorIdentity, QWidget *parent = nullptr);
protected:
void resizeEvent(QResizeEvent *event) override;
};
#endif // DECK_PREVIEW_COLOR_IDENTITY_WIDGET_H

View File

@ -0,0 +1,31 @@
#include "deck_preview_deck_tags_display_widget.h"
#include "../../general/layout_containers/flow_widget.h"
#include "deck_preview_tag_addition_widget.h"
#include "deck_preview_tag_display_widget.h"
#include "deck_preview_widget.h"
#include <QHBoxLayout>
#include <QLabel>
DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(DeckPreviewWidget *_parent, DeckLoader *_deckLoader)
: QWidget(_parent), parent(_parent), deckLoader(_deckLoader)
{
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
// Create layout
auto *layout = new QHBoxLayout(this);
layout->setContentsMargins(5, 5, 5, 5);
layout->setSpacing(5);
setFixedHeight(100);
auto *flowWidget = new FlowWidget(this, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
for (const QString &tag : this->deckLoader->getTags()) {
flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag));
}
flowWidget->addWidget(new DeckPreviewTagAdditionWidget(this, tr("Edit tags ...")));
layout->addWidget(flowWidget);
}

View File

@ -0,0 +1,19 @@
#ifndef DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H
#define DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H
#include "../../../../../deck/deck_loader.h"
#include "deck_preview_widget.h"
#include <QWidget>
class DeckPreviewWidget;
class DeckPreviewDeckTagsDisplayWidget : public QWidget
{
Q_OBJECT
public:
explicit DeckPreviewDeckTagsDisplayWidget(DeckPreviewWidget *_parent, DeckLoader *_deckLoader);
DeckPreviewWidget *parent;
DeckLoader *deckLoader;
};
#endif // DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H

View File

@ -0,0 +1,97 @@
#include "deck_preview_tag_addition_widget.h"
#include "deck_preview_tag_dialog.h"
#include <QFontMetrics>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QPainter>
DeckPreviewTagAdditionWidget::DeckPreviewTagAdditionWidget(DeckPreviewDeckTagsDisplayWidget *_parent,
const QString &_tagName)
: QWidget(_parent), parent(_parent), tagName_(_tagName)
{
// Create layout
auto *layout = new QHBoxLayout(this);
layout->setContentsMargins(5, 5, 5, 5);
layout->setSpacing(5);
// Adjust widget size
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
}
QSize DeckPreviewTagAdditionWidget::sizeHint() const
{
// Calculate the size based on the tag name
QFontMetrics fm(font());
int textWidth = fm.horizontalAdvance(tagName_);
int width = textWidth + 50; // Add extra padding
int height = fm.height() + 10; // Height based on font size + padding
return QSize(width, height);
}
void DeckPreviewTagAdditionWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
emit tagClicked();
}
QWidget::mousePressEvent(event);
QStringList knownTags = parent->parent->parent->gatherAllTagsFromFlowWidget();
QStringList activeTags = parent->deckLoader->getTags();
DeckPreviewTagDialog dialog(knownTags, activeTags);
if (dialog.exec() == QDialog::Accepted) {
QStringList updatedTags = dialog.getActiveTags();
parent->deckLoader->setTags(updatedTags);
parent->deckLoader->saveToFile(parent->parent->filePath, DeckLoader::CockatriceFormat);
parent->parent->parent->refreshBannerCards();
}
}
void DeckPreviewTagAdditionWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
// Set background color
QColor backgroundColor = Qt::lightGray;
painter.setBrush(backgroundColor);
painter.setPen(Qt::NoPen);
// Draw background
painter.drawRect(rect());
// Draw border
QColor borderColor = Qt::gray;
QPen borderPen(borderColor, 1);
painter.setPen(borderPen);
painter.drawRect(rect().adjusted(0, 0, -1, -1)); // Adjust for pen width
// Calculate font size based on widget height
QFont font = painter.font();
int fontSize = std::max(10, height() / 2); // Ensure a minimum font size of 10
font.setPointSize(fontSize);
painter.setFont(font);
// Calculate text rect with margin
int margin = 10; // Left and right margins
QRect textRect(margin, 0, width() - margin * 2, height());
// Draw the text with a black border for better legibility
painter.setPen(Qt::black);
// Draw text border by offsetting
for (int dx = -1; dx <= 1; ++dx) {
for (int dy = -1; dy <= 1; ++dy) {
if (dx != 0 || dy != 0) {
painter.drawText(textRect.translated(dx, dy), Qt::AlignLeft | Qt::AlignVCenter, tagName_);
}
}
}
// Draw the actual text
painter.setPen(Qt::white);
painter.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, tagName_);
QWidget::paintEvent(event);
}

View File

@ -0,0 +1,33 @@
#ifndef DECK_PREVIEW_TAG_ADDITION_WIDGET_H
#define DECK_PREVIEW_TAG_ADDITION_WIDGET_H
#include "deck_preview_deck_tags_display_widget.h"
#include <QLabel>
#include <QPushButton>
#include <QWidget>
class DeckPreviewTagAdditionWidget : public QWidget
{
Q_OBJECT
public:
explicit DeckPreviewTagAdditionWidget(DeckPreviewDeckTagsDisplayWidget *_parent, const QString &tagName);
QSize sizeHint() const override;
signals:
void tagClicked(); // Emitted when the tag is clicked
void tagClosed(); // Emitted when the close button is clicked
protected:
void mousePressEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
private:
DeckPreviewDeckTagsDisplayWidget *parent;
QString tagName_;
QLabel *tagLabel_;
QPushButton *closeButton_;
};
#endif // DECK_PREVIEW_TAG_ADDITION_WIDGET_H

View File

@ -0,0 +1,223 @@
#include "deck_preview_tag_dialog.h"
#include "deck_preview_tag_item_widget.h"
#include <QCheckBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QVBoxLayout>
DeckPreviewTagDialog::DeckPreviewTagDialog(const QStringList &knownTags, const QStringList &activeTags, QWidget *parent)
: QDialog(parent), activeTags_(activeTags)
{
resize(400, 500);
QStringList defaultTags = {
// Strategies
"🏃️ Aggro",
"🧙‍️ Control",
"⚔️ Midrange",
"🌀 Combo",
"🪓 Mill",
"🔒 Stax",
"🗺️ Landfall",
"🛡️ Pillowfort",
"🌱 Ramp",
"⚡ Storm",
"💀 Aristocrats",
"☠️ Reanimator",
"👹 Sacrifice",
"🔥 Burn",
"🌟 Lifegain",
"🔮 Spellslinger",
"👥 Tokens",
"🎭 Blink",
"⏳ Time Manipulation",
"🌍 Domain",
"💫 Proliferate",
"📜 Saga",
"🎲 Chaos",
"🪄 Auras",
"🔫 Pingers",
// Themes
"👑 Monarch",
"🚀 Vehicles",
"💉 Infect",
"🩸 Madness",
"🌀 Morph",
// Card Types
"⚔️ Creature",
"💎 Artifact",
"🌔 Enchantment",
"📖 Sorcery",
"⚡ Instant",
"🌌 Planeswalker",
"🌏 Land",
"🪄 Aura",
// Kindred Types
"🐉 Kindred",
"🧙 Humans",
"⚔️ Soldiers",
"🛡️ Knights",
"🎻 Bards",
"🧝 Elves",
"🌲 Dryads",
"😇 Angels",
"🎩 Wizards",
"🧛 Vampires",
"🦴 Skeletons",
"💀 Zombies",
"👹 Demons",
"👾 Eldrazi",
"🐉 Dragons",
"🐠 Merfolk",
"🦁 Cats",
"🐺 Wolves",
"🐺 Werewolves",
"🦇 Bats",
"🐀 Rats",
"🦅 Birds",
"🦗 Insects",
"🍄 Fungus",
"🐚 Sea Creatures",
"🐗 Boars",
"🦊 Foxes",
"🦄 Unicorns",
"🐘 Elephants",
"🐻 Bears",
"🦏 Rhinos",
"🦂 Scorpions",
};
// Merge knownTags with defaultTags, ensuring no duplicates
QStringList combinedTags = defaultTags + knownTags + activeTags;
combinedTags.removeDuplicates();
// Main layout
auto *mainLayout = new QVBoxLayout(this);
// Filter bar
filterInput_ = new QLineEdit(this);
mainLayout->addWidget(filterInput_);
connect(filterInput_, &QLineEdit::textChanged, this, &DeckPreviewTagDialog::filterTags);
// Instruction label
instructionLabel = new QLabel(this);
instructionLabel->setWordWrap(true);
mainLayout->addWidget(instructionLabel);
// Tag list view
tagListView_ = new QListWidget(this);
mainLayout->addWidget(tagListView_);
// Populate combined tags
for (const auto &tag : combinedTags) {
auto *item = new QListWidgetItem(tagListView_);
auto *tagWidget = new DeckPreviewTagItemWidget(tag, activeTags.contains(tag), this);
tagListView_->addItem(item);
tagListView_->setItemWidget(item, tagWidget);
connect(tagWidget->checkBox(), &QCheckBox::toggled, this, &DeckPreviewTagDialog::onCheckboxStateChanged);
}
// Add tag input layout
auto *addTagLayout = new QHBoxLayout();
newTagInput_ = new QLineEdit(this);
addTagButton_ = new QPushButton(this);
addTagLayout->addWidget(newTagInput_);
addTagLayout->addWidget(addTagButton_);
mainLayout->addLayout(addTagLayout);
connect(addTagButton_, &QPushButton::clicked, this, &DeckPreviewTagDialog::addTag);
// OK and Cancel buttons
auto *buttonLayout = new QHBoxLayout();
okButton = new QPushButton(this);
cancelButton = new QPushButton(this);
buttonLayout->addStretch();
buttonLayout->addWidget(okButton);
buttonLayout->addWidget(cancelButton);
mainLayout->addLayout(buttonLayout);
connect(okButton, &QPushButton::clicked, this, &DeckPreviewTagDialog::accept);
connect(cancelButton, &QPushButton::clicked, this, &DeckPreviewTagDialog::reject);
retranslateUi();
}
void DeckPreviewTagDialog::retranslateUi()
{
setWindowTitle(tr("Deck Tags Manager"));
instructionLabel->setText(tr("Manage your deck tags. Check or uncheck tags as needed, or add new ones:"));
newTagInput_->setPlaceholderText(tr("Add a new tag (e.g., Aggro)"));
addTagButton_->setText(tr("Add Tag"));
filterInput_->setPlaceholderText(tr("Filter tags..."));
okButton->setText(tr("OK"));
cancelButton->setText(tr("Cancel"));
}
QStringList DeckPreviewTagDialog::getActiveTags() const
{
return activeTags_;
}
void DeckPreviewTagDialog::addTag()
{
QString newTag = newTagInput_->text().trimmed();
if (newTag.isEmpty()) {
QMessageBox::warning(this, tr("Invalid Input"), tr("Tag name cannot be empty!"));
return;
}
// Prevent duplicate tags
for (int i = 0; i < tagListView_->count(); ++i) {
auto *item = tagListView_->item(i);
auto *tagWidget = qobject_cast<DeckPreviewTagItemWidget *>(tagListView_->itemWidget(item));
if (tagWidget && tagWidget->checkBox()->text() == newTag) {
QMessageBox::warning(this, tr("Duplicate Tag"), tr("This tag already exists."));
return;
}
}
// Add the new tag
auto *item = new QListWidgetItem(tagListView_);
auto *tagWidget = new DeckPreviewTagItemWidget(newTag, true, this);
tagListView_->addItem(item);
tagListView_->setItemWidget(item, tagWidget);
activeTags_.append(newTag);
connect(tagWidget->checkBox(), &QCheckBox::toggled, this, &DeckPreviewTagDialog::onCheckboxStateChanged);
// Clear the input field
newTagInput_->clear();
}
void DeckPreviewTagDialog::filterTags(const QString &text)
{
for (int i = 0; i < tagListView_->count(); ++i) {
auto *item = tagListView_->item(i);
auto *tagWidget = qobject_cast<DeckPreviewTagItemWidget *>(tagListView_->itemWidget(item));
if (tagWidget) {
bool matches = tagWidget->checkBox()->text().contains(text, Qt::CaseInsensitive);
item->setHidden(!matches);
}
}
}
void DeckPreviewTagDialog::onCheckboxStateChanged()
{
activeTags_.clear();
for (int i = 0; i < tagListView_->count(); ++i) {
auto *item = tagListView_->item(i);
auto *tagWidget = qobject_cast<DeckPreviewTagItemWidget *>(tagListView_->itemWidget(item));
if (tagWidget && tagWidget->checkBox()->isChecked()) {
activeTags_.append(tagWidget->checkBox()->text());
}
}
}

View File

@ -0,0 +1,39 @@
#ifndef DECK_PREVIEW_TAG_DIALOG_H
#define DECK_PREVIEW_TAG_DIALOG_H
#include <QCheckBox>
#include <QDialog>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QPushButton>
#include <QStringList>
class DeckPreviewTagDialog : public QDialog
{
Q_OBJECT
public:
explicit DeckPreviewTagDialog(const QStringList &knownTags,
const QStringList &activeTags,
QWidget *parent = nullptr);
QStringList getActiveTags() const;
void filterTags(const QString &text);
private slots:
void addTag();
void onCheckboxStateChanged();
void retranslateUi();
private:
QLabel *instructionLabel;
QListWidget *tagListView_;
QLineEdit *filterInput_;
QLineEdit *newTagInput_;
QPushButton *addTagButton_;
QPushButton *okButton;
QPushButton *cancelButton;
QStringList activeTags_;
};
#endif // DECK_PREVIEW_TAG_DIALOG_H

View File

@ -0,0 +1,110 @@
#include "deck_preview_tag_display_widget.h"
#include <QFontMetrics>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QPainter>
DeckPreviewTagDisplayWidget::DeckPreviewTagDisplayWidget(QWidget *parent, const QString &_tagName)
: QWidget(parent), tagName(_tagName), isSelected(false)
{
// Create layout
auto *layout = new QHBoxLayout(this);
layout->setContentsMargins(5, 5, 5, 5);
layout->setSpacing(5);
// Add a stretch spacer for text and close button separation
// layout->addStretch(); // Ensures the close button stays at the far-right side
// Create close button
// closeButton = new QPushButton("x", this);
// closeButton->setFixedSize(16, 16); // Small square button
// closeButton->setFocusPolicy(Qt::NoFocus);
// Set font for close button to ensure the "x" appears correctly
// QFont closeButtonFont = closeButton->font();
// closeButtonFont.setPointSize(10); // Adjust the size to make the "x" clear
// closeButton->setFont(closeButtonFont);
// layout->addWidget(closeButton);
// Adjust widget size
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
// Connect close button to the remove signal
// connect(closeButton, &QPushButton::clicked, this, &DeckPreviewTagDisplayWidget::tagClosed);
}
QSize DeckPreviewTagDisplayWidget::sizeHint() const
{
// Calculate the size based on the tag name and close button
QFontMetrics fm(font());
int textWidth = fm.horizontalAdvance(tagName);
int width = textWidth + 50; // Add extra padding
int height = fm.height() + 10; // Height based on font size + padding
return QSize(width, height);
}
void DeckPreviewTagDisplayWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
setSelected(!isSelected);
emit tagClicked();
}
QWidget::mousePressEvent(event);
}
void DeckPreviewTagDisplayWidget::setSelected(bool selected)
{
isSelected = selected;
update(); // Trigger a repaint
}
void DeckPreviewTagDisplayWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
// Set background color
QColor backgroundColor = isSelected ? QColor(173, 216, 230) : Qt::white;
painter.setBrush(backgroundColor);
painter.setPen(Qt::NoPen);
// Draw background
painter.drawRect(rect());
// Draw border
QColor borderColor = isSelected ? Qt::blue : Qt::gray;
QPen borderPen(borderColor, isSelected ? 2 : 1);
painter.setPen(borderPen);
painter.drawRect(rect().adjusted(0, 0, -1, -1)); // Adjust for pen width
// Calculate font size based on widget height
QFont font = painter.font();
int fontSize = std::max(10, height() / 2); // Ensure a minimum font size of 10
font.setPointSize(fontSize);
painter.setFont(font);
// Calculate text rect to avoid overlap with the close button
// int closeButtonWidth = closeButton->width();
int margin = 10; // Left and right margins
QRect textRect(margin, 0, width() - margin * 2, height());
// Draw the text with a black border for better legibility
painter.setPen(Qt::black);
// Draw text border by offsetting
for (int dx = -1; dx <= 1; ++dx) {
for (int dy = -1; dy <= 1; ++dy) {
if (dx != 0 || dy != 0) {
painter.drawText(textRect.translated(dx, dy), Qt::AlignLeft | Qt::AlignVCenter, tagName);
}
}
}
// Draw the actual text
painter.setPen(Qt::white);
painter.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, tagName);
QWidget::paintEvent(event);
}

View File

@ -0,0 +1,67 @@
#ifndef DECK_PREVIEW_TAG_DISPLAY_WIDGET_H
#define DECK_PREVIEW_TAG_DISPLAY_WIDGET_H
#include <QLabel>
#include <QPushButton>
#include <QString>
#include <QWidget>
class DeckPreviewTagDisplayWidget : public QWidget
{
Q_OBJECT
public:
/**
* @brief Constructor for DeckPreviewTagDisplayWidget.
* @param parent The parent widget.
* @param tagName The name of the tag to display.
*/
explicit DeckPreviewTagDisplayWidget(QWidget *parent = nullptr, const QString &tagName = "");
QSize sizeHint() const override;
QString getTagName() const
{
return tagName;
}
bool getSelected() const
{
return isSelected;
}
/**
* @brief Sets the selected state of the tag.
* @param selected True if the tag is selected, false otherwise.
*/
void setSelected(bool selected);
signals:
/**
* @brief Emitted when the tag is clicked.
*/
void tagClicked();
/**
* @brief Emitted when the close button is clicked.
*/
void tagClosed();
protected:
/**
* @brief Custom paint event for drawing the widget.
* @param event The paint event.
*/
void paintEvent(QPaintEvent *event) override;
/**
* @brief Custom mouse press event handler.
* @param event The mouse event.
*/
void mousePressEvent(QMouseEvent *event) override;
private:
QLabel *tagLabel; ///< Label for displaying the tag name.
QPushButton *closeButton; ///< Button to close/remove the tag.
QString tagName; ///< The name of the tag.
bool isSelected; ///< Indicates whether the tag is selected.
};
#endif // DECK_PREVIEW_TAG_DISPLAY_WIDGET_H

View File

@ -0,0 +1,20 @@
#include "deck_preview_tag_item_widget.h"
DeckPreviewTagItemWidget::DeckPreviewTagItemWidget(const QString &tagName, bool isChecked, QWidget *parent)
: QWidget(parent), checkBox_(new QCheckBox(this))
{
auto *layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(5);
checkBox_->setText(tagName); // Set the tag name as the checkbox label
checkBox_->setChecked(isChecked); // Set the initial state of the checkbox
layout->addWidget(checkBox_); // Add the checkbox to the layout
setLayout(layout); // Set the layout of this widget
}
QCheckBox *DeckPreviewTagItemWidget::checkBox() const
{
return checkBox_; // Return the checkbox widget
}

View File

@ -0,0 +1,22 @@
#ifndef DECK_PREVIEW_TAG_ITEM_WIDGET_H
#define DECK_PREVIEW_TAG_ITEM_WIDGET_H
#include <QCheckBox>
#include <QHBoxLayout>
#include <QWidget>
class DeckPreviewTagItemWidget : public QWidget
{
Q_OBJECT
public:
// Constructor: Initializes the tag item widget with a tag name and initial checkbox state
DeckPreviewTagItemWidget(const QString &tagName, bool isChecked, QWidget *parent = nullptr);
// Accessor for the checkbox widget
QCheckBox *checkBox() const;
private:
QCheckBox *checkBox_; // Checkbox to represent the tag's state
};
#endif // DECK_PREVIEW_TAG_ITEM_WIDGET_H

View File

@ -0,0 +1,93 @@
#include "deck_preview_widget.h"
#include "../../../../../game/cards/card_database_manager.h"
#include "../../cards/deck_preview_card_picture_widget.h"
#include "deck_preview_deck_tags_display_widget.h"
#include <QFileInfo>
#include <QMouseEvent>
#include <QSet>
#include <QVBoxLayout>
DeckPreviewWidget::DeckPreviewWidget(VisualDeckStorageWidget *_parent, const QString &_filePath)
: QWidget(_parent), parent(_parent), filePath(_filePath)
{
layout = new QVBoxLayout(this);
setLayout(layout);
deckLoader = new DeckLoader();
deckLoader->loadFromFile(filePath, DeckLoader::CockatriceFormat);
auto bannerCard = deckLoader->getBannerCard().first.isEmpty()
? CardInfoPtr()
: CardDatabaseManager::getInstance()->getCardByNameAndProviderId(
deckLoader->getBannerCard().first, deckLoader->getBannerCard().second);
bannerCardDisplayWidget = new DeckPreviewCardPictureWidget(this);
connect(bannerCardDisplayWidget, &DeckPreviewCardPictureWidget::imageClicked, this,
&DeckPreviewWidget::imageClickedEvent);
connect(bannerCardDisplayWidget, &DeckPreviewCardPictureWidget::imageDoubleClicked, this,
&DeckPreviewWidget::imageDoubleClickedEvent);
bannerCardDisplayWidget->setCard(bannerCard);
bannerCardDisplayWidget->setOverlayText(
deckLoader->getName().isEmpty() ? QFileInfo(deckLoader->getLastFileName()).fileName() : deckLoader->getName());
bannerCardDisplayWidget->setFontSize(24);
setFilePath(deckLoader->getLastFileName());
colorIdentityWidget = new DeckPreviewColorIdentityWidget(getColorIdentity());
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader);
layout->addWidget(bannerCardDisplayWidget);
layout->addWidget(colorIdentityWidget);
layout->addWidget(deckTagsDisplayWidget);
}
QString DeckPreviewWidget::getColorIdentity()
{
QStringList cardList = deckLoader->getCardList();
if (cardList.isEmpty()) {
return {};
}
QSet<QChar> colorSet; // A set to collect unique color symbols (e.g., W, U, B, R, G)
for (const QString &cardName : cardList) {
CardInfoPtr currentCard = CardDatabaseManager::getInstance()->getCard(cardName);
if (currentCard) {
QString colors = currentCard->getColors(); // Assuming this returns something like "WUB"
for (const QChar &color : colors) {
colorSet.insert(color);
}
}
}
// Ensure the color identity is in WUBRG order
QString colorIdentity;
const QString wubrgOrder = "WUBRG";
for (const QChar &color : wubrgOrder) {
if (colorSet.contains(color)) {
colorIdentity.append(color);
}
}
return colorIdentity;
}
void DeckPreviewWidget::setFilePath(const QString &_filePath)
{
filePath = _filePath;
}
void DeckPreviewWidget::imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance)
{
Q_UNUSED(instance);
emit deckPreviewClicked(event, this);
}
void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance)
{
Q_UNUSED(instance);
emit deckPreviewDoubleClicked(event, this);
}

View File

@ -0,0 +1,40 @@
#ifndef DECK_PREVIEW_WIDGET_H
#define DECK_PREVIEW_WIDGET_H
#include "../../../../../deck/deck_loader.h"
#include "../../cards/deck_preview_card_picture_widget.h"
#include "../visual_deck_storage_widget.h"
#include "deck_preview_color_identity_widget.h"
#include "deck_preview_deck_tags_display_widget.h"
#include <QVBoxLayout>
#include <QWidget>
class VisualDeckStorageWidget;
class DeckPreviewDeckTagsDisplayWidget;
class DeckPreviewWidget final : public QWidget
{
Q_OBJECT
public:
explicit DeckPreviewWidget(VisualDeckStorageWidget *_parent, const QString &_filePath);
QString getColorIdentity();
VisualDeckStorageWidget *parent;
QVBoxLayout *layout;
QString filePath;
DeckLoader *deckLoader;
DeckPreviewCardPictureWidget *bannerCardDisplayWidget;
DeckPreviewColorIdentityWidget *colorIdentityWidget;
DeckPreviewDeckTagsDisplayWidget *deckTagsDisplayWidget;
signals:
void deckPreviewClicked(QMouseEvent *event, DeckPreviewWidget *instance);
void deckPreviewDoubleClicked(QMouseEvent *event, DeckPreviewWidget *instance);
public slots:
void setFilePath(const QString &filePath);
void imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
};
#endif // DECK_PREVIEW_WIDGET_H

View File

@ -38,22 +38,23 @@ QString VisualDeckStorageSearchWidget::getSearchText()
return searchBar->text();
}
QStringList VisualDeckStorageSearchWidget::filterFiles(const QStringList &files, const QString &searchText)
QList<DeckPreviewWidget *> VisualDeckStorageSearchWidget::filterFiles(QList<DeckPreviewWidget *> &widgets,
const QString &searchText)
{
if (searchText.isEmpty() || searchText.isNull()) {
return files;
return widgets;
}
QStringList filteredFiles;
QList<DeckPreviewWidget *> filteredWidgets;
for (const auto &file : files) {
QFileInfo fileInfo(file);
for (const auto &file : widgets) {
QFileInfo fileInfo(file->filePath);
QString fileName = fileInfo.fileName().toLower();
if (fileName.contains(searchText.toLower())) {
filteredFiles << file;
filteredWidgets << file;
}
}
return filteredFiles;
return filteredWidgets;
}

View File

@ -16,7 +16,7 @@ class VisualDeckStorageSearchWidget : public QWidget
public:
explicit VisualDeckStorageSearchWidget(VisualDeckStorageWidget *parent);
QString getSearchText();
QStringList filterFiles(const QStringList &files, const QString &searchText);
QList<DeckPreviewWidget *> filterFiles(QList<DeckPreviewWidget *> &widgets, const QString &searchText);
private:
QHBoxLayout *layout;

View File

@ -0,0 +1,93 @@
#include "visual_deck_storage_sort_widget.h"
#include "../../../../settings/cache_settings.h"
/**
* @brief Constructs a PrintingSelectorCardSortWidget for searching cards by set name or set code.
*
* This widget provides a search bar that allows users to search for cards by either their set name
* or set code. It uses a debounced timer to trigger the search action after the user stops typing.
*
* @param parent The parent PrintingSelector widget that will handle the search results.
*/
VisualDeckStorageSortWidget::VisualDeckStorageSortWidget(VisualDeckStorageWidget *parent)
: parent(parent), sortOrder(Alphabetical)
{
layout = new QHBoxLayout(this);
setLayout(layout);
// Initialize the ComboBox
sortComboBox = new QComboBox(this);
layout->addWidget(sortComboBox);
// Set the current sort order
sortComboBox->setCurrentIndex(SettingsCache::instance().getVisualDeckStorageSortingOrder());
// Connect sorting change signal to refresh the file list
connect(sortComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&VisualDeckStorageSortWidget::updateSortOrder);
connect(this, &VisualDeckStorageSortWidget::sortOrderChanged, parent, &VisualDeckStorageWidget::updateSortOrder);
retranslateUi();
}
void VisualDeckStorageSortWidget::retranslateUi()
{
// Block signals to avoid triggering unnecessary updates while changing text
sortComboBox->blockSignals(true);
// Clear and repopulate the ComboBox with translated items
sortComboBox->clear();
sortComboBox->addItem(tr("Sort Alphabetically (Deck Name)"), ByName);
sortComboBox->addItem(tr("Sort Alphabetically (Filename)"), Alphabetical);
sortComboBox->addItem(tr("Sort by Last Modified"), ByLastModified);
sortComboBox->addItem(tr("Sort by Last Loaded"), ByLastLoaded);
// Restore the current index
sortComboBox->setCurrentIndex(SettingsCache::instance().getVisualDeckStorageSortingOrder());
// Re-enable signals
sortComboBox->blockSignals(false);
}
void VisualDeckStorageSortWidget::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
sortComboBox->setCurrentIndex(SettingsCache::instance().getVisualDeckStorageSortingOrder());
updateSortOrder();
}
void VisualDeckStorageSortWidget::updateSortOrder()
{
sortOrder = static_cast<SortOrder>(sortComboBox->currentIndex());
SettingsCache::instance().setVisualDeckStorageSortingOrder(sortComboBox->currentIndex());
emit sortOrderChanged();
}
QList<DeckPreviewWidget *> &VisualDeckStorageSortWidget::filterFiles(QList<DeckPreviewWidget *> &widgets)
{
// Sort the widgets list based on the current sort order
std::sort(widgets.begin(), widgets.end(), [this](DeckPreviewWidget *widget1, DeckPreviewWidget *widget2) {
if (!widget1 || !widget2) {
return false; // Handle null pointers gracefully
}
QFileInfo info1(widget1->filePath);
QFileInfo info2(widget2->filePath);
switch (sortOrder) {
case ByName:
return widget1->deckLoader->getName() < widget2->deckLoader->getName();
case Alphabetical:
return info1.fileName().toLower() < info2.fileName().toLower();
case ByLastModified:
return info1.lastModified() > info2.lastModified();
case ByLastLoaded:
return widget1->deckLoader->getLastLoadedTimestamp() > widget2->deckLoader->getLastLoadedTimestamp();
}
return false; // Default case, no sorting applied
});
return widgets;
}

View File

@ -0,0 +1,42 @@
#ifndef VISUAL_DECK_STORAGE_SORT_WIDGET_H
#define VISUAL_DECK_STORAGE_SORT_WIDGET_H
#include "visual_deck_storage_widget.h"
#include <QComboBox>
#include <QHBoxLayout>
#include <QWidget>
class VisualDeckStorageWidget;
class VisualDeckStorageSortWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDeckStorageSortWidget(VisualDeckStorageWidget *parent);
void retranslateUi();
void updateSortOrder();
QString getSearchText();
QList<DeckPreviewWidget *> &filterFiles(QList<DeckPreviewWidget *> &widgets);
public slots:
void showEvent(QShowEvent *event) override;
signals:
void sortOrderChanged();
private:
enum SortOrder
{
ByName,
Alphabetical,
ByLastModified,
ByLastLoaded,
};
QHBoxLayout *layout;
VisualDeckStorageWidget *parent;
SortOrder sortOrder; // Current sorting option
QComboBox *sortComboBox;
};
#endif // VISUAL_DECK_STORAGE_SORT_WIDGET_H

View File

@ -0,0 +1,101 @@
#include "visual_deck_storage_tag_filter_widget.h"
#include "../general/layout_containers/flow_widget.h"
#include "deck_preview/deck_preview_tag_addition_widget.h"
#include "deck_preview/deck_preview_tag_display_widget.h"
#include "deck_preview/deck_preview_widget.h"
#include <QHBoxLayout>
#include <QLabel>
VisualDeckStorageTagFilterWidget::VisualDeckStorageTagFilterWidget(VisualDeckStorageWidget *_parent)
: QWidget(_parent), parent(_parent)
{
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
// Create layout
auto *layout = new QHBoxLayout(this);
layout->setContentsMargins(5, 5, 5, 5);
layout->setSpacing(5);
setFixedHeight(100);
auto *flowWidget = new FlowWidget(this, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
}
QList<DeckPreviewWidget *>
VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QList<DeckPreviewWidget *> &deckPreviews) const
{
// Collect selected tags from DeckPreviewTagDisplayWidget
QStringList selectedTags;
foreach (DeckPreviewTagDisplayWidget *tagWidget, findChildren<DeckPreviewTagDisplayWidget *>()) {
if (tagWidget->getSelected()) {
selectedTags.append(tagWidget->getTagName());
}
}
// If no tags are selected, return all decks
if (selectedTags.isEmpty()) {
return deckPreviews;
}
// Filter DeckPreviewWidgets that contain all of the selected tags
QList<DeckPreviewWidget *> filteredDecks;
for (DeckPreviewWidget *deckPreview : deckPreviews) {
QStringList deckTags = deckPreview->deckLoader->getTags();
// Check if all selectedTags are in deckTags
bool allTagsPresent = std::all_of(selectedTags.begin(), selectedTags.end(),
[&deckTags](const QString &tag) { return deckTags.contains(tag); });
if (allTagsPresent) {
filteredDecks.append(deckPreview);
}
}
return filteredDecks;
}
void VisualDeckStorageTagFilterWidget::removeTagsNotInList(const QStringList &tags)
{
// Iterate through all DeckPreviewTagDisplayWidgets
foreach (DeckPreviewTagDisplayWidget *tagWidget, findChildren<DeckPreviewTagDisplayWidget *>()) {
// If the tag is not in the provided tags list, remove the widget
if (!tags.contains(tagWidget->getTagName())) {
auto *flowWidget = findChild<FlowWidget *>();
flowWidget->removeWidget(tagWidget);
tagWidget->deleteLater(); // Safely delete the widget
}
}
}
void VisualDeckStorageTagFilterWidget::addTagsIfNotPresent(const QStringList &tags)
{
for (const QString &tag : tags) {
addTagIfNotPresent(tag);
}
}
void VisualDeckStorageTagFilterWidget::addTagIfNotPresent(const QString &tag)
{
// Check if the tag already exists in the flow widget
bool tagExists = false;
foreach (DeckPreviewTagDisplayWidget *tagWidget, findChildren<DeckPreviewTagDisplayWidget *>()) {
if (tagWidget->getTagName() == tag) {
tagExists = true;
break;
}
}
// If the tag doesn't exist, add a new DeckPreviewTagDisplayWidget
if (!tagExists) {
auto *newTagWidget = new DeckPreviewTagDisplayWidget(this, tag);
connect(newTagWidget, &DeckPreviewTagDisplayWidget::tagClicked, parent,
&VisualDeckStorageWidget::refreshBannerCards);
auto *flowWidget = findChild<FlowWidget *>();
flowWidget->addWidget(newTagWidget);
}
}

View File

@ -0,0 +1,23 @@
#ifndef VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H
#define VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H
#include "visual_deck_storage_widget.h"
#include <QWidget>
class VisualDeckStorageWidget;
class VisualDeckStorageTagFilterWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDeckStorageTagFilterWidget(VisualDeckStorageWidget *_parent);
void refreshTags();
QList<DeckPreviewWidget *> filterDecksBySelectedTags(const QList<DeckPreviewWidget *> &deckPreviews) const;
void removeTagsNotInList(const QStringList &tags);
void addTagsIfNotPresent(const QStringList &tags);
void addTagIfNotPresent(const QString &tag);
VisualDeckStorageWidget *parent;
};
#endif // VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H

View File

@ -3,14 +3,17 @@
#include "../../../../deck/deck_loader.h"
#include "../../../../game/cards/card_database_manager.h"
#include "../../../../settings/cache_settings.h"
#include "deck_preview/deck_preview_widget.h"
#include "visual_deck_storage_search_widget.h"
#include "visual_deck_storage_sort_widget.h"
#include "visual_deck_storage_tag_filter_widget.h"
#include <QComboBox>
#include <QDirIterator>
#include <QMouseEvent>
#include <QVBoxLayout>
VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(parent), sortOrder(Alphabetical)
VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(parent)
{
deckListModel = new DeckListModel(this);
deckListModel->setObjectName("visualDeckModel");
@ -18,27 +21,24 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare
layout = new QVBoxLayout();
setLayout(layout);
// ComboBox for sorting options
sortComboBox = new QComboBox(this);
sortComboBox->addItem("Sort Alphabetically (Filename)", Alphabetical);
sortComboBox->addItem("Sort by Last Modified", ByLastModified);
sortComboBox->setCurrentIndex(SettingsCache::instance().getVisualDeckStorageSortingOrder());
searchAndSortLayout = new QHBoxLayout();
sortWidget = new VisualDeckStorageSortWidget(this);
searchWidget = new VisualDeckStorageSearchWidget(this);
tagFilterWidget = new VisualDeckStorageTagFilterWidget(this);
deckPreviewColorIdentityFilterWidget = new DeckPreviewColorIdentityFilterWidget(this);
// Add combo box to the main layout
layout->addWidget(sortComboBox);
layout->addWidget(searchWidget);
searchAndSortLayout->addWidget(deckPreviewColorIdentityFilterWidget);
searchAndSortLayout->addWidget(sortWidget);
searchAndSortLayout->addWidget(searchWidget);
layout->addLayout(searchAndSortLayout);
layout->addWidget(tagFilterWidget);
flowWidget = new FlowWidget(this, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
cardSizeWidget = new CardSizeWidget(this, flowWidget, SettingsCache::instance().getVisualDeckStorageCardSize());
layout->addWidget(cardSizeWidget);
// Connect sorting change signal to refresh the file list
connect(sortComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&VisualDeckStorageWidget::updateSortOrder);
}
void VisualDeckStorageWidget::showEvent(QShowEvent *event)
@ -49,24 +49,23 @@ void VisualDeckStorageWidget::showEvent(QShowEvent *event)
void VisualDeckStorageWidget::updateSortOrder()
{
sortOrder = static_cast<SortOrder>(sortComboBox->currentData().toInt());
SettingsCache::instance().setVisualDeckStorageSortingOrder(sortComboBox->currentData().toInt());
refreshBannerCards(); // Refresh the banner cards with the new sort order
}
void VisualDeckStorageWidget::imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance)
void VisualDeckStorageWidget::deckPreviewClickedEvent(QMouseEvent *event, DeckPreviewWidget *instance)
{
emit imageClicked(event, instance);
emit deckPreviewClicked(event, instance);
}
void VisualDeckStorageWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance)
void VisualDeckStorageWidget::deckPreviewDoubleClickedEvent(QMouseEvent *event, DeckPreviewWidget *instance)
{
emit imageDoubleClicked(event, instance);
emit deckPreviewDoubleClicked(event, instance);
}
void VisualDeckStorageWidget::refreshBannerCards()
{
QStringList allFiles;
QList<DeckPreviewWidget *> allDecks;
// QDirIterator with QDir::Files and QDir::NoSymLinks ensures only files are listed (no directories or symlinks)
QDirIterator it(SettingsCache::instance().getDeckPath(), QDir::Files | QDir::NoSymLinks,
@ -76,46 +75,71 @@ void VisualDeckStorageWidget::refreshBannerCards()
allFiles << it.next(); // Add each file path to the list
}
// Sort files based on the current sort order
std::sort(allFiles.begin(), allFiles.end(), [this](const QString &file1, const QString &file2) {
QFileInfo info1(file1);
QFileInfo info2(file2);
foreach (const QString &file, allFiles) {
auto *display = new DeckPreviewWidget(this, file);
switch (sortOrder) {
case Alphabetical:
return info1.fileName().toLower() < info2.fileName().toLower();
case ByLastModified:
return info1.lastModified() > info2.lastModified();
}
connect(display, &DeckPreviewWidget::deckPreviewClicked, this,
&VisualDeckStorageWidget::deckPreviewClickedEvent);
connect(display, &DeckPreviewWidget::deckPreviewDoubleClicked, this,
&VisualDeckStorageWidget::deckPreviewDoubleClickedEvent);
connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, display->bannerCardDisplayWidget,
&CardInfoPictureWidget::setScaleFactor);
display->bannerCardDisplayWidget->setScaleFactor(cardSizeWidget->getSlider()->value());
allDecks.append(display);
}
return false; // Default case
});
auto filteredByColorIdentity =
deckPreviewColorIdentityFilterWidget->filterWidgets(sortWidget->filterFiles(allDecks));
auto filteredByTags = tagFilterWidget->filterDecksBySelectedTags(filteredByColorIdentity);
auto filteredFiles = searchWidget->filterFiles(filteredByTags, searchWidget->getSearchText());
auto filteredFiles = searchWidget->filterFiles(allFiles, searchWidget->getSearchText());
tagFilterWidget->removeTagsNotInList(gatherAllTags(filteredFiles));
tagFilterWidget->addTagsIfNotPresent(gatherAllTags(filteredFiles));
flowWidget->clearLayout(); // Clear existing widgets in the flow layout
foreach (const QString &file, filteredFiles) {
auto deckLoader = new DeckLoader();
deckLoader->loadFromFile(file, DeckLoader::CockatriceFormat);
deckListModel->setDeckList(new DeckLoader(*deckLoader));
auto *display = new DeckPreviewCardPictureWidget(flowWidget, false);
auto bannerCard = deckLoader->getBannerCard().isEmpty()
? CardInfoPtr()
: CardDatabaseManager::getInstance()->getCard(deckLoader->getBannerCard());
display->setCard(bannerCard);
display->setOverlayText(deckLoader->getName().isEmpty() ? QFileInfo(deckLoader->getLastFileName()).fileName()
: deckLoader->getName());
display->setFontSize(24);
display->setFilePath(deckLoader->getLastFileName());
connect(display, &DeckPreviewCardPictureWidget::imageClicked, this,
&VisualDeckStorageWidget::imageClickedEvent);
connect(display, &DeckPreviewCardPictureWidget::imageDoubleClicked, this,
&VisualDeckStorageWidget::imageDoubleClickedEvent);
connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, display, &CardInfoPictureWidget::setScaleFactor);
display->setScaleFactor(cardSizeWidget->getSlider()->value());
flowWidget->addWidget(display);
foreach (DeckPreviewWidget *deck, filteredFiles) {
flowWidget->addWidget(deck);
}
emit bannerCardsRefreshed();
}
QStringList VisualDeckStorageWidget::gatherAllTagsFromFlowWidget() const
{
QStringList allTags;
if (flowWidget) {
// Iterate through all DeckPreviewWidgets
foreach (DeckPreviewWidget *display, flowWidget->findChildren<DeckPreviewWidget *>()) {
// Get tags from each DeckPreviewWidget
QStringList tags = display->deckLoader->getTags();
// Add tags to the list while avoiding duplicates
allTags.append(tags);
}
}
// Remove duplicates by calling 'removeDuplicates'
allTags.removeDuplicates();
return allTags;
}
QStringList VisualDeckStorageWidget::gatherAllTags(const QList<DeckPreviewWidget *> &allDecks)
{
QStringList allTags;
// Iterate through all decks provided as input
for (DeckPreviewWidget *deck : allDecks) {
QStringList tags = deck->deckLoader->getTags();
// Add tags to the list while avoiding duplicates
allTags.append(tags);
}
// Remove duplicates
allTags.removeDuplicates();
return allTags;
}

View File

@ -3,15 +3,20 @@
#include "../../../../deck/deck_list_model.h"
#include "../../../../deck/deck_view.h"
#include "../../../ui/widgets/cards/deck_preview_card_picture_widget.h"
#include "../../../ui/widgets/general/layout_containers/flow_widget.h"
#include "../cards/card_size_widget.h"
#include "deck_preview/deck_preview_color_identity_filter_widget.h"
#include "deck_preview/deck_preview_widget.h"
#include "visual_deck_storage_search_widget.h"
#include "visual_deck_storage_sort_widget.h"
#include "visual_deck_storage_tag_filter_widget.h"
#include <QComboBox>
#include <QFileSystemModel>
class VisualDeckStorageSearchWidget;
class VisualDeckStorageSortWidget;
class VisualDeckStorageTagFilterWidget;
class DeckPreviewColorIdentityFilterWidget;
class VisualDeckStorageWidget final : public QWidget
{
Q_OBJECT
@ -20,31 +25,30 @@ public:
void retranslateUi();
public slots:
void imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void deckPreviewClickedEvent(QMouseEvent *event, DeckPreviewWidget *instance);
void deckPreviewDoubleClickedEvent(QMouseEvent *event, DeckPreviewWidget *instance);
void refreshBannerCards(); // Refresh the display of cards based on the current sorting option
QStringList gatherAllTagsFromFlowWidget() const;
QStringList gatherAllTags(const QList<DeckPreviewWidget *> &allDecks);
void showEvent(QShowEvent *event) override;
void updateSortOrder();
signals:
void imageClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void imageDoubleClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void bannerCardsRefreshed();
void deckPreviewClicked(QMouseEvent *event, DeckPreviewWidget *instance);
void deckPreviewDoubleClicked(QMouseEvent *event, DeckPreviewWidget *instance);
private:
enum SortOrder
{
Alphabetical,
ByLastModified
};
QVBoxLayout *layout;
QHBoxLayout *searchAndSortLayout;
FlowWidget *flowWidget;
DeckListModel *deckListModel;
QMap<QString, DeckViewCardContainer *> cardContainers;
SortOrder sortOrder; // Current sorting option
QComboBox *sortComboBox;
VisualDeckStorageSortWidget *sortWidget;
VisualDeckStorageSearchWidget *searchWidget;
VisualDeckStorageTagFilterWidget *tagFilterWidget;
DeckPreviewColorIdentityFilterWidget *deckPreviewColorIdentityFilterWidget;
CardSizeWidget *cardSizeWidget;
};

View File

@ -7,8 +7,12 @@
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QRegularExpression>
#include <QStringList>
#include <qloggingcategory.h>
Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader")
const QStringList DeckLoader::fileNameFilters = QStringList()
<< QObject::tr("Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck)")
@ -34,7 +38,7 @@ DeckLoader::DeckLoader(const DeckLoader &other)
{
}
bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt)
bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
@ -48,9 +52,9 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt)
break;
case CockatriceFormat: {
result = loadFromFile_Native(&file);
qDebug() << "Loaded from" << fileName << "-" << result;
qCDebug(DeckLoaderLog) << "Loaded from" << fileName << "-" << result;
if (!result) {
qDebug() << "Retying as plain format";
qCDebug(DeckLoaderLog) << "Retrying as plain format";
file.seek(0);
result = loadFromFile_Plain(&file);
fmt = PlainTextFormat;
@ -65,11 +69,14 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt)
if (result) {
lastFileName = fileName;
lastFileFormat = fmt;
if (userRequest) {
updateLastLoadedTimestamp(fileName, fmt);
}
emit deckLoaded();
}
qDebug() << "Deck was loaded -" << result;
qCDebug(DeckLoaderLog) << "Deck was loaded -" << result;
return result;
}
@ -110,6 +117,59 @@ bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt)
return result;
}
bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt)
{
QFileInfo fileInfo(fileName);
if (!fileInfo.exists()) {
qCWarning(DeckLoaderLog) << "File does not exist:" << fileName;
return false;
}
QDateTime originalTimestamp = fileInfo.lastModified();
// Open the file for writing
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qCWarning(DeckLoaderLog) << "Failed to open file for writing:" << fileName;
return false;
}
bool result = false;
// Perform file modifications
switch (fmt) {
case PlainTextFormat:
break;
case CockatriceFormat:
setLastLoadedTimestamp(QDateTime::currentDateTime().toString());
result = saveToFile_Native(&file);
break;
}
file.close(); // Close the file to ensure changes are flushed
if (result) {
lastFileName = fileName;
lastFileFormat = fmt;
// Re-open the file and set the original timestamp
if (!file.open(QIODevice::ReadWrite)) {
qCWarning(DeckLoaderLog) << "Failed to re-open file to set timestamp:" << fileName;
return false;
}
if (!file.setFileTime(originalTimestamp, QFileDevice::FileModificationTime)) {
qCWarning(DeckLoaderLog) << "Failed to set modification time for file:" << fileName;
file.close();
return false;
}
file.close();
}
return result;
}
// This struct is here to support the forEachCard function call, defined in decklist. It
// requires a function to be called for each card, and passes an inner node and a card for
// each card in the decklist.

View File

@ -42,9 +42,10 @@ public:
static FileFormat getFormatFromName(const QString &fileName);
bool loadFromFile(const QString &fileName, FileFormat fmt);
bool loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest = false);
bool loadFromRemote(const QString &nativeString, int remoteDeckId);
bool saveToFile(const QString &fileName, FileFormat fmt);
bool updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt);
QString exportDeckToDecklist();
void resolveSetNameAndNumberToProviderID();

View File

@ -350,9 +350,26 @@ AppearanceSettingsPage::AppearanceSettingsPage()
connect(&showVisualDeckStorageOnLoadCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings,
&SettingsCache::setVisualDeckStorageShowOnLoad);
visualDeckStorageDrawUnusedColorIdentitiesCheckBox.setChecked(
settings.getVisualDeckStorageDrawUnusedColorIdentities());
connect(&visualDeckStorageDrawUnusedColorIdentitiesCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings,
&SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities);
visualDeckStorageUnusedColorIdentitiesOpacitySpinBox.setMinimum(0);
visualDeckStorageUnusedColorIdentitiesOpacitySpinBox.setMaximum(100);
visualDeckStorageUnusedColorIdentitiesOpacitySpinBox.setValue(
settings.getVisualDeckStorageUnusedColorIdentitiesOpacity());
connect(&visualDeckStorageUnusedColorIdentitiesOpacitySpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
&settings, &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity);
visualDeckStorageUnusedColorIdentitiesOpacityLabel.setBuddy(&visualDeckStorageUnusedColorIdentitiesOpacitySpinBox);
auto *menuGrid = new QGridLayout;
menuGrid->addWidget(&showShortcutsCheckBox, 0, 0);
menuGrid->addWidget(&showVisualDeckStorageOnLoadCheckBox, 1, 0);
menuGrid->addWidget(&visualDeckStorageDrawUnusedColorIdentitiesCheckBox, 2, 0);
menuGrid->addWidget(&visualDeckStorageUnusedColorIdentitiesOpacityLabel, 3, 0);
menuGrid->addWidget(&visualDeckStorageUnusedColorIdentitiesOpacitySpinBox, 3, 1);
menuGroupBox = new QGroupBox;
menuGroupBox->setLayout(menuGrid);
@ -488,6 +505,10 @@ void AppearanceSettingsPage::retranslateUi()
menuGroupBox->setTitle(tr("Menu settings"));
showShortcutsCheckBox.setText(tr("Show keyboard shortcuts in right-click menus"));
showVisualDeckStorageOnLoadCheckBox.setText(tr("Show visual deck storage on database load"));
visualDeckStorageDrawUnusedColorIdentitiesCheckBox.setText(
tr("Draw missing color identities in visual deck storage without color label"));
visualDeckStorageUnusedColorIdentitiesOpacityLabel.setText(tr("Missing color identity opacity"));
visualDeckStorageUnusedColorIdentitiesOpacitySpinBox.setSuffix("%");
cardsGroupBox->setTitle(tr("Card rendering"));
displayCardNamesCheckBox.setText(tr("Display card names on cards having a picture"));

View File

@ -96,6 +96,9 @@ private:
QLabel maxFontSizeForCardsLabel;
QCheckBox showShortcutsCheckBox;
QCheckBox showVisualDeckStorageOnLoadCheckBox;
QCheckBox visualDeckStorageDrawUnusedColorIdentitiesCheckBox;
QLabel visualDeckStorageUnusedColorIdentitiesOpacityLabel;
QSpinBox visualDeckStorageUnusedColorIdentitiesOpacitySpinBox;
QCheckBox displayCardNamesCheckBox;
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox;

View File

@ -262,6 +262,10 @@ SettingsCache::SettingsCache()
visualDeckStorageCardSize = settings->value("cards/visualdeckstoragecardsize", 100).toInt();
visualDeckStorageShowOnLoad = settings->value("interface/visualdeckstorageshowonload", true).toBool();
visualDeckStorageSortingOrder = settings->value("interface/visualdeckstoragesortingorder", 0).toInt();
visualDeckStorageDrawUnusedColorIdentities =
settings->value("interface/visualdeckstoragedrawunusedcoloridentities", true).toBool();
visualDeckStorageUnusedColorIdentitiesOpacity =
settings->value("interface/visualdeckstorageunusedcoloridentitiesopacity", 15).toInt();
horizontalHand = settings->value("hand/horizontal", true).toBool();
invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool();
minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 4).toInt();
@ -631,6 +635,20 @@ void SettingsCache::setVisualDeckStorageShowOnLoad(QT_STATE_CHANGED_T _visualDec
settings->setValue("interface/visualdeckstorageshowonload", visualDeckStorageShowOnLoad);
}
void SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities(
QT_STATE_CHANGED_T _visualDeckStorageDrawUnusedColorIdentities)
{
visualDeckStorageDrawUnusedColorIdentities = _visualDeckStorageDrawUnusedColorIdentities;
settings->setValue("cards/visualdeckstoragedrawunusedcoloridentities", visualDeckStorageDrawUnusedColorIdentities);
}
void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visualDeckStorageUnusedColorIdentitiesOpacity)
{
visualDeckStorageUnusedColorIdentitiesOpacity = _visualDeckStorageUnusedColorIdentitiesOpacity;
settings->setValue("cards/visualdeckstorageunusedcoloridentitiesopacity",
visualDeckStorageUnusedColorIdentitiesOpacity);
}
void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand)
{
horizontalHand = static_cast<bool>(_horizontalHand);

View File

@ -126,6 +126,8 @@ private:
bool printingSelectorNavigationButtonsVisible;
int visualDeckStorageSortingOrder;
int visualDeckStorageCardSize;
bool visualDeckStorageDrawUnusedColorIdentities;
int visualDeckStorageUnusedColorIdentitiesOpacity;
bool visualDeckStorageShowOnLoad;
bool horizontalHand;
bool invertVerticalCoordinate;
@ -381,6 +383,14 @@ public:
{
return visualDeckStorageCardSize;
}
bool getVisualDeckStorageDrawUnusedColorIdentities() const
{
return visualDeckStorageDrawUnusedColorIdentities;
}
int getVisualDeckStorageUnusedColorIdentitiesOpacity() const
{
return visualDeckStorageUnusedColorIdentitiesOpacity;
}
bool getVisualDeckStorageShowOnLoad() const
{
return visualDeckStorageShowOnLoad;
@ -695,6 +705,8 @@ public slots:
void setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible);
void setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder);
void setVisualDeckStorageCardSize(int _visualDeckStorageCardSize);
void setVisualDeckStorageDrawUnusedColorIdentities(QT_STATE_CHANGED_T _visualDeckStorageDrawUnusedColorIdentities);
void setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visualDeckStorageUnusedColorIdentitiesOpacity);
void setVisualDeckStorageShowOnLoad(QT_STATE_CHANGED_T _visualDeckStorageShowOnLoad);
void setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand);
void setInvertVerticalCoordinate(QT_STATE_CHANGED_T _invertVerticalCoordinate);

View File

@ -368,7 +368,8 @@ DeckList::DeckList()
// TODO: https://qt-project.org/doc/qt-4.8/qobject.html#no-copy-constructor-or-assignment-operator
DeckList::DeckList(const DeckList &other)
: QObject(), name(other.name), comments(other.comments), bannerCard(other.bannerCard), deckHash(other.deckHash)
: QObject(), name(other.name), comments(other.comments), bannerCard(other.bannerCard), deckHash(other.deckHash),
lastLoadedTimestamp(other.lastLoadedTimestamp)
{
root = new InnerDecklistNode(other.getRoot());
@ -419,25 +420,37 @@ bool DeckList::readElement(QXmlStreamReader *xml)
{
const QString childName = xml->name().toString();
if (xml->isStartElement()) {
if (childName == "deckname")
if (childName == "lastLoadedTimestamp") {
lastLoadedTimestamp = xml->readElementText();
} else if (childName == "deckname") {
name = xml->readElementText();
else if (childName == "comments")
} else if (childName == "comments") {
comments = xml->readElementText();
else if (childName == "bannerCard") {
bannerCard = xml->readElementText();
qDebug() << "Deckloader found the banner card " << bannerCard;
} else if (childName == "bannerCard") {
QString providerId = xml->attributes().value("providerId").toString();
QString cardName = xml->readElementText();
bannerCard = QPair<QString, QString>(cardName, providerId);
} else if (childName == "tags") {
tags.clear(); // Clear existing tags
while (xml->readNextStartElement()) {
if (xml->name().toString() == "tag") {
tags.append(xml->readElementText());
}
}
} else if (childName == "zone") {
InnerDecklistNode *newZone = getZoneObjFromName(xml->attributes().value("name").toString());
newZone->readElement(xml);
} else if (childName == "sideboard_plan") {
SideboardPlan *newSideboardPlan = new SideboardPlan;
if (newSideboardPlan->readElement(xml))
if (newSideboardPlan->readElement(xml)) {
sideboardPlans.insert(newSideboardPlan->getName(), newSideboardPlan);
else
} else {
delete newSideboardPlan;
}
}
} else if (xml->isEndElement() && (childName == "cockatrice_deck"))
} else if (xml->isEndElement() && (childName == "cockatrice_deck")) {
return false;
}
return true;
}
@ -445,17 +458,33 @@ void DeckList::write(QXmlStreamWriter *xml)
{
xml->writeStartElement("cockatrice_deck");
xml->writeAttribute("version", "1");
xml->writeTextElement("lastLoadedTimestamp", lastLoadedTimestamp);
xml->writeTextElement("deckname", name);
xml->writeTextElement("comments", comments);
xml->writeTextElement("bannerCard", bannerCard);
for (int i = 0; i < root->size(); i++)
root->at(i)->writeElement(xml);
QMapIterator<QString, SideboardPlan *> i(sideboardPlans);
while (i.hasNext())
i.next().value()->write(xml);
xml->writeStartElement("bannerCard");
xml->writeAttribute("providerId", bannerCard.second);
xml->writeCharacters(bannerCard.first);
xml->writeEndElement();
xml->writeTextElement("comments", comments);
// Write tags
xml->writeStartElement("tags");
for (const QString &tag : tags) {
xml->writeTextElement("tag", tag);
}
xml->writeEndElement();
// Write zones
for (int i = 0; i < root->size(); i++) {
root->at(i)->writeElement(xml);
}
// Write sideboard plans
QMapIterator<QString, SideboardPlan *> i(sideboardPlans);
while (i.hasNext()) {
i.next().value()->write(xml);
}
xml->writeEndElement(); // Close "cockatrice_deck"
}
bool DeckList::loadFromXml(QXmlStreamReader *xml)

View File

@ -250,8 +250,11 @@ class DeckList : public QObject
{
Q_OBJECT
private:
QString name, comments, bannerCard;
QString name, comments;
QPair<QString, QString> bannerCard;
QString deckHash;
QString lastLoadedTimestamp;
QStringList tags;
QMap<QString, SideboardPlan *> sideboardPlans;
InnerDecklistNode *root;
void getCardListHelper(InnerDecklistNode *node, QSet<QString> &result) const;
@ -279,10 +282,26 @@ public slots:
{
comments = _comments;
}
void setBannerCard(const QString &_bannerCard = QString())
void setTags(const QStringList &_tags = QStringList())
{
tags = _tags;
}
void addTag(const QString &_tag)
{
tags.append(_tag);
}
void clearTags()
{
tags.clear();
}
void setBannerCard(const QPair<QString, QString> &_bannerCard = QPair<QString, QString>())
{
bannerCard = _bannerCard;
}
void setLastLoadedTimestamp(const QString &_lastLoadedTimestamp = QString())
{
lastLoadedTimestamp = _lastLoadedTimestamp;
}
public:
explicit DeckList();
@ -297,10 +316,18 @@ public:
{
return comments;
}
QString getBannerCard() const
QStringList getTags() const
{
return tags;
}
QPair<QString, QString> getBannerCard() const
{
return bannerCard;
}
QString getLastLoadedTimestamp() const
{
return lastLoadedTimestamp;
}
QList<MoveCard_ToZone> getCurrentSideboardPlan();
void setCurrentSideboardPlan(const QList<MoveCard_ToZone> &plan);
const QMap<QString, SideboardPlan *> &getSideboardPlans() const

View File

@ -193,6 +193,14 @@ void SettingsCache::setVisualDeckStorageCardSize(int /* _visualDeckStorageCardSi
void SettingsCache::setVisualDeckStorageShowOnLoad(QT_STATE_CHANGED_T /* _visualDeckStorageShowOnLoad */)
{
}
void SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities(
QT_STATE_CHANGED_T /* _visualDeckStorageDrawUnusedColorIdentities */)
{
}
void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity(
int /* _visualDeckStorageUnusedColorIdentitiesOpacity */)
{
}
void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */)
{
}

View File

@ -197,6 +197,14 @@ void SettingsCache::setVisualDeckStorageCardSize(int /* _visualDeckStorageCardSi
void SettingsCache::setVisualDeckStorageShowOnLoad(QT_STATE_CHANGED_T /* _visualDeckStorageShowOnLoad */)
{
}
void SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities(
QT_STATE_CHANGED_T /* _visualDeckStorageDrawUnusedColorIdentities */)
{
}
void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity(
int /* _visualDeckStorageUnusedColorIdentitiesOpacity */)
{
}
void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */)
{
}