[Feature] TabArchidekt and Archidekt API integration (#6348)

* TabArchidekt and Archidekt API integration.


Took 37 seconds

Took 4 minutes

Took 40 seconds

Took 4 minutes

* Lint.

* Lont.

* Search bar, fancier display, resolve providerId

* Delegate click to base.

* Be explicit for pedantic compilers.

* Liiint.

* Leave them default I guess

* Leave them default I guess

* Small fixes.

* New utility display widgets.

* New style for deck listing.

* Lint.

* Lont.

* Scale things.

* Delegate paint to base.

* Use default Archidekt preview image for decks without featured.

* Consistent sizes.

* Increase font size, qt version guard.

* More version guards.

* Clean up filter layout, use mana symbols.

* Set content margins.

* Refresh on filter change.

* Lint.

* Better elision.

* Query actual new endpoints, new query parameters.

* Doxygen, reorder fields in constructor, readability.

* Update page size doc to min size.

* Update initial min deck size value.

* Add label to page selection.

* Okay, so, people upload a lot of 1 card decks frequently.

* Whoops.

* Add a selection combobox for sorting logic.

* Debounce and limit searches.

* Include.

* Lint.

* Don't imply that Archidekt supports multiple cards/commander names.

* Let's not lambda it and slot it instead.

* Overload.

* Add button to home tab.

Took 8 minutes

* Adjust to selection model change.

Took 5 minutes

* Cleanup auto-generated comments.

Took 8 minutes

* Remember card sizes.

Took 1 minute

* Initialize with correct size.

Took 3 minutes

* Use correct placeholders.

Took 2 minutes

* Style lint.

Took 16 minutes

* Parse double-faced cards correctly.

* Parse double-faced cards correctly.

* Allow TabArchidekt to use VDE group/sort/display buttons

* Lint.

* Indicate that things are clickable.

* Min treshold for nicer display.

* Lint.

* We have good labels at home.

* We do a little linting.

* Qt version guards.

* Qt5 is the devil.

* Update comments.

* Lint comments.

* More doxys.

* One more doxy.

* Lint.

* Update.

* Small fixes.

Took 7 minutes

Took 13 seconds

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2025-11-30 08:41:01 +01:00 committed by GitHub
parent de13c22552
commit eab4d435f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 3285 additions and 141 deletions

View file

@ -0,0 +1,124 @@
#include "visual_deck_display_options_widget.h"
#include "../tabs/visual_deck_editor/tab_deck_editor_visual.h"
VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent)
{
groupAndSortLayout = new QHBoxLayout(this);
groupAndSortLayout->setAlignment(Qt::AlignLeft);
this->setLayout(groupAndSortLayout);
groupByLabel = new QLabel(this);
groupByComboBox = new QComboBox(this);
if (auto visualDeckEditorWidget = qobject_cast<VisualDeckEditorWidget *>(parent)) {
if (auto tabWidget = qobject_cast<TabDeckEditorVisualTabWidget *>(visualDeckEditorWidget)) {
// Inside a central widget QWidget container inside TabDeckEditorVisual
if (auto tab = qobject_cast<TabDeckEditorVisual *>(tabWidget->parent()->parent())) {
auto originalBox = tab->getDeckDockWidget()->getGroupByComboBox();
groupByComboBox->setModel(originalBox->model());
groupByComboBox->setModelColumn(originalBox->modelColumn());
// Original -> clone
connect(originalBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
[this](int index) { groupByComboBox->setCurrentIndex(index); });
// Clone -> original
connect(groupByComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
[originalBox](int index) { originalBox->setCurrentIndex(index); });
}
}
} else {
groupByComboBox->addItem(
tr(qPrintable(DeckListModelGroupCriteria::toString(DeckListModelGroupCriteria::MAIN_TYPE))),
DeckListModelGroupCriteria::MAIN_TYPE);
groupByComboBox->addItem(
tr(qPrintable(DeckListModelGroupCriteria::toString(DeckListModelGroupCriteria::MANA_COST))),
DeckListModelGroupCriteria::MANA_COST);
groupByComboBox->addItem(
tr(qPrintable(DeckListModelGroupCriteria::toString(DeckListModelGroupCriteria::COLOR))),
DeckListModelGroupCriteria::COLOR);
groupByComboBox->setMinimumWidth(300);
connect(groupByComboBox, QOverload<const QString &>::of(&QComboBox::currentTextChanged), this,
&VisualDeckDisplayOptionsWidget::groupCriteriaChanged);
emit groupCriteriaChanged(groupByComboBox->currentText());
}
sortByLabel = new QLabel(this);
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,
&VisualDeckDisplayOptionsWidget::onSortCriteriaChange);
onSortCriteriaChange();
sortByListWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
sortCriteriaButton->addSettingsWidget(sortLabel);
sortCriteriaButton->addSettingsWidget(sortByListWidget);
displayTypeButton = new QPushButton(this);
connect(displayTypeButton, &QPushButton::clicked, this, &VisualDeckDisplayOptionsWidget::updateDisplayType);
groupAndSortLayout->addWidget(groupByLabel);
groupAndSortLayout->addWidget(groupByComboBox);
groupAndSortLayout->addWidget(sortByLabel);
groupAndSortLayout->addWidget(sortCriteriaButton);
groupAndSortLayout->addWidget(displayTypeButton);
retranslateUi();
}
void VisualDeckDisplayOptionsWidget::retranslateUi()
{
groupByLabel->setText(tr("Group by:"));
groupByComboBox->setToolTip(tr("Change how cards are divided into categories/groups."));
sortByLabel->setText(tr("Sort by:"));
sortLabel->setText(tr("Click and drag to change the sort order within the groups"));
sortCriteriaButton->setToolTip(tr("Configure how cards are sorted within their groups"));
displayTypeButton->setText(tr("Toggle Layout: Overlap"));
displayTypeButton->setToolTip(
tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)"));
}
void VisualDeckDisplayOptionsWidget::onSortCriteriaChange()
{
QStringList selectedCriteria;
for (int i = 0; i < sortByListWidget->count(); ++i) {
QListWidgetItem *item = sortByListWidget->item(i);
selectedCriteria.append(item->text()); // Collect user-defined sort order
}
emit sortCriteriaChanged(selectedCriteria);
}
void VisualDeckDisplayOptionsWidget::updateDisplayType()
{
// Toggle the display type
currentDisplayType = (currentDisplayType == DisplayType::Overlap) ? DisplayType::Flat : DisplayType::Overlap;
// Update UI and emit signal
switch (currentDisplayType) {
case DisplayType::Flat:
displayTypeButton->setText(tr("Toggle Layout: Flat"));
break;
case DisplayType::Overlap:
displayTypeButton->setText(tr("Toggle Layout: Overlap"));
break;
}
emit displayTypeChanged(currentDisplayType);
}

View file

@ -0,0 +1,138 @@
#ifndef COCKATRICE_VISUAL_DECK_DISPLAY_OPTIONS_WIDGET_H
#define COCKATRICE_VISUAL_DECK_DISPLAY_OPTIONS_WIDGET_H
#include "visual_deck_editor_widget.h"
#include <QComboBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QPushButton>
#include <QWidget>
/**
* @class VisualDeckDisplayOptionsWidget
* @brief A widget that controls how deck cards are displayed in the visual deck editor.
*
* This widget provides:
* - A **group-by** selector (QComboBox)
* - A **sort-by** multi-criteria, draggable list (QListWidget within a SettingsButtonWidget)
* - A **display-type toggler** (flat vs. overlap layout)
*
* Depending on whether the parent is a VisualDeckEditorWidget, this widget can mirror the
* original group by checkbox from the main deck editor UI to maintain synchronization.
*
* It emits signals whenever the grouping criterion, sorting criteria, or display mode changes.
*/
class VisualDeckDisplayOptionsWidget : public QWidget
{
Q_OBJECT
signals:
/**
* @brief Emitted when the display type (flat or overlapping layout) changes.
* @param displayType The newly selected display layout.
*/
void displayTypeChanged(const DisplayType &displayType);
/**
* @brief Emitted when a new grouping criterion is selected.
* @param activeGroupCriteria Name of the selected group-by criterion.
*/
void groupCriteriaChanged(const QString &activeGroupCriteria);
/**
* @brief Emitted when the order of sort criteria changes.
* @param activeSortCriteria Ordered list of sorting keys.
*/
void sortCriteriaChanged(const QStringList &activeSortCriteria);
public slots:
/**
* @brief Updates all UI text for retranslation/localization.
*
* Called when the application language changes.
*/
void retranslateUi();
public:
/**
* @brief Constructs a new VisualDeckDisplayOptionsWidget.
* @param parent The parent QWidgetmay trigger cloning of models if the parent is a visual deck editor.
*/
explicit VisualDeckDisplayOptionsWidget(QWidget *parent);
/**
* @brief Gets the current display type (Overlap or Flat).
*/
DisplayType getDisplayType() const
{
return currentDisplayType;
}
/**
* @brief Gets the currently active group-by criterion.
*/
QString getActiveGroupCriteria() const
{
return activeGroupCriteria;
}
/**
* @brief Gets the currently active ordered sort criteria.
*/
QStringList getActiveSortCriteria() const
{
return activeSortCriteria;
}
private slots:
/**
* @brief Slot triggered whenever the sort list is reordered.
*
* Reads the QListWidgets order and emits `sortCriteriaChanged()`.
*/
void onSortCriteriaChange();
/**
* @brief Toggles the display layout between flat and overlapping modes.
*
* Emits `displayTypeChanged()`.
*/
void updateDisplayType();
private:
/// Layout for grouping and sorting UI elements.
QHBoxLayout *groupAndSortLayout;
/// Current deck display type.
DisplayType currentDisplayType = DisplayType::Overlap;
/// Button used to toggle the display layout.
QPushButton *displayTypeButton;
/// Label for the group-by selector.
QLabel *groupByLabel;
/// Combo box listing group-by criteria.
QComboBox *groupByComboBox;
/// Currently active group-by criterion.
QString activeGroupCriteria = "maintype";
/// Encapsulates the sort settings widgets (label + list).
SettingsButtonWidget *sortCriteriaButton;
/// Label for “Sort by”.
QLabel *sortByLabel;
/// Descriptive label inside the sort criteria button.
QLabel *sortLabel;
/// Draggable list of sort criteria.
QListWidget *sortByListWidget;
/// Ordered list of current sort criteria.
QStringList activeSortCriteria = {"name", "cmc", "colors", "maintype"};
};
#endif // COCKATRICE_VISUAL_DECK_DISPLAY_OPTIONS_WIDGET_H

View file

@ -9,6 +9,7 @@
#include "../general/layout_containers/flow_widget.h"
#include "../tabs/visual_deck_editor/tab_deck_editor_visual.h"
#include "../tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h"
#include "visual_deck_display_options_widget.h"
#include <QCheckBox>
#include <QCompleter>
@ -109,75 +110,22 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent,
}
});
groupAndSortContainer = new QWidget(this);
groupAndSortLayout = new QHBoxLayout(groupAndSortContainer);
groupAndSortLayout->setAlignment(Qt::AlignLeft);
groupAndSortContainer->setLayout(groupAndSortLayout);
displayOptionsAndSearch = new QWidget(this);
displayOptionsAndSearchLayout = new QHBoxLayout(displayOptionsAndSearch);
displayOptionsAndSearchLayout->setAlignment(Qt::AlignLeft);
displayOptionsAndSearch->setLayout(displayOptionsAndSearchLayout);
groupByLabel = new QLabel(groupAndSortContainer);
displayOptionsWidget = new VisualDeckDisplayOptionsWidget(this);
connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::displayTypeChanged, this,
&VisualDeckEditorWidget::displayTypeChanged);
connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::groupCriteriaChanged, this,
&VisualDeckEditorWidget::activeGroupCriteriaChanged);
connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::sortCriteriaChanged, this,
&VisualDeckEditorWidget::activeSortCriteriaChanged);
groupByComboBox = new QComboBox(this);
if (auto tabWidget = qobject_cast<TabDeckEditorVisualTabWidget *>(parent)) {
// Inside a central widget QWidget container inside TabDeckEditorVisual
if (auto tab = qobject_cast<TabDeckEditorVisual *>(tabWidget->parent()->parent())) {
auto originalBox = tab->getDeckDockWidget()->getGroupByComboBox();
groupByComboBox->setModel(originalBox->model());
groupByComboBox->setModelColumn(originalBox->modelColumn());
// Original -> clone
connect(originalBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
[this](int index) { groupByComboBox->setCurrentIndex(index); });
// Clone -> original
connect(groupByComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
[originalBox](int index) { originalBox->setCurrentIndex(index); });
}
} else {
QStringList groupProperties = {"maintype", "colors", "cmc", "name"};
groupByComboBox->addItems(groupProperties);
groupByComboBox->setMinimumWidth(300);
connect(groupByComboBox, QOverload<const QString &>::of(&QComboBox::currentTextChanged), this,
&VisualDeckEditorWidget::actChangeActiveGroupCriteria);
actChangeActiveGroupCriteria();
}
sortByLabel = new QLabel(groupAndSortContainer);
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(groupByLabel);
groupAndSortLayout->addWidget(groupByComboBox);
groupAndSortLayout->addWidget(sortByLabel);
groupAndSortLayout->addWidget(sortCriteriaButton);
groupAndSortLayout->addWidget(displayTypeButton);
groupAndSortLayout->addWidget(searchBar);
groupAndSortLayout->addWidget(searchPushButton);
displayOptionsAndSearchLayout->addWidget(displayOptionsWidget);
displayOptionsAndSearchLayout->addWidget(searchBar);
displayOptionsAndSearchLayout->addWidget(searchPushButton);
scrollArea = new QScrollArea(this);
scrollArea->setWidgetResizable(true);
@ -197,7 +145,7 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent,
connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance(),
&SettingsCache::setVisualDeckEditorCardSize);
mainLayout->addWidget(groupAndSortContainer);
mainLayout->addWidget(displayOptionsAndSearch);
mainLayout->addWidget(scrollArea);
mainLayout->addWidget(cardSizeWidget);
@ -218,17 +166,9 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent,
void VisualDeckEditorWidget::retranslateUi()
{
searchBar->setPlaceholderText(tr("Type a card name here for suggestions from the database..."));
groupByLabel->setText(tr("Group by:"));
groupByComboBox->setToolTip(tr("Change how cards are divided into categories/groups."));
sortByLabel->setText(tr("Sort by:"));
sortLabel->setText(tr("Click and drag to change the sort order within the groups"));
searchPushButton->setText(tr("Quick search and add card"));
searchPushButton->setToolTip(tr("Search for closest match in the database (with auto-suggestions) and add "
"preferred printing to the deck on pressing enter"));
sortCriteriaButton->setToolTip(tr("Configure how cards are sorted within their groups"));
displayTypeButton->setText(tr("Toggle Layout: Overlap"));
displayTypeButton->setToolTip(
tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)"));
}
void VisualDeckEditorWidget::setSelectionModel(QItemSelectionModel *model)
@ -326,8 +266,9 @@ void VisualDeckEditorWidget::constructZoneWidgetForIndex(QPersistentModelIndex p
{
DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget(
zoneContainer, deckListModel, selectionModel, persistent,
deckListModel->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), activeGroupCriteria,
activeSortCriteria, currentDisplayType, 20, 10, cardSizeWidget);
deckListModel->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(),
displayOptionsWidget->getActiveGroupCriteria(), displayOptionsWidget->getActiveSortCriteria(),
displayOptionsWidget->getDisplayType(), 20, 10, cardSizeWidget);
connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover);
connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick);
connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::requestCleanup, this,
@ -338,7 +279,7 @@ void VisualDeckEditorWidget::constructZoneWidgetForIndex(QPersistentModelIndex p
&DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged);
connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget,
&DeckCardZoneDisplayWidget::refreshDisplayType);
zoneDisplayWidget->refreshDisplayType(currentDisplayType);
zoneDisplayWidget->refreshDisplayType(displayOptionsWidget->getDisplayType());
zoneContainerLayout->addWidget(zoneDisplayWidget);
indexToWidgetMap.insert(persistent, zoneDisplayWidget);
@ -370,48 +311,12 @@ void VisualDeckEditorWidget::updateZoneWidgets()
{
}
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:
displayTypeButton->setText(tr("Toggle Layout: Flat"));
break;
case DisplayType::Overlap:
displayTypeButton->setText(tr("Toggle Layout: Overlap"));
break;
}
emit displayTypeChanged(currentDisplayType);
}
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::decklistModelReset()
{
clearAllDisplayWidgets();

View file

@ -23,6 +23,7 @@
#include <libcockatrice/models/deck_list/deck_list_model.h>
#include <qscrollarea.h>
class VisualDeckDisplayOptionsWidget;
class DeckCardZoneDisplayWidget;
enum class DisplayType
{
@ -55,7 +56,6 @@ public:
public slots:
void decklistDataChanged(QModelIndex topLeft, QModelIndex bottomRight);
void updateZoneWidgets();
void updateDisplayType();
void cleanupInvalidZones(DeckCardZoneDisplayWidget *displayWidget);
void onCardAddition(const QModelIndex &parent, int first, int last);
void onCardRemoval(const QModelIndex &parent, int first, int last);
@ -73,8 +73,6 @@ signals:
protected slots:
void onHover(const ExactCard &hoveredCard);
void onCardClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *instance, QString zoneName);
void actChangeActiveGroupCriteria();
void actChangeActiveSortCriteria();
void decklistModelReset();
private:
@ -85,19 +83,10 @@ private:
CardDatabaseDisplayModel *cardDatabaseDisplayModel;
CardCompleterProxyModel *proxyModel;
QCompleter *completer;
QWidget *displayOptionsAndSearch;
QHBoxLayout *displayOptionsAndSearchLayout;
VisualDeckDisplayOptionsWidget *displayOptionsWidget;
QPushButton *searchPushButton;
DisplayType currentDisplayType = DisplayType::Overlap;
QPushButton *displayTypeButton;
QWidget *groupAndSortContainer;
QHBoxLayout *groupAndSortLayout;
QLabel *groupByLabel;
QComboBox *groupByComboBox;
QString activeGroupCriteria = "maintype";
SettingsButtonWidget *sortCriteriaButton;
QLabel *sortByLabel;
QLabel *sortLabel;
QListWidget *sortByListWidget;
QStringList activeSortCriteria = {"name", "cmc", "colors", "maintype"};
QScrollArea *scrollArea;
QWidget *zoneContainer;
QVBoxLayout *zoneContainerLayout;