Introduce a tagging system.

This commit is contained in:
Lukas Brübach 2025-01-12 19:01:46 +01:00
parent 7d76221e39
commit 7b3ded013e
22 changed files with 760 additions and 20 deletions

View file

@ -161,12 +161,19 @@ set(cockatrice_SOURCES
src/game/zones/view_zone_widget.cpp
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/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

@ -87,6 +87,11 @@ void FlowWidget::addWidget(QWidget *widget_to_add) const
flowLayout->addWidget(widget_to_add);
}
void FlowWidget::removeWidget(QWidget *widget_to_remove) const
{
flowLayout->removeWidget(widget_to_remove);
}
/**
* @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 *widget_to_remove) const;
void clearLayout();
[[nodiscard]] int count() const;
[[nodiscard]] QLayoutItem *itemAt(int index) const;

View file

@ -17,8 +17,8 @@ void DeckPreviewColorCircleWidget::resizeEvent(QResizeEvent *event)
// Get the parent of the DeckPreviewColorIdentityWidget
QWidget *identityParent = parentWidget() ? parentWidget()->parentWidget() : nullptr;
if (identityParent) {
// Calculate the circle diameter as 10% of the parent's height
int maxSize = identityParent->height() * 0.1;
// 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
@ -124,6 +124,8 @@ DeckPreviewColorIdentityWidget::DeckPreviewColorIdentityWidget(const QString &co
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";

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, "Add 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,49 @@
#include "deck_preview_tag_addition_widget.h"
#include "deck_preview_tag_dialog.h"
#include <QHBoxLayout>
#include <QMouseEvent>
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);
// Create label for the tag name
tagLabel_ = new QLabel(tagName_, this);
layout->addWidget(tagLabel_);
// Create close button
closeButton_ = new QPushButton("x", this);
closeButton_->setFixedSize(16, 16); // Small square button
layout->addWidget(closeButton_);
// Adjust widget size
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
// Connect close button to the remove signal
connect(closeButton_, &QPushButton::clicked, this, &DeckPreviewTagAdditionWidget::tagClosed);
}
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();
qDebug() << "Updated active tags:" << updatedTags;
parent->deckLoader->setTags(updatedTags);
parent->deckLoader->saveToFile(parent->parent->filePath, DeckLoader::CockatriceFormat);
}
}

View file

@ -0,0 +1,31 @@
#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);
signals:
void tagClicked(); // Emitted when the tag is clicked
void tagClosed(); // Emitted when the close button is clicked
protected:
void mousePressEvent(QMouseEvent *event) override;
private:
DeckPreviewDeckTagsDisplayWidget *parent;
QString tagName_;
QLabel *tagLabel_;
QPushButton *closeButton_;
};
#endif // DECK_PREVIEW_TAG_ADDITION_WIDGET_H

View file

@ -0,0 +1,120 @@
#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)
{
setWindowTitle("Deck Tags Manager");
resize(400, 500);
QStringList defaultTags = {"🏃Aggro", "🧙‍ Control", "⚔️ Midrange", "🌀 Combo",
"🪓 Mill", "🐉 Tribal", "🔒 Stax"};
// Merge knownTags with defaultTags, ensuring no duplicates
QStringList combinedTags = knownTags + defaultTags + activeTags;
combinedTags.removeDuplicates();
// Main layout
auto *mainLayout = new QVBoxLayout(this);
// Instruction label
auto *instructionLabel =
new QLabel("Manage your deck tags. Check or uncheck tags as needed, or add new ones:", 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::checkStateChanged, this,
&DeckPreviewTagDialog::onCheckboxStateChanged);
}
// Add tag input layout
auto *addTagLayout = new QHBoxLayout();
newTagInput_ = new QLineEdit(this);
newTagInput_->setPlaceholderText("Add a new tag (e.g., Aggro)");
addTagButton_ = new QPushButton("Add Tag", 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();
auto *okButton = new QPushButton("OK", this);
auto *cancelButton = new QPushButton("Cancel", 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);
}
QStringList DeckPreviewTagDialog::getActiveTags() const
{
return activeTags_;
}
void DeckPreviewTagDialog::addTag()
{
QString newTag = newTagInput_->text().trimmed();
if (newTag.isEmpty()) {
QMessageBox::warning(this, "Invalid Input", "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, "Duplicate Tag", "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::checkStateChanged, this, &DeckPreviewTagDialog::onCheckboxStateChanged);
// Clear the input field
newTagInput_->clear();
}
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,31 @@
#ifndef DECK_PREVIEW_TAG_DIALOG_H
#define DECK_PREVIEW_TAG_DIALOG_H
#include <QDialog>
#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;
private slots:
void addTag();
void onCheckboxStateChanged();
private:
QListWidget *tagListView_;
QLineEdit *newTagInput_;
QPushButton *addTagButton_;
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 + closeButton->width() + 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() - closeButtonWidth - 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

@ -2,13 +2,15 @@
#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(QWidget *parent, const QString &_filePath) : QWidget(parent), filePath(_filePath)
DeckPreviewWidget::DeckPreviewWidget(VisualDeckStorageWidget *_parent, const QString &_filePath)
: QWidget(_parent), parent(_parent), filePath(_filePath)
{
layout = new QVBoxLayout(this);
setLayout(layout);
@ -35,9 +37,11 @@ DeckPreviewWidget::DeckPreviewWidget(QWidget *parent, const QString &_filePath)
setFilePath(deckLoader->getLastFileName());
colorIdentityWidget = new DeckPreviewColorIdentityWidget(getColorIdentity());
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader);
layout->addWidget(bannerCardDisplayWidget);
layout->addWidget(colorIdentityWidget);
layout->addWidget(deckTagsDisplayWidget);
}
QString DeckPreviewWidget::getColorIdentity()

View file

@ -3,23 +3,29 @@
#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(QWidget *parent, const QString &_filePath);
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);

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

@ -6,6 +6,7 @@
#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>
@ -24,11 +25,13 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare
sortWidget = new VisualDeckStorageSortWidget(this);
searchWidget = new VisualDeckStorageSearchWidget(this);
tagFilterWidget = new VisualDeckStorageTagFilterWidget(this);
deckPreviewColorIdentityFilterWidget = new DeckPreviewColorIdentityFilterWidget(this);
searchAndSortLayout->addWidget(sortWidget);
searchAndSortLayout->addWidget(searchWidget);
layout->addLayout(searchAndSortLayout);
layout->addWidget(tagFilterWidget);
layout->addWidget(deckPreviewColorIdentityFilterWidget);
flowWidget = new FlowWidget(this, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
@ -73,7 +76,7 @@ void VisualDeckStorageWidget::refreshBannerCards()
}
foreach (const QString &file, allFiles) {
auto *display = new DeckPreviewWidget(flowWidget, file);
auto *display = new DeckPreviewWidget(this, file);
connect(display, &DeckPreviewWidget::deckPreviewClicked, this,
&VisualDeckStorageWidget::deckPreviewClickedEvent);
@ -87,11 +90,56 @@ void VisualDeckStorageWidget::refreshBannerCards()
auto filteredByColorIdentity =
deckPreviewColorIdentityFilterWidget->filterWidgets(sortWidget->filterFiles(allDecks));
auto filteredFiles = searchWidget->filterFiles(filteredByColorIdentity, searchWidget->getSearchText());
auto filteredByTags = tagFilterWidget->filterDecksBySelectedTags(filteredByColorIdentity);
auto filteredFiles = searchWidget->filterFiles(filteredByTags, searchWidget->getSearchText());
tagFilterWidget->removeTagsNotInList(gatherAllTags(filteredFiles));
tagFilterWidget->addTagsIfNotPresent(gatherAllTags(filteredFiles));
flowWidget->clearLayout(); // Clear existing widgets in the flow layout
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

@ -9,11 +9,13 @@
#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 <QFileSystemModel>
class VisualDeckStorageSearchWidget;
class VisualDeckStorageSortWidget;
class VisualDeckStorageTagFilterWidget;
class DeckPreviewColorIdentityFilterWidget;
class VisualDeckStorageWidget final : public QWidget
{
@ -26,10 +28,13 @@ public slots:
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 bannerCardsRefreshed();
void deckPreviewClicked(QMouseEvent *event, DeckPreviewWidget *instance);
void deckPreviewDoubleClicked(QMouseEvent *event, DeckPreviewWidget *instance);
@ -42,6 +47,7 @@ private:
VisualDeckStorageSortWidget *sortWidget;
VisualDeckStorageSearchWidget *searchWidget;
VisualDeckStorageTagFilterWidget *tagFilterWidget;
DeckPreviewColorIdentityFilterWidget *deckPreviewColorIdentityFilterWidget;
CardSizeWidget *cardSizeWidget;
};

View file

@ -422,26 +422,35 @@ bool DeckList::readElement(QXmlStreamReader *xml)
if (xml->isStartElement()) {
if (childName == "lastLoadedTimestamp") {
lastLoadedTimestamp = xml->readElementText();
} else if (childName == "deckname")
} else if (childName == "deckname") {
name = xml->readElementText();
else if (childName == "comments")
} else if (childName == "comments") {
comments = xml->readElementText();
else if (childName == "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->isEndElement() && xml->name() == "tags")) {
if (xml->readNextStartElement() && xml->name() == "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;
}
@ -457,14 +466,25 @@ void DeckList::write(QXmlStreamWriter *xml)
xml->writeEndElement();
xml->writeTextElement("comments", comments);
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);
// 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

@ -254,6 +254,7 @@ private:
QPair<QString, QString> bannerCard;
QString deckHash;
QString lastLoadedTimestamp;
QStringList tags;
QMap<QString, SideboardPlan *> sideboardPlans;
InnerDecklistNode *root;
void getCardListHelper(InnerDecklistNode *node, QSet<QString> &result) const;
@ -281,6 +282,18 @@ public slots:
{
comments = _comments;
}
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;
@ -303,6 +316,10 @@ public:
{
return comments;
}
QStringList getTags() const
{
return tags;
}
QPair<QString, QString> getBannerCard() const
{
return bannerCard;