Visual Deck Editor Base (#5834)

* Visual Deck Editor.

* Lint.

* Address comments.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2025-04-16 14:02:53 +02:00 committed by GitHub
parent a55a287a9d
commit 42c56898d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 1949 additions and 3 deletions

View file

@ -44,6 +44,8 @@ set(cockatrice_SOURCES
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_editor/tab_deck_editor_visual.cpp
src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp
src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
src/client/tapped_out_interface.cpp
src/client/translate_counter_name.cpp
@ -60,6 +62,9 @@ set(cockatrice_SOURCES
src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp
src/client/ui/widgets/cards/additional_info/mana_cost_widget.cpp
src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp
src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp
src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp
src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp
src/client/ui/widgets/cards/card_info_display_widget.cpp
src/client/ui/widgets/cards/card_info_frame_widget.cpp
src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp
@ -67,6 +72,7 @@ set(cockatrice_SOURCES
src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp
src/client/ui/widgets/cards/card_info_text_widget.cpp
src/client/ui/widgets/cards/card_size_widget.cpp
src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp
src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp
src/client/ui/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.cpp
@ -101,6 +107,7 @@ set(cockatrice_SOURCES
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_editor/visual_deck_editor_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
@ -149,6 +156,7 @@ set(cockatrice_SOURCES
src/game/board/counter_general.cpp
src/game/cards/abstract_card_drag_item.cpp
src/game/cards/abstract_card_item.cpp
src/game/cards/card_completer_proxy_model.cpp
src/game/cards/card_database.cpp
src/game/cards/card_database_manager.cpp
src/game/cards/card_database_model.cpp
@ -159,6 +167,7 @@ set(cockatrice_SOURCES
src/game/cards/card_info.cpp
src/game/cards/card_item.cpp
src/game/cards/card_list.cpp
src/game/cards/card_search_model.cpp
src/game/deckview/deck_view.cpp
src/game/deckview/deck_view_container.cpp
src/game/filters/filter_builder.cpp
@ -214,6 +223,7 @@ set(cockatrice_SOURCES
src/settings/shortcut_treeview.cpp
src/settings/shortcuts_settings.cpp
src/utility/card_info_comparator.cpp
src/utility/levenshtein.cpp
src/utility/logger.cpp
src/utility/sequence_edit.cpp
)

View file

@ -26,6 +26,8 @@
#include "tab_room.h"
#include "tab_server.h"
#include "tab_visual_database_display.h"
#include "visual_deck_editor/tab_deck_editor_visual.h"
#include "visual_deck_editor/tab_deck_editor_visual_tab_widget.h"
#include "visual_deck_storage/tab_deck_storage_visual.h"
#include <QApplication>
@ -131,6 +133,9 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *
aTabDeckEditor = new QAction(this);
connect(aTabDeckEditor, &QAction::triggered, this, [this] { addDeckEditorTab(nullptr); });
aTabVisualDeckEditor = new QAction(this);
connect(aTabVisualDeckEditor, &QAction::triggered, this, [this] { addVisualDeckEditorTab(nullptr); });
aTabVisualDeckStorage = new QAction(this);
aTabVisualDeckStorage->setCheckable(true);
connect(aTabVisualDeckStorage, &QAction::triggered, this, &TabSupervisor::actTabVisualDeckStorage);
@ -180,6 +185,7 @@ void TabSupervisor::retranslateUi()
{
// tab menu actions
aTabDeckEditor->setText(tr("Deck Editor"));
aTabVisualDeckEditor->setText(tr("Visual Deck Editor"));
aTabVisualDeckStorage->setText(tr("&Visual Deck Storage"));
aTabVisualDatabaseDisplay->setText(tr("Visual Database Display"));
aTabServer->setText(tr("Server"));
@ -228,6 +234,7 @@ void TabSupervisor::refreshShortcuts()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
aTabDeckEditor->setShortcuts(shortcuts.getShortcut("Tabs/aTabDeckEditor"));
aTabVisualDeckEditor->setShortcuts(shortcuts.getShortcut("Tabs/aTabVisualDeckEditor"));
aTabVisualDeckStorage->setShortcuts(shortcuts.getShortcut("Tabs/aTabVisualDeckStorage"));
aTabServer->setShortcuts(shortcuts.getShortcut("Tabs/aTabServer"));
aTabAccount->setShortcuts(shortcuts.getShortcut("Tabs/aTabAccount"));
@ -375,6 +382,7 @@ void TabSupervisor::resetTabsMenu()
{
tabsMenu->clear();
tabsMenu->addAction(aTabDeckEditor);
tabsMenu->addAction(aTabVisualDeckEditor);
tabsMenu->addSeparator();
tabsMenu->addAction(aTabVisualDeckStorage);
tabsMenu->addAction(aTabVisualDatabaseDisplay);
@ -810,6 +818,19 @@ TabDeckEditor *TabSupervisor::addDeckEditorTab(const DeckLoader *deckToOpen)
return tab;
}
TabDeckEditorVisual *TabSupervisor::addVisualDeckEditorTab(const DeckLoader *deckToOpen)
{
auto *tab = new TabDeckEditorVisual(this);
if (deckToOpen)
tab->openDeck(new DeckLoader(*deckToOpen));
connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed);
connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addVisualDeckEditorTab);
myAddTab(tab);
deckEditorTabs.append(tab);
setCurrentWidget(tab);
return tab;
}
TabVisualDatabaseDisplay *TabSupervisor::addVisualDatabaseDisplayTab()
{
auto *tab = new TabVisualDatabaseDisplay(this);

View file

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

View file

@ -0,0 +1,467 @@
#include "tab_deck_editor_visual.h"
#include "../../../deck/deck_list_model.h"
#include "../../../deck/deck_stats_interface.h"
#include "../../../game/cards/card_database_model.h"
#include "../../../game/filters/filter_builder.h"
#include "../../../server/pending_command.h"
#include "../../../settings/cache_settings.h"
#include "../../ui/pixel_map_generator.h"
#include "../../ui/widgets/cards/card_info_frame_widget.h"
#include "../../ui/widgets/visual_deck_editor/visual_deck_editor_widget.h"
#include "../tab_deck_editor.h"
#include "../tab_supervisor.h"
#include "pb/command_deck_upload.pb.h"
#include "tab_deck_editor_visual_tab_widget.h"
#include "trice_limits.h"
#include <QAction>
#include <QApplication>
#include <QCloseEvent>
#include <QCompleter>
#include <QDir>
#include <QDockWidget>
#include <QFileDialog>
#include <QHeaderView>
#include <QLineEdit>
#include <QMenu>
#include <QPrintPreviewDialog>
#include <QProcessEnvironment>
#include <QSplitter>
#include <QTextStream>
#include <QTimer>
#include <QTreeView>
#include <QVBoxLayout>
TabDeckEditorVisual::TabDeckEditorVisual(TabSupervisor *_tabSupervisor) : AbstractTabDeckEditor(_tabSupervisor)
{
setObjectName("TabDeckEditorVisual");
createCentralFrame();
TabDeckEditorVisual::createMenus();
installEventFilter(this);
TabDeckEditorVisual::retranslateUi();
connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
TabDeckEditorVisual::refreshShortcuts();
TabDeckEditorVisual::loadLayout();
databaseDisplayDockWidget->setHidden(true);
}
void TabDeckEditorVisual::createCentralFrame()
{
centralWidget = new QWidget(this);
centralWidget->setObjectName("centralWidget");
centralFrame = new QVBoxLayout;
centralWidget->setLayout(centralFrame);
tabContainer = new TabDeckEditorVisualTabWidget(centralWidget, this, deckDockWidget->deckModel,
databaseDisplayDockWidget->databaseModel,
databaseDisplayDockWidget->databaseDisplayModel);
connect(tabContainer, &TabDeckEditorVisualTabWidget::cardChanged, this,
&TabDeckEditorVisual::changeModelIndexAndCardInfo);
connect(tabContainer, &TabDeckEditorVisualTabWidget::cardChangedDatabaseDisplay, this,
&AbstractTabDeckEditor::updateCard);
connect(tabContainer, &TabDeckEditorVisualTabWidget::cardClicked, this,
&TabDeckEditorVisual::processMainboardCardClick);
connect(tabContainer, &TabDeckEditorVisualTabWidget::cardClickedDatabaseDisplay, this,
&TabDeckEditorVisual::processCardClickDatabaseDisplay);
centralFrame->addWidget(tabContainer);
setCentralWidget(centralWidget);
setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks);
}
void TabDeckEditorVisual::onDeckChanged()
{
AbstractTabDeckEditor::onDeckChanged();
tabContainer->visualDeckView->decklistDataChanged(QModelIndex(), QModelIndex());
}
void TabDeckEditorVisual::createMenus()
{
deckMenu = new DeckEditorMenu(this);
addTabMenu(deckMenu);
viewMenu = new QMenu(this);
cardInfoDockMenu = viewMenu->addMenu(QString());
deckDockMenu = viewMenu->addMenu(QString());
deckAnalyticsMenu = viewMenu->addMenu(QString());
filterDockMenu = viewMenu->addMenu(QString());
printingSelectorDockMenu = viewMenu->addMenu(QString());
aCardInfoDockVisible = cardInfoDockMenu->addAction(QString());
aCardInfoDockVisible->setCheckable(true);
connect(aCardInfoDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
aCardInfoDockFloating = cardInfoDockMenu->addAction(QString());
aCardInfoDockFloating->setCheckable(true);
connect(aCardInfoDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
aDeckDockVisible = deckDockMenu->addAction(QString());
aDeckDockVisible->setCheckable(true);
connect(aDeckDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
aDeckDockFloating = deckDockMenu->addAction(QString());
aDeckDockFloating->setCheckable(true);
connect(aDeckDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
aDeckAnalyticsDockVisible = deckAnalyticsMenu->addAction(QString());
aDeckAnalyticsDockVisible->setCheckable(true);
connect(aDeckAnalyticsDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
aDeckAnalyticsDockFloating = deckAnalyticsMenu->addAction(QString());
aDeckAnalyticsDockFloating->setCheckable(true);
connect(aDeckAnalyticsDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
aFilterDockVisible = filterDockMenu->addAction(QString());
aFilterDockVisible->setCheckable(true);
connect(aFilterDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
aFilterDockFloating = filterDockMenu->addAction(QString());
aFilterDockFloating->setCheckable(true);
connect(aFilterDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
aPrintingSelectorDockVisible = printingSelectorDockMenu->addAction(QString());
aPrintingSelectorDockVisible->setCheckable(true);
connect(aPrintingSelectorDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
aPrintingSelectorDockFloating = printingSelectorDockMenu->addAction(QString());
aPrintingSelectorDockFloating->setCheckable(true);
connect(aPrintingSelectorDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
viewMenu->addSeparator();
aResetLayout = viewMenu->addAction(QString());
connect(aResetLayout, SIGNAL(triggered()), this, SLOT(restartLayout()));
viewMenu->addAction(aResetLayout);
deckMenu->setSaveStatus(false);
addTabMenu(viewMenu);
}
QString TabDeckEditorVisual::getTabText() const
{
QString result = tr("Visual Deck: %1").arg(deckDockWidget->getSimpleDeckName());
if (modified)
result.prepend("* ");
return result;
}
void TabDeckEditorVisual::changeModelIndexAndCardInfo(const CardInfoPtr &activeCard)
{
updateCard(activeCard);
changeModelIndexToCard(activeCard);
}
void TabDeckEditorVisual::changeModelIndexToCard(const CardInfoPtr &activeCard)
{
QString cardName = activeCard->getName();
QModelIndex index = deckDockWidget->deckModel->findCard(cardName, DECK_ZONE_MAIN);
if (!index.isValid()) {
index = deckDockWidget->deckModel->findCard(cardName, DECK_ZONE_SIDE);
}
deckDockWidget->deckView->setCurrentIndex(index);
}
void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event,
CardInfoPictureWithTextOverlayWidget *instance,
QString zoneName)
{
if (event->button() == Qt::LeftButton) {
actSwapCard(instance->getInfo(), zoneName);
} else if (event->button() == Qt::RightButton) {
actDecrementCard(instance->getInfo());
} else if (event->button() == Qt::MiddleButton) {
deckDockWidget->actRemoveCard();
}
}
void TabDeckEditorVisual::processCardClickDatabaseDisplay(QMouseEvent *event,
CardInfoPictureWithTextOverlayWidget *instance)
{
if (event->button() == Qt::LeftButton) {
actAddCard(instance->getInfo());
} else if (event->button() == Qt::RightButton) {
actDecrementCard(instance->getInfo());
} else if (event->button() == Qt::MiddleButton) {
deckDockWidget->actRemoveCard();
}
}
bool TabDeckEditorVisual::actSaveDeckAs()
{
// We have to disable the quick-add search bar or else it'll steal focus after dialog creation.
tabContainer->visualDeckView->searchBar->setEnabled(false);
auto result = AbstractTabDeckEditor::actSaveDeckAs();
tabContainer->visualDeckView->searchBar->setEnabled(true);
return result;
}
void TabDeckEditorVisual::showPrintingSelector()
{
printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getInfo(), DECK_ZONE_MAIN);
printingSelectorDockWidget->printingSelector->updateDisplay();
aPrintingSelectorDockVisible->setChecked(true);
printingSelectorDockWidget->setVisible(true);
}
void TabDeckEditorVisual::restartLayout()
{
deckDockWidget->setVisible(true);
cardInfoDockWidget->setVisible(true);
filterDockWidget->setVisible(true);
deckDockWidget->setFloating(false);
cardInfoDockWidget->setFloating(false);
filterDockWidget->setFloating(false);
aCardInfoDockVisible->setChecked(true);
aDeckDockVisible->setChecked(true);
aFilterDockVisible->setChecked(true);
aCardInfoDockFloating->setChecked(false);
aDeckDockFloating->setChecked(false);
aFilterDockFloating->setChecked(false);
addDockWidget(static_cast<Qt::DockWidgetArea>(2), deckDockWidget);
addDockWidget(static_cast<Qt::DockWidgetArea>(2), cardInfoDockWidget);
addDockWidget(static_cast<Qt::DockWidgetArea>(1), deckAnalyticsDock);
addDockWidget(static_cast<Qt::DockWidgetArea>(2), filterDockWidget);
splitDockWidget(cardInfoDockWidget, deckDockWidget, Qt::Horizontal);
splitDockWidget(cardInfoDockWidget, filterDockWidget, Qt::Vertical);
splitDockWidget(searchAndDatabaseDock, deckAnalyticsDock, Qt::Vertical);
deckDockWidget->setMinimumWidth(360);
deckDockWidget->setMaximumWidth(360);
cardInfoDockWidget->setMinimumSize(250, 360);
cardInfoDockWidget->setMaximumSize(250, 360);
QTimer::singleShot(100, this, SLOT(freeDocksSize()));
}
void TabDeckEditorVisual::freeDocksSize()
{
deckDockWidget->setMinimumSize(100, 100);
deckDockWidget->setMaximumSize(5000, 5000);
cardInfoDockWidget->setMinimumSize(100, 100);
cardInfoDockWidget->setMaximumSize(5000, 5000);
filterDockWidget->setMinimumSize(100, 100);
filterDockWidget->setMaximumSize(5000, 5000);
databaseDisplayDockWidget->setMinimumSize(100, 100);
databaseDisplayDockWidget->setMaximumSize(1400, 5000);
}
void TabDeckEditorVisual::refreshShortcuts()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
aResetLayout->setShortcuts(shortcuts.getShortcut("TabDeckEditorVisual/aResetLayout"));
}
void TabDeckEditorVisual::loadLayout()
{
LayoutsSettings &layouts = SettingsCache::instance().layouts();
auto &layoutState = layouts.getDeckEditorLayoutState();
if (layoutState.isNull()) {
restartLayout();
} else {
restoreState(layoutState);
restoreGeometry(layouts.getDeckEditorGeometry());
}
aCardInfoDockVisible->setChecked(!cardInfoDockWidget->isHidden());
aFilterDockVisible->setChecked(!filterDockWidget->isHidden());
aDeckDockVisible->setChecked(!deckDockWidget->isHidden());
aPrintingSelectorDockVisible->setChecked(!printingSelectorDockWidget->isHidden());
aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked());
aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked());
aCardInfoDockFloating->setChecked(cardInfoDockWidget->isFloating());
aFilterDockFloating->setChecked(filterDockWidget->isFloating());
aDeckDockFloating->setChecked(deckDockWidget->isFloating());
aPrintingSelectorDockFloating->setChecked(printingSelectorDockWidget->isFloating());
cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize());
cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize());
filterDockWidget->setMinimumSize(layouts.getDeckEditorFilterSize());
filterDockWidget->setMaximumSize(layouts.getDeckEditorFilterSize());
deckDockWidget->setMinimumSize(layouts.getDeckEditorDeckSize());
deckDockWidget->setMaximumSize(layouts.getDeckEditorDeckSize());
printingSelectorDockWidget->setMinimumSize(layouts.getDeckEditorPrintingSelectorSize());
printingSelectorDockWidget->setMaximumSize(layouts.getDeckEditorPrintingSelectorSize());
databaseDisplayDockWidget->setMinimumSize(100, 100);
databaseDisplayDockWidget->setMaximumSize(1400, 5000);
QTimer::singleShot(100, this, &TabDeckEditorVisual::freeDocksSize);
}
void TabDeckEditorVisual::retranslateUi()
{
deckMenu->setTitle(tr("&Visual Deck Editor"));
cardInfoDockWidget->setWindowTitle(tr("Card Info"));
deckDockWidget->setWindowTitle(tr("Deck"));
filterDockWidget->setWindowTitle(tr("Filters"));
viewMenu->setTitle(tr("&View"));
cardInfoDockMenu->setTitle(tr("Card Info"));
deckDockMenu->setTitle(tr("Deck"));
deckAnalyticsMenu->setTitle(tr("Deck Analytics"));
filterDockMenu->setTitle(tr("Filters"));
printingSelectorDockMenu->setTitle(tr("Printing"));
aCardInfoDockVisible->setText(tr("Visible"));
aCardInfoDockFloating->setText(tr("Floating"));
aDeckDockVisible->setText(tr("Visible"));
aDeckDockFloating->setText(tr("Floating"));
aDeckAnalyticsDockVisible->setText(tr("Visible"));
aDeckAnalyticsDockFloating->setText(tr("Floating"));
aFilterDockVisible->setText(tr("Visible"));
aFilterDockFloating->setText(tr("Floating"));
aPrintingSelectorDockVisible->setText(tr("Visible"));
aPrintingSelectorDockFloating->setText(tr("Floating"));
aResetLayout->setText(tr("Reset layout"));
}
// Method uses to sync docks state with menu items state
bool TabDeckEditorVisual::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::Close) {
if (o == cardInfoDockWidget) {
aCardInfoDockVisible->setChecked(false);
aCardInfoDockFloating->setEnabled(false);
} else if (o == deckDockWidget) {
aDeckDockVisible->setChecked(false);
aDeckDockFloating->setEnabled(false);
} else if (o == deckAnalyticsDock) {
aDeckAnalyticsDockVisible->setChecked(false);
aDeckAnalyticsDockFloating->setEnabled(false);
} else if (o == filterDockWidget) {
aFilterDockVisible->setChecked(false);
aFilterDockFloating->setEnabled(false);
} else if (o == printingSelectorDockWidget) {
aPrintingSelectorDockVisible->setChecked(false);
aPrintingSelectorDockFloating->setEnabled(false);
}
}
if (o == this && e->type() == QEvent::Hide) {
LayoutsSettings &layouts = SettingsCache::instance().layouts();
layouts.setDeckEditorLayoutState(saveState());
layouts.setDeckEditorGeometry(saveGeometry());
layouts.setDeckEditorCardSize(cardInfoDockWidget->size());
layouts.setDeckEditorFilterSize(filterDockWidget->size());
layouts.setDeckEditorDeckSize(deckDockWidget->size());
layouts.setDeckEditorPrintingSelectorSize(printingSelectorDockWidget->size());
}
return false;
}
void TabDeckEditorVisual::dockVisibleTriggered()
{
QObject *o = sender();
if (o == aCardInfoDockVisible) {
cardInfoDockWidget->setHidden(!aCardInfoDockVisible->isChecked());
aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
return;
}
if (o == aDeckDockVisible) {
deckDockWidget->setHidden(!aDeckDockVisible->isChecked());
aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked());
return;
}
if (o == aDeckAnalyticsDockVisible) {
deckAnalyticsDock->setHidden(!aDeckAnalyticsDockVisible->isChecked());
aDeckAnalyticsDockFloating->setEnabled(aDeckAnalyticsDockVisible->isChecked());
return;
}
if (o == aFilterDockVisible) {
filterDockWidget->setHidden(!aFilterDockVisible->isChecked());
aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
return;
}
if (o == aPrintingSelectorDockVisible) {
printingSelectorDockWidget->setHidden(!aPrintingSelectorDockVisible->isChecked());
aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked());
return;
}
}
void TabDeckEditorVisual::dockFloatingTriggered()
{
QObject *o = sender();
if (o == aCardInfoDockFloating) {
cardInfoDockWidget->setFloating(aCardInfoDockFloating->isChecked());
return;
}
if (o == aDeckDockFloating) {
deckDockWidget->setFloating(aDeckDockFloating->isChecked());
return;
}
if (o == aDeckAnalyticsDockFloating) {
deckAnalyticsDock->setFloating(aDeckAnalyticsDockFloating->isChecked());
return;
}
if (o == aFilterDockFloating) {
filterDockWidget->setFloating(aFilterDockFloating->isChecked());
return;
}
if (o == aPrintingSelectorDockFloating) {
printingSelectorDockWidget->setFloating(aPrintingSelectorDockFloating->isChecked());
return;
}
}
void TabDeckEditorVisual::dockTopLevelChanged(bool topLevel)
{
QObject *o = sender();
if (o == cardInfoDockWidget) {
aCardInfoDockFloating->setChecked(topLevel);
return;
}
if (o == deckDockWidget) {
aDeckDockFloating->setChecked(topLevel);
return;
}
if (o == filterDockWidget) {
aFilterDockFloating->setChecked(topLevel);
return;
}
if (o == deckAnalyticsDock) {
aDeckAnalyticsDockFloating->setChecked(topLevel);
return;
}
if (o == printingSelectorDockWidget) {
aPrintingSelectorDockFloating->setChecked(topLevel);
return;
}
}

View file

@ -0,0 +1,53 @@
#ifndef WINDOW_DECKEDITORVISUAL_H
#define WINDOW_DECKEDITORVISUAL_H
#include "../tab.h"
#include "tab_deck_editor_visual_tab_widget.h"
class TabDeckEditorVisual : public AbstractTabDeckEditor
{
Q_OBJECT
protected slots:
void loadLayout() override;
void restartLayout() override;
void freeDocksSize() override;
void refreshShortcuts() override;
bool eventFilter(QObject *o, QEvent *e) override;
void dockVisibleTriggered() override;
void dockFloatingTriggered() override;
void dockTopLevelChanged(bool topLevel) override;
protected:
TabDeckEditorVisualTabWidget *tabContainer;
QVBoxLayout *centralFrame;
QVBoxLayout *searchAndDatabaseFrame;
QHBoxLayout *searchLayout;
QDockWidget *searchAndDatabaseDock;
QDockWidget *deckAnalyticsDock;
QWidget *centralWidget;
QMenu *deckAnalyticsMenu;
QAction *aDeckAnalyticsDockVisible, *aDeckAnalyticsDockFloating;
public:
explicit TabDeckEditorVisual(TabSupervisor *_tabSupervisor);
void retranslateUi() override;
QString getTabText() const override;
void changeModelIndexAndCardInfo(const CardInfoPtr &activeCard);
void changeModelIndexToCard(const CardInfoPtr &activeCard);
void createDeckAnalyticsDock();
void createMenus() override;
void createSearchAndDatabaseFrame();
void createCentralFrame();
public slots:
void onDeckChanged() override;
void showPrintingSelector() override;
void
processMainboardCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName);
void processCardClickDatabaseDisplay(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance);
bool actSaveDeckAs() override;
};
#endif

View file

@ -0,0 +1,106 @@
#include "tab_deck_editor_visual_tab_widget.h"
#include "../../ui/widgets/visual_database_display/visual_database_display_widget.h"
#include "../abstract_tab_deck_editor.h"
TabDeckEditorVisualTabWidget::TabDeckEditorVisualTabWidget(QWidget *parent,
AbstractTabDeckEditor *_deckEditor,
DeckListModel *_deckModel,
CardDatabaseModel *_cardDatabaseModel,
CardDatabaseDisplayModel *_cardDatabaseDisplayModel)
: QTabWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), cardDatabaseModel(_cardDatabaseModel),
cardDatabaseDisplayModel(_cardDatabaseDisplayModel)
{
this->setTabsClosable(true); // Enable tab closing
connect(this, &QTabWidget::tabCloseRequested, this, &TabDeckEditorVisualTabWidget::handleTabClose);
// Set up the layout and add tab widget
layout = new QVBoxLayout(this);
setLayout(layout);
visualDeckView = new VisualDeckEditorWidget(this, deckModel);
visualDeckView->setObjectName("visualDeckView");
connect(visualDeckView, &VisualDeckEditorWidget::activeCardChanged, this,
&TabDeckEditorVisualTabWidget::onCardChanged);
connect(visualDeckView, &VisualDeckEditorWidget::cardClicked, this,
&TabDeckEditorVisualTabWidget::onCardClickedDeckEditor);
connect(visualDeckView, &VisualDeckEditorWidget::cardAdditionRequested, deckEditor,
&AbstractTabDeckEditor::actAddCard);
visualDatabaseDisplay =
new VisualDatabaseDisplayWidget(this, deckEditor, _cardDatabaseModel, _cardDatabaseDisplayModel);
visualDatabaseDisplay->setObjectName("visualDatabaseView");
connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardHoveredDatabaseDisplay, this,
&TabDeckEditorVisualTabWidget::onCardChangedDatabaseDisplay);
connect(visualDatabaseDisplay, &VisualDatabaseDisplayWidget::cardClickedDatabaseDisplay, this,
&TabDeckEditorVisualTabWidget::onCardClickedDatabaseDisplay);
this->addNewTab(visualDeckView, tr("Visual Deck View"));
this->addNewTab(visualDatabaseDisplay, tr("Visual Database Display"));
}
void TabDeckEditorVisualTabWidget::onCardChanged(CardInfoPtr activeCard)
{
emit cardChanged(activeCard);
}
void TabDeckEditorVisualTabWidget::onCardChangedDatabaseDisplay(CardInfoPtr activeCard)
{
emit cardChangedDatabaseDisplay(activeCard);
}
void TabDeckEditorVisualTabWidget::onCardClickedDeckEditor(QMouseEvent *event,
CardInfoPictureWithTextOverlayWidget *instance,
QString zoneName)
{
emit cardClicked(event, instance, zoneName);
}
void TabDeckEditorVisualTabWidget::onCardClickedDatabaseDisplay(QMouseEvent *event,
CardInfoPictureWithTextOverlayWidget *instance)
{
emit cardClickedDatabaseDisplay(event, instance);
}
void TabDeckEditorVisualTabWidget::addNewTab(QWidget *widget, const QString &title)
{
// Add new tab to the tab widget
this->addTab(widget, title);
}
void TabDeckEditorVisualTabWidget::removeCurrentTab()
{
// Remove the currently selected tab
int currentIndex = this->currentIndex();
if (currentIndex != -1) {
this->removeTab(currentIndex);
}
}
void TabDeckEditorVisualTabWidget::setTabTitle(int index, const QString &title)
{
// Set the title of the tab at the given index
if (index >= 0 && index < this->count()) {
this->setTabText(index, title);
}
}
QWidget *TabDeckEditorVisualTabWidget::getCurrentTab() const
{
// Return the currently selected tab widget
return this->currentWidget();
}
int TabDeckEditorVisualTabWidget::getTabCount() const
{
// Return the number of tabs
return this->count();
}
void TabDeckEditorVisualTabWidget::handleTabClose(int index)
{
// Handle closing of the tab at the given index
QWidget *tab = this->widget(index);
this->removeTab(index);
delete tab; // Delete the tab's widget to free memory
}

View file

@ -0,0 +1,58 @@
#ifndef TAB_DECK_EDITOR_VISUAL_TAB_WIDGET_H
#define TAB_DECK_EDITOR_VISUAL_TAB_WIDGET_H
#include "../../ui/widgets/printing_selector/printing_selector.h"
#include "../../ui/widgets/visual_database_display/visual_database_display_widget.h"
#include "../../ui/widgets/visual_deck_editor/visual_deck_editor_widget.h"
#include "../abstract_tab_deck_editor.h"
#include <QTabWidget>
#include <QVBoxLayout>
#include <QWidget>
class TabDeckEditorVisualTabWidget : public QTabWidget
{
Q_OBJECT
public:
explicit TabDeckEditorVisualTabWidget(QWidget *parent,
AbstractTabDeckEditor *_deckEditor,
DeckListModel *_deckModel,
CardDatabaseModel *_cardDatabaseModel,
CardDatabaseDisplayModel *_cardDatabaseDisplayModel);
// Utility functions
void addNewTab(QWidget *widget, const QString &title);
void removeCurrentTab();
void setTabTitle(int index, const QString &title);
QWidget *getCurrentTab() const;
int getTabCount() const;
VisualDeckEditorWidget *visualDeckView;
VisualDatabaseDisplayWidget *visualDatabaseDisplay;
PrintingSelector *printingSelector;
public slots:
void onCardChanged(CardInfoPtr activeCard);
void onCardChangedDatabaseDisplay(CardInfoPtr activeCard);
void onCardClickedDeckEditor(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName);
void onCardClickedDatabaseDisplay(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance);
signals:
void cardChanged(CardInfoPtr activeCard);
void cardChangedDatabaseDisplay(CardInfoPtr activeCard);
void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName);
void cardClickedDatabaseDisplay(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance);
private:
QVBoxLayout *layout; // Layout for the tab widget and other controls
AbstractTabDeckEditor *deckEditor;
DeckListModel *deckModel;
CardDatabaseModel *cardDatabaseModel;
CardDatabaseDisplayModel *cardDatabaseDisplayModel;
private slots:
void handleTabClose(int index); // Slot for closing a tab
};
#endif // TAB_DECK_EDITOR_VISUAL_TAB_WIDGET_H

View file

@ -12,7 +12,7 @@
TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor)
: Tab(_tabSupervisor), visualDeckStorageWidget(new VisualDeckStorageWidget(this))
{
connect(this, &TabDeckStorageVisual::openDeckEditor, tabSupervisor, &TabSupervisor::addDeckEditorTab);
connect(this, &TabDeckStorageVisual::openDeckEditor, tabSupervisor, &TabSupervisor::addVisualDeckEditorTab);
connect(visualDeckStorageWidget, &VisualDeckStorageWidget::deckLoadRequested, this,
&TabDeckStorageVisual::actOpenLocalDeck);
connect(visualDeckStorageWidget, &VisualDeckStorageWidget::openDeckEditor, this,

View file

@ -0,0 +1,81 @@
#include "card_group_display_widget.h"
#include "../../../../../deck/deck_list_model.h"
#include "../../../../../game/cards/card_database_manager.h"
#include "../../../../../utility/card_info_comparator.h"
#include "../card_info_picture_with_text_overlay_widget.h"
#include <QResizeEvent>
CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent,
DeckListModel *_deckListModel,
QString _zoneName,
QString _cardGroupCategory,
QString _activeGroupCriteria,
QStringList _activeSortCriteria,
int bannerOpacity,
CardSizeWidget *_cardSizeWidget)
: QWidget(parent), deckListModel(_deckListModel), zoneName(_zoneName), cardGroupCategory(_cardGroupCategory),
activeGroupCriteria(_activeGroupCriteria), activeSortCriteria(_activeSortCriteria),
cardSizeWidget(_cardSizeWidget)
{
layout = new QVBoxLayout(this);
setLayout(layout);
setMinimumSize(QSize(0, 0));
banner = new BannerWidget(this, cardGroupCategory, Qt::Orientation::Vertical, bannerOpacity);
layout->addWidget(banner);
updateCardDisplays();
}
void CardGroupDisplayWidget::updateCardDisplays()
{
}
QList<CardInfoPtr> CardGroupDisplayWidget::getCardsMatchingGroup(QList<CardInfoPtr> cardsToSort)
{
cardsToSort = sortCardList(cardsToSort, activeSortCriteria, Qt::SortOrder::AscendingOrder);
QList<CardInfoPtr> activeList;
for (const CardInfoPtr &info : cardsToSort) {
if (info && info->getProperty(activeGroupCriteria) == cardGroupCategory) {
activeList.append(info);
}
}
return activeList;
}
QList<CardInfoPtr> CardGroupDisplayWidget::sortCardList(QList<CardInfoPtr> cardsToSort,
const QStringList properties,
Qt::SortOrder order = Qt::AscendingOrder)
{
CardInfoComparator comparator(properties, order);
std::sort(cardsToSort.begin(), cardsToSort.end(), comparator);
return cardsToSort;
}
void CardGroupDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSortCriteria)
{
if (activeSortCriteria != _activeSortCriteria) {
activeSortCriteria = _activeSortCriteria;
updateCardDisplays(); // Refresh display with new sorting
}
}
void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card)
{
emit cardClicked(event, card);
}
void CardGroupDisplayWidget::onHover(CardInfoPtr card)
{
emit cardHovered(card);
}
void CardGroupDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
}

View file

@ -0,0 +1,53 @@
#ifndef CARD_GROUP_DISPLAY_WIDGET_H
#define CARD_GROUP_DISPLAY_WIDGET_H
#include "../../../../../deck/deck_list_model.h"
#include "../../../../../game/cards/card_database.h"
#include "../../general/display/banner_widget.h"
#include "../card_info_picture_with_text_overlay_widget.h"
#include "../card_size_widget.h"
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>
class CardGroupDisplayWidget : public QWidget
{
Q_OBJECT
public:
CardGroupDisplayWidget(QWidget *parent,
DeckListModel *deckListModel,
QString zoneName,
QString cardGroupCategory,
QString activeGroupCriteria,
QStringList activeSortCriteria,
int bannerOpacity,
CardSizeWidget *cardSizeWidget);
QList<CardInfoPtr> getCardsMatchingGroup(QList<CardInfoPtr> cardsToSort);
void resizeEvent(QResizeEvent *event) override;
DeckListModel *deckListModel;
QString zoneName;
QString cardGroupCategory;
QString activeGroupCriteria;
QStringList activeSortCriteria;
CardSizeWidget *cardSizeWidget;
public slots:
QList<CardInfoPtr> sortCardList(QList<CardInfoPtr> cardsToSort, QStringList properties, Qt::SortOrder order);
void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card);
void onHover(CardInfoPtr card);
virtual void updateCardDisplays();
void onActiveSortCriteriaChanged(QStringList activeSortCriteria);
signals:
void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card);
void cardHovered(CardInfoPtr card);
protected:
QVBoxLayout *layout;
BannerWidget *banner;
};
#endif // CARD_GROUP_DISPLAY_WIDGET_H

View file

@ -0,0 +1,107 @@
#include "flat_card_group_display_widget.h"
#include "../../../../../deck/deck_list_model.h"
#include "../../../../../game/cards/card_database_manager.h"
#include "../../../../../utility/card_info_comparator.h"
#include "../card_info_picture_with_text_overlay_widget.h"
#include <QResizeEvent>
FlatCardGroupDisplayWidget::FlatCardGroupDisplayWidget(QWidget *parent,
DeckListModel *_deckListModel,
QString _zoneName,
QString _cardGroupCategory,
QString _activeGroupCriteria,
QStringList _activeSortCriteria,
int bannerOpacity,
CardSizeWidget *_cardSizeWidget)
: CardGroupDisplayWidget(parent,
_deckListModel,
_zoneName,
_cardGroupCategory,
_activeGroupCriteria,
_activeSortCriteria,
bannerOpacity,
_cardSizeWidget)
{
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff);
banner->setBuddy(flowWidget);
layout->addWidget(flowWidget);
FlatCardGroupDisplayWidget::updateCardDisplays();
connect(deckListModel, &DeckListModel::dataChanged, this, &FlatCardGroupDisplayWidget::updateCardDisplays);
}
void FlatCardGroupDisplayWidget::updateCardDisplays()
{
// Retrieve and sort cards
QList<CardInfoPtr> cardsInZone = getCardsMatchingGroup(deckListModel->getCardsAsCardInfoPtrsForZone(zoneName));
// Show or hide widget
bool shouldBeVisible = !cardsInZone.isEmpty();
if (shouldBeVisible != isVisible()) {
setVisible(shouldBeVisible);
}
// Retrieve existing widgets
QList<CardInfoPictureWithTextOverlayWidget *> existingWidgets =
flowWidget->findChildren<CardInfoPictureWithTextOverlayWidget *>();
QHash<QString, QList<CardInfoPictureWithTextOverlayWidget *>> widgetMap;
for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) {
widgetMap[widget->getInfo()->getName()].append(widget);
}
QList<CardInfoPictureWithTextOverlayWidget *> sortedWidgets;
QSet<CardInfoPictureWithTextOverlayWidget *> usedWidgets;
// Ensure widgets are ordered to match the sorted cards
for (const CardInfoPtr &card : cardsInZone) {
QString name = card->getName();
CardInfoPictureWithTextOverlayWidget *widget = nullptr;
if (!widgetMap[name].isEmpty()) {
// Reuse an existing widget
widget = widgetMap[name].takeFirst();
} else {
// Create a new widget if needed
widget = new CardInfoPictureWithTextOverlayWidget(flowWidget, true);
widget->setScaleFactor(cardSizeWidget->getSlider()->value());
widget->setCard(card);
connect(widget, &CardInfoPictureWithTextOverlayWidget::imageClicked, this,
&FlatCardGroupDisplayWidget::onClick);
connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this,
&FlatCardGroupDisplayWidget::onHover);
connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget,
&CardInfoPictureWidget::setScaleFactor);
flowWidget->addWidget(widget);
}
// Store in sorted order
sortedWidgets.append(widget);
usedWidgets.insert(widget);
}
// Remove extra widgets
for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) {
if (!usedWidgets.contains(widget)) {
flowWidget->layout()->removeWidget(widget);
widget->deleteLater();
}
}
// **Reorder widgets in place**
for (int i = 0; i < sortedWidgets.size(); ++i) {
sortedWidgets[i]->setParent(nullptr); // Temporarily detach
}
for (int i = 0; i < sortedWidgets.size(); ++i) {
flowWidget->addWidget(sortedWidgets[i]); // Reattach in correct order
}
}
void FlatCardGroupDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
}

View file

@ -0,0 +1,30 @@
#ifndef FLAT_CARD_GROUP_DISPLAY_WIDGET_H
#define FLAT_CARD_GROUP_DISPLAY_WIDGET_H
#include "../../general/layout_containers/flow_widget.h"
#include "card_group_display_widget.h"
class FlatCardGroupDisplayWidget : public CardGroupDisplayWidget
{
Q_OBJECT
public:
FlatCardGroupDisplayWidget(QWidget *parent,
DeckListModel *deckListModel,
QString zoneName,
QString cardGroupCategory,
QString activeGroupCriteria,
QStringList activeSortCriteria,
int bannerOpacity,
CardSizeWidget *cardSizeWidget);
void resizeEvent(QResizeEvent *event) override;
public slots:
void updateCardDisplays() override;
private:
FlowWidget *flowWidget;
};
#endif // FLAT_CARD_GROUP_DISPLAY_WIDGET_H

View file

@ -0,0 +1,122 @@
#include "overlapped_card_group_display_widget.h"
#include "../../../../../deck/deck_list_model.h"
#include "../../../../../game/cards/card_database_manager.h"
#include "../../../../../utility/card_info_comparator.h"
#include "../card_info_picture_with_text_overlay_widget.h"
#include <QResizeEvent>
OverlappedCardGroupDisplayWidget::OverlappedCardGroupDisplayWidget(QWidget *parent,
DeckListModel *_deckListModel,
QString _zoneName,
QString _cardGroupCategory,
QString _activeGroupCriteria,
QStringList _activeSortCriteria,
int bannerOpacity,
CardSizeWidget *_cardSizeWidget)
: CardGroupDisplayWidget(parent,
_deckListModel,
_zoneName,
_cardGroupCategory,
_activeGroupCriteria,
_activeSortCriteria,
bannerOpacity,
_cardSizeWidget)
{
overlapWidget = new OverlapWidget(this, 80, 1, 1, Qt::Vertical, true);
banner->setBuddy(overlapWidget);
layout->addWidget(overlapWidget);
OverlappedCardGroupDisplayWidget::updateCardDisplays();
connect(deckListModel, &DeckListModel::dataChanged, this, &OverlappedCardGroupDisplayWidget::updateCardDisplays);
connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, this,
[this]() { overlapWidget->adjustMaxColumnsAndRows(); });
}
void OverlappedCardGroupDisplayWidget::updateCardDisplays()
{
overlapWidget->setUpdatesEnabled(false);
// Retrieve and sort cards
QList<CardInfoPtr> cardsInZone = getCardsMatchingGroup(deckListModel->getCardsAsCardInfoPtrsForZone(zoneName));
// Show or hide widget
bool shouldBeVisible = !cardsInZone.isEmpty();
if (shouldBeVisible != isVisible()) {
setVisible(shouldBeVisible);
}
// Retrieve existing widgets
QList<CardInfoPictureWithTextOverlayWidget *> existingWidgets =
overlapWidget->findChildren<CardInfoPictureWithTextOverlayWidget *>();
QHash<QString, QList<CardInfoPictureWithTextOverlayWidget *>> widgetMap;
for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) {
widgetMap[widget->getInfo()->getName()].append(widget);
}
QList<CardInfoPictureWithTextOverlayWidget *> sortedWidgets;
QSet<CardInfoPictureWithTextOverlayWidget *> usedWidgets;
// Ensure widgets are ordered to match the sorted cards
for (const CardInfoPtr &card : cardsInZone) {
QString name = card->getName();
CardInfoPictureWithTextOverlayWidget *widget = nullptr;
if (!widgetMap[name].isEmpty()) {
// Reuse an existing widget
widget = widgetMap[name].takeFirst();
} else {
// Create a new widget if needed
widget = new CardInfoPictureWithTextOverlayWidget(overlapWidget, true);
widget->setScaleFactor(cardSizeWidget->getSlider()->value());
widget->setCard(card);
connect(widget, &CardInfoPictureWithTextOverlayWidget::imageClicked, this,
&OverlappedCardGroupDisplayWidget::onClick);
connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this,
&OverlappedCardGroupDisplayWidget::onHover);
connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget,
&CardInfoPictureWidget::setScaleFactor);
overlapWidget->addWidget(widget);
}
// Store in sorted order
sortedWidgets.append(widget);
usedWidgets.insert(widget);
}
// Remove extra widgets
for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) {
if (!usedWidgets.contains(widget)) {
overlapWidget->layout()->removeWidget(widget);
delete widget;
}
}
// **Reorder widgets in place**
for (int i = 0; i < sortedWidgets.size(); ++i) {
sortedWidgets[i]->setParent(nullptr); // Temporarily detach
}
for (int i = 0; i < sortedWidgets.size(); ++i) {
overlapWidget->addWidget(sortedWidgets[i]); // Reattach in correct order
}
// Ensure proper layering
for (CardInfoPictureWithTextOverlayWidget *widget : sortedWidgets) {
widget->raise();
}
overlapWidget->adjustMaxColumnsAndRows();
overlapWidget->setUpdatesEnabled(true);
overlapWidget->update();
}
void OverlappedCardGroupDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
overlapWidget->resize(event->size());
overlapWidget->adjustMaxColumnsAndRows();
}

View file

@ -0,0 +1,30 @@
#ifndef OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H
#define OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H
#include "../../general/layout_containers/overlap_widget.h"
#include "card_group_display_widget.h"
class OverlappedCardGroupDisplayWidget : public CardGroupDisplayWidget
{
Q_OBJECT
public:
OverlappedCardGroupDisplayWidget(QWidget *parent,
DeckListModel *deckListModel,
QString zoneName,
QString cardGroupCategory,
QString activeGroupCriteria,
QStringList activeSortCriteria,
int bannerOpacity,
CardSizeWidget *cardSizeWidget);
void resizeEvent(QResizeEvent *event) override;
public slots:
void updateCardDisplays() override;
private:
OverlapWidget *overlapWidget;
};
#endif // OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H

View file

@ -61,6 +61,7 @@ CardInfoFrameWidget::CardInfoFrameWidget(const QString &cardName, QWidget *paren
setViewMode(SettingsCache::instance().getCardInfoViewMode());
// TODO: Change this to be by UUID
setCard(CardDatabaseManager::getInstance()->getCard(cardName));
}

View file

@ -0,0 +1,168 @@
#include "deck_card_zone_display_widget.h"
#include "../../../../deck/deck_list_model.h"
#include "../../../../utility/card_info_comparator.h"
#include "card_group_display_widgets/flat_card_group_display_widget.h"
#include "card_group_display_widgets/overlapped_card_group_display_widget.h"
#include <QResizeEvent>
DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent,
DeckListModel *_deckListModel,
QString _zoneName,
QString _activeGroupCriteria,
QStringList _activeSortCriteria,
int bannerOpacity,
int subBannerOpacity,
CardSizeWidget *_cardSizeWidget)
: QWidget(parent), deckListModel(_deckListModel), zoneName(_zoneName), activeGroupCriteria(_activeGroupCriteria),
activeSortCriteria(_activeSortCriteria), bannerOpacity(bannerOpacity), subBannerOpacity(subBannerOpacity),
cardSizeWidget(_cardSizeWidget)
{
layout = new QVBoxLayout(this);
setLayout(layout);
banner = new BannerWidget(this, zoneName, Qt::Orientation::Vertical, bannerOpacity);
layout->addWidget(banner);
cardGroupContainer = new QWidget(this);
cardGroupLayout = new QVBoxLayout(cardGroupContainer);
cardGroupContainer->setLayout(cardGroupLayout);
layout->addWidget(cardGroupContainer);
banner->setBuddy(cardGroupContainer);
displayCards();
connect(deckListModel, &DeckListModel::dataChanged, this, &DeckCardZoneDisplayWidget::displayCards);
}
void DeckCardZoneDisplayWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
for (QObject *child : layout->children()) {
QWidget *widget = qobject_cast<QWidget *>(child);
if (widget) {
widget->setMaximumWidth(width());
}
}
}
void DeckCardZoneDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card)
{
emit cardClicked(event, card, zoneName);
}
void DeckCardZoneDisplayWidget::onHover(CardInfoPtr card)
{
emit cardHovered(card);
}
void DeckCardZoneDisplayWidget::displayCards()
{
addCardGroupIfItDoesNotExist();
deleteCardGroupIfItDoesNotExist();
}
void DeckCardZoneDisplayWidget::refreshDisplayType(const QString &_displayType)
{
displayType = _displayType;
QLayoutItem *item;
while ((item = cardGroupLayout->takeAt(0)) != nullptr) {
if (item->widget()) {
item->widget()->deleteLater();
} else if (item->layout()) {
delete item->layout();
}
delete item;
}
// We gotta wait for all the deleteLater's to finish so we fire after the next event cycle
auto timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, this, [this]() { displayCards(); });
timer->start();
}
void DeckCardZoneDisplayWidget::addCardGroupIfItDoesNotExist()
{
QList<CardGroupDisplayWidget *> cardGroupsDisplayWidgets =
cardGroupContainer->findChildren<CardGroupDisplayWidget *>();
QList<QString> cardGroups = getGroupCriteriaValueList();
for (QString cardGroup : cardGroups) {
bool found = false;
for (CardGroupDisplayWidget *cardGroupDisplayWidget : cardGroupsDisplayWidgets) {
if (cardGroupDisplayWidget->cardGroupCategory == cardGroup) {
found = true;
}
}
if (found) {
continue;
}
if (displayType == "overlap") {
auto *display_widget = new OverlappedCardGroupDisplayWidget(
cardGroupContainer, deckListModel, zoneName, cardGroup, activeGroupCriteria, activeSortCriteria,
subBannerOpacity, cardSizeWidget);
connect(display_widget, SIGNAL(cardClicked(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)), this,
SLOT(onClick(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)));
connect(display_widget, SIGNAL(cardHovered(CardInfoPtr)), this, SLOT(onHover(CardInfoPtr)));
connect(this, &DeckCardZoneDisplayWidget::activeSortCriteriaChanged, display_widget,
&CardGroupDisplayWidget::onActiveSortCriteriaChanged);
cardGroupLayout->addWidget(display_widget);
} else if (displayType == "flat") {
auto *display_widget = new FlatCardGroupDisplayWidget(cardGroupContainer, deckListModel, zoneName,
cardGroup, activeGroupCriteria, activeSortCriteria,
subBannerOpacity, cardSizeWidget);
connect(display_widget, SIGNAL(cardClicked(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)), this,
SLOT(onClick(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)));
connect(display_widget, SIGNAL(cardHovered(CardInfoPtr)), this, SLOT(onHover(CardInfoPtr)));
connect(this, &DeckCardZoneDisplayWidget::activeSortCriteriaChanged, display_widget,
&CardGroupDisplayWidget::onActiveSortCriteriaChanged);
cardGroupLayout->addWidget(display_widget);
}
}
}
void DeckCardZoneDisplayWidget::deleteCardGroupIfItDoesNotExist()
{
QList<CardGroupDisplayWidget *> cardGroupsDisplayWidgets =
cardGroupContainer->findChildren<CardGroupDisplayWidget *>();
QList<QString> validGroups = getGroupCriteriaValueList();
for (CardGroupDisplayWidget *cardGroupDisplayWidget : cardGroupsDisplayWidgets) {
if (!validGroups.contains(cardGroupDisplayWidget->cardGroupCategory)) {
cardGroupLayout->removeWidget(cardGroupDisplayWidget);
cardGroupDisplayWidget->deleteLater(); // Properly delete the widget after the event loop cycles
}
}
}
void DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged(QString _activeGroupCriteria)
{
activeGroupCriteria = _activeGroupCriteria;
displayCards();
}
void DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSortCriteria)
{
activeSortCriteria = _activeSortCriteria;
emit activeSortCriteriaChanged(activeSortCriteria);
}
QList<QString> DeckCardZoneDisplayWidget::getGroupCriteriaValueList()
{
QList<QString> groupCriteriaValues;
QList<CardInfoPtr> cardsInZone = deckListModel->getCardsAsCardInfoPtrsForZone(zoneName);
for (CardInfoPtr cardInZone : cardsInZone) {
groupCriteriaValues.append(cardInZone->getProperty(activeGroupCriteria));
}
groupCriteriaValues.removeDuplicates();
groupCriteriaValues.sort();
return groupCriteriaValues;
}

View file

@ -0,0 +1,62 @@
#ifndef DECK_CARD_ZONE_DISPLAY_WIDGET_H
#define DECK_CARD_ZONE_DISPLAY_WIDGET_H
#include "../../../../deck/deck_list_model.h"
#include "../../../../game/cards/card_database.h"
#include "../general/display/banner_widget.h"
#include "../general/layout_containers/overlap_widget.h"
#include "card_info_picture_with_text_overlay_widget.h"
#include "card_size_widget.h"
#include <QVBoxLayout>
#include <QWidget>
class DeckCardZoneDisplayWidget : public QWidget
{
Q_OBJECT
public:
DeckCardZoneDisplayWidget(QWidget *parent,
DeckListModel *deckListModel,
QString zoneName,
QString activeGroupCriteria,
QStringList activeSortCriteria,
int bannerOpacity,
int subBannerOpacity,
CardSizeWidget *_cardSizeWidget);
DeckListModel *deckListModel;
QString zoneName;
void addCardsToOverlapWidget();
void resizeEvent(QResizeEvent *event) override;
public slots:
void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card);
void onHover(CardInfoPtr card);
void displayCards();
void refreshDisplayType(const QString &displayType);
void addCardGroupIfItDoesNotExist();
void deleteCardGroupIfItDoesNotExist();
void onActiveGroupCriteriaChanged(QString activeGroupCriteria);
void onActiveSortCriteriaChanged(QStringList activeSortCriteria);
QList<QString> getGroupCriteriaValueList();
signals:
void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card, QString zoneName);
void cardHovered(CardInfoPtr card);
void activeSortCriteriaChanged(QStringList activeSortCriteria);
private:
QString activeGroupCriteria;
QStringList activeSortCriteria;
int bannerOpacity = 20;
int subBannerOpacity = 10;
CardSizeWidget *cardSizeWidget;
QVBoxLayout *layout;
BannerWidget *banner;
QWidget *cardGroupContainer;
QVBoxLayout *cardGroupLayout;
QString displayType = "flat";
OverlapWidget *overlapWidget;
};
#endif // DECK_CARD_ZONE_DISPLAY_WIDGET_H

View file

@ -309,6 +309,8 @@ void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck)
deckView->expandAll();
deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList());
emit deckChanged();
}
DeckLoader *DeckEditorDeckDockWidget::getDeckList()
@ -325,6 +327,7 @@ void DeckEditorDeckDockWidget::cleanDeck()
nameEdit->setText(QString());
commentsEdit->setText(QString());
hashLabel->setText(QString());
emit deckChanged();
updateBannerCardComboBox();
deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList());
}

View file

@ -0,0 +1,315 @@
#include "visual_deck_editor_widget.h"
#include "../../../../deck/deck_list_model.h"
#include "../../../../deck/deck_loader.h"
#include "../../../../game/cards/card_completer_proxy_model.h"
#include "../../../../game/cards/card_database.h"
#include "../../../../game/cards/card_database_manager.h"
#include "../../../../game/cards/card_database_model.h"
#include "../../../../game/cards/card_search_model.h"
#include "../../../../main.h"
#include "../../../../utility/card_info_comparator.h"
#include "../../layouts/overlap_layout.h"
#include "../cards/card_info_picture_with_text_overlay_widget.h"
#include "../cards/deck_card_zone_display_widget.h"
#include "../general/layout_containers/flow_widget.h"
#include "../general/layout_containers/overlap_control_widget.h"
#include <QCheckBox>
#include <QCompleter>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QResizeEvent>
#include <qscrollarea.h>
VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, DeckListModel *_deckListModel)
: QWidget(parent), deckListModel(_deckListModel)
{
connect(deckListModel, &DeckListModel::dataChanged, this, &VisualDeckEditorWidget::decklistDataChanged);
// The Main Widget and Main Layout, which contain a single Widget: The Scroll Area
setMinimumSize(0, 0);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mainLayout = new QVBoxLayout(this);
setLayout(mainLayout);
mainLayout->setContentsMargins(9, 0, 9, 5);
mainLayout->setSpacing(0);
searchContainer = new QWidget(this);
searchLayout = new QHBoxLayout(searchContainer);
searchContainer->setLayout(searchLayout);
searchBar = new QLineEdit(this);
connect(searchBar, &QLineEdit::returnPressed, this, [=, this]() {
if (!searchBar->hasFocus())
return;
CardInfoPtr card = CardDatabaseManager::getInstance()->getCard(searchBar->text());
if (card) {
emit cardAdditionRequested(card);
}
});
setFocusProxy(searchBar);
setFocusPolicy(Qt::ClickFocus);
cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this);
cardDatabaseDisplayModel = new CardDatabaseDisplayModel(this);
cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel);
CardSearchModel *searchModel = new CardSearchModel(cardDatabaseDisplayModel, this);
proxyModel = new CardCompleterProxyModel(this);
proxyModel->setSourceModel(searchModel);
proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
proxyModel->setFilterRole(Qt::DisplayRole);
completer = new QCompleter(proxyModel, this);
completer->setCompletionRole(Qt::DisplayRole);
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setFilterMode(Qt::MatchContains);
completer->setMaxVisibleItems(15);
searchBar->setCompleter(completer);
// Update suggestions dynamically
connect(searchBar, &QLineEdit::textEdited, searchModel, &CardSearchModel::updateSearchResults);
connect(searchBar, &QLineEdit::textEdited, this, [=, this](const QString &text) {
// Ensure substring matching
QString pattern = ".*" + QRegularExpression::escape(text) + ".*";
proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
if (!text.isEmpty()) {
completer->complete(); // Force the dropdown to appear
}
});
connect(completer, static_cast<void (QCompleter::*)(const QString &)>(&QCompleter::activated), this,
[=, this](const QString &completion) {
// Prevent the text from changing automatically when navigating with arrow keys
if (searchBar->text() != completion) {
searchBar->setText(completion); // Set the completion explicitly
searchBar->setCursorPosition(searchBar->text().length()); // Move cursor to the end
}
});
// Ensure that the text stays consistent during selection
connect(searchBar, &QLineEdit::textEdited, this, [=, this](const QString &text) {
if (searchBar->hasFocus() && !searchBar->completer()->popup()->isVisible()) {
// Allow text to change when typing, but not when navigating the completer
QString pattern = ".*" + QRegularExpression::escape(text) + ".*";
proxyModel->setFilterRegularExpression(
QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
}
});
// Search button functionality
searchPushButton = new QPushButton(this);
connect(searchPushButton, &QPushButton::clicked, this, [=, this]() {
CardInfoPtr card = CardDatabaseManager::getInstance()->getCard(searchBar->text());
if (card) {
emit cardAdditionRequested(card);
}
});
searchLayout->addWidget(searchBar);
searchLayout->addWidget(searchPushButton);
mainLayout->addWidget(searchContainer);
groupAndSortContainer = new QWidget(this);
groupAndSortLayout = new QHBoxLayout(groupAndSortContainer);
groupAndSortLayout->setAlignment(Qt::AlignLeft);
groupAndSortContainer->setLayout(groupAndSortLayout);
groupByComboBox = new QComboBox();
QStringList groupProperties = {"maintype", "colors", "cmc", "name"};
groupByComboBox->addItems(groupProperties);
groupByComboBox->setMinimumWidth(300);
connect(groupByComboBox, QOverload<const QString &>::of(&QComboBox::currentTextChanged), this,
&VisualDeckEditorWidget::actChangeActiveGroupCriteria);
actChangeActiveGroupCriteria();
sortCriteriaButton = new SettingsButtonWidget(this);
sortLabel = new QLabel(sortCriteriaButton);
sortLabel->setWordWrap(true);
QStringList sortProperties = {"colors", "cmc", "name", "maintype"};
sortByListWidget = new QListWidget();
sortByListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
sortByListWidget->setDragDropMode(QAbstractItemView::InternalMove);
sortByListWidget->setDefaultDropAction(Qt::MoveAction);
for (const QString &property : sortProperties) {
QListWidgetItem *item = new QListWidgetItem(property, sortByListWidget);
item->setFlags(item->flags() | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
}
connect(sortByListWidget->model(), &QAbstractItemModel::rowsMoved, this,
&VisualDeckEditorWidget::actChangeActiveSortCriteria);
actChangeActiveSortCriteria();
sortByListWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
sortCriteriaButton->addSettingsWidget(sortLabel);
sortCriteriaButton->addSettingsWidget(sortByListWidget);
displayTypeButton = new QPushButton(this);
connect(displayTypeButton, &QPushButton::clicked, this, &VisualDeckEditorWidget::updateDisplayType);
groupAndSortLayout->addWidget(groupByComboBox);
groupAndSortLayout->addWidget(sortCriteriaButton);
groupAndSortLayout->addWidget(displayTypeButton);
scrollArea = new QScrollArea();
scrollArea->setWidgetResizable(true);
scrollArea->setMinimumSize(0, 0);
// Set scrollbar policies
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
zoneContainer = new QWidget(scrollArea);
zoneContainerLayout = new QVBoxLayout(zoneContainer);
zoneContainer->setLayout(zoneContainerLayout);
scrollArea->addScrollBarWidget(zoneContainer, Qt::AlignHCenter);
scrollArea->setWidget(zoneContainer);
updateZoneWidgets();
cardSizeWidget = new CardSizeWidget(this);
mainLayout->addWidget(groupAndSortContainer);
mainLayout->addWidget(scrollArea);
mainLayout->addWidget(cardSizeWidget);
retranslateUi();
}
void VisualDeckEditorWidget::retranslateUi()
{
sortLabel->setText(tr("Click and drag to change the sort order within the groups"));
searchPushButton->setText(tr("Quick search and add card"));
displayTypeButton->setText(tr("Flat Layout"));
}
void VisualDeckEditorWidget::updateZoneWidgets()
{
addZoneIfDoesNotExist();
deleteZoneIfDoesNotExist();
}
void VisualDeckEditorWidget::updateDisplayType()
{
// Toggle the display type
currentDisplayType = (currentDisplayType == DisplayType::Overlap) ? DisplayType::Flat : DisplayType::Overlap;
// Update UI and emit signal
switch (currentDisplayType) {
case DisplayType::Flat:
emit displayTypeChanged("flat");
displayTypeButton->setText(tr("Flat Layout"));
break;
case DisplayType::Overlap:
emit displayTypeChanged("overlap");
displayTypeButton->setText(tr("Overlap Layout"));
break;
}
}
void VisualDeckEditorWidget::addZoneIfDoesNotExist()
{
QList<DeckCardZoneDisplayWidget *> cardZoneDisplayWidgets =
zoneContainer->findChildren<DeckCardZoneDisplayWidget *>();
for (const QString &zone : *deckListModel->getZones()) {
bool found = false;
for (DeckCardZoneDisplayWidget *displayWidget : cardZoneDisplayWidgets) {
if (displayWidget->zoneName == zone) {
found = true;
break;
}
}
if (found) {
continue;
}
DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget(
zoneContainer, deckListModel, zone, activeGroupCriteria, activeSortCriteria, 20, 10, cardSizeWidget);
connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover);
connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick);
connect(this, &VisualDeckEditorWidget::activeSortCriteriaChanged, zoneDisplayWidget,
&DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged);
connect(this, &VisualDeckEditorWidget::activeGroupCriteriaChanged, zoneDisplayWidget,
&DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged);
connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget,
&DeckCardZoneDisplayWidget::refreshDisplayType);
zoneContainerLayout->addWidget(zoneDisplayWidget);
}
}
void VisualDeckEditorWidget::deleteZoneIfDoesNotExist()
{
QList<DeckCardZoneDisplayWidget *> cardZoneDisplayWidgets =
zoneContainer->findChildren<DeckCardZoneDisplayWidget *>();
for (DeckCardZoneDisplayWidget *displayWidget : cardZoneDisplayWidgets) {
bool found = false;
for (const QString &zone : *deckListModel->getZones()) {
if (displayWidget->zoneName == zone) {
found = true;
break;
}
}
if (!found) {
zoneContainerLayout->removeWidget(displayWidget);
}
}
}
void VisualDeckEditorWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
zoneContainer->setMaximumWidth(scrollArea->viewport()->width());
}
void VisualDeckEditorWidget::actChangeActiveGroupCriteria()
{
activeGroupCriteria = groupByComboBox->currentText();
emit activeGroupCriteriaChanged(activeGroupCriteria);
}
void VisualDeckEditorWidget::actChangeActiveSortCriteria()
{
QStringList selectedCriteria;
for (int i = 0; i < sortByListWidget->count(); ++i) {
QListWidgetItem *item = sortByListWidget->item(i);
selectedCriteria.append(item->text()); // Collect user-defined sort order
}
activeSortCriteria = selectedCriteria;
emit activeSortCriteriaChanged(selectedCriteria);
}
void VisualDeckEditorWidget::decklistDataChanged(QModelIndex topLeft, QModelIndex bottomRight)
{
// Might use these at some point.
Q_UNUSED(topLeft);
Q_UNUSED(bottomRight);
// Necessary to delay this in this manner else the updateDisplay will nuke widgets while their onClick event
// hasn't returned yet. Interval of 0 means QT will schedule this after the current event loop has finished.
updateZoneWidgets();
}
void VisualDeckEditorWidget::onHover(CardInfoPtr hoveredCard)
{
emit activeCardChanged(hoveredCard);
}
void VisualDeckEditorWidget::onCardClick(QMouseEvent *event,
CardInfoPictureWithTextOverlayWidget *instance,
QString zoneName)
{
emit cardClicked(event, instance, zoneName);
}

View file

@ -0,0 +1,88 @@
#ifndef VISUAL_DECK_EDITOR_H
#define VISUAL_DECK_EDITOR_H
#include "../../../../deck/deck_list_model.h"
#include "../../../../game/cards/card_completer_proxy_model.h"
#include "../../../../game/cards/card_database.h"
#include "../../../../game/cards/card_database_model.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 "../quick_settings/settings_button_widget.h"
#include <QCheckBox>
#include <QListWidget>
#include <QPushButton>
#include <QWidget>
#include <qscrollarea.h>
enum class DisplayType
{
Flat,
Overlap
};
class VisualDeckEditorWidget : public QWidget
{
Q_OBJECT
public:
explicit VisualDeckEditorWidget(QWidget *parent, DeckListModel *deckListModel);
void retranslateUi();
void resizeEvent(QResizeEvent *event) override;
void setDeckList(const DeckList &_deckListModel);
QLineEdit *searchBar;
CardSizeWidget *cardSizeWidget;
public slots:
void decklistDataChanged(QModelIndex topLeft, QModelIndex bottomRight);
void updateZoneWidgets();
void updateDisplayType();
void addZoneIfDoesNotExist();
void deleteZoneIfDoesNotExist();
signals:
void activeCardChanged(CardInfoPtr activeCard);
void activeGroupCriteriaChanged(QString activeGroupCriteria);
void activeSortCriteriaChanged(QStringList activeSortCriteria);
void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName);
void cardAdditionRequested(CardInfoPtr card);
void displayTypeChanged(QString displayType);
protected slots:
void onHover(CardInfoPtr hoveredCard);
void onCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName);
void actChangeActiveGroupCriteria();
void actChangeActiveSortCriteria();
private:
DeckListModel *deckListModel;
QVBoxLayout *mainLayout;
QWidget *searchContainer;
QHBoxLayout *searchLayout;
CardDatabaseModel *cardDatabaseModel;
CardDatabaseDisplayModel *cardDatabaseDisplayModel;
CardCompleterProxyModel *proxyModel;
QCompleter *completer;
QPushButton *searchPushButton;
DisplayType currentDisplayType = DisplayType::Overlap;
QPushButton *displayTypeButton;
QWidget *groupAndSortContainer;
QHBoxLayout *groupAndSortLayout;
QComboBox *groupByComboBox;
QString activeGroupCriteria = "maintype";
SettingsButtonWidget *sortCriteriaButton;
QLabel *sortLabel;
QListWidget *sortByListWidget;
QStringList activeSortCriteria = {"name", "cmc", "colors", "maintype"};
QScrollArea *scrollArea;
QWidget *zoneContainer;
QVBoxLayout *zoneContainerLayout;
// OverlapControlWidget *overlapControlWidget;
QWidget *container;
};
#endif // VISUAL_DECK_EDITOR_H

View file

@ -0,0 +1,18 @@
#include "card_completer_proxy_model.h"
CardCompleterProxyModel::CardCompleterProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
{
}
bool CardCompleterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
if (filterRegularExpression().pattern().isEmpty()) {
return true;
}
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
QString data = index.data(Qt::DisplayRole).toString();
// Ensure substring matching
return data.contains(filterRegularExpression());
}

View file

@ -0,0 +1,16 @@
#ifndef CARD_COMPLETER_PROXY_MODEL_H
#define CARD_COMPLETER_PROXY_MODEL_H
#include <QSortFilterProxyModel>
class CardCompleterProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit CardCompleterProxyModel(QObject *parent = nullptr);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
};
#endif // CARD_COMPLETER_PROXY_MODEL_H

View file

@ -0,0 +1,71 @@
#include "card_search_model.h"
#include "../../utility/levenshtein.h"
#include <algorithm>
CardSearchModel::CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent)
: QAbstractListModel(parent), sourceModel(sourceModel)
{
}
int CardSearchModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return searchResults.size();
}
QVariant CardSearchModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= searchResults.size())
return QVariant();
if (role == Qt::DisplayRole) {
return searchResults.at(index.row()).card->getName();
}
return QVariant();
}
void CardSearchModel::updateSearchResults(const QString &query)
{
beginResetModel();
searchResults.clear();
if (query.isEmpty() || !sourceModel)
return;
// Set the filter for the display model
sourceModel->setCardName(query);
// Collect matching cards and compute Levenshtein distance
for (int i = 0; i < sourceModel->rowCount(); ++i) {
QModelIndex modelIndex = sourceModel->index(i, 0);
QModelIndex sourceIndex = sourceModel->mapToSource(modelIndex);
CardDatabaseModel *sourceDbModel = qobject_cast<CardDatabaseModel *>(sourceModel->sourceModel());
if (!sourceDbModel || !sourceIndex.isValid())
return;
CardInfoPtr card = sourceDbModel->getCard(sourceIndex.row());
if (!card)
continue;
int distance = levenshteinDistance(query.toLower(), card->getName().toLower());
searchResults.append({card, distance});
}
// Sort by Levenshtein distance (lower distance = better match)
std::sort(searchResults.begin(), searchResults.end(),
[](const SearchResult &a, const SearchResult &b) { return a.distance < b.distance; });
// Keep only the top 5 results
if (searchResults.size() > 10)
searchResults = searchResults.mid(0, 10);
emit dataChanged(index(0, 0), index(rowCount() - 1, 0));
emit layoutChanged();
endResetModel();
}

View file

@ -0,0 +1,30 @@
#ifndef CARD_SEARCH_MODEL_H
#define CARD_SEARCH_MODEL_H
#include "card_database_model.h"
#include <QAbstractListModel>
class CardSearchModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void updateSearchResults(const QString &query); // Update results based on input
private:
struct SearchResult
{
CardInfoPtr card;
int distance;
};
CardDatabaseDisplayModel *sourceModel;
QList<SearchResult> searchResults;
};
#endif // CARD_SEARCH_MODEL_H

View file

@ -0,0 +1,25 @@
#include "levenshtein.h"
#include <algorithm>
#include <vector>
int levenshteinDistance(const QString &s1, const QString &s2)
{
int len1 = s1.size();
int len2 = s2.size();
std::vector<std::vector<int>> dp(len1 + 1, std::vector<int>(len2 + 1));
for (int i = 0; i <= len1; i++)
dp[i][0] = i;
for (int j = 0; j <= len2; j++)
dp[0][j] = j;
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
dp[i][j] = std::min({dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost});
}
}
return dp[len1][len2];
}

View file

@ -0,0 +1,8 @@
#ifndef LEVENSHTEIN_H
#define LEVENSHTEIN_H
#include <QString>
int levenshteinDistance(const QString &s1, const QString &s2);
#endif // LEVENSHTEIN_H