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

* Visual Database Display Tab.

* Address comments.

* Readd dropped method.

* Update filterTree properly in case the filter is empty after modification.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2025-04-15 03:25:49 +02:00 committed by GitHub
parent ae90b6c93f
commit c4d0921a15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 2197 additions and 20 deletions

View File

@ -43,6 +43,7 @@ set(cockatrice_SOURCES
src/client/tabs/tab_room.cpp
src/client/tabs/tab_server.cpp
src/client/tabs/tab_supervisor.cpp
src/client/tabs/tab_visual_database_display.cpp
src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
src/client/tapped_out_interface.cpp
src/client/translate_counter_name.cpp
@ -92,6 +93,14 @@ set(cockatrice_SOURCES
src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp
src/client/ui/widgets/quick_settings/settings_button_widget.cpp
src/client/ui/widgets/quick_settings/settings_popup_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_filter_display_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_deck_tags_display_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp
@ -204,6 +213,7 @@ set(cockatrice_SOURCES
src/settings/settings_manager.cpp
src/settings/shortcut_treeview.cpp
src/settings/shortcuts_settings.cpp
src/utility/card_info_comparator.cpp
src/utility/logger.cpp
src/utility/sequence_edit.cpp
)

View File

@ -25,6 +25,7 @@
#include "tab_replays.h"
#include "tab_room.h"
#include "tab_server.h"
#include "tab_visual_database_display.h"
#include "visual_deck_storage/tab_deck_storage_visual.h"
#include <QApplication>
@ -134,6 +135,9 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *
aTabVisualDeckStorage->setCheckable(true);
connect(aTabVisualDeckStorage, &QAction::triggered, this, &TabSupervisor::actTabVisualDeckStorage);
aTabVisualDatabaseDisplay = new QAction(this);
connect(aTabVisualDatabaseDisplay, &QAction::triggered, this, [this] { addVisualDatabaseDisplayTab(); });
aTabServer = new QAction(this);
aTabServer->setCheckable(true);
connect(aTabServer, &QAction::triggered, this, &TabSupervisor::actTabServer);
@ -177,6 +181,7 @@ void TabSupervisor::retranslateUi()
// tab menu actions
aTabDeckEditor->setText(tr("Deck Editor"));
aTabVisualDeckStorage->setText(tr("&Visual Deck Storage"));
aTabVisualDatabaseDisplay->setText(tr("Visual Database Display"));
aTabServer->setText(tr("Server"));
aTabAccount->setText(tr("Account"));
aTabDeckStorage->setText(tr("Deck Storage"));
@ -372,6 +377,7 @@ void TabSupervisor::resetTabsMenu()
tabsMenu->addAction(aTabDeckEditor);
tabsMenu->addSeparator();
tabsMenu->addAction(aTabVisualDeckStorage);
tabsMenu->addAction(aTabVisualDatabaseDisplay);
tabsMenu->addAction(aTabDeckStorage);
tabsMenu->addAction(aTabReplays);
}
@ -804,6 +810,14 @@ TabDeckEditor *TabSupervisor::addDeckEditorTab(const DeckLoader *deckToOpen)
return tab;
}
TabVisualDatabaseDisplay *TabSupervisor::addVisualDatabaseDisplayTab()
{
auto *tab = new TabVisualDatabaseDisplay(this);
myAddTab(tab);
setCurrentWidget(tab);
return tab;
}
TabEdhRec *TabSupervisor::addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander)
{
auto *tab = new TabEdhRec(this);

View File

@ -5,6 +5,7 @@
#include "../../server/user/user_list_proxy.h"
#include "abstract_tab_deck_editor.h"
#include "api/edhrec/tab_edhrec.h"
#include "tab_visual_database_display.h"
#include "visual_deck_storage/tab_deck_storage_visual.h"
#include <QAbstractButton>
@ -91,8 +92,8 @@ private:
QList<AbstractTabDeckEditor *> deckEditorTabs;
bool isLocalGame;
QAction *aTabDeckEditor, *aTabVisualDeckStorage, *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays,
*aTabAdmin, *aTabLog;
QAction *aTabDeckEditor, *aTabVisualDeckStorage, *aTabVisualDatabaseDisplay, *aTabServer, *aTabAccount,
*aTabDeckStorage, *aTabReplays, *aTabAdmin, *aTabLog;
int myAddTab(Tab *tab, QAction *manager = nullptr);
void addCloseButtonToTab(Tab *tab, int tabIndex, QAction *manager);
@ -149,6 +150,7 @@ signals:
public slots:
TabDeckEditor *addDeckEditorTab(const DeckLoader *deckToOpen);
TabVisualDatabaseDisplay *addVisualDatabaseDisplayTab();
TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false);
void openReplay(GameReplay *replay);
void maximizeMainWindow();

View File

@ -0,0 +1,20 @@
#include "tab_visual_database_display.h"
#include "tab_deck_editor.h"
TabVisualDatabaseDisplay::TabVisualDatabaseDisplay(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
{
deckEditor = new TabDeckEditor(_tabSupervisor);
deckEditor->setHidden(true);
visualDatabaseDisplayWidget =
new VisualDatabaseDisplayWidget(this, deckEditor, deckEditor->databaseDisplayDockWidget->databaseModel,
deckEditor->databaseDisplayDockWidget->databaseDisplayModel);
setCentralWidget(visualDatabaseDisplayWidget);
TabVisualDatabaseDisplay::retranslateUi();
}
void TabVisualDatabaseDisplay::retranslateUi()
{
}

View File

@ -0,0 +1,26 @@
#ifndef TAB_VISUAL_DATABASE_DISPLAY_H
#define TAB_VISUAL_DATABASE_DISPLAY_H
#include "../ui/widgets/visual_database_display/visual_database_display_widget.h"
#include "tab.h"
#include <QVBoxLayout>
class TabVisualDatabaseDisplay : public Tab
{
Q_OBJECT
private:
TabDeckEditor *deckEditor;
VisualDatabaseDisplayWidget *visualDatabaseDisplayWidget;
public:
TabVisualDatabaseDisplay(TabSupervisor *_tabSupervisor);
void retranslateUi() override;
QString getTabText() const override
{
return tr("Visual Database Display");
}
};
#endif // TAB_VISUAL_DATABASE_DISPLAY_H

View File

@ -235,6 +235,42 @@ void DeckEditorDatabaseDisplayWidget::saveDbHeaderState()
SettingsCache::instance().layouts().setDeckEditorDbHeaderState(databaseView->header()->saveState());
}
void DeckEditorDatabaseDisplayWidget::showSearchSyntaxHelp()
{
QFile file("theme:help/search.md");
if (!file.open(QFile::ReadOnly | QFile::Text)) {
return;
}
QTextStream in(&file);
QString text = in.readAll();
file.close();
// Poor Markdown Converter
auto opts = QRegularExpression::MultilineOption;
text = text.replace(QRegularExpression("^(###)(.*)", opts), "<h3>\\2</h3>")
.replace(QRegularExpression("^(##)(.*)", opts), "<h2>\\2</h2>")
.replace(QRegularExpression("^(#)(.*)", opts), "<h1>\\2</h1>")
.replace(QRegularExpression("^------*", opts), "<hr />")
.replace(QRegularExpression(R"(\[([^[]+)\]\(([^\)]+)\))", opts), R"(<a href='\2'>\1</a>)");
auto browser = new QTextBrowser;
browser->setParent(this, Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint |
Qt::WindowCloseButtonHint | Qt::WindowFullscreenButtonHint);
browser->setWindowTitle("Search Help");
browser->setReadOnly(true);
browser->setMinimumSize({500, 600});
QString sheet = QString("a { text-decoration: underline; color: rgb(71,158,252) };");
browser->document()->setDefaultStyleSheet(sheet);
browser->setHtml(text);
connect(browser, &QTextBrowser::anchorClicked, [this](const QUrl &link) { searchEdit->setText(link.fragment()); });
browser->show();
}
void DeckEditorDatabaseDisplayWidget::setFilterTree(FilterTree *filterTree)
{
databaseDisplayModel->setFilterTree(filterTree);

View File

@ -22,6 +22,7 @@ public:
CardDatabaseDisplayModel *databaseDisplayModel;
public slots:
void showSearchSyntaxHelp();
CardInfoPtr currentCardInfo() const;
void setFilterTree(FilterTree *filterTree);
void clearAllDatabaseFilters();

View File

@ -0,0 +1,228 @@
#include "visual_database_display_color_filter_widget.h"
#include "../../../../game/filters/filter_tree.h"
#include "../cards/additional_info/mana_symbol_widget.h"
#include <QSet>
#include <QTimer>
/**
* This widget provides a graphical control element for the CardFilter::Attr::AttrColor filters applied to the filter
* model.
* @param parent The Qt Widget that this widget will parent to
* @param _filterModel The filter model that this widget will manipulate
*/
VisualDatabaseDisplayColorFilterWidget::VisualDatabaseDisplayColorFilterWidget(QWidget *parent,
FilterTreeModel *_filterModel)
: QWidget(parent), filterModel(_filterModel), 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,
&VisualDatabaseDisplayColorFilterWidget::handleColorToggled);
}
toggleButton = new QPushButton(this);
toggleButton->setCheckable(true);
layout->addWidget(toggleButton);
// Connect the button's toggled signal
connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplayColorFilterWidget::updateFilterMode);
connect(this, &VisualDatabaseDisplayColorFilterWidget::activeColorsChanged, this,
&VisualDatabaseDisplayColorFilterWidget::updateColorFilter);
connect(this, &VisualDatabaseDisplayColorFilterWidget::filterModeChanged, this,
&VisualDatabaseDisplayColorFilterWidget::updateColorFilter);
connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() {
if (blockSync) {
return; // Skip sync if we're blocking it
}
QTimer::singleShot(100, this, &VisualDatabaseDisplayColorFilterWidget::syncWithFilterModel);
});
// Call retranslateUi to set the initial text
retranslateUi();
}
void VisualDatabaseDisplayColorFilterWidget::retranslateUi()
{
switch (currentMode) {
case FilterMode::ExactMatch:
toggleButton->setText(tr("Mode: Exact Match"));
break;
case FilterMode::Includes:
toggleButton->setText(tr("Mode: Includes"));
break;
case FilterMode::IncludeExclude:
toggleButton->setText(tr("Mode: Include/Exclude"));
break;
}
}
void VisualDatabaseDisplayColorFilterWidget::handleColorToggled(QChar color, bool active)
{
activeColors[color] = active;
emit activeColorsChanged(); // Notify listeners that the active colors have changed
}
void VisualDatabaseDisplayColorFilterWidget::updateColorFilter()
{
blockSync = true;
// Clear previous filters
filterModel->blockSignals(true);
filterModel->filterTree()->blockSignals(true);
filterModel->clearFiltersOfType(CardFilter::Attr::AttrColor);
QSet<QString> selectedColors;
QSet<QString> excludedColors;
// Collect active colors in the selected and excluded sets
for (const auto &color : activeColors.keys()) {
if (activeColors[color]) {
selectedColors.insert(color); // Include this color
} else {
excludedColors.insert(color); // Exclude this color
}
}
switch (currentMode) {
case FilterMode::ExactMatch:
// Exact Match Mode: Only selected colors are allowed
if (!selectedColors.isEmpty()) {
// Require all selected colors (TypeAnd)
for (const auto &color : selectedColors) {
QString colorString = color;
filterModel->addFilter(
new CardFilter(colorString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrColor));
}
// Exclude all other colors
QStringList allPossibleColors = {"W", "U", "B", "R", "G"};
for (const auto &color : allPossibleColors) {
if (!selectedColors.contains(color)) {
QString colorString = color;
filterModel->addFilter(
new CardFilter(colorString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrColor));
}
}
}
break;
case FilterMode::Includes:
// Includes Mode: Just include selected colors without restrictions
for (const auto &color : selectedColors) {
QString colorString = color;
filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeOr,
CardFilter::Attr::AttrColor)); // OR for selected colors
}
break;
case FilterMode::IncludeExclude:
// Include/Exclude Mode: Include selected colors and exclude unselected colors
for (const auto &color : selectedColors) {
QString colorString = color;
filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeOr,
CardFilter::Attr::AttrColor)); // OR for selected colors
}
for (const auto &color : excludedColors) {
QString colorString = color;
filterModel->addFilter(new CardFilter(colorString, CardFilter::Type::TypeAndNot,
CardFilter::Attr::AttrColor)); // AND NOT for excluded colors
}
break;
}
filterModel->blockSignals(false);
filterModel->filterTree()->blockSignals(false);
emit filterModel->filterTree()->changed();
emit filterModel->layoutChanged();
blockSync = false;
}
void VisualDatabaseDisplayColorFilterWidget::updateFilterMode()
{
blockSync = true;
switch (currentMode) {
case FilterMode::ExactMatch:
currentMode = FilterMode::Includes; // Switch to Includes
break;
case FilterMode::Includes:
currentMode = FilterMode::IncludeExclude; // Switch to Include/Exclude
break;
case FilterMode::IncludeExclude:
currentMode = FilterMode::ExactMatch; // Switch to Exact Match
break;
}
retranslateUi(); // Update button text based on the mode
emit filterModeChanged(currentMode); // Signal mode change
updateColorFilter(); // Reapply the filter based on the new mode
blockSync = false;
}
void VisualDatabaseDisplayColorFilterWidget::syncWithFilterModel()
{
blockSync = true;
QSet<QString> currentFilters;
// Get current filters of type color
for (const auto &filter : filterModel->getFiltersOfType(CardFilter::Attr::AttrColor)) {
if (filter->type() == CardFilter::Type::TypeAnd || filter->type() == CardFilter::Type::TypeOr) {
currentFilters.insert(filter->term());
}
}
QSet<QString> activeFilterList;
// Iterate over the activeColors map and collect the active colors as strings
for (auto it = activeColors.constBegin(); it != activeColors.constEnd(); ++it) {
if (it.value()) { // Only add active colors
activeFilterList.insert(QString(it.key()));
}
}
// Check if the filters in the model match the active filters
if (currentFilters == activeFilterList) {
return;
}
// Remove filters that are in the UI but not in the model
for (const auto &color : activeFilterList) {
if (!currentFilters.contains(color)) {
activeColors[color[0]] = false; // Disable the color
}
}
// Add filters that are in the model but not in the UI
for (const auto &color : currentFilters) {
if (!activeFilterList.contains(color)) {
activeColors[color[0]] = true; // Enable the color
}
}
QList<ManaSymbolWidget *> manaSymbolWidgets = findChildren<ManaSymbolWidget *>();
for (ManaSymbolWidget *manaSymbolWidget : manaSymbolWidgets) {
manaSymbolWidget->setColorActive(activeColors[manaSymbolWidget->getSymbolChar()]);
}
updateColorFilter();
blockSync = false;
}

View File

@ -0,0 +1,63 @@
#ifndef VISUAL_DATABASE_DISPLAY_COLOR_FILTER_WIDGET_H
#define VISUAL_DATABASE_DISPLAY_COLOR_FILTER_WIDGET_H
#include "../../../../game/filters/filter_tree_model.h"
#include <QHBoxLayout>
#include <QPushButton>
#include <QWidget>
class VisualDatabaseDisplayColorFilterCircleWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDatabaseDisplayColorFilterCircleWidget(QChar color, QWidget *parent = nullptr);
void setColorActive(bool active);
bool isColorActive() const;
QChar getColorChar() const;
signals:
void colorToggled(QChar color, bool active);
private:
QChar colorChar;
bool isActive;
int circleDiameter;
};
enum class FilterMode
{
ExactMatch, // Only selected colors are included, all others are excluded.
Includes, // Include selected colors (OR condition).
IncludeExclude // Include selected colors (OR) and exclude unselected colors (AND NOT).
};
class VisualDatabaseDisplayColorFilterWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDatabaseDisplayColorFilterWidget(QWidget *parent, FilterTreeModel *filterModel);
void retranslateUi();
signals:
void filterModeChanged(FilterMode filterMode);
void activeColorsChanged();
private slots:
void handleColorToggled(QChar color, bool active);
void updateColorFilter();
void updateFilterMode();
void syncWithFilterModel();
private:
FilterTreeModel *filterModel;
QHBoxLayout *layout;
QPushButton *toggleButton;
QMap<QChar, bool> activeColors;
FilterMode currentMode = FilterMode::Includes; // Default mode
bool blockSync = false;
};
#endif // VISUAL_DATABASE_DISPLAY_COLOR_FILTER_WIDGET_H

View File

@ -0,0 +1,153 @@
#include "visual_database_display_filter_save_load_widget.h"
#include "../../../../game/filters/filter_tree.h"
#include "../../../../settings/cache_settings.h"
#include "visual_database_filter_display_widget.h"
#include <QHBoxLayout>
#include <QJsonArray>
#include <QJsonObject>
#include <QMessageBox>
VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWidget(QWidget *parent,
FilterTreeModel *_filterModel)
: QWidget(parent), filterModel(_filterModel)
{
setMinimumWidth(300);
setMaximumHeight(300);
layout = new QVBoxLayout(this);
setLayout(layout);
// Input for filter filename
filenameInput = new QLineEdit(this);
layout->addWidget(filenameInput);
// Save button
saveButton = new QPushButton(this);
layout->addWidget(saveButton);
connect(saveButton, &QPushButton::clicked, this, &VisualDatabaseDisplayFilterSaveLoadWidget::saveFilter);
// File list container
fileListWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(fileListWidget);
refreshFilterList(); // Populate the file list on startup
retranslateUi();
}
void VisualDatabaseDisplayFilterSaveLoadWidget::retranslateUi()
{
saveButton->setText(tr("Save Filter"));
filenameInput->setPlaceholderText(tr("Enter filename..."));
}
void VisualDatabaseDisplayFilterSaveLoadWidget::saveFilter()
{
QString filename = filenameInput->text().trimmed();
if (filename.isEmpty())
return;
QString filePath = SettingsCache::instance().getFiltersPath() + QDir::separator() + filename + ".json";
// Serialize the filter model to JSON
QJsonArray filtersArray;
QList<const CardFilter *> cardFilters = filterModel->allFilters();
for (const CardFilter *filter : cardFilters) {
filtersArray.append(filter->toJson());
}
QJsonObject root;
root["filters"] = filtersArray;
QFile file(filePath);
if (file.open(QIODevice::WriteOnly)) {
file.write(QJsonDocument(root).toJson(QJsonDocument::Indented));
file.close();
}
filenameInput->clear();
refreshFilterList(); // Update the file list
}
void VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter(const QString &filename)
{
QString filePath = SettingsCache::instance().getFiltersPath() + QDir::separator() + filename;
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly))
return;
QByteArray jsonData = file.readAll();
file.close();
QJsonDocument doc = QJsonDocument::fromJson(jsonData);
if (!doc.isObject())
return;
QJsonObject root = doc.object();
if (!root.contains("filters") || !root["filters"].isArray())
return;
QJsonArray filtersArray = root["filters"].toArray();
filterModel->clear();
filterModel->blockSignals(true);
filterModel->filterTree()->blockSignals(true);
for (const QJsonValue &value : filtersArray) {
if (value.isObject()) {
QJsonObject filterObj = value.toObject();
CardFilter *filter = CardFilter::fromJson(filterObj);
if (filter) {
filterModel->addFilter(filter);
}
}
}
filterModel->blockSignals(false);
filterModel->filterTree()->blockSignals(false);
emit filterModel->filterTree()->changed();
emit filterModel->layoutChanged();
}
void VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList()
{
fileListWidget->clearLayout();
// Clear existing widgets
for (auto buttonPair : fileButtons) {
buttonPair.first->deleteLater();
buttonPair.second->deleteLater();
}
fileButtons.clear(); // Clear the list of buttons
// Refresh the filter file list
QDir dir(SettingsCache::instance().getFiltersPath());
QStringList filterFiles = dir.entryList(QStringList() << "*.json", QDir::Files, QDir::Time);
// Loop through the filter files and create widgets for them
for (const QString &filename : filterFiles) {
bool alreadyAdded = false;
// Check if the widget for this filter file already exists to avoid duplicates
for (const auto &pair : fileButtons) {
if (pair.first->text() == filename) {
alreadyAdded = true;
break;
}
}
if (!alreadyAdded) {
// Create a new custom widget for the filter
FilterDisplayWidget *filterWidget = new FilterDisplayWidget(this, filename, filterModel);
fileListWidget->addWidget(filterWidget);
// Connect signals to handle loading and deletion
connect(filterWidget, &FilterDisplayWidget::filterLoadRequested, this,
&VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter);
connect(filterWidget, &FilterDisplayWidget::filterDeleted, this,
&VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList);
}
}
}

View File

@ -0,0 +1,43 @@
#ifndef VISUAL_DATABASE_DISPLAY_FILTER_SAVE_LOAD_WIDGET_H
#define VISUAL_DATABASE_DISPLAY_FILTER_SAVE_LOAD_WIDGET_H
#include "../../../../game/filters/filter_tree_model.h"
#include "../general/layout_containers/flow_widget.h"
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLineEdit>
#include <QMap>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
class VisualDatabaseDisplayFilterSaveLoadWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDatabaseDisplayFilterSaveLoadWidget(QWidget *parent, FilterTreeModel *filterModel);
void saveFilter();
void loadFilter(const QString &filename);
void refreshFilterList();
void deleteFilter(const QString &filename, QPushButton *deleteButton);
public slots:
void retranslateUi();
private:
FilterTreeModel *filterModel;
QVBoxLayout *layout;
QLineEdit *filenameInput;
QPushButton *saveButton;
FlowWidget *fileListWidget;
QMap<QString, QPair<QPushButton *, QPushButton *>> fileButtons;
};
#endif // VISUAL_DATABASE_DISPLAY_FILTER_SAVE_LOAD_WIDGET_H

View File

@ -0,0 +1,196 @@
#include "visual_database_display_main_type_filter_widget.h"
#include "../../../../game/cards/card_database_manager.h"
#include "../../../../game/filters/filter_tree.h"
#include "../../../../game/filters/filter_tree_model.h"
#include <QPushButton>
#include <QSpinBox>
#include <QTimer>
VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWidget(QWidget *parent,
FilterTreeModel *_filterModel)
: QWidget(parent), filterModel(_filterModel)
{
allMainCardTypesWithCount = CardDatabaseManager::getInstance()->getAllMainCardTypesWithCount();
// Get all main card types with their count
setMaximumHeight(75);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
layout = new QHBoxLayout(this);
setLayout(layout);
layout->setContentsMargins(0, 1, 0, 1);
layout->setSpacing(1);
layout->setAlignment(Qt::AlignTop);
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
// Create the spinbox
spinBox = new QSpinBox(this);
spinBox->setMinimum(1);
spinBox->setMaximum(getMaxMainTypeCount()); // Set the max value dynamically
spinBox->setValue(150);
layout->addWidget(spinBox);
connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
&VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeButtonsVisibility);
// Create the toggle button for Exact Match/Includes mode
toggleButton = new QPushButton(this);
toggleButton->setCheckable(true);
layout->addWidget(toggleButton);
connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplayMainTypeFilterWidget::updateFilterMode);
connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() {
QTimer::singleShot(100, this, &VisualDatabaseDisplayMainTypeFilterWidget::syncWithFilterModel);
});
createMainTypeButtons(); // Populate buttons initially
updateFilterMode(false); // Initialize toggle button text
}
void VisualDatabaseDisplayMainTypeFilterWidget::createMainTypeButtons()
{
// Iterate through main types and create buttons
for (auto it = allMainCardTypesWithCount.begin(); it != allMainCardTypesWithCount.end(); ++it) {
auto *button = new QPushButton(it.key(), flowWidget);
button->setCheckable(true);
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
"QPushButton:checked { background-color: green; color: white; }");
flowWidget->addWidget(button);
typeButtons[it.key()] = button;
// Connect toggle signal
connect(button, &QPushButton::toggled, this,
[this, mainType = it.key()](bool checked) { handleMainTypeToggled(mainType, checked); });
}
updateMainTypeButtonsVisibility(); // Ensure visibility is updated initially
}
void VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeButtonsVisibility()
{
int threshold = spinBox->value(); // Get the current spinbox value
// Iterate through buttons and hide/disable those below the threshold
for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) {
bool visible = allMainCardTypesWithCount[it.key()] >= threshold;
it.value()->setVisible(visible);
it.value()->setEnabled(visible);
}
}
int VisualDatabaseDisplayMainTypeFilterWidget::getMaxMainTypeCount() const
{
int maxCount = 1;
for (auto it = allMainCardTypesWithCount.begin(); it != allMainCardTypesWithCount.end(); ++it) {
maxCount = qMax(maxCount, it.value());
}
return maxCount;
}
void VisualDatabaseDisplayMainTypeFilterWidget::handleMainTypeToggled(const QString &mainType, bool active)
{
activeMainTypes[mainType] = active;
if (typeButtons.contains(mainType)) {
typeButtons[mainType]->setChecked(active);
}
updateMainTypeFilter();
}
void VisualDatabaseDisplayMainTypeFilterWidget::updateMainTypeFilter()
{
// Clear existing filters related to main type
filterModel->blockSignals(true);
filterModel->filterTree()->blockSignals(true);
filterModel->clearFiltersOfType(CardFilter::Attr::AttrMainType);
if (exactMatchMode) {
// Exact Match: Only selected main types are allowed
QSet<QString> selectedTypes;
for (const auto &type : activeMainTypes.keys()) {
if (activeMainTypes[type]) {
selectedTypes.insert(type);
}
}
if (!selectedTypes.isEmpty()) {
// Require all selected types (TypeAnd)
for (const auto &type : selectedTypes) {
QString typeString = type;
filterModel->addFilter(
new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrMainType));
}
// Exclude any other types (TypeAndNot)
for (const auto &type : typeButtons.keys()) {
if (!selectedTypes.contains(type)) {
QString typeString = type;
filterModel->addFilter(
new CardFilter(typeString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrMainType));
}
}
}
} else {
// Default Includes Mode (TypeOr) - match any selected main types
for (const auto &type : activeMainTypes.keys()) {
if (activeMainTypes[type]) {
QString typeString = type;
filterModel->addFilter(
new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrMainType));
}
}
}
filterModel->blockSignals(false);
filterModel->filterTree()->blockSignals(false);
emit filterModel->filterTree()->changed();
emit filterModel->layoutChanged();
}
void VisualDatabaseDisplayMainTypeFilterWidget::updateFilterMode(bool checked)
{
exactMatchMode = checked;
toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes"));
updateMainTypeFilter();
}
void VisualDatabaseDisplayMainTypeFilterWidget::syncWithFilterModel()
{
// Temporarily block signals for each button to prevent toggling while updating button states
for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) {
it.value()->blockSignals(true);
}
// Uncheck all buttons
for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) {
it.value()->setChecked(false);
}
// Get active filters for main types
QSet<QString> activeTypes;
for (const auto &filter : filterModel->getFiltersOfType(CardFilter::AttrMainType)) {
if (filter->type() == CardFilter::Type::TypeAnd) {
activeTypes.insert(filter->term());
}
}
// Check the buttons for active types
for (const auto &type : activeTypes) {
activeMainTypes[type] = true;
if (typeButtons.contains(type)) {
typeButtons[type]->setChecked(true);
}
}
// Re-enable signal emissions for each button
for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) {
it.value()->blockSignals(false);
}
// Update the visibility of buttons
updateMainTypeButtonsVisibility();
}

View File

@ -0,0 +1,42 @@
#ifndef VISUAL_DATABASE_DISPLAY_MAIN_TYPE_FILTER_WIDGET_H
#define VISUAL_DATABASE_DISPLAY_MAIN_TYPE_FILTER_WIDGET_H
#include "../../../../game/filters/filter_tree_model.h"
#include "../general/layout_containers/flow_widget.h"
#include <QMap>
#include <QPushButton>
#include <QSpinBox>
#include <QToolButton>
#include <QVBoxLayout>
#include <QWidget>
class VisualDatabaseDisplayMainTypeFilterWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDatabaseDisplayMainTypeFilterWidget(QWidget *parent, FilterTreeModel *filterModel);
void createMainTypeButtons();
void updateMainTypeButtonsVisibility();
int getMaxMainTypeCount() const;
void handleMainTypeToggled(const QString &mainType, bool active);
void updateMainTypeFilter();
void updateFilterMode(bool checked);
void syncWithFilterModel();
private:
FilterTreeModel *filterModel;
QMap<QString, int> allMainCardTypesWithCount;
QSpinBox *spinBox;
QHBoxLayout *layout;
FlowWidget *flowWidget;
QPushButton *toggleButton; // Mode switch button
QMap<QString, bool> activeMainTypes; // Track active filters
QMap<QString, QPushButton *> typeButtons; // Store toggle buttons
bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes"
};
#endif // VISUAL_DATABASE_DISPLAY_MAIN_TYPE_FILTER_WIDGET_H

View File

@ -0,0 +1,160 @@
#include "visual_database_display_name_filter_widget.h"
#include "../../../tabs/abstract_tab_deck_editor.h"
#include <QHBoxLayout>
VisualDatabaseDisplayNameFilterWidget::VisualDatabaseDisplayNameFilterWidget(QWidget *parent,
AbstractTabDeckEditor *_deckEditor,
FilterTreeModel *_filterModel)
: QWidget(parent), deckEditor(_deckEditor), filterModel(_filterModel)
{
setMinimumWidth(300);
setMaximumHeight(300);
layout = new QVBoxLayout(this);
setLayout(layout);
searchBox = new QLineEdit(this);
layout->addWidget(searchBox);
connect(searchBox, &QLineEdit::returnPressed, this, [this]() {
QString text = searchBox->text().trimmed();
if (!text.isEmpty() && !activeFilters.contains(text)) {
createNameFilter(text);
searchBox->clear();
updateFilterModel();
}
});
// Create container for active filters
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
loadFromDeckButton = new QPushButton(this);
layout->addWidget(loadFromDeckButton);
connect(loadFromDeckButton, &QPushButton::clicked, this, &VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck);
connect(filterModel, &FilterTreeModel::layoutChanged, this,
[this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplayNameFilterWidget::syncWithFilterModel); });
retranslateUi();
}
void VisualDatabaseDisplayNameFilterWidget::retranslateUi()
{
searchBox->setPlaceholderText(tr("Filter by name..."));
loadFromDeckButton->setText(tr("Load from Deck"));
}
void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck()
{
DeckListModel *deckListModel = deckEditor->deckDockWidget->deckModel;
if (!deckListModel)
return;
DeckList *decklist = deckListModel->getDeckList();
if (!decklist)
return;
InnerDecklistNode *listRoot = decklist->getRoot();
if (!listRoot)
return;
for (int i = 0; i < listRoot->size(); i++) {
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
if (!currentZone)
continue;
for (int j = 0; j < currentZone->size(); j++) {
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
if (!currentCard)
continue;
createNameFilter(currentCard->getName());
}
}
updateFilterModel();
}
void VisualDatabaseDisplayNameFilterWidget::createNameFilter(const QString &name)
{
if (activeFilters.contains(name))
return;
// Create a button for the filter
auto *button = new QPushButton(name, flowWidget);
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
"QPushButton:hover { background-color: red; color: white; }");
connect(button, &QPushButton::clicked, this, [this, name]() { removeNameFilter(name); });
flowWidget->addWidget(button);
activeFilters[name] = button;
}
void VisualDatabaseDisplayNameFilterWidget::removeNameFilter(const QString &name)
{
if (activeFilters.contains(name)) {
activeFilters[name]->deleteLater(); // Safe deletion
activeFilters.remove(name);
QTimer::singleShot(0, this,
&VisualDatabaseDisplayNameFilterWidget::updateFilterModel); // Avoid concurrent modification
}
}
void VisualDatabaseDisplayNameFilterWidget::updateFilterModel()
{
// Clear existing name filters
emit filterModel->layoutAboutToBeChanged();
filterModel->clearFiltersOfType(CardFilter::Attr::AttrName);
filterModel->blockSignals(true);
filterModel->filterTree()->blockSignals(true);
for (const auto &name : activeFilters.keys()) {
QString nameString = name;
filterModel->addFilter(new CardFilter(nameString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrName));
}
filterModel->blockSignals(false);
filterModel->filterTree()->blockSignals(false);
filterModel->blockSignals(false);
filterModel->filterTree()->blockSignals(false);
emit filterModel->filterTree()->changed();
emit filterModel->layoutChanged();
}
void VisualDatabaseDisplayNameFilterWidget::syncWithFilterModel()
{
QStringList currentFilters;
for (const auto &filter : filterModel->getFiltersOfType(CardFilter::Attr::AttrName)) {
if (filter->type() == CardFilter::Type::TypeOr) {
currentFilters.append(filter->term());
}
}
QStringList activeFilterList = activeFilters.keys();
// Sort lists for efficient comparison
std::sort(currentFilters.begin(), currentFilters.end());
std::sort(activeFilterList.begin(), activeFilterList.end());
if (currentFilters == activeFilterList) {
return;
}
// Remove filters that are in the UI but not in the model
for (const auto &name : activeFilterList) {
if (!currentFilters.contains(name)) {
removeNameFilter(name);
}
}
// Add filters that are in the model but not in the UI
for (const auto &name : currentFilters) {
if (!activeFilters.contains(name)) {
createNameFilter(name);
}
}
}

View File

@ -0,0 +1,45 @@
#ifndef VISUAL_DATABASE_DISPLAY_NAME_FILTER_WIDGET_H
#define VISUAL_DATABASE_DISPLAY_NAME_FILTER_WIDGET_H
#include "../../../../game/filters/filter_tree_model.h"
#include "../../../tabs/abstract_tab_deck_editor.h"
#include "../general/layout_containers/flow_widget.h"
#include <QLineEdit>
#include <QMap>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
class VisualDatabaseDisplayNameFilterWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDatabaseDisplayNameFilterWidget(QWidget *parent,
AbstractTabDeckEditor *deckEditor,
FilterTreeModel *filterModel);
void createNameFilter(const QString &name);
void removeNameFilter(const QString &name);
void updateFilterList();
void updateFilterModel();
void syncWithFilterModel();
public slots:
void retranslateUi();
private:
AbstractTabDeckEditor *deckEditor;
FilterTreeModel *filterModel;
QVBoxLayout *layout;
QLineEdit *searchBox;
FlowWidget *flowWidget;
QPushButton *loadFromDeckButton;
QMap<QString, QPushButton *> activeFilters; // Store active name filter buttons
private slots:
void actLoadFromDeck();
};
#endif // VISUAL_DATABASE_DISPLAY_NAME_FILTER_WIDGET_H

View File

@ -0,0 +1,195 @@
#include "visual_database_display_set_filter_widget.h"
#include "../../../../game/cards/card_database_manager.h"
#include "../../../../game/filters/filter_tree.h"
#include "../../../../game/filters/filter_tree_model.h"
#include <QLineEdit>
#include <QPushButton>
#include <QTimer>
#include <algorithm>
VisualDatabaseDisplaySetFilterWidget::VisualDatabaseDisplaySetFilterWidget(QWidget *parent,
FilterTreeModel *_filterModel)
: QWidget(parent), filterModel(_filterModel)
{
setMinimumWidth(300);
setMaximumHeight(300);
layout = new QVBoxLayout(this);
setLayout(layout);
searchBox = new QLineEdit(this);
searchBox->setPlaceholderText(tr("Search sets..."));
layout->addWidget(searchBox);
connect(searchBox, &QLineEdit::textChanged, this,
&VisualDatabaseDisplaySetFilterWidget::updateSetButtonsVisibility);
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(flowWidget);
// Create the toggle button for Exact Match/Includes mode
toggleButton = new QPushButton(this);
toggleButton->setCheckable(true);
layout->addWidget(toggleButton);
connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplaySetFilterWidget::updateFilterMode);
connect(filterModel, &FilterTreeModel::layoutChanged, this,
[this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplaySetFilterWidget::syncWithFilterModel); });
createSetButtons(); // Populate buttons initially
updateFilterMode(false); // Initialize toggle button text
}
void VisualDatabaseDisplaySetFilterWidget::createSetButtons()
{
SetList shared_pointerses = CardDatabaseManager::getInstance()->getSetList();
// Sort by release date
std::sort(shared_pointerses.begin(), shared_pointerses.end(),
[](const auto &a, const auto &b) { return a->getReleaseDate() > b->getReleaseDate(); });
int setsToPreactivate = 10;
int setsActivated = 0;
for (const auto &shared_pointer : shared_pointerses) {
QString shortName = shared_pointer->getShortName();
QString longName = shared_pointer->getLongName();
auto *button = new QPushButton(longName + " (" + shortName + ")", flowWidget);
button->setCheckable(true);
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
"QPushButton:checked { background-color: green; color: white; }");
flowWidget->addWidget(button);
setButtons[shortName] = button;
// Connect toggle signal
connect(button, &QPushButton::toggled, this,
[this, shortName](bool checked) { handleSetToggled(shortName, checked); });
if (setsActivated < setsToPreactivate) {
setsActivated++;
activeSets[shortName] = true;
button->setChecked(true);
}
}
updateSetFilter();
updateSetButtonsVisibility(); // Ensure visibility is updated initially
}
void VisualDatabaseDisplaySetFilterWidget::updateSetButtonsVisibility()
{
QString filterText = searchBox->text().trimmed().toLower();
for (const QString &setName : setButtons.keys()) {
QPushButton *buttonForSet = setButtons[setName];
QString shortName = setName.toLower();
QString longName = buttonForSet->text().toLower();
bool alwaysVisible = activeSets.contains(setName) && activeSets[setName];
bool visible =
alwaysVisible || filterText.isEmpty() || shortName.contains(filterText) || longName.contains(filterText);
buttonForSet->setVisible(visible);
}
}
void VisualDatabaseDisplaySetFilterWidget::handleSetToggled(const QString &setShortName, bool active)
{
activeSets[setShortName] = active;
if (setButtons.contains(setShortName)) {
setButtons[setShortName]->setChecked(active);
}
updateSetFilter();
}
void VisualDatabaseDisplaySetFilterWidget::updateSetFilter()
{
// Clear existing filters related to sets
filterModel->blockSignals(true);
filterModel->filterTree()->blockSignals(true);
filterModel->clearFiltersOfType(CardFilter::Attr::AttrSet);
if (exactMatchMode) {
// Exact Match: Only selected sets are allowed
QSet<QString> selectedSets;
for (const auto &set : activeSets.keys()) {
if (activeSets[set]) {
selectedSets.insert(set);
}
}
if (!selectedSets.isEmpty()) {
// Require all selected sets (TypeAnd)
for (const auto &set : selectedSets) {
QString setString = set;
filterModel->addFilter(new CardFilter(setString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrSet));
}
// Exclude any other sets (TypeAndNot)
for (const auto &set : setButtons.keys()) {
if (!selectedSets.contains(set)) {
QString setString = set;
filterModel->addFilter(
new CardFilter(setString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrSet));
}
}
}
} else {
// Default Includes Mode (TypeOr) - match any selected sets
for (const auto &set : activeSets.keys()) {
if (activeSets[set]) {
QString setString = set;
filterModel->addFilter(new CardFilter(setString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrSet));
}
}
}
filterModel->blockSignals(false);
filterModel->filterTree()->blockSignals(false);
emit filterModel->filterTree()->changed();
emit filterModel->layoutChanged();
}
void VisualDatabaseDisplaySetFilterWidget::syncWithFilterModel()
{
// Clear activeSets
activeSets.clear();
// Read the current filters in filterModel
auto currentFilters = filterModel->getFiltersOfType(CardFilter::Attr::AttrSet);
// Determine if we're in Exact Match mode or Includes mode
QSet<QString> selectedSets;
QSet<QString> excludedSets;
for (const auto &filter : currentFilters) {
if (filter->type() == CardFilter::Type::TypeAnd) {
selectedSets.insert(filter->term());
} else if (filter->type() == CardFilter::Type::TypeAndNot) {
excludedSets.insert(filter->term());
} else if (filter->type() == CardFilter::Type::TypeOr) {
selectedSets.insert(filter->term());
}
}
// Determine exact match mode based on filter structure
bool newExactMatchMode = !excludedSets.isEmpty();
if (newExactMatchMode != exactMatchMode) {
exactMatchMode = newExactMatchMode;
toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes"));
}
// Sync button states with selected sets
for (const auto &key : setButtons.keys()) {
bool active = selectedSets.contains(key);
activeSets[key] = active;
setButtons[key]->setChecked(active);
}
}
void VisualDatabaseDisplaySetFilterWidget::updateFilterMode(bool checked)
{
exactMatchMode = checked;
toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes"));
updateSetFilter();
}

View File

@ -0,0 +1,43 @@
#ifndef VISUAL_DATABASE_DISPLAY_SET_FILTER_WIDGET_H
#define VISUAL_DATABASE_DISPLAY_SET_FILTER_WIDGET_H
#include "../../../../game/filters/filter_tree_model.h"
#include "../general/layout_containers/flow_widget.h"
#include <QLineEdit>
#include <QMap>
#include <QPushButton>
#include <QToolButton>
#include <QVBoxLayout>
#include <QWidget>
class VisualDatabaseDisplaySetFilterWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDatabaseDisplaySetFilterWidget(QWidget *parent, FilterTreeModel *filterModel);
void createSetButtons();
void updateSetButtonsVisibility();
void handleSetToggled(const QString &setShortName, bool active);
void updateSetFilter();
void syncWithFilterModel();
void updateFilterMode(bool checked);
private:
FilterTreeModel *filterModel;
QMap<QString, int> allMainCardTypesWithCount;
QVBoxLayout *layout;
QLineEdit *searchBox;
FlowWidget *flowWidget;
QPushButton *toggleButton; // Mode switch button
QMap<QString, bool> activeMainTypes; // Track active filters
QMap<QString, QPushButton *> typeButtons; // Store toggle buttons
QMap<QString, QPushButton *> setButtons; // Store set filter buttons
QMap<QString, bool> activeSets; // Track active set filters
bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes"
};
#endif // VISUAL_DATABASE_DISPLAY_SET_FILTER_WIDGET_H

View File

@ -0,0 +1,203 @@
#include "visual_database_display_sub_type_filter_widget.h"
#include "../../../../game/cards/card_database_manager.h"
#include "../../../../game/filters/filter_tree.h"
#include "../../../../game/filters/filter_tree_model.h"
#include <QLineEdit>
#include <QPushButton>
#include <QSpinBox>
#include <QTimer>
VisualDatabaseDisplaySubTypeFilterWidget::VisualDatabaseDisplaySubTypeFilterWidget(QWidget *parent,
FilterTreeModel *_filterModel)
: QWidget(parent), filterModel(_filterModel)
{
allSubCardTypesWithCount = CardDatabaseManager::getInstance()->getAllSubCardTypesWithCount();
setMinimumWidth(300);
layout = new QVBoxLayout(this);
setLayout(layout);
// Create and setup the spinbox
spinBox = new QSpinBox(this);
spinBox->setMinimum(1);
spinBox->setMaximum(getMaxSubTypeCount());
spinBox->setValue(150);
layout->addWidget(spinBox);
connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged), this,
&VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeButtonsVisibility);
// Create search box
searchBox = new QLineEdit(this);
searchBox->setPlaceholderText(tr("Search subtypes..."));
layout->addWidget(searchBox);
connect(searchBox, &QLineEdit::textChanged, this,
&VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeButtonsVisibility);
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
flowWidget->setMaximumHeight(300);
layout->addWidget(flowWidget);
// Toggle button setup (Exact Match / Includes mode)
toggleButton = new QPushButton(this);
toggleButton->setCheckable(true);
layout->addWidget(toggleButton);
connect(toggleButton, &QPushButton::toggled, this, &VisualDatabaseDisplaySubTypeFilterWidget::updateFilterMode);
connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() {
QTimer::singleShot(100, this, &VisualDatabaseDisplaySubTypeFilterWidget::syncWithFilterModel);
});
createSubTypeButtons(); // Populate buttons initially
updateFilterMode(false); // Initialize the toggle button text
}
void VisualDatabaseDisplaySubTypeFilterWidget::createSubTypeButtons()
{
// Iterate through sub types and create buttons
for (auto it = allSubCardTypesWithCount.begin(); it != allSubCardTypesWithCount.end(); ++it) {
auto *button = new QPushButton(it.key(), flowWidget);
button->setCheckable(true);
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
"QPushButton:checked { background-color: green; color: white; }");
flowWidget->addWidget(button);
typeButtons[it.key()] = button;
// Connect toggle signal for each button
connect(button, &QPushButton::toggled, this,
[this, subType = it.key()](bool checked) { handleSubTypeToggled(subType, checked); });
}
updateSubTypeButtonsVisibility(); // Ensure visibility is updated initially
}
void VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeButtonsVisibility()
{
int threshold = spinBox->value();
QString filterText = searchBox->text().trimmed().toLower();
// Iterate through buttons and hide/disable those below the threshold
for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) {
QString subType = it.key().toLower();
bool isActive = activeSubTypes.value(it.key(), false);
bool visible = isActive || (allSubCardTypesWithCount[it.key()] >= threshold &&
(filterText.isEmpty() || subType.contains(filterText)));
it.value()->setVisible(visible);
it.value()->setEnabled(visible);
}
}
int VisualDatabaseDisplaySubTypeFilterWidget::getMaxSubTypeCount() const
{
int maxCount = 1;
for (auto it = allSubCardTypesWithCount.begin(); it != allSubCardTypesWithCount.end(); ++it) {
maxCount = qMax(maxCount, it.value());
}
return maxCount;
}
void VisualDatabaseDisplaySubTypeFilterWidget::handleSubTypeToggled(const QString &subType, bool active)
{
activeSubTypes[subType] = active;
if (typeButtons.contains(subType)) {
typeButtons[subType]->setChecked(active);
}
updateSubTypeFilter();
}
void VisualDatabaseDisplaySubTypeFilterWidget::updateSubTypeFilter()
{
filterModel->blockSignals(true);
// Clear existing filters related to sub types
filterModel->clearFiltersOfType(CardFilter::Attr::AttrSubType);
if (exactMatchMode) {
// Exact Match: Only selected sub types are allowed
QSet<QString> selectedTypes;
for (const auto &type : activeSubTypes.keys()) {
if (activeSubTypes[type]) {
selectedTypes.insert(type);
}
}
if (!selectedTypes.isEmpty()) {
// Require all selected subtypes (TypeAnd)
for (const auto &type : selectedTypes) {
QString typeString = type;
filterModel->addFilter(
new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrSubType));
}
// Exclude any other types (TypeAndNot)
for (const auto &type : typeButtons.keys()) {
if (!selectedTypes.contains(type)) {
QString typeString = type;
filterModel->addFilter(
new CardFilter(typeString, CardFilter::Type::TypeAndNot, CardFilter::Attr::AttrSubType));
}
}
}
} else {
// Default Includes Mode (TypeOr) - match any selected subtypes
for (const auto &type : activeSubTypes.keys()) {
if (activeSubTypes[type]) {
QString typeString = type;
filterModel->addFilter(
new CardFilter(typeString, CardFilter::Type::TypeAnd, CardFilter::Attr::AttrSubType));
}
}
}
filterModel->blockSignals(false);
filterModel->filterTree()->blockSignals(false);
emit filterModel->filterTree()->changed();
emit filterModel->layoutChanged();
}
void VisualDatabaseDisplaySubTypeFilterWidget::updateFilterMode(bool checked)
{
exactMatchMode = checked;
toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes"));
updateSubTypeFilter();
}
void VisualDatabaseDisplaySubTypeFilterWidget::syncWithFilterModel()
{
// Temporarily block signals for each button to prevent toggling while updating button states
for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) {
it.value()->blockSignals(true);
}
// Uncheck all buttons
for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) {
it.value()->setChecked(false);
}
// Get active filters for sub types
QSet<QString> activeTypes;
for (const auto &filter : filterModel->getFiltersOfType(CardFilter::AttrSubType)) {
if (filter->type() == CardFilter::Type::TypeAnd) {
activeTypes.insert(filter->term());
}
}
// Check the buttons for active types
for (const auto &type : activeTypes) {
activeSubTypes[type] = true;
if (typeButtons.contains(type)) {
typeButtons[type]->setChecked(true);
}
}
// Re-enable signal emissions for each button
for (auto it = typeButtons.begin(); it != typeButtons.end(); ++it) {
it.value()->blockSignals(false);
}
// Update the visibility of buttons
updateSubTypeButtonsVisibility();
}

View File

@ -0,0 +1,43 @@
#ifndef VISUAL_DATABASE_DISPLAY_SUB_TYPE_FILTER_WIDGET_H
#define VISUAL_DATABASE_DISPLAY_SUB_TYPE_FILTER_WIDGET_H
#include "../../../../game/filters/filter_tree_model.h"
#include "../general/layout_containers/flow_widget.h"
#include <QMap>
#include <QPushButton>
#include <QSpinBox>
#include <QToolButton>
#include <QVBoxLayout>
#include <QWidget>
class VisualDatabaseDisplaySubTypeFilterWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDatabaseDisplaySubTypeFilterWidget(QWidget *parent, FilterTreeModel *filterModel);
void createSubTypeButtons();
void updateSubTypeButtonsVisibility();
int getMaxSubTypeCount() const;
void handleSubTypeToggled(const QString &mainType, bool active);
void updateSubTypeFilter();
void updateFilterMode(bool checked);
void syncWithFilterModel();
private:
FilterTreeModel *filterModel;
QMap<QString, int> allSubCardTypesWithCount;
QSpinBox *spinBox;
QVBoxLayout *layout;
QLineEdit *searchBox;
FlowWidget *flowWidget;
QPushButton *toggleButton; // Mode switch button
QMap<QString, bool> activeSubTypes; // Track active filters
QMap<QString, QPushButton *> typeButtons; // Store toggle buttons
bool exactMatchMode = false; // Toggle between "Exact Match" and "Includes"
};
#endif // VISUAL_DATABASE_DISPLAY_SUB_TYPE_FILTER_WIDGET_H

View File

@ -0,0 +1,291 @@
#include "visual_database_display_widget.h"
#include "../../../../deck/custom_line_edit.h"
#include "../../../../game/cards/card_database.h"
#include "../../../../game/cards/card_database_manager.h"
#include "../../../../game/filters/filter_tree_model.h"
#include "../../../../settings/cache_settings.h"
#include "../../../../utility/card_info_comparator.h"
#include "../../pixel_map_generator.h"
#include "../cards/card_info_picture_with_text_overlay_widget.h"
#include "../quick_settings/settings_button_widget.h"
#include "visual_database_display_color_filter_widget.h"
#include "visual_database_display_filter_save_load_widget.h"
#include "visual_database_display_main_type_filter_widget.h"
#include "visual_database_display_name_filter_widget.h"
#include "visual_database_display_set_filter_widget.h"
#include "visual_database_display_sub_type_filter_widget.h"
#include <QHeaderView>
#include <QScrollBar>
#include <qpropertyanimation.h>
#include <utility>
VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent,
AbstractTabDeckEditor *_deckEditor,
CardDatabaseModel *database_model,
CardDatabaseDisplayModel *database_display_model)
: QWidget(parent), deckEditor(_deckEditor), databaseModel(database_model),
databaseDisplayModel(database_display_model)
{
cards = new QList<CardInfoPtr>;
connect(databaseDisplayModel, &CardDatabaseDisplayModel::modelDirty, this,
&VisualDatabaseDisplayWidget::modelDirty);
// Set up main layout and widgets
setMinimumSize(0, 0);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mainLayout = new QVBoxLayout(this);
setLayout(mainLayout);
mainLayout->setSpacing(1);
mainLayout->setContentsMargins(0, 0, 0, 0);
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarPolicy::ScrollBarAsNeeded);
cardSizeWidget = new CardSizeWidget(this, flowWidget);
searchContainer = new QWidget(this);
searchLayout = new QHBoxLayout(searchContainer);
searchContainer->setLayout(searchLayout);
searchEdit = new SearchLineEdit();
searchEdit->setObjectName("searchEdit");
searchEdit->setPlaceholderText(tr("Search by card name (or search expressions)"));
searchEdit->setClearButtonEnabled(true);
searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition);
searchEdit->installEventFilter(&searchKeySignals);
setFocusProxy(searchEdit);
setFocusPolicy(Qt::ClickFocus);
filterModel = new FilterTreeModel();
filterModel->setObjectName("filterModel");
databaseDisplayModel->setFilterTree(filterModel->filterTree());
connect(filterModel, &FilterTreeModel::layoutChanged, this, &VisualDatabaseDisplayWidget::searchModelChanged);
searchKeySignals.setObjectName("searchKeySignals");
connect(searchEdit, &QLineEdit::textChanged, this, &VisualDatabaseDisplayWidget::updateSearch);
/*connect(&searchKeySignals, SIGNAL(onEnter()), this, SLOT(actAddCard()));
connect(&searchKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actAddCard()));
connect(&searchKeySignals, SIGNAL(onCtrlAltRBracket()), this, SLOT(actAddCardToSideboard()));
connect(&searchKeySignals, SIGNAL(onCtrlAltMinus()), this, SLOT(actDecrementCard()));
connect(&searchKeySignals, SIGNAL(onCtrlAltLBracket()), this, SLOT(actDecrementCardFromSideboard()));
connect(&searchKeySignals, SIGNAL(onCtrlAltEnter()), this, SLOT(actAddCardToSideboard()));
connect(&searchKeySignals, SIGNAL(onCtrlEnter()), this, SLOT(actAddCardToSideboard()));
connect(&searchKeySignals, SIGNAL(onCtrlC()), this, SLOT(copyDatabaseCellContents()));*/
connect(help, &QAction::triggered, deckEditor->databaseDisplayDockWidget,
&DeckEditorDatabaseDisplayWidget::showSearchSyntaxHelp);
databaseView = new QTreeView(this);
databaseView->setObjectName("databaseView");
databaseView->setFocusProxy(searchEdit);
databaseView->setRootIsDecorated(false);
databaseView->setItemDelegate(nullptr);
databaseView->setSortingEnabled(true);
databaseView->sortByColumn(0, Qt::AscendingOrder);
databaseView->setModel(databaseDisplayModel);
databaseView->setVisible(false);
searchEdit->setTreeView(databaseView);
colorFilterWidget = new VisualDatabaseDisplayColorFilterWidget(this, filterModel);
quickFilterWidget = new SettingsButtonWidget(this);
saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel);
nameFilterWidget = new VisualDatabaseDisplayNameFilterWidget(this, deckEditor, filterModel);
mainTypeFilterWidget = new VisualDatabaseDisplayMainTypeFilterWidget(this, filterModel);
subTypeFilterWidget = new VisualDatabaseDisplaySubTypeFilterWidget(this, filterModel);
setFilterWidget = new VisualDatabaseDisplaySetFilterWidget(this, filterModel);
quickFilterWidget->addSettingsWidget(saveLoadWidget);
quickFilterWidget->addSettingsWidget(nameFilterWidget);
// quickFilterWidget->addSettingsWidget(mainTypeFilterWidget);
quickFilterWidget->addSettingsWidget(subTypeFilterWidget);
quickFilterWidget->addSettingsWidget(setFilterWidget);
searchLayout->addWidget(colorFilterWidget);
searchLayout->addWidget(quickFilterWidget);
searchLayout->addWidget(searchEdit);
mainLayout->addWidget(searchContainer);
mainLayout->addWidget(mainTypeFilterWidget);
mainLayout->addWidget(flowWidget);
mainLayout->addWidget(cardSizeWidget);
debounceTimer = new QTimer(this);
debounceTimer->setSingleShot(true); // Ensure it only fires once after the timeout
connect(debounceTimer, &QTimer::timeout, this, &VisualDatabaseDisplayWidget::searchModelChanged);
loadCardsTimer = new QTimer(this);
loadCardsTimer->setSingleShot(true); // Ensure it only fires once after the timeout
connect(loadCardsTimer, &QTimer::timeout, this, [this]() { loadCurrentPage(); });
loadCardsTimer->start(5000);
}
void VisualDatabaseDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
loadCurrentPage();
}
void VisualDatabaseDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance)
{
emit cardClickedDatabaseDisplay(event, instance);
}
void VisualDatabaseDisplayWidget::onHover(const CardInfoPtr &hoveredCard)
{
emit cardHoveredDatabaseDisplay(hoveredCard);
}
void VisualDatabaseDisplayWidget::addCard(const CardInfoPtr &cardToAdd)
{
cards->append(cardToAdd);
auto *display = new CardInfoPictureWithTextOverlayWidget(flowWidget, false);
display->setScaleFactor(cardSizeWidget->getSlider()->value());
display->setCard(cardToAdd);
flowWidget->addWidget(display);
connect(display, &CardInfoPictureWithTextOverlayWidget::imageClicked, this, &VisualDatabaseDisplayWidget::onClick);
connect(display, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, &VisualDatabaseDisplayWidget::onHover);
connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, display, &CardInfoPictureWidget::setScaleFactor);
}
void VisualDatabaseDisplayWidget::populateCards()
{
int rowCount = databaseDisplayModel->rowCount();
cards->clear();
// Calculate the start and end indices for the current page
int start = currentPage * cardsPerPage;
int end = qMin(start + cardsPerPage, rowCount);
qCDebug(VisualDatabaseDisplayLog) << "Fetching from " << start << " to " << end << " cards";
// Load more cards if we are at the end of the current list and can fetch more
if (end >= rowCount && databaseDisplayModel->canFetchMore(QModelIndex())) {
qCDebug(VisualDatabaseDisplayLog) << "We gotta load more";
databaseDisplayModel->fetchMore(QModelIndex());
}
for (int row = start; row < end; ++row) {
qCDebug(VisualDatabaseDisplayLog) << "Adding " << row;
QModelIndex index = databaseDisplayModel->index(row, CardDatabaseModel::NameColumn);
QVariant name = databaseDisplayModel->data(index, Qt::DisplayRole);
qCDebug(VisualDatabaseDisplayLog) << name.toString();
if (CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(name.toString())) {
addCard(info);
} else {
qCDebug(VisualDatabaseDisplayLog) << "Card not found in database!";
}
}
currentPage++;
}
void VisualDatabaseDisplayWidget::updateSearch(const QString &search) const
{
databaseDisplayModel->setStringFilter(search);
QModelIndexList sel = databaseView->selectionModel()->selectedRows();
if (sel.isEmpty() && databaseDisplayModel->rowCount())
databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0),
QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
}
void VisualDatabaseDisplayWidget::searchModelChanged()
{
// Clear the current page and prepare for new data
flowWidget->clearLayout(); // Clear existing cards
cards->clear(); // Clear the card list
// Reset scrollbar position to the top after loading new cards
if (QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar()) {
scrollBar->setValue(0); // Reset scrollbar to top
}
currentPage = 0;
loadCurrentPage();
qCDebug(VisualDatabaseDisplayLog) << "Search model changed";
}
void VisualDatabaseDisplayWidget::loadNextPage()
{
// Calculate the start and end indices for the next page
int rowCount = databaseDisplayModel->rowCount();
int start = currentPage * cardsPerPage;
int end = qMin(start + cardsPerPage, rowCount);
// Load more cards if we are at the end of the current list and can fetch more
if (end >= rowCount && databaseDisplayModel->canFetchMore(QModelIndex())) {
databaseDisplayModel->fetchMore(QModelIndex());
}
// Load the next page of cards and add them to the flow widget
for (int row = start; row < end; ++row) {
QModelIndex index = databaseDisplayModel->index(row, CardDatabaseModel::NameColumn);
QVariant name = databaseDisplayModel->data(index, Qt::DisplayRole);
if (CardInfoPtr info = CardDatabaseManager::getInstance()->getCard(name.toString())) {
addCard(info);
} else {
qCDebug(VisualDatabaseDisplayLog) << "Card " << name.toString() << " not found in database!";
}
}
// Update the current page
currentPage++;
}
void VisualDatabaseDisplayWidget::loadCurrentPage()
{
// Ensure only the initial page is loaded
if (currentPage == 0) {
// Only load the first page initially
qCDebug(VisualDatabaseDisplayLog) << "Loading the first page";
populateCards();
} else {
// If not the first page, just load the next page and append to the flow widget
loadNextPage();
}
}
void VisualDatabaseDisplayWidget::modelDirty() const
{
debounceTimer->start(debounceTime);
}
void VisualDatabaseDisplayWidget::sortCardList(const QStringList &properties,
Qt::SortOrder order = Qt::AscendingOrder) const
{
CardInfoComparator comparator(properties, order);
std::sort(cards->begin(), cards->end(), comparator);
}
void VisualDatabaseDisplayWidget::databaseDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
(void)topLeft;
(void)bottomRight;
qCDebug(VisualDatabaseDisplayLog) << "Database Data changed";
}
void VisualDatabaseDisplayWidget::wheelEvent(QWheelEvent *event)
{
int totalRows = databaseDisplayModel->rowCount(); // Total number of cards
int nextPageStartIndex = (currentPage + 1) * cardsPerPage;
// Handle scrolling down
if (event->angleDelta().y() < 0) {
// Check if the next page has any cards to load
if (nextPageStartIndex < totalRows) {
loadCurrentPage(); // Load the next page
event->accept(); // Accept the event as valid
return;
}
qCDebug(VisualDatabaseDisplayLog) << nextPageStartIndex << ":" << totalRows;
}
// Prevent overscrolling when there's no more data to load
event->ignore();
}

View File

@ -0,0 +1,103 @@
#ifndef VISUAL_DATABASE_DISPLAY_WIDGET_H
#define VISUAL_DATABASE_DISPLAY_WIDGET_H
#include "../../../../deck/custom_line_edit.h"
#include "../../../../deck/deck_list_model.h"
#include "../../../../game/cards/card_database.h"
#include "../../../../game/cards/card_database_model.h"
#include "../../../../game/filters/filter_tree_model.h"
#include "../../../game_logic/key_signals.h"
#include "../../../tabs/abstract_tab_deck_editor.h"
#include "../../layouts/flow_layout.h"
#include "../cards/card_info_picture_with_text_overlay_widget.h"
#include "../cards/card_size_widget.h"
#include "../general/layout_containers/flow_widget.h"
#include "../general/layout_containers/overlap_control_widget.h"
#include "visual_database_display_color_filter_widget.h"
#include "visual_database_display_filter_save_load_widget.h"
#include "visual_database_display_main_type_filter_widget.h"
#include "visual_database_display_name_filter_widget.h"
#include "visual_database_display_set_filter_widget.h"
#include "visual_database_display_sub_type_filter_widget.h"
#include <QLoggingCategory>
#include <QTreeView>
#include <QVBoxLayout>
#include <QWheelEvent>
#include <QWidget>
#include <qscrollarea.h>
inline Q_LOGGING_CATEGORY(VisualDatabaseDisplayLog, "visual_database_display");
class VisualDatabaseDisplayWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDatabaseDisplayWidget(QWidget *parent,
AbstractTabDeckEditor *deckEditor,
CardDatabaseModel *database_model,
CardDatabaseDisplayModel *database_display_model);
void adjustCardsPerPage();
void populateCards();
void loadNextPage();
void loadCurrentPage();
void sortCardList(const QStringList &properties, Qt::SortOrder order) const;
void setDeckList(const DeckList &new_deck_list_model);
QWidget *searchContainer;
QHBoxLayout *searchLayout;
SearchLineEdit *searchEdit;
FilterTreeModel *filterModel;
VisualDatabaseDisplayColorFilterWidget *colorFilterWidget;
public slots:
void searchModelChanged();
signals:
void cardClickedDatabaseDisplay(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance);
void cardHoveredDatabaseDisplay(CardInfoPtr hoveredCard);
protected slots:
void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance);
void onHover(const CardInfoPtr &hoveredCard);
void addCard(const CardInfoPtr &cardToAdd);
void databaseDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
void wheelEvent(QWheelEvent *event) override;
void modelDirty() const;
void updateSearch(const QString &search) const;
private:
SettingsButtonWidget *quickFilterWidget;
VisualDatabaseDisplayFilterSaveLoadWidget *saveLoadWidget;
VisualDatabaseDisplayNameFilterWidget *nameFilterWidget;
VisualDatabaseDisplayMainTypeFilterWidget *mainTypeFilterWidget;
VisualDatabaseDisplaySubTypeFilterWidget *subTypeFilterWidget;
VisualDatabaseDisplaySetFilterWidget *setFilterWidget;
KeySignals searchKeySignals;
AbstractTabDeckEditor *deckEditor;
CardDatabaseModel *databaseModel;
CardDatabaseDisplayModel *databaseDisplayModel;
QTreeView *databaseView;
QList<CardInfoPtr> *cards;
QVBoxLayout *mainLayout;
QScrollArea *scrollArea;
FlowWidget *flowWidget;
QWidget *overlapCategories;
QVBoxLayout *overlapCategoriesLayout;
OverlapControlWidget *overlapControlWidget;
CardSizeWidget *cardSizeWidget;
QWidget *container;
QTimer *debounceTimer;
QTimer *loadCardsTimer;
int debounceTime = 300; // in Ms
int currentPage = 0; // Current page index
int cardsPerPage = 100; // Number of cards per page
protected:
void resizeEvent(QResizeEvent *event) override;
};
#endif // VISUAL_DATABASE_DISPLAY_WIDGET_H

View File

@ -0,0 +1,80 @@
#include "visual_database_filter_display_widget.h"
#include "../../../../settings/cache_settings.h"
#include <QDir>
#include <QFile>
#include <QFontMetrics>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QPushButton>
FilterDisplayWidget::FilterDisplayWidget(QWidget *parent, const QString &filename, FilterTreeModel *_filterModel)
: QWidget(parent), filterFilename(filename), filterModel(_filterModel)
{
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
// Create layout
auto *layout = new QHBoxLayout(this);
layout->setContentsMargins(5, 5, 5, 5);
layout->setSpacing(5);
// Filter button (displays filename)
filterButton = new QPushButton(filterFilename, this);
layout->addWidget(filterButton);
// Create close button (inside the filter button)
closeButton = new QPushButton("x", this);
closeButton->setFixedSize(20, 20); // Small square close button
closeButton->setFocusPolicy(Qt::NoFocus);
layout->addWidget(closeButton);
// Connect the filter button for loading the filter
connect(filterButton, &QPushButton::clicked, this, &FilterDisplayWidget::loadFilter);
// Connect the close button for deleting the filter
connect(closeButton, &QPushButton::clicked, this, &FilterDisplayWidget::deleteFilter);
}
QSize FilterDisplayWidget::sizeHint() const
{
// Calculate the size based on the filter name and the close button
QFontMetrics fm(font());
int textWidth = fm.horizontalAdvance(filterFilename);
int width = textWidth + 30 + 16; // Space for the filename and close button
int height = fm.height() + 10; // Height based on font size + padding
return QSize(width, height);
}
void FilterDisplayWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
emit filterClicked();
}
QWidget::mousePressEvent(event);
}
void FilterDisplayWidget::loadFilter()
{
// Trigger the loading of the filter
emit filterLoadRequested(filterFilename);
}
void FilterDisplayWidget::deleteFilter()
{
// Show confirmation dialog before deleting the filter
QMessageBox::StandardButton reply = QMessageBox::question(
this, tr("Confirm Delete"), tr("Are you sure you want to delete the filter '%1'?").arg(filterFilename),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
// If confirmed, delete the filter
QString filePath = SettingsCache::instance().getFiltersPath() + QDir::separator() + filterFilename;
QFile file(filePath);
if (file.remove()) {
emit filterDeleted(filterFilename); // Emit signal for deletion
delete this; // Delete this widget
} else {
QMessageBox::warning(this, tr("Delete Failed"), tr("Failed to delete filter '%1'.").arg(filterFilename));
}
}
}

View File

@ -0,0 +1,40 @@
#ifndef VISUAL_DATABASE_FILTER_DISPLAY_WIDGET_H
#define VISUAL_DATABASE_FILTER_DISPLAY_WIDGET_H
#include <QMouseEvent>
#include <QPushButton>
#include <QString>
#include <QWidget>
class FilterTreeModel;
class FilterDisplayWidget : public QWidget
{
Q_OBJECT
public:
explicit FilterDisplayWidget(QWidget *parent, const QString &filename, FilterTreeModel *_filterModel);
~FilterDisplayWidget() = default;
QSize sizeHint() const override;
protected:
void mousePressEvent(QMouseEvent *event) override;
private slots:
void loadFilter();
void deleteFilter();
signals:
void filterClicked();
void filterLoadRequested(const QString &filename);
void filterDeleted(const QString &filename);
private:
QString filterFilename;
FilterTreeModel *filterModel;
QPushButton *filterButton; // Button for the filter text
QPushButton *closeButton; // Close button to delete the filter
};
#endif // VISUAL_DATABASE_FILTER_DISPLAY_WIDGET_H

View File

@ -101,6 +101,11 @@ GeneralSettingsPage::GeneralSettingsPage()
QPushButton *deckPathButton = new QPushButton("...");
connect(deckPathButton, SIGNAL(clicked()), this, SLOT(deckPathButtonClicked()));
filtersPathEdit = new QLineEdit(settings.getFiltersPath());
filtersPathEdit->setReadOnly(true);
QPushButton *filtersPathButton = new QPushButton("...");
connect(filtersPathButton, SIGNAL(clicked()), this, SLOT(filtersPathButtonClicked()));
replaysPathEdit = new QLineEdit(settings.getReplaysPath());
replaysPathEdit->setReadOnly(true);
QPushButton *replaysPathButton = new QPushButton("...");
@ -132,6 +137,7 @@ GeneralSettingsPage::GeneralSettingsPage()
bool isPortable = settings.getIsPortableBuild();
if (isPortable) {
deckPathEdit->setEnabled(false);
filtersPathEdit->setEnabled(false);
replaysPathEdit->setEnabled(false);
picsPathEdit->setEnabled(false);
cardDatabasePathEdit->setEnabled(false);
@ -154,24 +160,27 @@ GeneralSettingsPage::GeneralSettingsPage()
pathsGrid->addWidget(&deckPathLabel, 0, 0);
pathsGrid->addWidget(deckPathEdit, 0, 1);
pathsGrid->addWidget(deckPathButton, 0, 2);
pathsGrid->addWidget(&replaysPathLabel, 1, 0);
pathsGrid->addWidget(replaysPathEdit, 1, 1);
pathsGrid->addWidget(replaysPathButton, 1, 2);
pathsGrid->addWidget(&picsPathLabel, 2, 0);
pathsGrid->addWidget(picsPathEdit, 2, 1);
pathsGrid->addWidget(picsPathButton, 2, 2);
pathsGrid->addWidget(&cardDatabasePathLabel, 3, 0);
pathsGrid->addWidget(cardDatabasePathEdit, 3, 1);
pathsGrid->addWidget(cardDatabasePathButton, 3, 2);
pathsGrid->addWidget(&customCardDatabasePathLabel, 4, 0);
pathsGrid->addWidget(customCardDatabasePathEdit, 4, 1);
pathsGrid->addWidget(customCardDatabasePathButton, 4, 2);
pathsGrid->addWidget(&tokenDatabasePathLabel, 5, 0);
pathsGrid->addWidget(tokenDatabasePathEdit, 5, 1);
pathsGrid->addWidget(tokenDatabasePathButton, 5, 2);
pathsGrid->addWidget(&filtersPathLabel, 1, 0);
pathsGrid->addWidget(filtersPathEdit, 1, 1);
pathsGrid->addWidget(filtersPathButton, 1, 2);
pathsGrid->addWidget(&replaysPathLabel, 2, 0);
pathsGrid->addWidget(replaysPathEdit, 2, 1);
pathsGrid->addWidget(replaysPathButton, 2, 2);
pathsGrid->addWidget(&picsPathLabel, 3, 0);
pathsGrid->addWidget(picsPathEdit, 3, 1);
pathsGrid->addWidget(picsPathButton, 3, 2);
pathsGrid->addWidget(&cardDatabasePathLabel, 4, 0);
pathsGrid->addWidget(cardDatabasePathEdit, 4, 1);
pathsGrid->addWidget(cardDatabasePathButton, 4, 2);
pathsGrid->addWidget(&customCardDatabasePathLabel, 5, 0);
pathsGrid->addWidget(customCardDatabasePathEdit, 5, 1);
pathsGrid->addWidget(customCardDatabasePathButton, 5, 2);
pathsGrid->addWidget(&tokenDatabasePathLabel, 6, 0);
pathsGrid->addWidget(tokenDatabasePathEdit, 6, 1);
pathsGrid->addWidget(tokenDatabasePathButton, 6, 2);
if (!isPortable) {
pathsGrid->addWidget(resetAllPathsButton, 6, 0);
pathsGrid->addWidget(allPathsResetLabel, 6, 1);
pathsGrid->addWidget(resetAllPathsButton, 7, 0);
pathsGrid->addWidget(allPathsResetLabel, 7, 1);
}
pathsGroupBox = new QGroupBox;
pathsGroupBox->setLayout(pathsGrid);
@ -226,6 +235,16 @@ void GeneralSettingsPage::deckPathButtonClicked()
SettingsCache::instance().setDeckPath(path);
}
void GeneralSettingsPage::filtersPathButtonClicked()
{
QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), filtersPathEdit->text());
if (path.isEmpty())
return;
filtersPathEdit->setText(path);
SettingsCache::instance().setFiltersPath(path);
}
void GeneralSettingsPage::replaysPathButtonClicked()
{
QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), replaysPathEdit->text());
@ -307,6 +326,7 @@ void GeneralSettingsPage::retranslateUi()
advertiseTranslationPageLabel.setText(
QString("<a href='%1'>%2</a>").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations")));
deckPathLabel.setText(tr("Decks directory:"));
filtersPathLabel.setText(tr("Filters directory:"));
replaysPathLabel.setText(tr("Replays directory:"));
picsPathLabel.setText(tr("Pictures directory:"));
cardDatabasePathLabel.setText(tr("Card database:"));

View File

@ -46,6 +46,7 @@ public:
private slots:
void deckPathButtonClicked();
void filtersPathButtonClicked();
void replaysPathButtonClicked();
void picsPathButtonClicked();
void cardDatabasePathButtonClicked();
@ -58,6 +59,7 @@ private:
QStringList findQmFiles();
QString languageName(const QString &lang);
QLineEdit *deckPathEdit;
QLineEdit *filtersPathEdit;
QLineEdit *replaysPathEdit;
QLineEdit *picsPathEdit;
QLineEdit *cardDatabasePathEdit;
@ -74,6 +76,7 @@ private:
QComboBox updateReleaseChannelBox;
QLabel languageLabel;
QLabel deckPathLabel;
QLabel filtersPathLabel;
QLabel replaysPathLabel;
QLabel picsPathLabel;
QLabel cardDatabasePathLabel;

View File

@ -451,6 +451,12 @@ void SettingsCache::setDeckPath(const QString &_deckPath)
settings->setValue("paths/decks", deckPath);
}
void SettingsCache::setFiltersPath(const QString &_filtersPath)
{
filtersPath = _filtersPath;
settings->setValue("paths/filters", filtersPath);
}
void SettingsCache::setReplaysPath(const QString &_replaysPath)
{
replaysPath = _replaysPath;
@ -1312,6 +1318,7 @@ void SettingsCache::loadPaths()
{
QString dataPath = getDataPath();
deckPath = getSafeConfigPath("paths/decks", dataPath + "/decks/");
filtersPath = getSafeConfigPath("paths/filters", dataPath + "/filters/");
replaysPath = getSafeConfigPath("paths/replays", dataPath + "/replays/");
themesPath = getSafeConfigPath("paths/themes", dataPath + "/themes/");
picsPath = getSafeConfigPath("paths/pics", dataPath + "/pics/");

View File

@ -103,7 +103,7 @@ private:
QByteArray tokenDialogGeometry;
QByteArray setsDialogGeometry;
QString lang;
QString deckPath, replaysPath, picsPath, redirectCachePath, customPicsPath, cardDatabasePath,
QString deckPath, filtersPath, replaysPath, picsPath, redirectCachePath, customPicsPath, cardDatabasePath,
customCardDatabasePath, themesPath, spoilerDatabasePath, tokenDatabasePath, themeName;
bool tabVisualDeckStorageOpen, tabServerOpen, tabAccountOpen, tabDeckStorageOpen, tabReplaysOpen, tabAdminOpen,
tabLogOpen;
@ -233,6 +233,10 @@ public:
{
return deckPath;
}
QString getFiltersPath() const
{
return filtersPath;
}
QString getReplaysPath() const
{
return replaysPath;
@ -754,6 +758,7 @@ public slots:
void setShowTipsOnStartup(bool _showTipsOnStartup);
void setSeenTips(const QList<int> &_seenTips);
void setDeckPath(const QString &_deckPath);
void setFiltersPath(const QString &_filtersPath);
void setReplaysPath(const QString &_replaysPath);
void setThemesPath(const QString &_themesPath);
void setCustomCardDatabasePath(const QString &_customCardDatabasePath);

View File

@ -0,0 +1,75 @@
#include "card_info_comparator.h"
CardInfoComparator::CardInfoComparator(const QStringList &properties, Qt::SortOrder order)
: m_properties(properties), m_order(order)
{
}
bool CardInfoComparator::operator()(const CardInfoPtr &a, const CardInfoPtr &b) const
{
// Iterate over each property in the list
for (const QString &property : m_properties) {
QVariant valueA = getProperty(a, property);
QVariant valueB = getProperty(b, property);
// Compare the current property
if (valueA != valueB) {
// If values differ, perform comparison
return compareVariants(valueA, valueB) ? (m_order == Qt::AscendingOrder) : (m_order == Qt::DescendingOrder);
}
}
// If all properties are equal, return false (indicating they are considered equal for sorting purposes)
return false;
}
bool CardInfoComparator::compareVariants(const QVariant &a, const QVariant &b) const
{
// Determine the type of QVariant based on Qt version
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (a.typeId() != b.typeId()) {
#else
if (a.type() != b.type()) {
#endif
// If they are not the same type, compare as strings
return a.toString() < b.toString();
}
// Perform type-specific comparison
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
switch (a.typeId()) {
#else
switch (a.type()) {
#endif
case QMetaType::Int:
return a.toInt() < b.toInt();
case QMetaType::Double:
return a.toDouble() < b.toDouble();
case QMetaType::QString:
return a.toString() < b.toString();
case QMetaType::Bool:
return a.toBool() < b.toBool();
default:
// Default to comparing as strings
return a.toString() < b.toString();
}
}
QVariant CardInfoComparator::getProperty(const CardInfoPtr &card, const QString &property) const
{
// Check if the property exists in the main fields of the class
if (property == "name") {
return card->getName();
} else if (property == "text") {
return card->getText();
} else if (property == "isToken") {
return card->getIsToken();
}
// Otherwise, check if it's a custom property in the QVariantHash
if (card->hasProperty(property)) {
return card->getProperty(property);
}
return QVariant(); // Return an invalid variant if the property does not exist
}

View File

@ -0,0 +1,24 @@
#ifndef CARD_INFO_COMPARATOR_H
#define CARD_INFO_COMPARATOR_H
#include "../game/cards/card_database.h"
#include <QStringList>
#include <QVariant>
#include <Qt>
class CardInfoComparator
{
public:
explicit CardInfoComparator(const QStringList &properties, Qt::SortOrder order = Qt::AscendingOrder);
bool operator()(const CardInfoPtr &a, const CardInfoPtr &b) const;
private:
QStringList m_properties; // List of properties to sort by
Qt::SortOrder m_order;
QVariant getProperty(const CardInfoPtr &card, const QString &property) const;
bool compareVariants(const QVariant &a, const QVariant &b) const;
};
#endif // CARD_INFO_COMPARATOR_H

View File

@ -106,6 +106,9 @@ void SettingsCache::setSeenTips(const QList<int> & /* _seenTips */)
void SettingsCache::setDeckPath(const QString & /* _deckPath */)
{
}
void SettingsCache::setFiltersPath(const QString & /*_filtersPath */)
{
}
void SettingsCache::setReplaysPath(const QString & /* _replaysPath */)
{
}

View File

@ -110,6 +110,9 @@ void SettingsCache::setSeenTips(const QList<int> & /* _seenTips */)
void SettingsCache::setDeckPath(const QString & /* _deckPath */)
{
}
void SettingsCache::setFiltersPath(const QString & /*_filtersPath */)
{
}
void SettingsCache::setReplaysPath(const QString & /* _replaysPath */)
{
}