* move message_log_widget to game

* move files

* update headers

* fix cmakelists

* oracle fixes

* split implementation out to cpp

* fix recursive import

* fix main file

* format
This commit is contained in:
ebbit1q 2025-09-20 14:35:52 +02:00 committed by GitHub
parent f484c98152
commit 17dcaf9afa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
337 changed files with 728 additions and 721 deletions

View file

@ -0,0 +1,121 @@
#include "deck_preview_color_identity_filter_widget.h"
#include "../../cards/additional_info/mana_symbol_widget.h"
#include "deck_preview_widget.h"
#include <QMouseEvent>
#include <QPainter>
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 *manaSymbol = new ManaSymbolWidget(this, color, false, true);
manaSymbol->setFixedWidth(25);
layout->addWidget(manaSymbol);
// Initialize the activeColors map
activeColors[color] = false;
// Connect the color toggled signal
connect(manaSymbol, &ManaSymbolWidget::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::updateColorFilter);
connect(this, &DeckPreviewColorIdentityFilterWidget::filterModeChanged, parent,
&VisualDeckStorageWidget::updateColorFilter);
// 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"));
toggleButton->setToolTip(tr("Color identity filter mode (AND/OR/NOT conjunctions of filters)"));
}
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);
}
void DeckPreviewColorIdentityFilterWidget::filterWidgets(QList<DeckPreviewWidget *> widgets)
{
// 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) {
for (DeckPreviewWidget *previewWidget : widgets) {
previewWidget->filteredByColor = false;
}
}
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;
}
}
}
widget->filteredByColor = !matchesFilter;
}
}

View file

@ -0,0 +1,39 @@
#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 DeckPreviewColorIdentityFilterWidget : public QWidget
{
Q_OBJECT
public:
explicit DeckPreviewColorIdentityFilterWidget(VisualDeckStorageWidget *parent);
void retranslateUi();
void 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,188 @@
#include "deck_preview_deck_tags_display_widget.h"
#include "../../../../dialogs/dlg_convert_deck_to_cod_format.h"
#include "../../../../settings/cache_settings.h"
#include "../../../../tabs/tab_deck_editor.h"
#include "../../general/layout_containers/flow_widget.h"
#include "deck_preview_tag_addition_widget.h"
#include "deck_preview_tag_dialog.h"
#include "deck_preview_tag_display_widget.h"
#include "deck_preview_widget.h"
#include <QDirIterator>
#include <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList)
: QWidget(_parent), deckList(nullptr)
{
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
// Create layout
auto *layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
setFixedHeight(100);
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
if (_deckList) {
connectDeckList(_deckList);
}
layout->addWidget(flowWidget);
}
void DeckPreviewDeckTagsDisplayWidget::connectDeckList(DeckList *_deckList)
{
if (deckList) {
disconnect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags);
}
deckList = _deckList;
connect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags);
refreshTags();
}
void DeckPreviewDeckTagsDisplayWidget::refreshTags()
{
flowWidget->clearLayout();
for (const QString &tag : deckList->getTags()) {
flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag));
}
auto tagAdditionWidget = new DeckPreviewTagAdditionWidget(this, tr("Edit tags ..."));
connect(tagAdditionWidget, &DeckPreviewTagAdditionWidget::tagClicked, this,
&DeckPreviewDeckTagsDisplayWidget::openTagEditDlg);
flowWidget->addWidget(tagAdditionWidget);
}
/**
* Gets the filepath of all files (no directories) in target directory and all subdirectories
*/
static QStringList getAllFiles(const QString &filePath)
{
QStringList allFiles;
// QDirIterator with QDir::Files ensures only files are listed (no directories)
QDirIterator it(filePath, QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
while (it.hasNext()) {
allFiles << it.next(); // Add each file path to the list
}
return allFiles;
}
bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath)
{
QFileInfo fileInfo(filePath);
QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod");
if (QFile::exists(newFileName)) {
QMessageBox::StandardButton reply =
QMessageBox::question(parent, QObject::tr("Overwrite Existing File?"),
QObject::tr("A .cod version of this deck already exists. Overwrite it?"),
QMessageBox::Yes | QMessageBox::No);
return reply == QMessageBox::Yes;
}
return true; // Safe to proceed
}
void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg()
{
if (qobject_cast<DeckPreviewWidget *>(parentWidget())) {
auto *deckPreviewWidget = qobject_cast<DeckPreviewWidget *>(parentWidget());
QStringList knownTags = deckPreviewWidget->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags();
QStringList activeTags = deckList->getTags();
bool canAddTags = true;
if (DeckLoader::getFormatFromName(deckPreviewWidget->filePath) != DeckLoader::CockatriceFormat) {
canAddTags = false;
// Retrieve saved preference if the prompt is disabled
if (!SettingsCache::instance().getVisualDeckStoragePromptForConversion()) {
if (SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) {
if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath))
return;
deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath);
deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastFileName();
deckPreviewWidget->refreshBannerCardText();
canAddTags = true;
}
} else {
// Show the dialog to the user
DialogConvertDeckToCodFormat conversionDialog(parentWidget());
if (conversionDialog.exec() == QDialog::Accepted) {
if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath))
return;
deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath);
deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastFileName();
deckPreviewWidget->refreshBannerCardText();
canAddTags = true;
if (conversionDialog.dontAskAgain()) {
SettingsCache::instance().setVisualDeckStoragePromptForConversion(false);
SettingsCache::instance().setVisualDeckStorageAlwaysConvert(true);
}
} else {
SettingsCache::instance().setVisualDeckStorageAlwaysConvert(false);
if (conversionDialog.dontAskAgain()) {
SettingsCache::instance().setVisualDeckStoragePromptForConversion(false);
} else {
SettingsCache::instance().setVisualDeckStoragePromptForConversion(true);
}
}
}
}
if (canAddTags) {
DeckPreviewTagDialog dialog(knownTags, activeTags);
if (dialog.exec() == QDialog::Accepted) {
QStringList updatedTags = dialog.getActiveTags();
deckList->setTags(updatedTags);
deckPreviewWidget->deckLoader->saveToFile(deckPreviewWidget->filePath, DeckLoader::CockatriceFormat);
}
}
} else if (parentWidget()) {
// If we're the child of an AbstractTabDeckEditor, we are buried under a ton of childWidgets in the
// DeckInfoDock.
QWidget *currentParent = parentWidget();
while (currentParent) {
if (qobject_cast<AbstractTabDeckEditor *>(currentParent)) {
break;
}
currentParent = currentParent->parentWidget();
}
if (qobject_cast<AbstractTabDeckEditor *>(currentParent)) {
auto *deckEditor = qobject_cast<AbstractTabDeckEditor *>(currentParent);
QStringList knownTags;
QStringList allFiles = getAllFiles(SettingsCache::instance().getDeckPath());
DeckLoader loader;
for (const QString &file : allFiles) {
loader.loadFromFile(file, DeckLoader::getFormatFromName(file), false);
QStringList tags = loader.getTags();
knownTags.append(tags);
knownTags.removeDuplicates();
}
QStringList activeTags = deckList->getTags();
DeckPreviewTagDialog dialog(knownTags, activeTags);
if (dialog.exec() == QDialog::Accepted) {
QStringList updatedTags = dialog.getActiveTags();
deckList->setTags(updatedTags);
deckEditor->setModified(true);
}
}
}
}

View file

@ -0,0 +1,26 @@
#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>
inline bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath);
class DeckPreviewWidget;
class DeckPreviewDeckTagsDisplayWidget : public QWidget
{
Q_OBJECT
public:
explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList);
void connectDeckList(DeckList *_deckList);
void refreshTags();
DeckList *deckList;
FlowWidget *flowWidget;
public slots:
void openTagEditDlg();
};
#endif // DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H

View file

@ -0,0 +1,89 @@
#include "deck_preview_tag_addition_widget.h"
#include "../../../../settings/cache_settings.h"
#include "../../../../tabs/abstract_tab_deck_editor.h"
#include "deck_preview_tag_dialog.h"
#include <QFontMetrics>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QPainter>
#include <utility>
DeckPreviewTagAdditionWidget::DeckPreviewTagAdditionWidget(QWidget *_parent, QString _tagName)
: QWidget(_parent), tagName_(std::move(_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 {width, height};
}
void DeckPreviewTagAdditionWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
emit tagClicked();
}
QWidget::mousePressEvent(event);
}
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,30 @@
#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(QWidget *_parent, QString _tagName);
[[nodiscard]] 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:
QString tagName_;
};
#endif // DECK_PREVIEW_TAG_ADDITION_WIDGET_H

View file

@ -0,0 +1,191 @@
#include "deck_preview_tag_dialog.h"
#include "../../../../dialogs/dlg_default_tags_editor.h"
#include "../../../../settings/cache_settings.h"
#include "deck_preview_tag_item_widget.h"
#include <QCheckBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QTimer>
#include <QVBoxLayout>
DeckPreviewTagDialog::DeckPreviewTagDialog(const QStringList &knownTags,
const QStringList &_activeTags,
QWidget *parent)
: QDialog(parent), activeTags(_activeTags), knownTags_(knownTags)
{
resize(400, 500);
QStringList defaultTags = SettingsCache::instance().getVisualDeckStorageDefaultTagsList();
// Merge knownTags with defaultTags, ensuring no duplicates
QStringList combinedTags = defaultTags + knownTags + activeTags;
combinedTags.removeDuplicates();
// Main layout
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);
editButton = new QPushButton(this);
connect(editButton, &QPushButton::clicked, this, [this]() {
auto *editor = new DlgDefaultTagsEditor(this);
editor->setAttribute(Qt::WA_DeleteOnClose);
editor->setModal(true);
editor->show();
QTimer::singleShot(0, editor, [editor]() {
editor->raise();
editor->activateWindow();
});
});
addTagLayout->addWidget(newTagInput);
addTagLayout->addWidget(addTagButton);
addTagLayout->addWidget(editButton);
mainLayout->addLayout(addTagLayout);
connect(addTagButton, &QPushButton::clicked, this, &DeckPreviewTagDialog::addTag);
connect(newTagInput, &QLineEdit::textChanged, this,
[this](const QString &text) { addTagButton->setEnabled(!text.trimmed().isEmpty()); });
// OK and Cancel buttons
buttonLayout = new QHBoxLayout(this);
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);
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageDefaultTagsListChanged, this,
&DeckPreviewTagDialog::refreshTagList);
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"));
editButton->setText(tr("Edit default tags"));
cancelButton->setText(tr("Cancel"));
}
void DeckPreviewTagDialog::refreshTagList()
{
// First, clear the current tags in the list view
tagListView->clear();
// Get the updated list of tags from SettingsCache
QStringList defaultTags = SettingsCache::instance().getVisualDeckStorageDefaultTagsList();
QStringList combinedTags = defaultTags + knownTags_ + activeTags;
combinedTags.removeDuplicates();
// Re-populate the tag list view
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);
}
}
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,46 @@
#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>
#include <QVBoxLayout>
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();
void refreshTagList();
private:
QVBoxLayout *mainLayout;
QLabel *instructionLabel;
QListWidget *tagListView;
QLineEdit *filterInput;
QHBoxLayout *addTagLayout;
QLineEdit *newTagInput;
QPushButton *addTagButton;
QHBoxLayout *buttonLayout;
QPushButton *okButton;
QPushButton *editButton;
QPushButton *cancelButton;
QStringList activeTags;
QStringList knownTags_;
};
#endif // DECK_PREVIEW_TAG_DIALOG_H

View file

@ -0,0 +1,132 @@
#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), state(TagState::NotSelected)
{
// 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)
{
switch (event->button()) {
case Qt::LeftButton:
setState(state != TagState::Selected ? TagState::Selected : TagState::NotSelected);
break;
case Qt::RightButton:
setState(state != TagState::Excluded ? TagState::Excluded : TagState::NotSelected);
break;
case Qt::MiddleButton:
setState(TagState::NotSelected);
break;
default:
break;
}
emit tagClicked();
QWidget::mousePressEvent(event);
}
void DeckPreviewTagDisplayWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QColor backgroundColor;
QColor borderColor;
int borderWidth;
switch (state) {
case TagState::Selected:
backgroundColor = QColor(173, 216, 230); // Light blue
borderColor = Qt::blue;
borderWidth = 2;
break;
case TagState::Excluded:
backgroundColor = QColor(255, 182, 193); // Light red/pink
borderColor = Qt::red;
borderWidth = 2;
break;
case TagState::NotSelected:
default:
backgroundColor = Qt::white;
borderColor = Qt::gray;
borderWidth = 1;
break;
}
painter.setBrush(backgroundColor);
painter.setPen(Qt::NoPen);
painter.drawRect(rect());
QPen borderPen(borderColor, borderWidth);
painter.setPen(borderPen);
painter.drawRect(rect().adjusted(0, 0, -1, -1));
// 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,74 @@
#ifndef DECK_PREVIEW_TAG_DISPLAY_WIDGET_H
#define DECK_PREVIEW_TAG_DISPLAY_WIDGET_H
#include <QLabel>
#include <QPushButton>
#include <QString>
#include <QWidget>
enum class TagState
{
NotSelected,
Selected,
Excluded
};
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;
}
TagState getState() const
{
return state;
}
void setState(const TagState newState)
{
state = newState;
update();
};
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.
TagState state; ///< Indicates whether the tag is unselected, selected, or excluded.
};
#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,441 @@
#include "deck_preview_widget.h"
#include "../../../../database/card_database_manager.h"
#include "../../../../settings/cache_settings.h"
#include "../../cards/additional_info/color_identity_widget.h"
#include "../../cards/deck_preview_card_picture_widget.h"
#include "deck_preview_deck_tags_display_widget.h"
#include <QClipboard>
#include <QFileInfo>
#include <QInputDialog>
#include <QMenu>
#include <QMessageBox>
#include <QMouseEvent>
#include <QSet>
#include <QStandardItemModel>
#include <QVBoxLayout>
DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent,
VisualDeckStorageWidget *_visualDeckStorageWidget,
const QString &_filePath)
: QWidget(_parent), visualDeckStorageWidget(_visualDeckStorageWidget), filePath(_filePath),
colorIdentityWidget(nullptr), deckTagsDisplayWidget(nullptr)
{
layout = new QVBoxLayout(this);
setLayout(layout);
deckLoader = new DeckLoader();
deckLoader->setParent(this);
connect(deckLoader, &DeckLoader::loadFinished, this, &DeckPreviewWidget::initializeUi);
/* TODO: We shouldn't update the tags on *every* deck load, since it's kinda expensive. We should instead count how
many deck loads have finished already and if we've loaded all decks and THEN load all the tags at once. */
connect(deckLoader, &DeckLoader::loadFinished, visualDeckStorageWidget->tagFilterWidget,
&VisualDeckStorageTagFilterWidget::refreshTags);
deckLoader->loadFromFileAsync(filePath, DeckLoader::getFormatFromName(filePath), false);
bannerCardDisplayWidget =
new DeckPreviewCardPictureWidget(this, false, visualDeckStorageWidget->deckPreviewSelectionAnimationEnabled);
connect(bannerCardDisplayWidget, &DeckPreviewCardPictureWidget::imageClicked, this,
&DeckPreviewWidget::imageClickedEvent);
connect(bannerCardDisplayWidget, &DeckPreviewCardPictureWidget::imageDoubleClicked, this,
&DeckPreviewWidget::imageDoubleClickedEvent);
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageShowTagsOnDeckPreviewsChanged, this,
&DeckPreviewWidget::updateTagsVisibility);
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageShowBannerCardComboBoxChanged, this,
&DeckPreviewWidget::updateBannerCardComboBoxVisibility);
connect(visualDeckStorageWidget->settings(), &VisualDeckStorageQuickSettingsWidget::deckPreviewTooltipChanged, this,
&DeckPreviewWidget::refreshBannerCardToolTip);
layout->addWidget(bannerCardDisplayWidget);
}
void DeckPreviewWidget::retranslateUi()
{
bannerCardLabel->setText(tr("Banner Card"));
}
void DeckPreviewWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
if (bannerCardDisplayWidget == nullptr) {
return;
}
QList<QWidget *> widgets = findChildren<QWidget *>();
for (QWidget *widget : widgets) {
widget->setMaximumWidth(bannerCardDisplayWidget->width());
}
}
void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess)
{
if (!deckLoadSuccess) {
return;
}
auto bannerCard = deckLoader->getBannerCard().name.isEmpty()
? ExactCard()
: CardDatabaseManager::getInstance()->getCard(deckLoader->getBannerCard());
bannerCardDisplayWidget->setCard(bannerCard);
bannerCardDisplayWidget->setFontSize(24);
setFilePath(deckLoader->getLastFileName());
colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity());
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader);
bannerCardLabel = new QLabel(this);
bannerCardLabel->setObjectName("bannerCardLabel");
bannerCardComboBox = new QComboBox(this);
bannerCardComboBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
bannerCardComboBox->setObjectName("bannerCardComboBox");
bannerCardComboBox->setCurrentText(deckLoader->getBannerCard().name);
bannerCardComboBox->installEventFilter(new NoScrollFilter());
connect(bannerCardComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&DeckPreviewWidget::setBannerCard);
updateBannerCardComboBox();
updateBannerCardComboBoxVisibility(SettingsCache::instance().getVisualDeckStorageShowBannerCardComboBox());
updateTagsVisibility(SettingsCache::instance().getVisualDeckStorageShowTagsOnDeckPreviews());
layout->addWidget(colorIdentityWidget);
layout->addWidget(deckTagsDisplayWidget);
layout->addWidget(bannerCardLabel);
layout->addWidget(bannerCardComboBox);
refreshBannerCardText();
retranslateUi();
}
void DeckPreviewWidget::updateVisibility()
{
setHidden(!checkVisibility());
}
bool DeckPreviewWidget::checkVisibility() const
{
if (filteredBySearch || filteredByColor || filteredByTags) {
return false;
}
return true;
}
void DeckPreviewWidget::updateBannerCardComboBoxVisibility(bool visible)
{
if (bannerCardComboBox == nullptr) {
return;
}
if (visible) {
bannerCardComboBox->setVisible(true);
bannerCardLabel->setVisible(true);
} else {
bannerCardComboBox->setHidden(true);
bannerCardLabel->setHidden(true);
}
}
void DeckPreviewWidget::updateTagsVisibility(bool visible)
{
if (deckTagsDisplayWidget == nullptr) {
return;
}
if (visible) {
deckTagsDisplayWidget->setVisible(true);
} else {
deckTagsDisplayWidget->setHidden(true);
}
}
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()->getCardInfo(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;
}
/**
* The display name is given by the deck name, or the filename if the deck name is not set.
*/
QString DeckPreviewWidget::getDisplayName() const
{
return deckLoader->getName().isEmpty() ? QFileInfo(deckLoader->getLastFileName()).fileName()
: deckLoader->getName();
}
void DeckPreviewWidget::setFilePath(const QString &_filePath)
{
filePath = _filePath;
}
/**
* Refreshes the banner card text.
* This also calls `refreshBannerCardToolTip`, since those two often need to be updated together.
*/
void DeckPreviewWidget::refreshBannerCardText()
{
bannerCardDisplayWidget->setOverlayText(getDisplayName());
refreshBannerCardToolTip();
}
void DeckPreviewWidget::refreshBannerCardToolTip()
{
auto type = visualDeckStorageWidget->settings()->getDeckPreviewTooltip();
switch (type) {
case VisualDeckStorageQuickSettingsWidget::TooltipType::None:
bannerCardDisplayWidget->setToolTip("");
break;
case VisualDeckStorageQuickSettingsWidget::TooltipType::Filepath:
bannerCardDisplayWidget->setToolTip(filePath);
break;
}
}
void DeckPreviewWidget::updateBannerCardComboBox()
{
// Store the current text of the combo box
QString currentText = bannerCardComboBox->currentText();
// Block signals temporarily
bool wasBlocked = bannerCardComboBox->blockSignals(true);
bannerCardComboBox->setUpdatesEnabled(false);
// Clear the existing items in the combo box
bannerCardComboBox->clear();
// Prepare the new items with deduplication
QSet<QPair<QString, QString>> bannerCardSet;
InnerDecklistNode *listRoot = deckLoader->getRoot();
for (auto i : *listRoot) {
auto *currentZone = dynamic_cast<InnerDecklistNode *>(i);
for (auto j : *currentZone) {
auto *currentCard = dynamic_cast<DecklistCardNode *>(j);
if (!currentCard)
continue;
for (int k = 0; k < currentCard->getNumber(); ++k) {
bannerCardSet.insert(QPair<QString, QString>(currentCard->getName(), currentCard->getCardProviderId()));
}
}
}
QList<QPair<QString, QString>> pairList = bannerCardSet.values();
// 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();
});
// This is *slightly* more performant than using addItem in a loop.
QStandardItemModel *model = new QStandardItemModel(pairList.size(), 1, bannerCardComboBox);
int row = 0;
for (const auto &pair : pairList) {
QStandardItem *item = new QStandardItem(pair.first);
item->setData(QVariant::fromValue(pair), Qt::UserRole);
model->setItem(row++, 0, item);
}
bannerCardComboBox->setModel(model);
// Try to restore the previous selection by finding the currentText
int restoredIndex = bannerCardComboBox->findText(currentText);
if (restoredIndex != -1) {
bannerCardComboBox->setCurrentIndex(restoredIndex);
} else {
// Add a placeholder "-" and set it as the current selection
int bannerIndex = bannerCardComboBox->findText(deckLoader->getBannerCard().name);
if (bannerIndex != -1) {
bannerCardComboBox->setCurrentIndex(bannerIndex);
} else {
bannerCardComboBox->insertItem(0, "-");
bannerCardComboBox->setCurrentIndex(0);
}
}
// Restore the previous signal blocking state
bannerCardComboBox->blockSignals(wasBlocked);
bannerCardComboBox->setUpdatesEnabled(true);
}
void DeckPreviewWidget::setBannerCard(int /* changedIndex */)
{
auto [name, id] = bannerCardComboBox->currentData().value<QPair<QString, QString>>();
CardRef cardRef = {name, id};
deckLoader->setBannerCard(cardRef);
deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath));
bannerCardDisplayWidget->setCard(CardDatabaseManager::getInstance()->getCard(cardRef));
}
void DeckPreviewWidget::imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance)
{
Q_UNUSED(instance);
if (event && event->button() == Qt::RightButton) {
createRightClickMenu()->popup(QCursor::pos());
}
}
void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance)
{
Q_UNUSED(event);
Q_UNUSED(instance);
emit deckLoadRequested(filePath);
}
QMenu *DeckPreviewWidget::createRightClickMenu()
{
auto *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
connect(menu->addAction(tr("Open in deck editor")), &QAction::triggered, this,
[this] { emit openDeckEditor(deckLoader); });
connect(menu->addAction(tr("Edit Tags")), &QAction::triggered, deckTagsDisplayWidget,
&DeckPreviewDeckTagsDisplayWidget::openTagEditDlg);
addSetBannerCardMenu(menu);
menu->addSeparator();
connect(menu->addAction(tr("Rename Deck")), &QAction::triggered, this, &DeckPreviewWidget::actRenameDeck);
auto saveToClipboardMenu = menu->addMenu(tr("Save Deck to Clipboard"));
connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this,
[this] { deckLoader->saveToClipboard(true, true); });
connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this,
[this] { deckLoader->saveToClipboard(true, false); });
connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this,
[this] { deckLoader->saveToClipboard(false, true); });
connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this,
[this] { deckLoader->saveToClipboard(false, false); });
menu->addSeparator();
connect(menu->addAction(tr("Rename File")), &QAction::triggered, this, &DeckPreviewWidget::actRenameFile);
connect(menu->addAction(tr("Delete File")), &QAction::triggered, this, &DeckPreviewWidget::actDeleteFile);
return menu;
}
/**
* Adds the "Set Banner Card" submenu to the given menu. Does nothing if bannerCardComboBox is null.
* @param menu The menu to add the submenu to
*/
void DeckPreviewWidget::addSetBannerCardMenu(QMenu *menu)
{
if (!bannerCardComboBox) {
return;
}
auto bannerCardMenu = menu->addMenu(tr("Set Banner Card"));
for (int i = 0; i < bannerCardComboBox->count(); ++i) {
auto action = bannerCardMenu->addAction(bannerCardComboBox->itemText(i));
connect(action, &QAction::triggered, this, [this, i] { bannerCardComboBox->setCurrentIndex(i); });
// the checkability is purely for visuals
action->setCheckable(true);
action->setChecked(bannerCardComboBox->currentIndex() == i);
}
}
void DeckPreviewWidget::actRenameDeck()
{
// read input
const QString oldName = deckLoader->getName();
bool ok;
QString newName = QInputDialog::getText(this, "Rename deck", tr("New name:"), QLineEdit::Normal, oldName, &ok);
if (!ok || oldName == newName) {
return;
}
// write change
deckLoader->setName(newName);
deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath));
// update VDS
refreshBannerCardText();
}
void DeckPreviewWidget::actRenameFile()
{
// read input
const auto info = QFileInfo(filePath);
const QString oldName = info.baseName();
bool ok;
QString newName = QInputDialog::getText(this, "Rename file", tr("New name:"), QLineEdit::Normal, oldName, &ok);
if (!ok || newName.isEmpty() || oldName == newName) {
return;
}
QString newFileName = newName;
if (!info.suffix().isEmpty()) {
newFileName += "." + info.suffix();
}
// write change
const QString newFilePath = QFileInfo(info.dir(), newFileName).filePath();
if (!QFile::rename(info.filePath(), newFilePath)) {
QMessageBox::critical(this, tr("Error"), tr("Rename failed"));
return;
}
deckLoader->setLastFileName(newFilePath);
// update VDS
setFilePath(newFilePath);
refreshBannerCardText();
}
void DeckPreviewWidget::actDeleteFile()
{
// read input
auto res = QMessageBox::warning(this, tr("Delete file"), tr("Are you sure you want to delete the selected file?"),
QMessageBox::Yes | QMessageBox::No);
if (res != QMessageBox::Yes) {
return;
}
// write change
if (!QFile::remove(QFileInfo(filePath).filePath())) {
QMessageBox::critical(this, tr("Error"), tr("Delete failed"));
return;
}
// update VDS
this->deleteLater();
}

View file

@ -0,0 +1,102 @@
#ifndef DECK_PREVIEW_WIDGET_H
#define DECK_PREVIEW_WIDGET_H
#include "../../../../deck/deck_loader.h"
#include "../../cards/additional_info/color_identity_widget.h"
#include "../../cards/deck_preview_card_picture_widget.h"
#include "../visual_deck_storage_widget.h"
#include "deck_preview_deck_tags_display_widget.h"
#include <QAbstractItemView>
#include <QApplication>
#include <QComboBox>
#include <QEvent>
#include <QVBoxLayout>
#include <QWidget>
class QMenu;
class VisualDeckStorageWidget;
class DeckPreviewDeckTagsDisplayWidget;
class DeckPreviewWidget final : public QWidget
{
Q_OBJECT
public:
explicit DeckPreviewWidget(QWidget *_parent,
VisualDeckStorageWidget *_visualDeckStorageWidget,
const QString &_filePath);
void retranslateUi();
QString getColorIdentity();
QString getDisplayName() const;
VisualDeckStorageWidget *visualDeckStorageWidget;
QVBoxLayout *layout;
QString filePath;
DeckLoader *deckLoader;
DeckPreviewCardPictureWidget *bannerCardDisplayWidget = nullptr;
ColorIdentityWidget *colorIdentityWidget = nullptr;
DeckPreviewDeckTagsDisplayWidget *deckTagsDisplayWidget = nullptr;
QLabel *bannerCardLabel = nullptr;
QComboBox *bannerCardComboBox = nullptr;
bool filteredBySearch = false;
bool filteredByColor = false;
bool filteredByTags = false;
bool checkVisibility() const;
signals:
void deckLoadRequested(const QString &filePath);
void openDeckEditor(const DeckLoader *deck);
public slots:
void setFilePath(const QString &filePath);
void refreshBannerCardText();
void refreshBannerCardToolTip();
void updateBannerCardComboBox();
void setBannerCard(int);
void imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance);
void initializeUi(bool deckLoadSuccess);
void updateVisibility();
void updateBannerCardComboBoxVisibility(bool visible);
void updateTagsVisibility(bool visible);
void resizeEvent(QResizeEvent *event) override;
private:
QMenu *createRightClickMenu();
void addSetBannerCardMenu(QMenu *menu);
private slots:
void actRenameDeck();
void actRenameFile();
void actDeleteFile();
};
class NoScrollFilter : public QObject
{
Q_OBJECT
protected:
bool eventFilter(QObject *obj, QEvent *event) override
{
if (event->type() == QEvent::Wheel) {
if (auto *combo = qobject_cast<QComboBox *>(obj)) {
// If popup is not open, forward event to parent scroll area
if (!combo->view()->isVisible()) {
// Try to find a scrollable parent and manually send the event
QWidget *parent = combo->parentWidget();
while (parent) {
if (auto *scroll = qobject_cast<QAbstractScrollArea *>(parent)) {
QApplication::sendEvent(scroll->viewport(), event);
return true; // Mark event as handled
}
parent = parent->parentWidget();
}
// If no scrollable parent found, just block
return true;
}
}
}
return QObject::eventFilter(obj, event);
}
};
#endif // DECK_PREVIEW_WIDGET_H

View file

@ -0,0 +1,224 @@
#include "visual_deck_storage_folder_display_widget.h"
#include "../../../settings/cache_settings.h"
#include "deck_preview/deck_preview_widget.h"
#include <QDirIterator>
#include <QMouseEvent>
VisualDeckStorageFolderDisplayWidget::VisualDeckStorageFolderDisplayWidget(
QWidget *parent,
VisualDeckStorageWidget *_visualDeckStorageWidget,
QString _filePath,
bool canBeHidden,
bool _showFolders)
: QWidget(parent), showFolders(_showFolders), visualDeckStorageWidget(_visualDeckStorageWidget), filePath(_filePath)
{
layout = new QVBoxLayout(this);
setLayout(layout);
header = new BannerWidget(this, "");
header->setClickable(canBeHidden);
header->setHidden(!showFolders);
layout->addWidget(header);
container = new QWidget(this);
containerLayout = new QVBoxLayout(container);
container->setLayout(containerLayout);
header->setBuddy(container);
layout->addWidget(container);
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
containerLayout->addWidget(flowWidget);
createWidgetsForFiles();
createWidgetsForFolders();
refreshUi();
}
void VisualDeckStorageFolderDisplayWidget::refreshUi()
{
QString bannerText = tr("Deck Storage");
QString deckPath = SettingsCache::instance().getDeckPath();
if (filePath != deckPath) {
QString relativePath = filePath;
if (filePath.startsWith(deckPath)) {
relativePath = filePath.mid(deckPath.length()); // Remove the deckPath prefix
if (relativePath.startsWith('/')) {
relativePath.remove(0, 1); // Remove leading '/' if it exists
}
}
bannerText = relativePath;
}
header->setText(bannerText);
}
/**
* Gets all files in the directory that have an accepted decklist file extension
*
* @param filePath The directory to search through
* @param recursive Whether to search through subdirectories
*/
static QStringList getAllFiles(const QString &filePath, bool recursive)
{
QStringList allFiles;
// QDirIterator with QDir::Files ensures only files are listed (no directories)
auto flags =
recursive ? QDirIterator::Subdirectories | QDirIterator::FollowSymlinks : QDirIterator::NoIteratorFlags;
QDirIterator it(filePath, DeckLoader::ACCEPTED_FILE_EXTENSIONS, QDir::Files, flags);
while (it.hasNext()) {
allFiles << it.next(); // Add each file path to the list
}
return allFiles;
}
void VisualDeckStorageFolderDisplayWidget::createWidgetsForFiles()
{
QList<DeckPreviewWidget *> allDecks;
for (const QString &file : getAllFiles(filePath, !showFolders)) {
auto *display = new DeckPreviewWidget(flowWidget, visualDeckStorageWidget, file);
connect(display, &DeckPreviewWidget::deckLoadRequested, visualDeckStorageWidget,
&VisualDeckStorageWidget::deckLoadRequested);
connect(display, &DeckPreviewWidget::openDeckEditor, visualDeckStorageWidget,
&VisualDeckStorageWidget::openDeckEditor);
connect(visualDeckStorageWidget->settings(), &VisualDeckStorageQuickSettingsWidget::cardSizeChanged,
display->bannerCardDisplayWidget, &CardInfoPictureWidget::setScaleFactor);
display->bannerCardDisplayWidget->setScaleFactor(visualDeckStorageWidget->settings()->getCardSize());
allDecks.append(display);
}
flowWidget->clearLayout(); // Clear existing widgets in the flow layout
for (DeckPreviewWidget *deck : allDecks) {
flowWidget->addWidget(deck);
}
}
/**
* Updates the visibility of this folder and all its DeckPreviewWidgets
*
* @param recursive Also update the visibility of all subfolders and their DeckPreviewWidgets.
*/
void VisualDeckStorageFolderDisplayWidget::updateVisibility(bool recursive)
{
bool atLeastOneWidgetVisible = checkVisibility();
if (atLeastOneWidgetVisible) {
setVisible(true);
for (DeckPreviewWidget *display : flowWidget->findChildren<DeckPreviewWidget *>()) {
display->updateVisibility();
}
if (recursive) {
for (auto *subFolder : findChildren<VisualDeckStorageFolderDisplayWidget *>()) {
subFolder->updateVisibility(false);
}
}
} else {
setVisible(false);
}
}
bool VisualDeckStorageFolderDisplayWidget::checkVisibility()
{
bool atLeastOneWidgetVisible = false;
if (flowWidget) {
// Iterate through all DeckPreviewWidgets
for (DeckPreviewWidget *display : flowWidget->findChildren<DeckPreviewWidget *>()) {
if (display->checkVisibility()) {
atLeastOneWidgetVisible = true;
}
}
}
for (VisualDeckStorageFolderDisplayWidget *subFolder : findChildren<VisualDeckStorageFolderDisplayWidget *>()) {
if (subFolder->checkVisibility()) {
atLeastOneWidgetVisible = true;
}
}
return atLeastOneWidgetVisible;
}
static QStringList getAllSubFolders(const QString &filePath)
{
QStringList allFolders;
// QDirIterator with QDir::Files ensures only files are listed (no directories)
QDirIterator it(filePath, QDir::Dirs | QDir::NoDotAndDotDot);
while (it.hasNext()) {
allFolders << it.next(); // Add each file path to the list
}
return allFolders;
}
void VisualDeckStorageFolderDisplayWidget::createWidgetsForFolders()
{
if (!showFolders) {
return;
}
for (const QString &dir : getAllSubFolders(filePath)) {
auto *display = new VisualDeckStorageFolderDisplayWidget(this, visualDeckStorageWidget, dir, true, showFolders);
containerLayout->addWidget(display);
}
}
void VisualDeckStorageFolderDisplayWidget::updateShowFolders(bool enabled)
{
showFolders = enabled;
if (!showFolders) {
flattenFolderStructure();
} else {
// if setting was switched from disabled to enabled, we assume that there aren't any existing subfolders
createWidgetsForFiles();
createWidgetsForFolders();
}
header->setHidden(!showFolders);
}
/**
* Steals all DeckPreviewWidgets from this widget's nested subfolders, and deletes those subfolders
*/
void VisualDeckStorageFolderDisplayWidget::flattenFolderStructure()
{
for (auto *subFolder : findChildren<VisualDeckStorageFolderDisplayWidget *>()) {
// steal all DeckPreviewWidgets from the subfolder
for (auto *deck : subFolder->getFlowWidget()->findChildren<DeckPreviewWidget *>()) {
flowWidget->addWidget(deck);
}
// delete the subfolder
subFolder->deleteLater();
}
}
QStringList VisualDeckStorageFolderDisplayWidget::gatherAllTagsFromFlowWidget() const
{
QStringList allTags;
if (flowWidget) {
// Iterate through all DeckPreviewWidgets
for (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;
}

View file

@ -0,0 +1,46 @@
#ifndef VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H
#define VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H
#include "../general/display/banner_widget.h"
#include "../general/layout_containers/flow_widget.h"
#include "visual_deck_storage_widget.h"
#include <QVBoxLayout>
#include <QWidget>
class VisualDeckStorageFolderDisplayWidget : public QWidget
{
Q_OBJECT
public:
VisualDeckStorageFolderDisplayWidget(QWidget *parent,
VisualDeckStorageWidget *_visualDeckStorageWidget,
QString _filePath,
bool canBeHidden,
bool _showFolders);
void refreshUi();
void createWidgetsForFiles();
void createWidgetsForFolders();
void flattenFolderStructure();
QStringList gatherAllTagsFromFlowWidget() const;
FlowWidget *getFlowWidget() const
{
return flowWidget;
};
public slots:
void updateVisibility(bool recursive = true);
bool checkVisibility();
void updateShowFolders(bool enabled);
private:
bool showFolders;
QVBoxLayout *layout;
VisualDeckStorageWidget *visualDeckStorageWidget;
QString filePath;
BannerWidget *header;
QWidget *container;
QVBoxLayout *containerLayout;
FlowWidget *flowWidget;
};
#endif // VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H

View file

@ -0,0 +1,171 @@
#include "visual_deck_storage_quick_settings_widget.h"
#include "../../../settings/cache_settings.h"
#include "visual_deck_storage_widget.h"
#include <QCheckBox>
#include <QComboBox>
#include <QSpinBox>
VisualDeckStorageQuickSettingsWidget::VisualDeckStorageQuickSettingsWidget(QWidget *parent)
: SettingsButtonWidget(parent)
{
// show folders checkbox
showFoldersCheckBox = new QCheckBox(this);
showFoldersCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowFolders());
connect(showFoldersCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
&VisualDeckStorageQuickSettingsWidget::showFoldersChanged);
connect(showFoldersCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
&SettingsCache::setVisualDeckStorageShowFolders);
// show tag filter widget checkbox
showTagFilterCheckBox = new QCheckBox(this);
showTagFilterCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowTagFilter());
connect(showTagFilterCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
&VisualDeckStorageQuickSettingsWidget::showTagFilterChanged);
connect(showTagFilterCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
&SettingsCache::setVisualDeckStorageShowTagFilter);
// show tags on DeckPreviewWidget checkbox
showTagsOnDeckPreviewsCheckBox = new QCheckBox(this);
showTagsOnDeckPreviewsCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowTagsOnDeckPreviews());
connect(showTagsOnDeckPreviewsCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
&VisualDeckStorageQuickSettingsWidget::showTagsOnDeckPreviewsChanged);
connect(showTagsOnDeckPreviewsCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
&SettingsCache::setVisualDeckStorageShowTagsOnDeckPreviews);
// show banner card selector checkbox
showBannerCardComboBoxCheckBox = new QCheckBox(this);
showBannerCardComboBoxCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowBannerCardComboBox());
connect(showBannerCardComboBoxCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
&VisualDeckStorageQuickSettingsWidget::showBannerCardComboBoxChanged);
connect(showBannerCardComboBoxCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
&SettingsCache::setVisualDeckStorageShowBannerCardComboBox);
// draw unused color identities checkbox
drawUnusedColorIdentitiesCheckBox = new QCheckBox(this);
drawUnusedColorIdentitiesCheckBox->setChecked(
SettingsCache::instance().getVisualDeckStorageDrawUnusedColorIdentities());
connect(drawUnusedColorIdentitiesCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
&VisualDeckStorageQuickSettingsWidget::drawUnusedColorIdentitiesChanged);
connect(drawUnusedColorIdentitiesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
&SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities);
// color identity opacity selector
auto unusedColorIdentityOpacityWidget = new QWidget(this);
unusedColorIdentitiesOpacityLabel = new QLabel(unusedColorIdentityOpacityWidget);
unusedColorIdentitiesOpacitySpinBox = new QSpinBox(unusedColorIdentityOpacityWidget);
unusedColorIdentitiesOpacitySpinBox->setMinimum(0);
unusedColorIdentitiesOpacitySpinBox->setMaximum(100);
unusedColorIdentitiesOpacitySpinBox->setValue(
SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity());
connect(unusedColorIdentitiesOpacitySpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
&VisualDeckStorageQuickSettingsWidget::unusedColorIdentitiesOpacityChanged);
connect(unusedColorIdentitiesOpacitySpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
&SettingsCache::instance(), &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity);
unusedColorIdentitiesOpacityLabel->setBuddy(unusedColorIdentitiesOpacitySpinBox);
auto unusedColorIdentityOpacityLayout = new QHBoxLayout(unusedColorIdentityOpacityWidget);
unusedColorIdentityOpacityLayout->setContentsMargins(11, 0, 11, 0);
unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacityLabel);
unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacitySpinBox);
// tooltip selector
auto deckPreviewTooltipWidget = new QWidget(this);
deckPreviewTooltipLabel = new QLabel(deckPreviewTooltipWidget);
deckPreviewTooltipComboBox = new QComboBox(deckPreviewTooltipWidget);
deckPreviewTooltipComboBox->setFocusPolicy(Qt::StrongFocus);
deckPreviewTooltipComboBox->addItem("", TooltipType::None);
deckPreviewTooltipComboBox->addItem("", TooltipType::Filepath);
deckPreviewTooltipComboBox->setCurrentIndex(SettingsCache::instance().getVisualDeckStorageTooltipType());
connect(deckPreviewTooltipComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
[this] { emit deckPreviewTooltipChanged(getDeckPreviewTooltip()); });
connect(deckPreviewTooltipComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), &SettingsCache::instance(),
&SettingsCache::setVisualDeckStorageTooltipType);
auto deckPreviewTooltipLayout = new QHBoxLayout(deckPreviewTooltipWidget);
deckPreviewTooltipLayout->setContentsMargins(11, 0, 11, 0);
deckPreviewTooltipLayout->addWidget(deckPreviewTooltipLabel);
deckPreviewTooltipLayout->addWidget(deckPreviewTooltipComboBox);
// card size slider
cardSizeWidget = new CardSizeWidget(this, nullptr, SettingsCache::instance().getVisualDeckStorageCardSize());
connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, this,
&VisualDeckStorageQuickSettingsWidget::cardSizeChanged);
connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(),
&SettingsCache::setVisualDeckStorageCardSize);
// putting everything together
this->addSettingsWidget(showFoldersCheckBox);
this->addSettingsWidget(showTagFilterCheckBox);
this->addSettingsWidget(showTagsOnDeckPreviewsCheckBox);
this->addSettingsWidget(showBannerCardComboBoxCheckBox);
this->addSettingsWidget(drawUnusedColorIdentitiesCheckBox);
this->addSettingsWidget(unusedColorIdentityOpacityWidget);
this->addSettingsWidget(deckPreviewTooltipWidget);
this->addSettingsWidget(cardSizeWidget);
connect(&SettingsCache::instance(), &SettingsCache::langChanged, this,
&VisualDeckStorageQuickSettingsWidget::retranslateUi);
retranslateUi();
}
void VisualDeckStorageQuickSettingsWidget::retranslateUi()
{
showFoldersCheckBox->setText(tr("Show Folders"));
showTagFilterCheckBox->setText(tr("Show Tag Filter"));
showTagsOnDeckPreviewsCheckBox->setText(tr("Show Tags On Deck Previews"));
showBannerCardComboBoxCheckBox->setText(tr("Show Banner Card Selection Option"));
drawUnusedColorIdentitiesCheckBox->setText(tr("Draw unused Color Identities"));
unusedColorIdentitiesOpacityLabel->setText(tr("Unused Color Identities Opacity"));
unusedColorIdentitiesOpacitySpinBox->setSuffix("%");
deckPreviewTooltipLabel->setText(tr("Deck tooltip:"));
deckPreviewTooltipComboBox->setItemText(0, tr("None"));
deckPreviewTooltipComboBox->setItemText(1, tr("Filepath"));
}
bool VisualDeckStorageQuickSettingsWidget::getShowFolders() const
{
return showFoldersCheckBox->isChecked();
}
bool VisualDeckStorageQuickSettingsWidget::getDrawUnusedColorIdentities() const
{
return drawUnusedColorIdentitiesCheckBox->isChecked();
}
bool VisualDeckStorageQuickSettingsWidget::getShowBannerCardComboBox() const
{
return showBannerCardComboBoxCheckBox->isChecked();
}
bool VisualDeckStorageQuickSettingsWidget::getShowTagFilter() const
{
return showTagFilterCheckBox->isChecked();
}
bool VisualDeckStorageQuickSettingsWidget::getShowTagsOnDeckPreviews() const
{
return showTagsOnDeckPreviewsCheckBox->isChecked();
}
int VisualDeckStorageQuickSettingsWidget::getUnusedColorIdentitiesOpacity() const
{
return unusedColorIdentitiesOpacitySpinBox->value();
}
VisualDeckStorageQuickSettingsWidget::TooltipType VisualDeckStorageQuickSettingsWidget::getDeckPreviewTooltip() const
{
return deckPreviewTooltipComboBox->currentData().value<TooltipType>();
}
int VisualDeckStorageQuickSettingsWidget::getCardSize() const
{
return cardSizeWidget->getSlider()->value();
}

View file

@ -0,0 +1,67 @@
#ifndef VISUAL_DECK_STORAGE_QUICK_SETTINGS_WIDGET_H
#define VISUAL_DECK_STORAGE_QUICK_SETTINGS_WIDGET_H
#include "../quick_settings/settings_button_widget.h"
class CardSizeWidget;
class QLabel;
class QSpinBox;
class QCheckBox;
class QComboBox;
/**
* The VDS's quick settings menu.
* Manages the widgets in the quick settings menu dropdown, as well as syncing their values with SettingsCache.
* The current values of the settings are exposed through getters and signals.
*/
class VisualDeckStorageQuickSettingsWidget : public SettingsButtonWidget
{
Q_OBJECT
QCheckBox *showFoldersCheckBox;
QCheckBox *drawUnusedColorIdentitiesCheckBox;
QCheckBox *showBannerCardComboBoxCheckBox;
QCheckBox *showTagFilterCheckBox;
QCheckBox *showTagsOnDeckPreviewsCheckBox;
QLabel *unusedColorIdentitiesOpacityLabel;
QSpinBox *unusedColorIdentitiesOpacitySpinBox;
QLabel *deckPreviewTooltipLabel;
QComboBox *deckPreviewTooltipComboBox;
CardSizeWidget *cardSizeWidget;
public:
/**
* The info to display in the deck preview's banner card tooltip.
*/
enum TooltipType
{
None,
Filepath
};
Q_ENUM(TooltipType)
explicit VisualDeckStorageQuickSettingsWidget(QWidget *parent = nullptr);
void retranslateUi();
bool getShowFolders() const;
bool getDrawUnusedColorIdentities() const;
bool getShowBannerCardComboBox() const;
bool getShowTagFilter() const;
bool getShowTagsOnDeckPreviews() const;
int getUnusedColorIdentitiesOpacity() const;
TooltipType getDeckPreviewTooltip() const;
int getCardSize() const;
signals:
void showFoldersChanged(bool enabled);
void drawUnusedColorIdentitiesChanged(bool enabled);
void showBannerCardComboBoxChanged(bool enabled);
void showTagFilterChanged(bool enabled);
void showTagsOnDeckPreviewsChanged(bool enabled);
void unusedColorIdentitiesOpacityChanged(int opacity);
void deckPreviewTooltipChanged(TooltipType tooltip);
void cardSizeChanged(int scale);
};
#endif // VISUAL_DECK_STORAGE_QUICK_SETTINGS_WIDGET_H

View file

@ -0,0 +1,80 @@
#include "visual_deck_storage_search_widget.h"
#include "../../../filters/deck_filter_string.h"
#include "../../../filters/syntax_help.h"
#include "../../../settings/cache_settings.h"
#include "../../pixel_map_generator.h"
#include <QAction>
/**
* @brief Constructs a PrintingSelectorCardSearchWidget 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.
*/
VisualDeckStorageSearchWidget::VisualDeckStorageSearchWidget(VisualDeckStorageWidget *parent) : parent(parent)
{
layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
searchBar = new QLineEdit(this);
searchBar->setPlaceholderText(tr("Search by filename (or search expression)"));
searchBar->setClearButtonEnabled(true);
searchBar->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
auto help = searchBar->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition);
connect(help, &QAction::triggered, this, [this] { createDeckSearchSyntaxHelpWindow(searchBar); });
layout->addWidget(searchBar);
// Add a debounce timer for the search bar to limit frequent updates
searchDebounceTimer = new QTimer(this);
searchDebounceTimer->setSingleShot(true);
connect(searchBar, &QLineEdit::textChanged, this, [this]() {
searchDebounceTimer->start(300); // 300ms debounce
});
connect(searchDebounceTimer, &QTimer::timeout, parent, &VisualDeckStorageWidget::updateSearchFilter);
}
/**
* @brief Retrieves the current text in the search bar.
*
* @return The text entered by the user in the search bar.
*/
QString VisualDeckStorageSearchWidget::getSearchText()
{
return searchBar->text();
}
/**
* Converts the filepath into a relative filepath starting from the deck folder.
* If the file isn't in the deck folder, then this will just return the filename.
*
* @param filePath The filepath to convert into a relative filepath
*/
static QString toRelativeFilepath(const QString &filePath)
{
QString deckPath = SettingsCache::instance().getDeckPath();
if (filePath.startsWith(deckPath)) {
return filePath.mid(deckPath.length());
}
QFileInfo fileInfo(filePath);
QString fileName = fileInfo.fileName();
return fileName;
}
void VisualDeckStorageSearchWidget::filterWidgets(QList<DeckPreviewWidget *> widgets, const QString &searchText)
{
auto filterString = DeckFilterString(searchText);
for (auto widget : widgets) {
QString relativeFilePath = toRelativeFilepath(widget->filePath);
widget->filteredBySearch = !filterString.check(widget, {relativeFilePath});
}
}

View file

@ -0,0 +1,28 @@
#ifndef VISUAL_DECK_STORAGE_SEARCH_WIDGET_H
#define VISUAL_DECK_STORAGE_SEARCH_WIDGET_H
#include "visual_deck_storage_widget.h"
#include <QHBoxLayout>
#include <QLineEdit>
#include <QTimer>
#include <QWidget>
class VisualDeckStorageWidget;
class VisualDeckStorageSearchWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDeckStorageSearchWidget(VisualDeckStorageWidget *parent);
QString getSearchText();
void filterWidgets(QList<DeckPreviewWidget *> widgets, const QString &searchText);
private:
QHBoxLayout *layout;
VisualDeckStorageWidget *parent;
QLineEdit *searchBar;
QTimer *searchDebounceTimer;
};
#endif // VISUAL_DECK_STORAGE_SEARCH_WIDGET_H

View file

@ -0,0 +1,112 @@
#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);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
// Initialize the ComboBox
sortComboBox = new QComboBox(this);
layout->addWidget(sortComboBox);
// Need to retranslateUi first so that the sortComboBox actually has entries and doesn't get its currentIndex
// immediately capped to 0 when we try to set it
retranslateUi();
// Set the current sort order
sortComboBox->setCurrentIndex(SettingsCache::instance().getVisualDeckStorageSortingOrder());
sortOrder = static_cast<SortOrder>(sortComboBox->currentIndex());
// 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);
}
void VisualDeckStorageSortWidget::retranslateUi()
{
// Block signals to avoid triggering unnecessary updates while changing text
sortComboBox->blockSignals(true);
int oldIndex = sortComboBox->currentIndex();
// 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(oldIndex);
// Re-enable signals
sortComboBox->blockSignals(false);
}
void VisualDeckStorageSortWidget::updateSortOrder()
{
sortOrder = static_cast<SortOrder>(sortComboBox->currentIndex());
SettingsCache::instance().setVisualDeckStorageSortingOrder(sortComboBox->currentIndex());
emit sortOrderChanged();
}
void VisualDeckStorageSortWidget::sortFolder(VisualDeckStorageFolderDisplayWidget *folderWidget)
{
auto children =
folderWidget->getFlowWidget()->findChildren<QWidget *>(QString(), Qt::FindChildOption::FindDirectChildrenOnly);
for (auto widget : children) {
auto deckPreviewWidgets =
widget->findChildren<DeckPreviewWidget *>(QString(), Qt::FindChildOption::FindDirectChildrenOnly);
auto newOrder = filterFiles(deckPreviewWidgets);
for (DeckPreviewWidget *previewWidget : newOrder) {
folderWidget->getFlowWidget()->removeWidget(previewWidget);
}
for (DeckPreviewWidget *previewWidget : newOrder) {
folderWidget->getFlowWidget()->addWidget(previewWidget);
}
}
}
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 QString::localeAwareCompare(info1.fileName(), info2.fileName()) <= 0;
case ByLastModified:
return info1.lastModified() > info2.lastModified();
case ByLastLoaded: {
QDateTime time1 = QDateTime::fromString(widget1->deckLoader->getLastLoadedTimestamp());
QDateTime time2 = QDateTime::fromString(widget2->deckLoader->getLastLoadedTimestamp());
return time1 > time2;
}
}
return false; // Default case, no sorting applied
});
return widgets;
}

View file

@ -0,0 +1,41 @@
#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 VisualDeckStorageFolderDisplayWidget;
class VisualDeckStorageSortWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDeckStorageSortWidget(VisualDeckStorageWidget *parent);
void retranslateUi();
void updateSortOrder();
void sortFolder(VisualDeckStorageFolderDisplayWidget *folderWidget);
QString getSearchText();
QList<DeckPreviewWidget *> filterFiles(QList<DeckPreviewWidget *> widgets);
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,178 @@
#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, 0, 5, 0);
setFixedHeight(100);
auto *flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
}
void VisualDeckStorageTagFilterWidget::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
refreshTags();
}
void VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QList<DeckPreviewWidget *> &deckPreviews) const
{
QStringList selectedTags;
QStringList excludedTags;
// Collect selected and excluded tags
for (DeckPreviewTagDisplayWidget *tagWidget : findChildren<DeckPreviewTagDisplayWidget *>()) {
switch (tagWidget->getState()) {
case TagState::Selected:
selectedTags.append(tagWidget->getTagName());
break;
case TagState::Excluded:
excludedTags.append(tagWidget->getTagName());
break;
default:
break;
}
}
// If no tags are selected or excluded, show all
if (selectedTags.isEmpty() && excludedTags.isEmpty()) {
for (DeckPreviewWidget *deckPreview : deckPreviews) {
deckPreview->filteredByTags = false;
}
return;
}
for (DeckPreviewWidget *deckPreview : deckPreviews) {
QStringList deckTags = deckPreview->deckLoader->getTags();
bool hasAllSelected = std::all_of(selectedTags.begin(), selectedTags.end(),
[&deckTags](const QString &tag) { return deckTags.contains(tag); });
bool hasAnyExcluded = std::any_of(excludedTags.begin(), excludedTags.end(),
[&deckTags](const QString &tag) { return deckTags.contains(tag); });
// Filter out if any excluded tag is present or if any selected tag is missing
deckPreview->filteredByTags = !(hasAllSelected && !hasAnyExcluded);
}
}
void VisualDeckStorageTagFilterWidget::refreshTags()
{
QSet<QString> allTags = gatherAllTags();
removeTagsNotInList(allTags);
addTagsIfNotPresent(allTags);
sortTags();
}
void VisualDeckStorageTagFilterWidget::removeTagsNotInList(const QSet<QString> &tags)
{
auto *flowWidget = findChild<FlowWidget *>();
for (DeckPreviewTagDisplayWidget *tagWidget : findChildren<DeckPreviewTagDisplayWidget *>()) {
const QString &tagName = tagWidget->getTagName();
// Keep the tag widget if it is either selected or excluded
if (!tags.contains(tagName) && tagWidget->getState() == TagState::NotSelected) {
flowWidget->removeWidget(tagWidget);
tagWidget->deleteLater();
}
}
}
void VisualDeckStorageTagFilterWidget::addTagsIfNotPresent(const QSet<QString> &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;
for (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::updateTagFilter);
connect(newTagWidget, &DeckPreviewTagDisplayWidget::tagClicked, this,
&VisualDeckStorageTagFilterWidget::refreshTags);
auto *flowWidget = findChild<FlowWidget *>();
flowWidget->addWidget(newTagWidget);
}
}
void VisualDeckStorageTagFilterWidget::sortTags()
{
auto *flowWidget = findChild<FlowWidget *>();
if (!flowWidget)
return;
// Get all tag widgets
QList<DeckPreviewTagDisplayWidget *> tagWidgets = findChildren<DeckPreviewTagDisplayWidget *>();
// Sort widgets by tag name
std::sort(tagWidgets.begin(), tagWidgets.end(), [](DeckPreviewTagDisplayWidget *a, DeckPreviewTagDisplayWidget *b) {
return a->getTagName().toLower() < b->getTagName().toLower();
});
// Clear and re-add widgets in sorted order
for (DeckPreviewTagDisplayWidget *tagWidget : tagWidgets) {
flowWidget->removeWidget(tagWidget);
}
for (DeckPreviewTagDisplayWidget *tagWidget : tagWidgets) {
flowWidget->addWidget(tagWidget);
}
}
QSet<QString> VisualDeckStorageTagFilterWidget::gatherAllTags() const
{
QSet<QString> allTags;
QList<DeckPreviewWidget *> deckWidgets = parent->findChildren<DeckPreviewWidget *>();
for (DeckPreviewWidget *widget : deckWidgets) {
if (widget->checkVisibility()) {
for (const QString &tag : widget->deckLoader->getTags()) {
allTags.insert(tag);
}
}
}
return allTags;
}
QStringList VisualDeckStorageTagFilterWidget::getAllKnownTags() const
{
QStringList allTags;
for (DeckPreviewTagDisplayWidget *tagWidget : findChildren<DeckPreviewTagDisplayWidget *>()) {
allTags.append(tagWidget->getTagName());
}
// Remove duplicates by calling 'removeDuplicates'
allTags.removeDuplicates();
return allTags;
}

View file

@ -0,0 +1,31 @@
#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
VisualDeckStorageWidget *parent;
QSet<QString> gatherAllTags() const;
void removeTagsNotInList(const QSet<QString> &tags);
void addTagsIfNotPresent(const QSet<QString> &tags);
void addTagIfNotPresent(const QString &tag);
void sortTags();
public:
explicit VisualDeckStorageTagFilterWidget(VisualDeckStorageWidget *_parent);
QStringList getAllKnownTags() const;
void filterDecksBySelectedTags(const QList<DeckPreviewWidget *> &deckPreviews) const;
public slots:
void refreshTags();
void showEvent(QShowEvent *event) override;
};
#endif // VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H

View file

@ -0,0 +1,215 @@
#include "visual_deck_storage_widget.h"
#include "../../../database/card_database_manager.h"
#include "../../../settings/cache_settings.h"
#include "../quick_settings/settings_button_widget.h"
#include "deck_preview/deck_preview_widget.h"
#include "visual_deck_storage_folder_display_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 <QSpinBox>
#include <QVBoxLayout>
VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(parent), folderWidget(nullptr)
{
deckListModel = new DeckListModel(this);
deckListModel->setObjectName("visualDeckModel");
layout = new QVBoxLayout(this);
layout->setSpacing(0);
layout->setContentsMargins(9, 0, 9, 5);
setLayout(layout);
// search bar row
searchAndSortContainer = new QWidget(this);
searchAndSortLayout = new QHBoxLayout(searchAndSortContainer);
searchAndSortLayout->setSpacing(3);
searchAndSortLayout->setContentsMargins(9, 0, 9, 0);
searchAndSortContainer->setLayout(searchAndSortLayout);
deckPreviewColorIdentityFilterWidget = new DeckPreviewColorIdentityFilterWidget(this);
sortWidget = new VisualDeckStorageSortWidget(this);
searchWidget = new VisualDeckStorageSearchWidget(this);
refreshButton = new QToolButton(this);
refreshButton->setIcon(QPixmap("theme:icons/reload"));
refreshButton->setFixedSize(32, 32);
connect(refreshButton, &QPushButton::clicked, this, &VisualDeckStorageWidget::refreshIfPossible);
quickSettingsWidget = new VisualDeckStorageQuickSettingsWidget(this);
connect(quickSettingsWidget, &VisualDeckStorageQuickSettingsWidget::showFoldersChanged, this,
&VisualDeckStorageWidget::updateShowFolders);
connect(quickSettingsWidget, &VisualDeckStorageQuickSettingsWidget::showTagFilterChanged, this,
&VisualDeckStorageWidget::updateTagsVisibility);
searchAndSortLayout->addWidget(deckPreviewColorIdentityFilterWidget);
searchAndSortLayout->addWidget(sortWidget);
searchAndSortLayout->addWidget(searchWidget);
searchAndSortLayout->addWidget(refreshButton);
searchAndSortLayout->addWidget(quickSettingsWidget);
// tag filter box
tagFilterWidget = new VisualDeckStorageTagFilterWidget(this);
updateTagsVisibility(SettingsCache::instance().getVisualDeckStorageShowTagFilter());
deckPreviewSelectionAnimationEnabled = SettingsCache::instance().getVisualDeckStorageSelectionAnimation();
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageSelectionAnimationChanged, this,
&VisualDeckStorageWidget::updateSelectionAnimationEnabled);
// deck area
scrollArea = new QScrollArea(this);
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setWidgetResizable(true);
// putting everything together
layout->addWidget(searchAndSortContainer);
layout->addWidget(tagFilterWidget);
layout->addWidget(scrollArea);
connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
&VisualDeckStorageWidget::createRootFolderWidget);
databaseLoadIndicator = new QLabel(this);
databaseLoadIndicator->setAlignment(Qt::AlignCenter);
retranslateUi();
// Don't waste time processing the cards if they're going to get refreshed anyway once the db finishes loading
if (CardDatabaseManager::getInstance()->getLoadStatus() == LoadStatus::Ok) {
createRootFolderWidget();
databaseLoadIndicator->setVisible(false);
} else {
scrollArea->setWidget(databaseLoadIndicator);
}
}
void VisualDeckStorageWidget::refreshIfPossible()
{
if (scrollArea->widget() != databaseLoadIndicator) {
createRootFolderWidget();
}
}
void VisualDeckStorageWidget::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
if (scrollArea->widget() == folderWidget) {
scrollArea->widget()->setMaximumWidth(scrollArea->viewport()->width());
scrollArea->widget()->adjustSize();
}
}
void VisualDeckStorageWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
if (scrollArea->widget() == folderWidget) {
scrollArea->widget()->setMaximumWidth(scrollArea->viewport()->width());
scrollArea->widget()->adjustSize();
}
}
void VisualDeckStorageWidget::retranslateUi()
{
databaseLoadIndicator->setText(tr("Loading database ..."));
refreshButton->setToolTip(tr("Refresh loaded files"));
quickSettingsWidget->setToolTip(tr("Visual Deck Storage Settings"));
}
/**
* Gets a const pointer to the quick settings so that the values can be accessed.
*/
const VisualDeckStorageQuickSettingsWidget *VisualDeckStorageWidget::settings() const
{
return quickSettingsWidget;
}
/**
* Reapplies all sort and filter options by calling the appropriate update methods.
*/
void VisualDeckStorageWidget::reapplySortAndFilters()
{
updateSortOrder();
updateTagFilter();
updateColorFilter();
updateSearchFilter();
}
void VisualDeckStorageWidget::createRootFolderWidget()
{
folderWidget = new VisualDeckStorageFolderDisplayWidget(this, this, SettingsCache::instance().getDeckPath(), false,
quickSettingsWidget->getShowFolders());
scrollArea->setWidget(folderWidget); // this automatically destroys the old folderWidget
scrollArea->widget()->setMaximumWidth(scrollArea->viewport()->width());
scrollArea->widget()->adjustSize();
/* We have to schedule a QTimer here so that the sorting logic doesn't try to access widgets that haven't been
* processed by the event loop yet. Otherwise, deck sorting will intermittently segfault on some systems.
*/
QTimer::singleShot(0, this, &VisualDeckStorageWidget::reapplySortAndFilters);
}
void VisualDeckStorageWidget::updateShowFolders(bool enabled)
{
if (folderWidget) {
folderWidget->updateShowFolders(enabled);
QTimer::singleShot(0, this, &VisualDeckStorageWidget::reapplySortAndFilters);
}
}
void VisualDeckStorageWidget::updateSortOrder()
{
if (folderWidget) {
sortWidget->sortFolder(folderWidget);
for (VisualDeckStorageFolderDisplayWidget *subFolderWidget :
folderWidget->findChildren<VisualDeckStorageFolderDisplayWidget *>()) {
sortWidget->sortFolder(subFolderWidget);
}
}
}
void VisualDeckStorageWidget::updateTagFilter()
{
if (folderWidget) {
tagFilterWidget->filterDecksBySelectedTags(folderWidget->findChildren<DeckPreviewWidget *>());
folderWidget->updateVisibility();
}
}
void VisualDeckStorageWidget::updateColorFilter()
{
if (folderWidget) {
deckPreviewColorIdentityFilterWidget->filterWidgets(folderWidget->findChildren<DeckPreviewWidget *>());
folderWidget->updateVisibility();
}
}
void VisualDeckStorageWidget::updateSearchFilter()
{
if (folderWidget) {
searchWidget->filterWidgets(folderWidget->findChildren<DeckPreviewWidget *>(), searchWidget->getSearchText());
folderWidget->updateVisibility();
}
}
void VisualDeckStorageWidget::updateTagsVisibility(const bool visible)
{
if (visible) {
tagFilterWidget->setVisible(true);
} else {
tagFilterWidget->setHidden(true);
}
}
void VisualDeckStorageWidget::updateSelectionAnimationEnabled(const bool enabled)
{
deckPreviewSelectionAnimationEnabled = enabled;
}

View file

@ -0,0 +1,72 @@
#ifndef VISUAL_DECK_STORAGE_WIDGET_H
#define VISUAL_DECK_STORAGE_WIDGET_H
#include "../../../deck/deck_list_model.h"
#include "../cards/card_size_widget.h"
#include "../general/layout_containers/flow_widget.h"
#include "../quick_settings/settings_button_widget.h"
#include "deck_preview/deck_preview_color_identity_filter_widget.h"
#include "deck_preview/deck_preview_widget.h"
#include "visual_deck_storage_folder_display_widget.h"
#include "visual_deck_storage_quick_settings_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 <QCheckBox>
#include <QFileSystemModel>
class QSpinBox;
class VisualDeckStorageSearchWidget;
class VisualDeckStorageSortWidget;
class VisualDeckStorageTagFilterWidget;
class VisualDeckStorageFolderDisplayWidget;
class DeckPreviewColorIdentityFilterWidget;
class VisualDeckStorageWidget final : public QWidget
{
Q_OBJECT
public:
explicit VisualDeckStorageWidget(QWidget *parent);
void refreshIfPossible();
void retranslateUi();
VisualDeckStorageTagFilterWidget *tagFilterWidget;
bool deckPreviewSelectionAnimationEnabled;
const VisualDeckStorageQuickSettingsWidget *settings() const;
public slots:
void createRootFolderWidget(); // Refresh the display of cards based on the current sorting option
void updateShowFolders(bool enabled);
void updateTagFilter();
void updateColorFilter();
void updateSearchFilter();
void updateTagsVisibility(bool visible);
void updateSelectionAnimationEnabled(bool enabled);
void updateSortOrder();
void resizeEvent(QResizeEvent *event) override;
void showEvent(QShowEvent *event) override;
signals:
void bannerCardsRefreshed();
void deckLoadRequested(const QString &filePath);
void openDeckEditor(const DeckLoader *deck);
private:
QVBoxLayout *layout;
QWidget *searchAndSortContainer;
QHBoxLayout *searchAndSortLayout;
DeckListModel *deckListModel;
QLabel *databaseLoadIndicator;
VisualDeckStorageSortWidget *sortWidget;
VisualDeckStorageSearchWidget *searchWidget;
DeckPreviewColorIdentityFilterWidget *deckPreviewColorIdentityFilterWidget;
QToolButton *refreshButton;
VisualDeckStorageQuickSettingsWidget *quickSettingsWidget;
QScrollArea *scrollArea;
VisualDeckStorageFolderDisplayWidget *folderWidget;
void reapplySortAndFilters();
};
#endif // VISUAL_DECK_STORAGE_WIDGET_H